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

Stabilize Open Test Reporting tests for Git info #4161

Merged
merged 3 commits into from
Nov 28, 2024
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 @@ -58,6 +58,7 @@
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -108,9 +109,16 @@ public class OpenTestReportGeneratingListener implements TestExecutionListener {
private final AtomicInteger idCounter = new AtomicInteger();
private final Map<UniqueId, String> inProgressIds = new ConcurrentHashMap<>();
private DocumentWriter<Events> eventsFileWriter = DocumentWriter.noop();
private final Path workingDir;
private Path outputDir;

@SuppressWarnings("unused") // Used via ServiceLoader
public OpenTestReportGeneratingListener() {
this(Paths.get(".").toAbsolutePath());
}

OpenTestReportGeneratingListener(Path workingDir) {
this.workingDir = workingDir;
}

@Override
Expand Down Expand Up @@ -160,7 +168,7 @@ private void reportInfrastructure() {
});
}

private static void addGitInfo(Infrastructure infrastructure) {
private void addGitInfo(Infrastructure infrastructure) {
boolean gitInstalled = exec("git", "--version").isPresent();
if (gitInstalled) {
exec("git", "config", "--get", "remote.origin.url") //
Expand All @@ -179,7 +187,7 @@ private static void addGitInfo(Infrastructure infrastructure) {
}
}

static Optional<String> exec(String... args) {
Optional<String> exec(String... args) {

Process process = startProcess(args);

Expand Down Expand Up @@ -211,10 +219,10 @@ private static BufferedReader newBufferedReader(InputStream stream) {
return new BufferedReader(new InputStreamReader(stream, Charset.defaultCharset()));
}

private static Process startProcess(String[] args) {
private Process startProcess(String[] command) {
Process process;
try {
process = Runtime.getRuntime().exec(args);
process = new ProcessBuilder().directory(workingDir.toFile()).command(command).start();
}
catch (IOException e) {
throw new UncheckedIOException("Failed to start process", e);
Expand Down
38 changes: 37 additions & 1 deletion platform-tests/platform-tests.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

import junitbuild.extensions.capitalized
import org.gradle.api.tasks.PathSensitivity.NONE
import org.gradle.api.tasks.PathSensitivity.RELATIVE
import org.gradle.internal.os.OperatingSystem
Expand All @@ -9,6 +11,18 @@ plugins {
id("junitbuild.jmh-conventions")
}

val processStarter by sourceSets.creating {
java {
srcDir("src/processStarter/java")
}
}

java {
registerFeature(processStarter.name) {
usingSourceSet(processStarter)
}
}

dependencies {
// --- Things we are testing --------------------------------------------------
testImplementation(projects.junitPlatformCommons)
Expand Down Expand Up @@ -37,16 +51,32 @@ dependencies {
testImplementation(libs.bundles.xmlunit)
testImplementation(testFixtures(projects.junitJupiterApi))
testImplementation(testFixtures(projects.junitPlatformReporting))
testImplementation(projects.platformTests) {
capabilities {
requireFeature("process-starter")
}
}

// --- Test run-time dependencies ---------------------------------------------
testRuntimeOnly(projects.junitVintageEngine)
testRuntimeOnly(libs.groovy4) {
because("`ReflectionUtilsTests.findNestedClassesWithInvalidNestedClassFile` needs it")
}

// --- https://openjdk.java.net/projects/code-tools/jmh/ -----------------------
// --- https://openjdk.java.net/projects/code-tools/jmh/ ----------------------
jmh(projects.junitJupiterApi)
jmh(libs.junit4)

// --- ProcessStarter dependencies --------------------------------------------
processStarter.implementationConfigurationName(libs.groovy4) {
because("it provides convenience methods to handle process output")
}
processStarter.implementationConfigurationName(libs.commons.io) {
because("it uses TeeOutputStream")
}
processStarter.implementationConfigurationName(libs.opentest4j) {
because("it throws TestAbortedException")
}
}

jmh {
Expand Down Expand Up @@ -80,6 +110,12 @@ tasks {
includeTags("junit4")
}
}
named<JavaCompile>(processStarter.compileJavaTaskName).configure {
options.release = 21
}
named<Checkstyle>("checkstyle${processStarter.name.capitalized()}").configure {
config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml"))
}
}

eclipse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/

package platform.tooling.support.process;
package org.junit.platform.tests.process;

import java.util.List;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/

package platform.tooling.support.process;
package org.junit.platform.tests.process;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
Expand Down Expand Up @@ -67,7 +67,7 @@ public ProcessResult startAndWait() throws InterruptedException {
}

public WatchedProcess start() {
var command = Stream.concat(Stream.of(executable.toAbsolutePath().toString()), arguments.stream()).toList();
var command = Stream.concat(Stream.of(executable.toString()), arguments.stream()).toList();
try {
var builder = new ProcessBuilder().command(command);
if (workingDir != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/

package platform.tooling.support.process;
package org.junit.platform.tests.process;

import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* https://www.eclipse.org/legal/epl-v20.html
*/

package platform.tooling.support.process;
package org.junit.platform.tests.process;

public class WatchedProcess {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
* @since 1.12
*/

package platform.tooling.support.process;
package org.junit.platform.tests.process;
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@

import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME;
import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER;
Expand All @@ -23,15 +24,21 @@
import static org.junit.platform.reporting.testutil.FileUtils.findPath;

import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.regex.Pattern;
import java.util.Map;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.platform.engine.TestEngine;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.reporting.ReportEntry;
import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine;
import org.junit.platform.tests.process.ProcessResult;
import org.junit.platform.tests.process.ProcessStarter;
import org.opentest4j.reporting.schema.Namespace;
import org.opentest4j.reporting.tooling.core.validator.DefaultValidator;
import org.opentest4j.reporting.tooling.core.validator.ValidationResult;
import org.xmlunit.assertj3.XmlAssert;
Expand All @@ -44,19 +51,16 @@
*/
public class OpenTestReportGeneratingListenerTests {

@TempDir(cleanup = ON_SUCCESS)
Path tempDirectory;

@Test
void writesValidXmlReport() throws Exception {
void writesValidXmlReport(@TempDir Path tempDirectory) throws Exception {
var engine = new DemoHierarchicalTestEngine("dummy");
engine.addTest("failingTest", "display<-->Name 😎", (context, descriptor) -> {
var listener = context.request.getEngineExecutionListener();
listener.reportingEntryPublished(descriptor, ReportEntry.from("key", "value"));
fail("failure message");
});

executeTests(engine);
executeTests(tempDirectory, engine, tempDirectory.resolve("junit-" + OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER));

var xmlFile = findPath(tempDirectory, "glob:**/open-test-report.xml");
assertThat(tempDirectory.relativize(xmlFile).toString()) //
Expand All @@ -79,10 +83,6 @@ void writesValidXmlReport() throws Exception {
<java:javaVersion>${xmlunit.ignore}</java:javaVersion>
<java:fileEncoding>${xmlunit.ignore}</java:fileEncoding>
<java:heapSize max="${xmlunit.isNumber}"/>
<git:repository originUrl="${xmlunit.matchesRegex#(git@|https://).+#}"/>
<git:branch>${xmlunit.ignore}</git:branch>
<git:commit>${xmlunit.matchesRegex#[0-9a-f]{40}#}</git:commit>
<git:status clean="${xmlunit.ignore}">${xmlunit.ignore}</git:status>
</infrastructure>
<e:started id="1" name="dummy" time="${xmlunit.isDateTime}">
<metadata>
Expand All @@ -108,7 +108,7 @@ void writesValidXmlReport() throws Exception {
<e:finished id="2" time="${xmlunit.isDateTime}">
<result status="FAILED">
<java:throwable assertionError="true" type="org.opentest4j.AssertionFailedError">
${xmlunit.matchesRegex#org\\.opentest4j\\.AssertionFailedError: failure message#}
${xmlunit.matchesRegex(org\\.opentest4j\\.AssertionFailedError: failure message)}
</java:throwable>
</result>
</e:finished>
Expand All @@ -119,25 +119,90 @@ void writesValidXmlReport() throws Exception {
""";

XmlAssert.assertThat(xmlFile).and(expected) //
.withDifferenceEvaluator(new PlaceholderDifferenceEvaluator(Pattern.quote("${"), Pattern.quote("}"),
Pattern.quote("#"), Pattern.quote("#"), ",")) //
.withDifferenceEvaluator(new PlaceholderDifferenceEvaluator()) //
.ignoreWhitespace() //
.areIdentical();
}

@ParameterizedTest
@ValueSource(strings = { "https://github.com/junit-team/junit5.git", "[email protected]:junit-team/junit5.git" })
void includesGitInfo(String originUrl, @TempDir Path tempDirectory) throws Exception {

assumeTrue(tryExecGit(tempDirectory, "--version").exitCode() == 0, "git not installed");
execGit(tempDirectory, "init", "--initial-branch=my_branch");
execGit(tempDirectory, "remote", "add", "origin", originUrl);

Files.writeString(tempDirectory.resolve("README.md"), "Hello, world!");
execGit(tempDirectory, "add", ".");

execGit(tempDirectory, "config", "user.name", "Alice");
execGit(tempDirectory, "config", "user.email", "[email protected]");
execGit(tempDirectory, "commit", "-m", "Initial commit");

var engine = new DemoHierarchicalTestEngine("dummy");

executeTests(tempDirectory, engine, tempDirectory.resolve("junit-reports"));

var xmlFile = findPath(tempDirectory, "glob:**/open-test-report.xml");
assertThat(validate(xmlFile)).isEmpty();

var namespaceContext = Map.of("core", Namespace.REPORTING_CORE.getUri(), "e",
Namespace.REPORTING_EVENTS.getUri(), "git", Namespace.REPORTING_GIT.getUri());

XmlAssert.assertThat(xmlFile) //
.withNamespaceContext(namespaceContext) //
.valueByXPath("/e:events/core:infrastructure/git:repository/@originUrl") //
.isEqualTo(originUrl);

XmlAssert.assertThat(xmlFile) //
.withNamespaceContext(namespaceContext) //
.valueByXPath("/e:events/core:infrastructure/git:branch") //
.isEqualTo("my_branch");

var commitHash = execGit(tempDirectory, "rev-parse", "--verify", "HEAD").stdOut().trim();
XmlAssert.assertThat(xmlFile) //
.withNamespaceContext(namespaceContext) //
.valueByXPath("/e:events/core:infrastructure/git:commit") //
.isEqualTo(commitHash);

XmlAssert.assertThat(xmlFile) //
.withNamespaceContext(namespaceContext) //
.valueByXPath("/e:events/core:infrastructure/git:status/@clean") //
.isEqualTo(false);

XmlAssert.assertThat(xmlFile) //
.withNamespaceContext(namespaceContext) //
.valueByXPath("/e:events/core:infrastructure/git:status") //
.startsWith("?? junit-reports");
}

private static ProcessResult execGit(Path workingDir, String... arguments) throws InterruptedException {
var result = tryExecGit(workingDir, arguments);
assertEquals(0, result.exitCode(), "git " + String.join(" ", arguments) + " failed");
return result;
}

private static ProcessResult tryExecGit(Path workingDir, String... arguments) throws InterruptedException {
System.out.println("$ git " + String.join(" ", arguments));
return new ProcessStarter() //
.executable(Path.of("git")) //
.workingDir(workingDir) //
.addArguments(arguments) //
.startAndWait();
}

private ValidationResult validate(Path xmlFile) throws URISyntaxException {
var catalogUri = requireNonNull(getClass().getResource("catalog.xml")).toURI();
return new DefaultValidator(catalogUri).validate(xmlFile);
}

private void executeTests(TestEngine engine) {
private void executeTests(Path tempDirectory, TestEngine engine, Path outputDir) {
var build = request() //
.selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) //
.configurationParameter(ENABLED_PROPERTY_NAME, String.valueOf(true)) //
.configurationParameter(OUTPUT_DIR_PROPERTY_NAME,
tempDirectory.resolve("junit-" + OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER).toString()) //
.configurationParameter(OUTPUT_DIR_PROPERTY_NAME, outputDir.toString()) //
.build();
createLauncher(engine).execute(build, new OpenTestReportGeneratingListener());
createLauncher(engine).execute(build, new OpenTestReportGeneratingListener(tempDirectory));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ dependencies {
implementation(libs.commons.io) {
because("moving/deleting directory trees")
}
implementation(libs.groovy4) {
because("it provides convenience methods to handle process output")
implementation(projects.platformTests) {
capabilities {
requireFeature("process-starter")
}
}
implementation(projects.junitJupiterApi) {
because("it throws TestAbortedException to abort tests and uses the OS enum")
because("it uses the OS enum to support Windows")
}

testImplementation(libs.archunit) {
Expand Down
Loading