Skip to content

Commit

Permalink
Allowing selecting methods by name in test suites
Browse files Browse the repository at this point in the history
This commit adds a new `@SelectMethod` annotation and a corresponding
`@SelectMethods` container annotation. The annotation can be used to
specify a method to select when running a test suite on the JUnit
Platform. These annotations allow users to selectively run certain test
methods within a test class, which can be useful for large test suites
with many methods.

The `@SelectMethod annotation` specifies the class and name of the
method to select, as well as the parameter types of the method if
applicable.

In addition to adding these new classes, three test cases have been
added to demonstrate their usage. These test cases cover scenarios such
as selecting a test method with no parameters, selecting a test method
with parameters, and using the @SelectMethods container to specify
multiple test methods to run.

Closes junit-team#3303.
Resolves junit-team#3012.

Co-authored-by: Marc Philipp <mail@marcphilipp.de>
  • Loading branch information
Mohammed2002k and marcphilipp committed May 19, 2023
1 parent 71c2381 commit 39cf2d5
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ JUnit repository on GitHub.

==== New Features and Improvements

*
* Add `@SelectMethod` selector support to `@Suite` test engine


[[release-notes-5.10.0-RC1-junit-jupiter]]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2015-2023 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.suite.api;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

/**
* {@code @SelectMethod} is a {@linkplain Repeatable repeatable} annotation that
* specifies a method to <em>select</em> when running a test suite on the JUnit
* Platform.
*
* @since 1.10
* @see Suite
* @see org.junit.platform.runner.JUnitPlatform
* @see org.junit.platform.engine.discovery.DiscoverySelectors#selectMethod(Class, String, String)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
@API(status = EXPERIMENTAL, since = "1.10")
@Repeatable(SelectMethods.class)
public @interface SelectMethod {

/**
* The class in which the method is declared, or a subclass thereof.
*/
Class<?> clazz();

/**
* The name of the method to select; never blank.
*/
String name();

/**
* The parameter types of the method to select.
*
* <p>This is typically a comma-separated list of atomic types, fully
* qualified class names, or array types; however, the exact syntax
* depends on the underlying test engine.
*
* <p>If the method takes no parameters, this attribute must be an
* empty string.
*
* <p>Array parameter types may be specified using either the JVM's internal
* String representation (e.g., {@code [[I} for {@code int[][]},
* {@code [Ljava.lang.String;} for {@code java.lang.String[]}, etc.) or
* <em>source code syntax</em> (e.g., {@code int[][]}, {@code java.lang.String[]},
* etc.).
* <p>
* <table class="plain">
* <caption>Examples</caption>
* <tr><th>Method</th><th>Parameter types list</th></tr>
* <tr><td>{@code java.lang.String.chars()}</td><td>The empty string</td></tr>
* <tr><td>{@code java.lang.String.equalsIgnoreCase(String)}</td><td>{@code java.lang.String}</td></tr>
* <tr><td>{@code java.lang.String.substring(int, int)}</td><td>{@code int, int}</td></tr>
* <tr><td>{@code example.Calc.avg(int[])}</td><td>{@code [I}</td></tr>
* <tr><td>{@code example.Calc.avg(int[])}</td><td>{@code int[]}</td></tr>
* <tr><td>{@code example.Matrix.multiply(double[][])}</td><td>{@code [[D}</td></tr>
* <tr><td>{@code example.Matrix.multiply(double[][])}</td><td>{@code double[][]}</td></tr>
* <tr><td>{@code example.Service.process(String[])}</td><td>{@code [Ljava.lang.String;}</td></tr>
* <tr><td>{@code example.Service.process(String[])}</td><td>{@code java.lang.String[]}</td></tr>
* <tr><td>{@code example.Service.process(String[][])}</td><td>{@code [[Ljava.lang.String;}</td></tr>
* <tr><td>{@code example.Service.process(String[][])}</td><td>{@code java.lang.String[][]}</td></tr>
* </table>
*/
String parameters() default "";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2015-2023 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.suite.api;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

/**
* {@code @SelectMethods} is a container for one or more
* {@link SelectMethod @SelectMethod} declarations.
*
* <p>Note, however, that use of the {@code @SelectMethods} container is
* completely optional since {@code @SelectMethod} is a
* {@linkplain java.lang.annotation.Repeatable repeatable} annotation.
*
* @since 1.10
* @see SelectMethod
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Documented
@API(status = EXPERIMENTAL, since = "1.10")
public @interface SelectMethods {

/**
* An array of one or more {@link SelectMethod @SelectMethod} declarations.
*/
SelectMethod[] value();

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.junit.platform.engine.discovery.DiscoverySelectors;
import org.junit.platform.engine.discovery.FilePosition;
import org.junit.platform.engine.discovery.FileSelector;
import org.junit.platform.engine.discovery.MethodSelector;
import org.junit.platform.engine.discovery.ModuleSelector;
import org.junit.platform.engine.discovery.PackageSelector;
import org.junit.platform.engine.discovery.UriSelector;
Expand Down Expand Up @@ -78,6 +79,12 @@ static List<ClassSelector> selectClasses(Class<?>... classes) {
// @formatter:on
}

static MethodSelector selectMethod(Class<?> clazz, String name, String parameters) {
Preconditions.notBlank(name, "Method name must not be null or blank");
Preconditions.notNull(parameters, "Method parameter types must not be null");
return DiscoverySelectors.selectMethod(clazz, name, parameters);
}

static List<ModuleSelector> selectModules(String... moduleNames) {
Preconditions.notNull(moduleNames, "Module names must not be null");
Preconditions.containsNoNullElements(moduleNames, "Individual module names must not be null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.junit.platform.engine.Filter;
import org.junit.platform.engine.discovery.ClassNameFilter;
import org.junit.platform.engine.discovery.ClassSelector;
import org.junit.platform.engine.discovery.MethodSelector;
import org.junit.platform.engine.discovery.PackageNameFilter;
import org.junit.platform.launcher.EngineFilter;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
Expand All @@ -55,6 +56,7 @@
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.SelectDirectories;
import org.junit.platform.suite.api.SelectFile;
import org.junit.platform.suite.api.SelectMethod;
import org.junit.platform.suite.api.SelectModules;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.SelectUris;
Expand Down Expand Up @@ -149,6 +151,10 @@ public SuiteLauncherDiscoveryRequestBuilder suite(Class<?> suiteClass) {
findAnnotationValues(suiteClass, SelectClasses.class, SelectClasses::value)
.map(this::selectClasses)
.ifPresent(this::selectors);
findRepeatableAnnotations(suiteClass, SelectMethod.class)
.stream()
.map(annotation -> selectMethod(annotation.clazz(), annotation.name(), annotation.parameters()))
.forEach(this::selectors);
findAnnotationValues(suiteClass, IncludeClassNamePatterns.class, IncludeClassNamePatterns::value)
.flatMap(SuiteLauncherDiscoveryRequestBuilder::trimmed)
.map(this::createIncludeClassNameFilter)
Expand Down Expand Up @@ -206,6 +212,11 @@ private List<ClassSelector> selectClasses(Class<?>... classes) {
return AdditionalDiscoverySelectors.selectClasses(classes);
}

private MethodSelector selectMethod(Class<?> clazz, String name, String parameters) {
selectedClassNames.add(clazz.getName());
return AdditionalDiscoverySelectors.selectMethod(clazz, name, parameters);
}

private ClassNameFilter createIncludeClassNameFilter(String... patterns) {
String[] combinedPatterns = Stream.concat(//
this.selectedClassNames.stream().map(Pattern::quote), //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@
import org.junit.platform.engine.discovery.ClassSelector;
import org.junit.platform.engine.discovery.ClasspathResourceSelector;
import org.junit.platform.engine.discovery.DirectorySelector;
import org.junit.platform.engine.discovery.DiscoverySelectors;
import org.junit.platform.engine.discovery.FilePosition;
import org.junit.platform.engine.discovery.FileSelector;
import org.junit.platform.engine.discovery.MethodSelector;
import org.junit.platform.engine.discovery.ModuleSelector;
import org.junit.platform.engine.discovery.PackageNameFilter;
import org.junit.platform.engine.discovery.PackageSelector;
Expand All @@ -58,6 +60,7 @@
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.SelectDirectories;
import org.junit.platform.suite.api.SelectFile;
import org.junit.platform.suite.api.SelectMethod;
import org.junit.platform.suite.api.SelectModules;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.SelectUris;
Expand Down Expand Up @@ -214,6 +217,59 @@ class Suite {
assertEquals(TestCase.class, exactlyOne(selectors).getJavaClass());
}

@Test
void selectOneMethodWithNoParameters() {
class TestClass {
void testMethod() {
}
}
@SelectMethod(clazz = TestClass.class, name = "testMethod")
class Suite {
}

LauncherDiscoveryRequest request = builder.suite(Suite.class).build();
List<MethodSelector> selectors = request.getSelectorsByType(MethodSelector.class);
assertEquals(DiscoverySelectors.selectMethod(TestClass.class, "testMethod"), exactlyOne(selectors));
}

@Test
void selectOneMethodWithOneParameters() {
class TestClass {
void testMethod(int i) {
}
}
@SelectMethod(clazz = TestClass.class, name = "testMethod", parameters = "int")
class Suite {
}

LauncherDiscoveryRequest request = builder.suite(Suite.class).build();
List<MethodSelector> selectors = request.getSelectorsByType(MethodSelector.class);
assertEquals(DiscoverySelectors.selectMethod(TestClass.class, "testMethod", "int"), exactlyOne(selectors));
}

@Test
void selectTwoMethodWithTwoParameters() {
class TestClass {
void firstTestMethod(int i, String j) {
}

void secondTestMethod(boolean i, float j) {
}
}
@SelectMethod(clazz = TestClass.class, name = "firstTestMethod", parameters = "int, java.lang.String")
@SelectMethod(clazz = TestClass.class, name = "secondTestMethod", parameters = "boolean, float")
class Suite {
}

LauncherDiscoveryRequest request = builder.suite(Suite.class).build();
List<MethodSelector> selectors = request.getSelectorsByType(MethodSelector.class);
assertEquals(2, selectors.size());
assertEquals(DiscoverySelectors.selectMethod(TestClass.class, "firstTestMethod", "int, java.lang.String"),
selectors.get(0));
assertEquals(DiscoverySelectors.selectMethod(TestClass.class, "secondTestMethod", "boolean, float"),
selectors.get(1));
}

@Test
void selectClasspathResource() {
@SelectClasspathResource("com.example.testcases")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.junit.platform.suite.engine.testsuites.MultipleSuite;
import org.junit.platform.suite.engine.testsuites.NestedSuite;
import org.junit.platform.suite.engine.testsuites.SelectClassesSuite;
import org.junit.platform.suite.engine.testsuites.SelectMethodsSuite;
import org.junit.platform.suite.engine.testsuites.SuiteDisplayNameSuite;
import org.junit.platform.suite.engine.testsuites.SuiteSuite;
import org.junit.platform.suite.engine.testsuites.ThreePartCyclicSuite;
Expand All @@ -74,6 +75,19 @@ void selectClasses() {
// @formatter:on
}

@Test
void selectMethods() {
// @formatter:off
EngineTestKit.engine(ENGINE_ID)
.selectors(selectClass(SelectMethodsSuite.class))
.execute()
.testEvents()
.assertThatEvents()
.haveExactly(1, event(test(MultipleTestsTestCase.class.getName(), "test()"), finishedSuccessfully()))
.doNotHave(event(test(MultipleTestsTestCase.class.getName(), "test2()")));
// @formatter:on
}

@Test
void suiteDisplayName() {
// @formatter:off
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2015-2023 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.suite.engine.testsuites;

import org.junit.platform.suite.api.SelectMethod;
import org.junit.platform.suite.api.Suite;
import org.junit.platform.suite.engine.testcases.MultipleTestsTestCase;

/**
* @since 1.10
*/
@Suite
@SelectMethod(clazz = MultipleTestsTestCase.class, name = "test")
public class SelectMethodsSuite {
}

0 comments on commit 39cf2d5

Please sign in to comment.