Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make jacoco work with @QuarkusIntegrationTest #20319

Merged
merged 1 commit into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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