Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -47,6 +47,8 @@ repository on GitHub.
assertion.
* Stop reporting discovery issues for synthetic methods, particularly in conjunction with
Kotlin suspend functions.
* Fix support for test methods with the same signature as a package-private methods
declared in super classes in different packages.

[[release-notes-6.0.1-junit-jupiter-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,36 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.junit.platform.commons.PreconditionViolationException;
import org.junit.platform.commons.support.ReflectionSupport;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.ReflectionUtils;

/**
* @since 5.0
*/
class MethodFinder {

// Pattern: methodName(comma-separated list of parameter type names)
private static final Pattern METHOD_PATTERN = Pattern.compile("(.+)\\((.*)\\)");
// Pattern: [declaringClassName#]methodName(comma-separated list of parameter type names)
private static final Pattern METHOD_PATTERN = Pattern.compile(
"(?:(?<declaringClass>.+)#)?(?<method>.+)\\((?<parameters>.*)\\)");

Optional<Method> findMethod(String methodSpecPart, Class<?> clazz) {
Matcher matcher = METHOD_PATTERN.matcher(methodSpecPart);

Preconditions.condition(matcher.matches(),
() -> "Method [%s] does not match pattern [%s]".formatted(methodSpecPart, METHOD_PATTERN));

String methodName = matcher.group(1);
String parameterTypeNames = matcher.group(2);
return ReflectionSupport.findMethod(clazz, methodName, parameterTypeNames);
Class<?> targetClass = clazz;
String declaringClass = matcher.group("declaringClass");
if (declaringClass != null) {
targetClass = ReflectionUtils.tryToLoadClass(declaringClass).getNonNullOrThrow(
cause -> new PreconditionViolationException(
"Could not load declaring class with name: " + declaringClass, cause));
}
String methodName = matcher.group("method");
String parameterTypeNames = matcher.group("parameters");
return ReflectionSupport.findMethod(targetClass, methodName, parameterTypeNames);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toSet;
import static org.junit.platform.commons.util.ReflectionUtils.isPackagePrivate;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.matches;
import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved;
Expand Down Expand Up @@ -223,15 +224,22 @@ Optional<TestDescriptor> resolveUniqueIdIntoTestDescriptor(UniqueId uniqueId, Co

private TestDescriptor createTestDescriptor(TestDescriptor parent, Class<?> testClass, Method method,
JupiterConfiguration configuration) {
UniqueId uniqueId = createUniqueId(method, parent);
UniqueId uniqueId = createUniqueId(method, parent, testClass);
return testDescriptorFactory.create(uniqueId, testClass, method,
((TestClassAware) parent)::getEnclosingTestClasses, configuration);
}

private UniqueId createUniqueId(Method method, TestDescriptor parent) {
String methodId = "%s(%s)".formatted(method.getName(),
ClassUtils.nullSafeToString(method.getParameterTypes()));
return parent.getUniqueId().append(segmentType, methodId);
private UniqueId createUniqueId(Method method, TestDescriptor parent, Class<?> testClass) {
return parent.getUniqueId().append(segmentType, computeMethodId(method, testClass));
}

private static String computeMethodId(Method method, Class<?> testClass) {
var parameterTypes = ClassUtils.nullSafeToString(method.getParameterTypes());
if (isPackagePrivate(method)
&& !method.getDeclaringClass().getPackageName().equals(testClass.getPackageName())) {
return "%s#%s(%s)".formatted(method.getDeclaringClass().getName(), method.getName(), parameterTypes);
}
return "%s(%s)".formatted(method.getName(), parameterTypes);
}

interface TestDescriptorFactory {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1872,7 +1872,11 @@ private static boolean isMethodOverriddenBy(Method upper, Method lower) {
return hasCompatibleSignature(upper, lower.getName(), lower.getParameterTypes());
}

private static boolean isPackagePrivate(Member member) {
/**
* @since 6.0.1
*/
@API(status = INTERNAL, since = "6.0.1")
public static boolean isPackagePrivate(Member member) {
int modifiers = member.getModifiers();
return !(Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers) || Modifier.isPrivate(modifiers));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,17 @@ public boolean equals(Object o) {
return false;
}
MethodSelector that = (MethodSelector) o;
return Objects.equals(this.className, that.className)//
var equal = Objects.equals(this.className, that.className)//
&& Objects.equals(this.methodName, that.methodName)//
&& Objects.equals(this.parameterTypeNames, that.parameterTypeNames);
if (equal) {
var thisJavaMethod = this.javaMethod;
var thatJavaMethod = that.javaMethod;
if (thisJavaMethod != null && thatJavaMethod != null) {
return thisJavaMethod.equals(thatJavaMethod);
}
}
return equal;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2015-2025 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.jupiter.engine;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;

import java.util.Map;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestReporter;
import org.junit.jupiter.engine.subpackage.SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase;
import org.junit.platform.engine.TestDescriptor;
import org.junit.platform.engine.reporting.ReportEntry;
import org.junit.platform.testkit.engine.EngineExecutionResults;

/**
* @since 6.0.1
*/
class TestMethodOverridingTests extends AbstractJupiterTestEngineTests {

@Test
void bothPackagePrivateTestMethodsAreDiscovered() {
var results = discoverTestsForClass(DuplicateTestMethodDueToPackagePrivateVisibilityTestCase.class);
var classDescriptor = getOnlyElement(results.getEngineDescriptor().getChildren());

assertThat(classDescriptor.getChildren()).hasSize(2);

var parentUniqueId = classDescriptor.getUniqueId();
var inheritedMethodUniqueId = parentUniqueId.append("method",
"org.junit.jupiter.engine.subpackage.SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase#"
+ "test(org.junit.jupiter.api.TestInfo, org.junit.jupiter.api.TestReporter)");
var declaredMethodUniqueId = parentUniqueId.append("method",
"test(org.junit.jupiter.api.TestInfo, org.junit.jupiter.api.TestReporter)");

assertThat(classDescriptor.getChildren()) //
.extracting(TestDescriptor::getUniqueId) //
.containsExactly(inheritedMethodUniqueId, declaredMethodUniqueId);

results = discoverTests(selectUniqueId(inheritedMethodUniqueId));
classDescriptor = getOnlyElement(results.getEngineDescriptor().getChildren());
assertThat(classDescriptor.getChildren()) //
.extracting(TestDescriptor::getUniqueId) //
.containsExactly(inheritedMethodUniqueId);

results = discoverTests(selectUniqueId(declaredMethodUniqueId));
classDescriptor = getOnlyElement(results.getEngineDescriptor().getChildren());
assertThat(classDescriptor.getChildren()) //
.extracting(TestDescriptor::getUniqueId) //
.containsExactly(declaredMethodUniqueId);
}

@Test
void bothPackagePrivateTestMethodsAreExecuted() throws Exception {
var results = executeTestsForClass(DuplicateTestMethodDueToPackagePrivateVisibilityTestCase.class);

results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2));
assertThat(allReportEntries(results)) //
.containsExactly(
Map.of("invokedSuper",
SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.class.getDeclaredMethod(
"test", TestInfo.class, TestReporter.class).toGenericString()),
Map.of("invokedChild",
DuplicateTestMethodDueToPackagePrivateVisibilityTestCase.class.getDeclaredMethod("test",
TestInfo.class, TestReporter.class).toGenericString()));
}

private static Stream<Map<String, String>> allReportEntries(EngineExecutionResults results) {
return results.allEvents().reportingEntryPublished() //
.map(event -> event.getRequiredPayload(ReportEntry.class)) //
.map(ReportEntry::getKeyValuePairs);
}

@SuppressWarnings("JUnitMalformedDeclaration")
static class DuplicateTestMethodDueToPackagePrivateVisibilityTestCase
extends SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase {

// @Override
@Test
void test(TestInfo testInfo, TestReporter reporter) {
reporter.publishEntry("invokedChild", testInfo.getTestMethod().orElseThrow().toGenericString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestReporter;

/**
* @since 5.9
Expand All @@ -28,7 +30,8 @@ void beforeEach() {
}

@Test
void test() {
void test(TestInfo testInfo, TestReporter reporter) {
reporter.publishEntry("invokedSuper", testInfo.getTestMethod().orElseThrow().toGenericString());
assertThat(this.beforeEachInvoked).isTrue();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,25 @@ void usesClassClassLoader() {
assertThat(selector.getClassLoader()).isNotNull().isSameAs(getClass().getClassLoader());
}

private static class TestCase {
@Test
void distinguishesDeclaringClass() throws Exception {
var parentMethodSelector = new MethodSelector(TestCase.class,
ParentTestCase.class.getDeclaredMethod("method", int.class, boolean.class));
var childMethodSelector = new MethodSelector(TestCase.class,
TestCase.class.getDeclaredMethod("method", int.class, boolean.class));

assertThat(parentMethodSelector).isNotEqualTo(childMethodSelector);
assertThat(childMethodSelector).isNotEqualTo(parentMethodSelector);
}

private static class ParentTestCase {

@SuppressWarnings("unused")
private void method(int num, boolean flag) {
}
}

private static class TestCase extends ParentTestCase {

@SuppressWarnings("unused")
void method(int num, boolean flag) {
Expand Down
Loading