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

7903930: Support running individual parameterized tests and @Nested test classes #241

Closed
wants to merge 13 commits into from
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## [Unreleased](https://git.openjdk.org/jtreg/compare/jtreg-7.5+1...master)

_nothing noteworthy, yet_
* Support running individual parameterized tests and @Nested test classes [CODETOOLS-7903930](https://bugs.openjdk.org/browse/CODETOOLS-7903930)

## [7.5](https://git.openjdk.org/jtreg/compare/jtreg-7.4+1...jtreg-7.5+1)

Expand Down
28 changes: 21 additions & 7 deletions src/share/classes/com/sun/javatest/regtest/agent/JUnitRunner.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -25,6 +25,7 @@

package com.sun.javatest.regtest.agent;

import org.junit.platform.engine.DiscoverySelector;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.TestSource;
import org.junit.platform.engine.UniqueId;
Expand Down Expand Up @@ -61,6 +62,8 @@ public class JUnitRunner implements MainActionHelper.TestRunner {
// this is a temporary flag while transitioning from JUnit 4 to 5
private static final boolean JUNIT_RUN_WITH_JUNIT_4 = Flags.get("runWithJUnit4");

private static final String JUNIT_SELECT_PREFIX = "junit-select:";

public static void main(String... args) throws Exception {
main(null, args);
}
Expand Down Expand Up @@ -127,13 +130,24 @@ private static void runWithJUnitPlatform(Class<?> mainClass) throws Exception {
// https://junit.org/junit5/docs/current/user-guide/#launcher-api-execution
Thread.currentThread().setContextClassLoader(mainClass.getClassLoader());
try {
// if test.query is set, treat it as a method name to be executed
String testQuery = System.getProperty("test.query");
String testQueryStr = System.getProperty("test.query");
DiscoverySelector selector;
if (testQueryStr != null && !testQueryStr.isEmpty()) {
if (testQueryStr.startsWith(JUNIT_SELECT_PREFIX)) {
// https://junit.org/junit5/docs/current/user-guide/#running-tests-discovery-selectors
String selectorStr = testQueryStr.substring(JUNIT_SELECT_PREFIX.length());
selector = DiscoverySelectors.parse(selectorStr)
.orElseThrow(() -> new IllegalArgumentException("Selector can not be parsed: " + selectorStr));
} else {
// legacy, assume method name
selector = DiscoverySelectors.selectMethod(mainClass, testQueryStr);
}
} else {
selector = DiscoverySelectors.selectClass(mainClass);
}
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
.selectors(testQuery == null
? DiscoverySelectors.selectClass(mainClass)
: DiscoverySelectors.selectMethod(mainClass, testQuery))
.build();
.selectors(selector)
.build();

SummaryGeneratingListener summaryGeneratingListener = new SummaryGeneratingListener();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -101,10 +101,15 @@ public static class GroupSpec {
* - two or more characters followed by :id
* Thus, letter:id is not accepted as a group spec, and so will be treated
* elsewhere as a plain absolute file path instead.
*
* Additionally, since query strings are indicated by '?', and can contain
* any string, the '?' character is not allowed to be in the file path, to
* avoid picking up tests with a query string that contains ':' as being
* group specs.
*/
static final Pattern groupPtn = System.getProperty("os.name").matches("(?i)windows.*")
? Pattern.compile("(?<dir>|[^A-Za-z]|.{2,}):(?<group>[A-Za-z0-9_,]+)")
: Pattern.compile("(?<dir>.*):(?<group>[A-Za-z0-9_,]+)");
? Pattern.compile("(?<dir>|[^A-Za-z?]|[^?]{2,}):(?<group>[A-Za-z0-9_,]+)")
: Pattern.compile("(?<dir>[^?]*):(?<group>[A-Za-z0-9_,]+)");
Comment on lines +111 to +112
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good improvement!


/**
* Returns true if a string may represent a named group of tests.
Expand Down
26 changes: 22 additions & 4 deletions src/share/doc/javatest/regtest/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,12 @@ and where `0` identifies the first test.

If you specify `?`_string_ after the name of a test, the _string_ will be
passed down to the test, for the test to filter the parts of the test to be
executed. For any tests executed by JUnit Platform, the string is interpreted
as the name of a single method in the test to be executed. If you give
conflicting values for the string, including not setting any value, the last
executed. For any tests executed by JUnit Platform, the string is by default interpreted
as the name of a single method in the test to be executed. However, it is also
possible to use other JUnit selectors by prefixing the query string with `junit-select:`.
The rest of the string can then be any selector identifier as listed in the left-most
column of the table found here: https://junit.org/junit5/docs/current/user-guide/#running-tests-discovery-selectors
If you give conflicting values for the string, including not setting any value, the last
one specified will be used.

If you wish to specify a long list of arguments, you can put the list in a file
Expand Down Expand Up @@ -491,12 +494,27 @@ Note that in addition to the command-line options just listed, a test
may contain tags such as `@requires` and `@modules` that determine whether
a test should be run on any particular system.

### How do I run a single test method in a JUnit test?
### How do I run a single test method or class in a JUnit test?

Specify the test and method name on the command-line with the `?` syntax:

path-to-test?method-name

This will run a method called `method-name`, having no parameters, in the top-level test class.

To run a parameterized test method, the extended selector syntax has to be used. For example:

path-to-test?junit-select:method:class-name#method-name(param-type, ...param-type)

The format supported by the `method` selector is described in greater detail in the documentation of
[DiscoverySelectors::selectMethod](https://junit.org/junit5/docs/current/api/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectMethod(java.lang.String)).

To run a specific nested test class, annotated with the `@Nested` annotation, the following can be used:

path-to-test?junit-select:class:enclosing-class-name$nested-class-name

Note that in this case, the string after `class:` is the binary name of the nested class, as returned by `Class::getName`.

See [How do I specify which tests to run?](#how-do-i-specify-which-tests-to-run).

### How do I specify the JDK to use?
Expand Down
88 changes: 83 additions & 5 deletions test/junitQueryTest/JUnitQueryTest.gmk
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -40,10 +40,10 @@ $(BUILDTESTDIR)/JUnitQueryTest.all.ok: $(JTREG_IMAGEDIR)/lib/javatest.jar \
$(TESTDIR)/junitQueryTest/ \
> $(@:%.ok=%)/jt.log 2>&1
OUT=$$( $(ECHO) $$( $(AWK) '/STDOUT:/,/STDERR:/{ print $0; } { }' $(@:%.ok=%)/jt.log | $(GREP) -v STD ) ) ; \
if [ "$$OUT" != "Test1.m11 Test1.m12 Test1.m13 Test2.m21 Test2.m22 Test2.m23" ]; then \
if [ "$$OUT" != "Test1.parameterized Test1.m11 Test1.m12 Test1.m13 Test1.nested Test2.m21 Test2.m22 Test2.m23" ]; then \
echo "unexpected set of tests run: $$OUT"; exit 1 ; \
fi
$(GREP) "a/b/c/ .* tests: 6, skipped: 0, started: 6, succeeded: 6, failed: 0, aborted: 0" $(@:%.ok=%)/report/text/junit.txt
$(GREP) "a/b/c/ .* tests: 8, skipped: 0, started: 8, succeeded: 8, failed: 0, aborted: 0" $(@:%.ok=%)/report/text/junit.txt
echo $@ passed at `date` > $@

TESTS.jtreg += $(BUILDTESTDIR)/JUnitQueryTest.all.ok
Expand Down Expand Up @@ -174,10 +174,10 @@ $(BUILDTESTDIR)/JUnitQueryTest.m13.all.ok: $(JTREG_IMAGEDIR)/lib/javatest.jar \
$(TESTDIR)/junitQueryTest/a/b/c/Test1.java \
> $(@:%.ok=%)/jt.log 2>&1
OUT=$$( $(ECHO) $$( $(AWK) '/STDOUT:/,/STDERR:/{ print $0; } { }' $(@:%.ok=%)/jt.log | $(GREP) -v STD ) ) ; \
if [ "$$OUT" != "Test1.m11 Test1.m12 Test1.m13" ]; then \
if [ "$$OUT" != "Test1.parameterized Test1.m11 Test1.m12 Test1.m13 Test1.nested" ]; then \
echo "unexpected set of tests run: $$OUT"; exit 1 ; \
fi
$(GREP) "a/b/c/ .* tests: 3, skipped: 0, started: 3, succeeded: 3, failed: 0, aborted: 0" $(@:%.ok=%)/report/text/junit.txt
$(GREP) "a/b/c/ .* tests: 5, skipped: 0, started: 5, succeeded: 5, failed: 0, aborted: 0" $(@:%.ok=%)/report/text/junit.txt
echo $@ passed at `date` > $@

TESTS.jtreg += $(BUILDTESTDIR)/JUnitQueryTest.m13.all.ok
Expand Down Expand Up @@ -209,6 +209,81 @@ $(BUILDTESTDIR)/JUnitQueryTest.all.m13.ok: $(JTREG_IMAGEDIR)/lib/javatest.jar \

TESTS.jtreg += $(BUILDTESTDIR)/JUnitQueryTest.all.m13.ok

#----------------------------------------------------------------------
#
# parameterizedTest

$(BUILDTESTDIR)/JUnitQueryTest.parameterized.ok: $(JTREG_IMAGEDIR)/lib/javatest.jar \
$(JTREG_IMAGEDIR)/lib/jtreg.jar \
$(JTREG_IMAGEDIR)/bin/jtreg
$(RM) $(@:%.ok=%)
$(MKDIR) $(@:%.ok=%)
JT_JAVA=$(JDKHOME) JTHOME=$(JTREG_IMAGEDIR) \
$(JTREG_IMAGEDIR)/bin/jtreg $(JTREG_OPTS) \
-w:$(@:%.ok=%)/work -r:$(@:%.ok=%)/report \
-jdk:$(JDKHOME) \
-va \
"$(TESTDIR)/junitQueryTest/a/b/c/Test1.java?junit-select:method:Test1#parameterized(java.lang.String,Test1\$$NestedClass,boolean,byte,char,short,int,long,float,double,[Ljava.lang.String;,[Z,[B,[C,[S,[I,[J,[F,[D)" \
> $(@:%.ok=%)/jt.log 2>&1
OUT=$$( $(ECHO) $$( $(AWK) '/STDOUT:/,/STDERR:/{ print $0; } { }' $(@:%.ok=%)/jt.log | $(GREP) -v STD ) ) ; \
if [ "$$OUT" != "Test1.parameterized" ]; then \
echo "unexpected set of tests run: $$OUT"; exit 1 ; \
fi
$(GREP) "a/b/c/ .* tests: 1, skipped: 0, started: 1, succeeded: 1, failed: 0, aborted: 0" $(@:%.ok=%)/report/text/junit.txt
echo $@ passed at `date` > $@

TESTS.jtreg += $(BUILDTESTDIR)/JUnitQueryTest.parameterized.ok

#----------------------------------------------------------------------
#
# nested class

$(BUILDTESTDIR)/JUnitQueryTest.nested.class.ok: $(JTREG_IMAGEDIR)/lib/javatest.jar \
$(JTREG_IMAGEDIR)/lib/jtreg.jar \
$(JTREG_IMAGEDIR)/bin/jtreg
$(RM) $(@:%.ok=%)
$(MKDIR) $(@:%.ok=%)
JT_JAVA=$(JDKHOME) JTHOME=$(JTREG_IMAGEDIR) \
$(JTREG_IMAGEDIR)/bin/jtreg $(JTREG_OPTS) \
-w:$(@:%.ok=%)/work -r:$(@:%.ok=%)/report \
-jdk:$(JDKHOME) \
-va \
"$(TESTDIR)/junitQueryTest/a/b/c/Test1.java?junit-select:class:Test1\$$NestedTests" \
> $(@:%.ok=%)/jt.log 2>&1
OUT=$$( $(ECHO) $$( $(AWK) '/STDOUT:/,/STDERR:/{ print $0; } { }' $(@:%.ok=%)/jt.log | $(GREP) -v STD ) ) ; \
if [ "$$OUT" != "Test1.nested" ]; then \
echo "unexpected set of tests run: $$OUT"; exit 1 ; \
fi
$(GREP) "a/b/c/ .* tests: 1, skipped: 0, started: 1, succeeded: 1, failed: 0, aborted: 0" $(@:%.ok=%)/report/text/junit.txt
echo $@ passed at `date` > $@

TESTS.jtreg += $(BUILDTESTDIR)/JUnitQueryTest.nested.class.ok

#----------------------------------------------------------------------
#
# Top-level class that looks like a group name. Testing whether we detect this correctly as a query string

$(BUILDTESTDIR)/JUnitQueryTest.toplevel.class.ok: $(JTREG_IMAGEDIR)/lib/javatest.jar \
$(JTREG_IMAGEDIR)/lib/jtreg.jar \
$(JTREG_IMAGEDIR)/bin/jtreg
$(RM) $(@:%.ok=%)
$(MKDIR) $(@:%.ok=%)
JT_JAVA=$(JDKHOME) JTHOME=$(JTREG_IMAGEDIR) \
$(JTREG_IMAGEDIR)/bin/jtreg $(JTREG_OPTS) \
-w:$(@:%.ok=%)/work -r:$(@:%.ok=%)/report \
-jdk:$(JDKHOME) \
-va \
"$(TESTDIR)/junitQueryTest/a/b/c/Test1.java?junit-select:class:Test1" \
> $(@:%.ok=%)/jt.log 2>&1
OUT=$$( $(ECHO) $$( $(AWK) '/STDOUT:/,/STDERR:/{ print $0; } { }' $(@:%.ok=%)/jt.log | $(GREP) -v STD ) ) ; \
if [ "$$OUT" != "Test1.parameterized Test1.m11 Test1.m12 Test1.m13 Test1.nested" ]; then \
echo "unexpected set of tests run: $$OUT"; exit 1 ; \
fi
$(GREP) "a/b/c/ .* tests: 5, skipped: 0, started: 5, succeeded: 5, failed: 0, aborted: 0" $(@:%.ok=%)/report/text/junit.txt
echo $@ passed at `date` > $@

TESTS.jtreg += $(BUILDTESTDIR)/JUnitQueryTest.toplevel.class.ok

#----------------------------------------------------------------------
#
# Invalid use of query
Expand Down Expand Up @@ -267,5 +342,8 @@ junit-query-tests: \
$(BUILDTESTDIR)/JUnitQueryTest.m12.m13.ok \
$(BUILDTESTDIR)/JUnitQueryTest.m13.all.ok \
$(BUILDTESTDIR)/JUnitQueryTest.all.m13.ok \
$(BUILDTESTDIR)/JUnitQueryTest.parameterized.ok \
$(BUILDTESTDIR)/JUnitQueryTest.nested.class.ok \
$(BUILDTESTDIR)/JUnitQueryTest.toplevel.class.ok \
$(BUILDTESTDIR)/JUnitQueryTest.invalidQuery.ok \
$(BUILDTESTDIR)/JUnitQueryTest.invalidMethod.ok
42 changes: 41 additions & 1 deletion test/junitQueryTest/a/b/c/Test1.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -21,7 +21,15 @@
* questions.
*/

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.params.provider.Arguments.arguments;

/**
* A collection of test methods, to help exercise the query mechanism.
Expand All @@ -42,4 +50,36 @@ public void m12() {
public void m13() {
System.out.println("Test1.m13");
}

@ParameterizedTest
@MethodSource("params")
public void parameterized(String str,
NestedClass nested,
boolean z, byte b, char c, short s, int i, long l, float f, double d,
String[] stra,
boolean[] za, byte[] ba, char[] ca, short[] sa, int[] ia, long[] la, float[] fa, double[] da) {
System.out.println("Test1.parameterized");
}

static Stream<Arguments> params() {
return Stream.of(
arguments(
"a",
new NestedClass(),
true, (byte) 42, 'x', (short) 42, 42, 42L, 42.0F, 42.0D,
new String[0],
new boolean[0], new byte[0], new char[0], new short[0], new int[0], new long[0], new float[0], new double[0]
)
);
}

static class NestedClass {}

@Nested
class NestedTests {
@Test
public void nested() {
System.out.println("Test1.nested");
}
}
}