diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java index 778fad0cbe869..01c8f851f692a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/PackageConfig.java @@ -156,6 +156,21 @@ public class PackageConfig { @ConfigItem public FernflowerConfig fernflower; + /** + * If set to {@code true}, it will result in the Quarkus writing the transformed application bytecode + * to the build tool's output directory. + * This is useful for post-build tools that need to scan the application bytecode - for example for offline + * code-coverage tools. + * + * For example, if using Maven, enabling this feature will result in the classes in {@code target/classes} being + * updated with the versions that result after Quarkus has applied its transformations. + * + * Setting this to {@code true} however, should be done with a lot of caution and only if subsequent builds are done + * in a clean environment (i.e. the build tool's output directory has been completely cleaned). + */ + @ConfigItem + public boolean writeTransformedBytecodeToBuildOutput; + public boolean isAnyJarType() { return (type.equalsIgnoreCase(PackageConfig.JAR) || type.equalsIgnoreCase(PackageConfig.FAST_JAR) || diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassTransformingBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassTransformingBuildStep.java index 25426d105d2dc..f145ec439df6c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassTransformingBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ClassTransformingBuildStep.java @@ -2,7 +2,10 @@ import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -34,6 +37,7 @@ import io.quarkus.deployment.QuarkusClassWriter; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; +import io.quarkus.deployment.builditem.ArchiveRootBuildItem; import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.LiveReloadBuildItem; @@ -41,6 +45,7 @@ import io.quarkus.deployment.builditem.TransformedClassesBuildItem; import io.quarkus.deployment.configuration.ClassLoadingConfig; import io.quarkus.deployment.index.ConstPoolScanner; +import io.quarkus.deployment.pkg.PackageConfig; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; import io.quarkus.runtime.LaunchMode; @@ -66,7 +71,8 @@ public static byte[] transform(String className, byte[] classData) { TransformedClassesBuildItem handleClassTransformation(List bytecodeTransformerBuildItems, ApplicationArchivesBuildItem appArchives, LiveReloadBuildItem liveReloadBuildItem, LaunchModeBuildItem launchModeBuildItem, ClassLoadingConfig classLoadingConfig, - CurateOutcomeBuildItem curateOutcomeBuildItem, List removedResourceBuildItems) + CurateOutcomeBuildItem curateOutcomeBuildItem, List removedResourceBuildItems, + ArchiveRootBuildItem archiveRoot, LaunchModeBuildItem launchMode, PackageConfig packageConfig) throws ExecutionException, InterruptedException { if (bytecodeTransformerBuildItems.isEmpty() && classLoadingConfig.removedResources.isEmpty() && removedResourceBuildItems.isEmpty()) { @@ -226,9 +232,39 @@ public TransformedClassesBuildItem.TransformedClass call() throws Exception { } } } + + if (packageConfig.writeTransformedBytecodeToBuildOutput && (launchMode.getLaunchMode() == LaunchMode.NORMAL)) { + // the idea here is to write the transformed classes into the build tool's output directory to make core coverage work + + for (Path path : archiveRoot.getRootDirs()) { + copyTransformedClasses(path, transformedClassesByJar.get(path)); + } + } + return new TransformedClassesBuildItem(transformedClassesByJar); } + private void copyTransformedClasses(Path originalClassesPath, + Set transformedClasses) { + if ((transformedClasses == null) || transformedClasses.isEmpty()) { + return; + } + + for (TransformedClassesBuildItem.TransformedClass transformedClass : transformedClasses) { + String classFileName = transformedClass.getFileName(); + String[] fileNameParts = classFileName.split("/"); + Path classFilePath = originalClassesPath; + for (String fileNamePart : fileNameParts) { + classFilePath = classFilePath.resolve(fileNamePart); + } + try { + Files.write(classFilePath, transformedClass.getData(), StandardOpenOption.WRITE); + } catch (IOException e) { + log.debug("Unable to overwrite file '" + classFilePath.toAbsolutePath() + "' with transformed class data"); + } + } + } + private void handleRemovedResources(ClassLoadingConfig classLoadingConfig, CurateOutcomeBuildItem curateOutcomeBuildItem, Map> transformedClassesByJar, List removedResourceBuildItems) { diff --git a/docs/src/main/asciidoc/tests-with-coverage.adoc b/docs/src/main/asciidoc/tests-with-coverage.adoc index 478e1d27177bc..ce3506ef35fe3 100644 --- a/docs/src/main/asciidoc/tests-with-coverage.adoc +++ b/docs/src/main/asciidoc/tests-with-coverage.adoc @@ -237,7 +237,13 @@ you can simply use the Jacoco plugin in the standard manner with no additional c === Coverage for Integration Tests -To get code coverage data from integration tests, Jacoco needs to be configured, and your `@QuarkusIntegrationTest` classes must be run using a jar package, +To get code coverage data from integration tests, the following need to be requirements need to be met: + +* The built artifact is a jar (and not a container or native binary). +* Jacoco needs to be configured in your build tool. +* The application must have been built with `quarkus.package.write-transformed-bytecode-to-build-output` set to `true` + +WARNING: Setting `quarkus.package.write-transformed-bytecode-to-build-output=true` should be done with a caution and only if subsequent builds are done in a clean environment - i.e. the build tool's output directory has been completely cleaned. In the `pom.xml`, you can add the following plugin configuration for Jacoco. This will append integration test data into the same destination file as unit tests, re-build the jacoco report after the integration tests are complete, and thus produce a comprehensive code-coverage report. @@ -259,7 +265,6 @@ re-build the jacoco report after the integration tests are complete, and thus pr ${project.build.directory}/jacoco-quarkus.exec true - *QuarkusClassLoader