diff --git a/docs/src/main/asciidoc/tests-with-coverage.adoc b/docs/src/main/asciidoc/tests-with-coverage.adoc index 68579b8c26921..9db25b1d84f2e 100644 --- a/docs/src/main/asciidoc/tests-with-coverage.adoc +++ b/docs/src/main/asciidoc/tests-with-coverage.adoc @@ -175,6 +175,9 @@ Now we need to add Jacoco to our project. To do this we need to add the followin This Quarkus extension takes care of everything that would usually be done via the Jacoco maven plugin, so no additional config is required. +WARNING: Using both the extension and the plugin requires special configuration, if you add both you will get lots of errors about classes +already being instrumented. The configuration needed is detailed below. + == Running the tests with coverage Run `mvn verify`, the tests will be run and the results will end up in `target/jacoco-reports`. This is all that is needed, @@ -189,86 +192,48 @@ include::{generated-dir}/config/quarkus-jacoco-jacoco-config.adoc[opts=optional, The Quarkus automatic Jacoco config will only work for tests that are annotated with `@QuarkusTest`. If you want to check the coverage of other tests as well then you will need to fall back to the Jacoco maven plugin. -Because Quarkus uses class file transformation it is not possible to use online transformation with the Jacoco agent. -Instead we need to use offline transformation. - -Instead of including the `quarkus-jacoco` extension in your pom you will need the following config: - -[source,xml,subs=attributes+] ----- - - ... - - ... - - org.jacoco - org.jacoco.agent - runtime - test - ${jacoco.version} - - ... - - - - ... - - org.jacoco - jacoco-maven-plugin - ${jacoco.version} - - - instrument-ut - - instrument - - - - restore-ut - - restore-instrumented-classes - - - - report-ut - - report - - - ${project.reporting.outputDirectory}/jacoco-reports - - - - - - - ----- - -It also requires a small change in the Surefire configuration. Note below that we specified `jacoco-agent.destfile` as a system property in the default case (unit tests) and for the integration tests. +In addition to including the `quarkus-jacoco` extension in your pom you will need the following config: [source,xml,subs=attributes+] ---- - ... ... - - maven-surefire-plugin - ${surefire-plugin.version} + + org.jacoco + jacoco-maven-plugin + + + default-prepare-agent + + prepare-agent + - - ${project.build.directory}/jacoco.exec - org.jboss.logmanager.LogManager - ${maven.home} - + *QuarkusClassLoader <1> - + + + default-prepare-agent-integration <2> + + prepare-agent-integration + + + *QuarkusClassLoader + + + + ---- +<1> This config tells it to ignore `@QuarkusTest` related classes, as they are loaded by `QuarkusClassLoader` +<2> This is only needed if you are using Failsafe to run integration tests + +WARNING: This config will only work if at least one `@QuarkusTest` is being run. If you are not using `@QuarkusTest` then +you can simply use the Jacoco plugin in the standard manner with no additional config. + == Conclusion diff --git a/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java b/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java index d9763c585fa20..bdbc6b480c63b 100644 --- a/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java +++ b/test-framework/jacoco/deployment/src/main/java/io/quarkus/jacoco/deployment/JacocoProcessor.java @@ -18,10 +18,12 @@ import org.jboss.jandex.ClassInfo; import io.quarkus.bootstrap.model.AppArtifactKey; +import io.quarkus.bootstrap.model.AppDependency; import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject; import io.quarkus.bootstrap.resolver.model.QuarkusModel; import io.quarkus.bootstrap.resolver.model.WorkspaceModule; import io.quarkus.bootstrap.utils.BuildToolHelper; +import io.quarkus.deployment.ApplicationArchive; import io.quarkus.deployment.IsTest; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -31,6 +33,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.pkg.builditem.BuildSystemTargetBuildItem; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; import io.quarkus.jacoco.runtime.JacocoConfig; import io.quarkus.jacoco.runtime.ReportCreator; @@ -49,6 +52,7 @@ void transformerBuildItem(BuildProducer transforme ApplicationArchivesBuildItem applicationArchivesBuildItem, BuildSystemTargetBuildItem buildSystemTargetBuildItem, ShutdownContextBuildItem shutdownContextBuildItem, + CurateOutcomeBuildItem curateOutcomeBuildItem, JacocoConfig config) throws Exception { String dataFile = outputTargetBuildItem.getOutputDirectory().toAbsolutePath().toString() + File.separator + config.dataFile; @@ -58,30 +62,32 @@ void transformerBuildItem(BuildProducer transforme Instrumenter instrumenter = new Instrumenter(new OfflineInstrumentationAccessGenerator()); Set seen = new HashSet<>(); - for (ClassInfo i : indexBuildItem.getIndex().getKnownClasses()) { - String className = i.name().toString(); - if (seen.contains(className)) { - continue; - } - seen.add(className); - transformers.produce( - new BytecodeTransformerBuildItem.Builder().setClassToTransform(className) - .setCacheable(true) - .setEager(true) - .setInputTransformer(new BiFunction() { - @Override - public byte[] apply(String className, byte[] bytes) { - try { - byte[] enhanced = instrumenter.instrument(bytes, className); - if (enhanced == null) { - return bytes; + for (ApplicationArchive archive : applicationArchivesBuildItem.getAllApplicationArchives()) { + for (ClassInfo i : archive.getIndex().getKnownClasses()) { + String className = i.name().toString(); + if (seen.contains(className)) { + continue; + } + seen.add(className); + transformers.produce( + new BytecodeTransformerBuildItem.Builder().setClassToTransform(className) + .setCacheable(true) + .setEager(true) + .setInputTransformer(new BiFunction() { + @Override + public byte[] apply(String className, byte[] bytes) { + try { + byte[] enhanced = instrumenter.instrument(bytes, className); + if (enhanced == null) { + return bytes; + } + return enhanced; + } catch (IOException e) { + throw new RuntimeException(e); } - return enhanced; - } catch (IOException e) { - throw new RuntimeException(e); } - } - }).build()); + }).build()); + } } if (config.report) { ReportInfo info = new ReportInfo(); @@ -99,16 +105,23 @@ public byte[] apply(String className, byte[] bytes) { Set sources = new HashSet<>(); MultiSourceFileLocator sourceFileLocator = new MultiSourceFileLocator(4); if (BuildToolHelper.isMavenProject(targetdir.toPath())) { + Set runtimeDeps = new HashSet<>(); + for (AppDependency i : curateOutcomeBuildItem.getEffectiveModel().getUserDependencies()) { + runtimeDeps.add(i.getArtifact().getKey()); + } LocalProject project = LocalProject.loadWorkspace(targetdir.toPath()); + runtimeDeps.add(project.getKey()); for (Map.Entry i : project.getWorkspace().getProjects().entrySet()) { - info.savedData.add(i.getValue().getOutputDir().resolve(config.dataFile).toAbsolutePath().toString()); - sources.add(i.getValue().getSourcesSourcesDir().toFile().getAbsolutePath()); - File classesDir = i.getValue().getClassesDir().toFile(); - if (classesDir.isDirectory()) { - for (final File file : FileUtils.getFiles(classesDir, includes, excludes, - true)) { - if (file.getName().endsWith(".class")) { - classes.add(file.getAbsolutePath()); + if (runtimeDeps.contains(i.getKey())) { + info.savedData.add(i.getValue().getOutputDir().resolve(config.dataFile).toAbsolutePath().toString()); + sources.add(i.getValue().getSourcesSourcesDir().toFile().getAbsolutePath()); + File classesDir = i.getValue().getClassesDir().toFile(); + if (classesDir.isDirectory()) { + for (final File file : FileUtils.getFiles(classesDir, includes, excludes, + true)) { + if (file.getName().endsWith(".class")) { + classes.add(file.getAbsolutePath()); + } } } } diff --git a/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/ReportCreator.java b/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/ReportCreator.java index be2ec2bd09a5e..0a5956c41905f 100644 --- a/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/ReportCreator.java +++ b/test-framework/jacoco/runtime/src/main/java/io/quarkus/jacoco/runtime/ReportCreator.java @@ -3,6 +3,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -102,9 +103,17 @@ public void run() { System.out.println("Generated Jacoco reports in " + targetdir); System.out.flush(); } catch (Exception e) { - System.err.println("Failed to generate Jacoco reports "); + System.err.println("Failed to generate Jacoco reports"); e.printStackTrace(); System.err.flush(); + File error = new File(targetdir, "error.txt"); + try (FileOutputStream out = new FileOutputStream(error)) { + PrintStream ps = new PrintStream(out); + ps.println("Failed to generate Jacoco reports"); + e.printStackTrace(ps); + } catch (IOException iugnore) { + //ignore + } } }