Skip to content
Open
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 @@ -143,7 +143,7 @@ public void junit5Suite() throws Exception {
.verifyTextInLog("Running pkg.domain.AxTest")
.assertThatLogLine(containsString("Running pkg.domain.BxTest"), equalTo(0));

TestFile xmlReportFile = outputValidator.getSurefireReportsXmlFile("TEST-pkg.JUnit5Tests.xml");
TestFile xmlReportFile = outputValidator.getSurefireReportsXmlFile("TEST-pkg.domain.AxTest.xml");
xmlReportFile.assertFileExists();

Source source = Input.fromFile(xmlReportFile.getFile()).build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,34 @@ private TestIdentifier findTopParent(TestIdentifier testIdentifier) {
// use deprecated method
testPlan.getTestIdentifier(
testIdentifier.getParentIdObject().get().toString());
return !parent.getParentIdObject().isPresent() ? testIdentifier : findTopParent(parent);
if (!parent.getParentIdObject().isPresent()) {
return testIdentifier;
}
// Inside a Suite the hierarchy contains a nested engine (like junit-jupiter under
// junit-platform-suite). Stop at that boundary so the test is attributed to its real test
// class rather than the Suite class. The ClassSource guard keeps traversing up for engines
// that expose no test class below them (like Cucumber features/scenarios), so those tests
// fall back to the enclosing Suite class instead of being dropped.
if (isEngineIdentifier(parent) && hasClassSource(testIdentifier)) {
return testIdentifier;
}
return findTopParent(parent);
}

private static boolean hasClassSource(TestIdentifier testIdentifier) {
return testIdentifier.getSource().filter(ClassSource.class::isInstance).isPresent();
}

private static boolean isEngineIdentifier(TestIdentifier testIdentifier) {
String uniqueId = testIdentifier.getUniqueId();
int lastOpen = uniqueId.lastIndexOf('[');
if (lastOpen >= 0) {
int colon = uniqueId.indexOf(':', lastOpen);
if (colon > lastOpen) {
return "engine".equals(uniqueId.substring(lastOpen + 1, colon));
}
}
return false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,123 @@ public void displayNamesIgnoredInReport() throws NoSuchMethodException {
assertEquals("some display name", value.getNameText());
}

@Test
public void notifiedWithActualTestClassNameWhenRunInsideSuite() throws Exception {
// Build a Suite hierarchy: SuiteEngine -> SuiteClass -> JupiterEngine -> TestClass -> method
// This simulates @Suite @SelectPackages("...") running tests from another class.
EngineDescriptor suiteEngine =
new EngineDescriptor(UniqueId.forEngine("junit-platform-suite"), "JUnit Platform Suite");

TestDescriptor suiteClass = new ClassTestDescriptor(
suiteEngine.getUniqueId().append("suite", MySuiteClass.class.getName()),
MySuiteClass.class,
new DefaultJupiterConfiguration(CONFIG_PARAMS, OUTPUT_DIRECTORY));
suiteEngine.addChild(suiteClass);

TestDescriptor jupiterEngine =
new AbstractTestDescriptor(
suiteClass.getUniqueId().append("engine", "junit-jupiter"), "JUnit Jupiter") {
@Override
public Type getType() {
return Type.CONTAINER;
}
};
suiteClass.addChild(jupiterEngine);

TestDescriptor testClass = new ClassTestDescriptor(
jupiterEngine.getUniqueId().append("class", MyTestClass.class.getName()),
MyTestClass.class,
new DefaultJupiterConfiguration(CONFIG_PARAMS, OUTPUT_DIRECTORY));
jupiterEngine.addChild(testClass);

TestDescriptor method = new TestMethodTestDescriptor(
testClass.getUniqueId().append("method", MY_TEST_METHOD_NAME),
MyTestClass.class,
MyTestClass.class.getDeclaredMethod(MY_TEST_METHOD_NAME),
Collections::emptyList,
new DefaultJupiterConfiguration(CONFIG_PARAMS, OUTPUT_DIRECTORY));
testClass.addChild(method);

TestPlan plan = TestPlan.from(false, singletonList(suiteEngine), CONFIG_PARAMS, OUTPUT_DIRECTORY);
adapter.testPlanExecutionStarted(plan);

adapter.executionStarted(TestIdentifier.from(suiteEngine));
adapter.executionStarted(TestIdentifier.from(suiteClass));
adapter.executionStarted(TestIdentifier.from(jupiterEngine));
adapter.executionStarted(TestIdentifier.from(testClass));
adapter.executionStarted(TestIdentifier.from(method));

ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass(ReportEntry.class);
adapter.executionFinished(TestIdentifier.from(method), failed(new AssertionError("fail")));
verify(listener).testFailed(entryCaptor.capture());

ReportEntry entry = entryCaptor.getValue();
// The source name must be the actual test class, not the Suite class
assertEquals(MyTestClass.class.getName(), entry.getSourceName());
assertEquals(MY_TEST_METHOD_NAME, entry.getName());
}

@Test
public void notifiedWithSuiteClassNameWhenEngineHasNoTestClass() {
// Build a Suite hierarchy whose nested engine exposes no test class right below it, as
// Cucumber does: SuiteEngine -> SuiteClass -> CucumberEngine -> feature -> scenario.
// The feature/scenario nodes carry no ClassSource, so the test must be attributed to the
// enclosing Suite class instead of being dropped (see issue #3264 follow-up / Cucumber IT).
EngineDescriptor suiteEngine =
new EngineDescriptor(UniqueId.forEngine("junit-platform-suite"), "JUnit Platform Suite");

TestDescriptor suiteClass = new ClassTestDescriptor(
suiteEngine.getUniqueId().append("suite", MySuiteClass.class.getName()),
MySuiteClass.class,
new DefaultJupiterConfiguration(CONFIG_PARAMS, OUTPUT_DIRECTORY));
suiteEngine.addChild(suiteClass);

TestDescriptor cucumberEngine =
new AbstractTestDescriptor(suiteClass.getUniqueId().append("engine", "cucumber"), "Cucumber") {
@Override
public Type getType() {
return Type.CONTAINER;
}
};
suiteClass.addChild(cucumberEngine);

TestDescriptor feature =
new AbstractTestDescriptor(cucumberEngine.getUniqueId().append("feature", "sum.feature"), "Sum test") {
@Override
public Type getType() {
return Type.CONTAINER;
}
};
cucumberEngine.addChild(feature);

TestDescriptor scenario =
new AbstractTestDescriptor(feature.getUniqueId().append("scenario", "1"), "Invalid test") {
@Override
public Type getType() {
return Type.TEST;
}
};
feature.addChild(scenario);

TestPlan plan = TestPlan.from(false, singletonList(suiteEngine), CONFIG_PARAMS, OUTPUT_DIRECTORY);
adapter.testPlanExecutionStarted(plan);

adapter.executionStarted(TestIdentifier.from(suiteEngine));
adapter.executionStarted(TestIdentifier.from(suiteClass));
adapter.executionStarted(TestIdentifier.from(cucumberEngine));
adapter.executionStarted(TestIdentifier.from(feature));
adapter.executionStarted(TestIdentifier.from(scenario));

ArgumentCaptor<ReportEntry> entryCaptor = ArgumentCaptor.forClass(ReportEntry.class);
adapter.executionFinished(TestIdentifier.from(scenario), failed(new AssertionError("fail")));
verify(listener).testFailed(entryCaptor.capture());

ReportEntry entry = entryCaptor.getValue();
// No test class exists below the Cucumber engine, so the Suite class is the attribution.
assertEquals(MySuiteClass.class.getName(), entry.getSourceName());
assertEquals("Invalid test", entry.getName());
}

private static TestIdentifier newMethodIdentifier() throws Exception {
return TestIdentifier.from(newMethodDescriptor());
}
Expand Down Expand Up @@ -720,6 +837,8 @@ void myTestMethod(String foo) {}
void myNamedTestMethod() {}
}

private static class MySuiteClass {}

static class TestMethodTestDescriptorWithDisplayName extends AbstractTestDescriptor {
private TestMethodTestDescriptorWithDisplayName(
UniqueId uniqueId, Class<?> testClass, Method testMethod, String displayName) {
Expand Down
Loading