Skip to content

Commit

Permalink
Make jacoco work with @QuarkusIntegrationTest
Browse files Browse the repository at this point in the history
To make this work, essentially what we do is to overwrite
the build system output with the transformed bytecode
See quarkusio#18559 (comment)
for more details.

Fixes: quarkusio#18559
  • Loading branch information
geoand committed Sep 22, 2021
1 parent 5b91e61 commit 18939d3
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -34,13 +37,15 @@
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;
import io.quarkus.deployment.builditem.RemovedResourceBuildItem;
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;

Expand All @@ -66,7 +71,8 @@ public static byte[] transform(String className, byte[] classData) {
TransformedClassesBuildItem handleClassTransformation(List<BytecodeTransformerBuildItem> bytecodeTransformerBuildItems,
ApplicationArchivesBuildItem appArchives, LiveReloadBuildItem liveReloadBuildItem,
LaunchModeBuildItem launchModeBuildItem, ClassLoadingConfig classLoadingConfig,
CurateOutcomeBuildItem curateOutcomeBuildItem, List<RemovedResourceBuildItem> removedResourceBuildItems)
CurateOutcomeBuildItem curateOutcomeBuildItem, List<RemovedResourceBuildItem> removedResourceBuildItems,
ArchiveRootBuildItem archiveRoot, LaunchModeBuildItem launchMode, PackageConfig packageConfig)
throws ExecutionException, InterruptedException {
if (bytecodeTransformerBuildItems.isEmpty() && classLoadingConfig.removedResources.isEmpty()
&& removedResourceBuildItems.isEmpty()) {
Expand Down Expand Up @@ -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<TransformedClassesBuildItem.TransformedClass> 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<Path, Set<TransformedClassesBuildItem.TransformedClass>> transformedClassesByJar,
List<RemovedResourceBuildItem> removedResourceBuildItems) {
Expand Down
9 changes: 7 additions & 2 deletions docs/src/main/asciidoc/tests-with-coverage.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -259,7 +265,6 @@ re-build the jacoco report after the integration tests are complete, and thus pr
<configuration>
<destFile>${project.build.directory}/jacoco-quarkus.exec</destFile>
<append>true</append>
<exclClassLoaders>*QuarkusClassLoader</exclClassLoaders>
</configuration>
</execution>
<execution>
Expand Down

0 comments on commit 18939d3

Please sign in to comment.