Skip to content

Commit

Permalink
Merge pull request #20319 from geoand/#18559
Browse files Browse the repository at this point in the history
Make jacoco work with @QuarkusIntegrationTest
  • Loading branch information
geoand authored Sep 22, 2021
2 parents 5b91e61 + 18939d3 commit 8181ed2
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 8181ed2

Please sign in to comment.