Skip to content

Commit

Permalink
Add JUnit-specific metadata to open-test-reporting's HTML report (#4116)
Browse files Browse the repository at this point in the history
When creating the event-based XML report, the
`OpenTestReportGeneratingListener` includes JUnit-specific metadata:
type, unique ID, and legacy reporting name. This PR implements 
open-test-reporting's `Contributor` SPI to include that data in the new
HTML report of open-test-reporting.

Moreover, it configures the build to create a single report for all test
tasks per subproject and uploads it as part of GitHub Action runs.
  • Loading branch information
marcphilipp authored Nov 11, 2024
1 parent be4edac commit 5e2b694
Show file tree
Hide file tree
Showing 28 changed files with 343 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .github/actions/main-build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,8 @@ runs:
with:
arguments: ${{ inputs.arguments }}
encryptionKey: ${{ inputs.encryptionKey }}
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ always() }}
with:
name: Open Test Reports (${{ github.job }})
path: '**/build/reports/open-test-report.html'
10 changes: 10 additions & 0 deletions .github/workflows/cross-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ jobs:
-Dscan.tag.JDK_${{ matrix.jdk.version }} \
build \
--configuration-cache
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ always() }}
with:
name: Open Test Reports (${{ github.job }} ${{ matrix.jdk.version }} (${{ matrix.jdk.release || matrix.jdk.type }}))
path: '**/build/reports/open-test-report.html'
openj9:
strategy:
fail-fast: false
Expand Down Expand Up @@ -102,3 +107,8 @@ jobs:
-Dscan.tag.OpenJ9 \
build \
--configuration-cache
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
if: ${{ always() }}
with:
name: Open Test Reports (${{ github.job }})
path: '**/build/reports/open-test-report.html'
28 changes: 23 additions & 5 deletions documentation/documentation.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import junitbuild.javadoc.ModuleSpecificJavadocFileOption
import org.asciidoctor.gradle.base.AsciidoctorAttributeProvider
import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask
import org.gradle.api.tasks.PathSensitivity.RELATIVE
import java.nio.file.Files

plugins {
alias(libs.plugins.asciidoctorConvert)
Expand Down Expand Up @@ -144,23 +145,40 @@ require(externalModulesWithoutModularJavadoc.values.all { it.endsWith("/") }) {

tasks {

val consoleLauncherTestReportsDir = project.layout.buildDirectory.dir("console-launcher-test-results")
val consoleLauncherTestEventXmlFiles =
files(consoleLauncherTestReportsDir.map { it.asFileTree.matching { include("junit-platform-events-*.xml") } })

val consoleLauncherTest by registering(RunConsoleLauncher::class) {
args.addAll("execute")
args.addAll("--scan-classpath")
args.addAll("--config=junit.platform.reporting.open.xml.enabled=true")
val reportsDir = project.layout.buildDirectory.dir("console-launcher-test-results")
outputs.dir(reportsDir)
outputs.dir(consoleLauncherTestReportsDir)
argumentProviders.add(CommandLineArgumentProvider {
listOf(
"--reports-dir=${reportsDir.get()}",
"--config=junit.platform.reporting.output.dir=${reportsDir.get()}"

"--reports-dir=${consoleLauncherTestReportsDir.get()}",
"--config=junit.platform.reporting.output.dir=${consoleLauncherTestReportsDir.get()}",
)
})
args.addAll("--include-classname", ".*Tests")
args.addAll("--include-classname", ".*Demo")
args.addAll("--exclude-tag", "exclude")
args.addAll("--exclude-tag", "timeout")

doFirst {
consoleLauncherTestEventXmlFiles.files.forEach {
Files.delete(it.toPath())
}
}

finalizedBy(generateOpenTestHtmlReport)
}

generateOpenTestHtmlReport {
mustRunAfter(consoleLauncherTest)
argumentProviders += CommandLineArgumentProvider {
consoleLauncherTestEventXmlFiles.files.map { it.absolutePath }.toList()
}
}

register<RunConsoleLauncher>("consoleLauncher") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ JUnit repository on GitHub.
`--select-file` and `--select-resource`.
* `ConsoleLauncher` now accepts multiple values for all `--select` options.
* Add `--select-unique-id` support to ConsoleLauncher.
* The `junit-platform-reporting` module now contributes a section containing
JUnit-specific metadata about each test/container to the HTML report written by
open-test-reporting when added to the classpath/module path.


[[release-notes-5.12.0-M1-junit-jupiter]]
Expand Down
6 changes: 4 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ junit4Min = "4.12"
ktlint = "1.4.1"
log4j = "2.24.1"
opentest4j = "1.3.0"
openTestReporting = "0.1.0-M2"
openTestReporting = "0.1.0-SNAPSHOT"
surefire = "3.5.2"
xmlunit = "2.10.0"

Expand Down Expand Up @@ -56,8 +56,10 @@ memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version =
mockito = { module = "org.mockito:mockito-junit-jupiter", version = "5.14.2" }
nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version = "0.0.11" }
opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" }
openTestReporting-cli = { module = "org.opentest4j.reporting:open-test-reporting-cli", version.ref = "openTestReporting" }
openTestReporting-events = { module = "org.opentest4j.reporting:open-test-reporting-events", version.ref = "openTestReporting" }
openTestReporting-tooling = { module = "org.opentest4j.reporting:open-test-reporting-tooling", version.ref = "openTestReporting" }
openTestReporting-tooling-core = { module = "org.opentest4j.reporting:open-test-reporting-tooling-core", version.ref = "openTestReporting" }
openTestReporting-tooling-spi = { module = "org.opentest4j.reporting:open-test-reporting-tooling-spi", version.ref = "openTestReporting" }
picocli = { module = "info.picocli:picocli", version = "4.7.6" }
slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.16" }
spock1 = { module = "org.spockframework:spock-core", version = "1.3-groovy-2.5" }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,50 @@

import com.gradle.develocity.agent.gradle.internal.test.PredictiveTestSelectionConfigurationInternal
import com.gradle.develocity.agent.gradle.test.PredictiveTestSelectionMode
import org.gradle.api.tasks.PathSensitivity.NONE
import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED
import org.gradle.internal.os.OperatingSystem
import java.nio.file.Files

plugins {
`java-library`
id("junitbuild.build-parameters")
}

var openTestReportingCli = configurations.dependencyScope("openTestReportingCli")
var openTestReportingCliClasspath = configurations.resolvable("openTestReportingCliClasspath") {
extendsFrom(openTestReportingCli.get())
}

val generateOpenTestHtmlReport by tasks.registering(JavaExec::class) {
mustRunAfter(tasks.withType<Test>())
mainClass.set("org.opentest4j.reporting.cli.ReportingCli")
args("html-report")
classpath(openTestReportingCliClasspath)
argumentProviders += objects.newInstance(HtmlReportParameters::class).apply {
eventXmlFiles.from(tasks.withType<Test>().map {
objects.fileTree()
.from(it.reports.junitXml.outputLocation)
.include("junit-platform-events-*.xml")
})
outputLocation = layout.buildDirectory.file("reports/open-test-report.html")
}
}

abstract class HtmlReportParameters : CommandLineArgumentProvider {

@get:InputFiles
@get:PathSensitive(NONE)
abstract val eventXmlFiles: ConfigurableFileCollection

@get:OutputFile
abstract val outputLocation: RegularFileProperty

override fun asArguments() = listOf("--output", outputLocation.get().asFile.absolutePath) +
eventXmlFiles.map { it.absolutePath }.toList()
}

tasks.withType<Test>().configureEach {
useJUnitPlatform {
includeEngines("junit-jupiter")
Expand Down Expand Up @@ -79,6 +115,15 @@ tasks.withType<Test>().configureEach {
"-Djunit.platform.reporting.output.dir=${reports.junitXml.outputLocation.get().asFile.absolutePath}"
)
}

val reportFiles = objects.fileTree().from(reports.junitXml.outputLocation).matching { include("junit-platform-events-*.xml") }
doFirst {
reportFiles.files.forEach {
Files.delete(it.toPath())
}
}

finalizedBy(generateOpenTestHtmlReport)
}

dependencies {
Expand All @@ -98,4 +143,7 @@ dependencies {
testRuntimeOnly(dependencyFromLibs("openTestReporting-events")) {
because("it's required to run tests via IntelliJ which does not consumed the shadowed jar of junit-platform-reporting")
}

openTestReportingCli(dependencyFromLibs("openTestReporting-cli"))
openTestReportingCli(project(":junit-platform-reporting"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ dependencies {
shadowed(libs.apiguardian) {
because("downstream projects need it to avoid compiler warnings")
}

osgiVerification(libs.openTestReporting.tooling.spi)
}

val jupiterVersion = rootProject.version
Expand Down
1 change: 1 addition & 0 deletions junit-platform-console/junit-platform-console.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies {

osgiVerification(projects.junitJupiterEngine)
osgiVerification(projects.junitPlatformLauncher)
osgiVerification(libs.openTestReporting.tooling.spi)
}

tasks {
Expand Down
10 changes: 9 additions & 1 deletion junit-platform-reporting/junit-platform-reporting.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id("junitbuild.java-library-conventions")
id("junitbuild.shadow-conventions")
`java-test-fixtures`
}

description = "JUnit Platform Reporting"
Expand All @@ -10,16 +11,23 @@ dependencies {
api(projects.junitPlatformLauncher)

compileOnlyApi(libs.apiguardian)
compileOnlyApi(libs.openTestReporting.tooling.spi)

shadowed(libs.openTestReporting.events)

osgiVerification(projects.junitJupiterEngine)
osgiVerification(projects.junitPlatformLauncher)
osgiVerification(libs.openTestReporting.tooling.spi)

testFixturesApi(projects.junitJupiterApi)
}

tasks {
shadowJar {
relocate("org.opentest4j.reporting", "org.junit.platform.reporting.shadow.org.opentest4j.reporting")
listOf("events", "schema").forEach { name ->
val packageName = "org.opentest4j.reporting.${name}"
relocate(packageName, "org.junit.platform.reporting.shadow.${packageName}")
}
from(projectDir) {
include("LICENSE-open-test-reporting.md")
into("META-INF")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2015-2024 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.reporting.open.xml;

import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.apiguardian.api.API.Status.INTERNAL;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.apiguardian.api.API;
import org.opentest4j.reporting.schema.Namespace;
import org.opentest4j.reporting.tooling.spi.htmlreport.Contributor;
import org.opentest4j.reporting.tooling.spi.htmlreport.KeyValuePairs;
import org.opentest4j.reporting.tooling.spi.htmlreport.Section;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
* Contributes a section containing JUnit-specific metadata for each test node
* to the open-test-reporting HTML report.
*
* @since 1.12
*/
@SuppressWarnings("exports") // we don't want to export 'org.opentest4j.reporting.tooling.spi' transitively
@API(status = INTERNAL, since = "1.12")
public class JUnitContributor implements Contributor {

public JUnitContributor() {
}

@Override
public List<Section> contributeSectionsForTestNode(Element testNodeElement) {
return findChild(testNodeElement, Namespace.REPORTING_CORE, "metadata") //
.map(metadata -> {
Map<String, String> table = new LinkedHashMap<>();
findChild(metadata, JUnitFactory.NAMESPACE, "type") //
.map(Node::getTextContent) //
.ifPresent(value -> table.put("Type", value));
findChild(metadata, JUnitFactory.NAMESPACE, "uniqueId") //
.map(Node::getTextContent) //
.ifPresent(value -> table.put("Unique ID", value));
findChild(metadata, JUnitFactory.NAMESPACE, "legacyReportingName") //
.map(Node::getTextContent) //
.ifPresent(value -> table.put("Legacy reporting name", value));
return table;
}) //
.filter(table -> !table.isEmpty()) //
.map(table -> singletonList(Section.builder() //
.title("JUnit metadata") //
.order(15) //
.addBlock(KeyValuePairs.builder().content(table).build()) //
.build())) //
.orElse(emptyList());
}

private static Optional<Node> findChild(Node parent, Namespace namespace, String localName) {
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (localName.equals(child.getLocalName()) && namespace.getUri().equals(child.getNamespaceURI())) {
return Optional.of(child);
}
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.junit.platform.reporting.open.xml.JUnitContributor
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
requires org.junit.platform.commons;
requires transitive org.junit.platform.engine;
requires transitive org.junit.platform.launcher;
requires org.opentest4j.reporting.tooling.spi;

// exports org.junit.platform.reporting; empty package
exports org.junit.platform.reporting.legacy;
Expand All @@ -27,4 +28,7 @@

provides org.junit.platform.launcher.TestExecutionListener
with org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener;

provides org.opentest4j.reporting.tooling.spi.htmlreport.Contributor
with org.junit.platform.reporting.open.xml.JUnitContributor;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2015-2024 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.reporting.open.xml;

import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.ENABLED_PROPERTY_NAME;

import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class OpenTestReportGenerationSystemPropertyOverride
implements BeforeTestExecutionCallback, AfterTestExecutionCallback {

@Override
public void beforeTestExecution(ExtensionContext context) {
var oldValue = System.clearProperty(ENABLED_PROPERTY_NAME);
getStore(context).put(ENABLED_PROPERTY_NAME, oldValue);
}

@Override
public void afterTestExecution(ExtensionContext context) {
var oldValue = getStore(context).get(ENABLED_PROPERTY_NAME, String.class);
if (oldValue == null) {
System.clearProperty(ENABLED_PROPERTY_NAME);
}
else {
System.setProperty(ENABLED_PROPERTY_NAME, oldValue);
}
}

private static ExtensionContext.Store getStore(ExtensionContext context) {
return context.getStore(
ExtensionContext.Namespace.create(OpenTestReportGenerationSystemPropertyOverride.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.junit.platform.reporting.open.xml.OpenTestReportGenerationSystemPropertyOverride
3 changes: 2 additions & 1 deletion platform-tests/platform-tests.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ dependencies {
exclude(group = "org.junit.vintage")
}
testImplementation(libs.joox)
testImplementation(libs.openTestReporting.tooling)
testImplementation(libs.openTestReporting.tooling.core)
testImplementation(libs.picocli)
testImplementation(libs.bundles.xmlunit)
testImplementation(testFixtures(projects.junitJupiterApi))
testImplementation(testFixtures(projects.junitPlatformReporting))

// --- Test run-time dependencies ---------------------------------------------
testRuntimeOnly(projects.junitVintageEngine)
Expand Down
Loading

0 comments on commit 5e2b694

Please sign in to comment.