From a40da537839f58705bbfb04ba9a65a4b95ce3559 Mon Sep 17 00:00:00 2001 From: Jean Gauthier Date: Fri, 6 Dec 2024 14:00:21 +0100 Subject: [PATCH 1/7] Add @ExpressionLanguage annotation, and reference it in ParameterizedTestMethodContext --- gradle/libs.versions.toml | 1 + .../params/ArgumentCountValidator.java | 2 +- .../jupiter/params/ExpressionLanguage.java | 10 + .../params/ExpressionLanguageAdapter.java | 8 + .../params/ParameterizedTestExtension.java | 13 +- .../ParameterizedTestMethodContext.java | 8 +- jupiter-tests/jupiter-tests.gradle.kts | 1 + .../params/ExpressionLanguageTests.java | 199 ++++++++++++++++++ .../ParameterizedTestExtensionTests.java | 2 +- .../ParameterizedTestMethodContextTests.java | 5 +- .../ParameterizedTestNameFormatterTests.java | 3 +- 11 files changed, 239 insertions(+), 13 deletions(-) create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguage.java create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c3bfb3f87128..be1a8450ac7e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -55,6 +55,7 @@ mavenSurefirePlugin = { module = "org.apache.maven.plugins:maven-surefire-plugin memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.8.1" } mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" } +mustache = { module = "com.github.spullara.mustache.java:compiler", version = "0.9.10"} nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version = "0.0.11" } opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" } openTestReporting-cli = { module = "org.opentest4j.reporting:open-test-reporting-cli", version.ref = "openTestReporting" } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java index 220825d9817a..4d12fa838fa7 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java @@ -69,7 +69,7 @@ private void validateArgumentCount(ExtensionContext extensionContext, Arguments } private ArgumentCountValidationMode getArgumentCountValidationMode(ExtensionContext extensionContext) { - ParameterizedTest parameterizedTest = methodContext.annotation; + ParameterizedTest parameterizedTest = methodContext.parameterizedTestAnnotation; if (parameterizedTest.argumentCountValidation() != ArgumentCountValidationMode.DEFAULT) { return parameterizedTest.argumentCountValidation(); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguage.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguage.java new file mode 100644 index 000000000000..b4287fa9908c --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguage.java @@ -0,0 +1,10 @@ +package org.junit.jupiter.params; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface ExpressionLanguage { + + Class value(); +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java new file mode 100644 index 000000000000..afeb81c8dac0 --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java @@ -0,0 +1,8 @@ +package org.junit.jupiter.params; + +import java.util.Map; + +public interface ExpressionLanguageAdapter { + + String evaluate(String template, Map context); +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java index 8aed7644f2d8..41dbd19a574c 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java @@ -46,13 +46,14 @@ public boolean supportsTestTemplate(ExtensionContext context) { } Method templateMethod = context.getTestMethod().get(); - Optional annotation = findAnnotation(templateMethod, ParameterizedTest.class); - if (!annotation.isPresent()) { + Optional expressionLanguageAnnotation = findAnnotation(templateMethod, ExpressionLanguage.class); + Optional parameterizedTestAnnotation = findAnnotation(templateMethod, ParameterizedTest.class); + if (!parameterizedTestAnnotation.isPresent()) { return false; } ParameterizedTestMethodContext methodContext = new ParameterizedTestMethodContext(templateMethod, - annotation.get()); + parameterizedTestAnnotation.get(), expressionLanguageAnnotation); Preconditions.condition(methodContext.hasPotentiallyValidSignature(), () -> String.format( @@ -86,7 +87,7 @@ public Stream provideTestTemplateInvocationContex return createInvocationContext(formatter, methodContext, arguments, invocationCount.intValue()); }) .onClose(() -> - Preconditions.condition(invocationCount.get() > 0 || methodContext.annotation.allowZeroInvocations(), + Preconditions.condition(invocationCount.get() > 0 || methodContext.parameterizedTestAnnotation.allowZeroInvocations(), "Configuration error: You must configure at least one set of arguments for this @ParameterizedTest")); // @formatter:on } @@ -94,7 +95,7 @@ public Stream provideTestTemplateInvocationContex @Override public boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext extensionContext) { ParameterizedTestMethodContext methodContext = getMethodContext(extensionContext); - return methodContext.annotation.allowZeroInvocations(); + return methodContext.parameterizedTestAnnotation.allowZeroInvocations(); } private ParameterizedTestMethodContext getMethodContext(ExtensionContext extensionContext) { @@ -115,7 +116,7 @@ private TestTemplateInvocationContext createInvocationContext(ParameterizedTestN private ParameterizedTestNameFormatter createNameFormatter(ExtensionContext extensionContext, ParameterizedTestMethodContext methodContext) { - String name = methodContext.annotation.name(); + String name = methodContext.parameterizedTestAnnotation.name(); String pattern = name.equals(DEFAULT_DISPLAY_NAME) ? extensionContext.getConfigurationParameter(DISPLAY_NAME_PATTERN_KEY) // .orElse(ParameterizedTest.DEFAULT_DISPLAY_NAME) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java index 4c07e01f81ca..10c0d624509f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java @@ -44,15 +44,17 @@ class ParameterizedTestMethodContext { final Method method; - final ParameterizedTest annotation; + final ParameterizedTest parameterizedTestAnnotation; + final Optional expressionLanguageAnnotation; private final Parameter[] parameters; private final Resolver[] resolvers; private final List resolverTypes; - ParameterizedTestMethodContext(Method method, ParameterizedTest annotation) { + ParameterizedTestMethodContext(Method method, ParameterizedTest parameterizedTestAnnotation, Optional expressionLanguageAnnotation) { this.method = Preconditions.notNull(method, "method must not be null"); - this.annotation = Preconditions.notNull(annotation, "annotation must not be null"); + this.parameterizedTestAnnotation = Preconditions.notNull(parameterizedTestAnnotation, "parameterizedTestAnnotation must not be null"); + this.expressionLanguageAnnotation = expressionLanguageAnnotation; this.parameters = method.getParameters(); this.resolvers = new Resolver[this.parameters.length]; this.resolverTypes = new ArrayList<>(this.parameters.length); diff --git a/jupiter-tests/jupiter-tests.gradle.kts b/jupiter-tests/jupiter-tests.gradle.kts index 061920f4d96d..8721fdc11811 100644 --- a/jupiter-tests/jupiter-tests.gradle.kts +++ b/jupiter-tests/jupiter-tests.gradle.kts @@ -21,6 +21,7 @@ dependencies { testImplementation(libs.kotlinx.coroutines) testImplementation(libs.groovy4) testImplementation(libs.memoryfilesystem) + testImplementation(libs.mustache) testImplementation(testFixtures(projects.junitJupiterApi)) testImplementation(testFixtures(projects.junitJupiterEngine)) testImplementation(testFixtures(projects.junitPlatformLauncher)) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java new file mode 100644 index 000000000000..32b80421fb60 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java @@ -0,0 +1,199 @@ +package org.junit.jupiter.params; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.ParameterizedTestExtension.METHOD_CONTEXT_KEY; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.engine.execution.NamespaceAwareStore; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; + +public class ExpressionLanguageTests { + + private final ParameterizedTestExtension parameterizedTestExtension = new ParameterizedTestExtension(); + + @Test + void throwsExceptionWhenParameterizedTestIsNotInvokedAtLeastOnce() { + var extensionContext = getExtensionContextReturningSingleMethod(new TestCaseWithAnnotatedMethod()); + this.parameterizedTestExtension.supportsTestTemplate(extensionContext); + var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext); + assertThat(stream.findFirst().get().getDisplayName(0)).isEqualTo("hello!"); + } + + private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase) { + return getExtensionContextReturningSingleMethod(testCase, ignored -> Optional.empty()); + } + + private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase, + Function> configurationSupplier) { + + var method = ReflectionUtils.findMethods(testCase.getClass(), + it -> "method".equals(it.getName())).stream().findFirst(); + + return new ExtensionContext() { + + private final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>(null); + + @Override + public Optional getTestMethod() { + return method; + } + + @Override + public Optional getParent() { + return Optional.empty(); + } + + @Override + public ExtensionContext getRoot() { + return this; + } + + @Override + public String getUniqueId() { + return null; + } + + @Override + public String getDisplayName() { + return null; + } + + @Override + public Set getTags() { + return null; + } + + @Override + public Optional getElement() { + return Optional.empty(); + } + + @Override + public Optional> getTestClass() { + return Optional.empty(); + } + + @Override + public Optional getTestInstanceLifecycle() { + return Optional.empty(); + } + + @Override + public java.util.Optional getTestInstance() { + return Optional.empty(); + } + + @Override + public Optional getTestInstances() { + return Optional.empty(); + } + + @Override + public Optional getExecutionException() { + return Optional.empty(); + } + + @Override + public Optional getConfigurationParameter(String key) { + return configurationSupplier.apply(key); + } + + @Override + public Optional getConfigurationParameter(String key, Function transformer) { + return configurationSupplier.apply(key).map(transformer); + } + + @Override + public void publishReportEntry(Map map) { + } + + @Override + public void publishFile(String fileName, ThrowingConsumer action) { + } + + @Override + public Store getStore(Namespace namespace) { + var store = new NamespaceAwareStore(this.store, namespace); + method // + .map(it -> new ParameterizedTestMethodContext(it, it.getAnnotation(ParameterizedTest.class), Optional.of(it.getAnnotation(ExpressionLanguage.class)))) // + .ifPresent(ctx -> store.put(METHOD_CONTEXT_KEY, ctx)); + return store; + } + + @Override + public ExecutionMode getExecutionMode() { + return ExecutionMode.SAME_THREAD; + } + + @Override + public ExecutableInvoker getExecutableInvoker() { + return new ExecutableInvoker() { + @Override + public Object invoke(Method method, Object target) { + return null; + } + + @Override + public T invoke(Constructor constructor, Object outerInstance) { + return ReflectionUtils.newInstance(constructor); + } + }; + } + }; + } + + static class TestCaseWithAnnotatedMethod { + + @ExpressionLanguage(MustacheAdapter.class) + @ParameterizedTest(name = "foo") + @ArgumentsSource(FooArgumentsProvider.class) + void method() { + } + + static class Foo { + String bar; + + public Foo(String bar) { + this.bar = bar; + } + } + + static class FooArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of(arguments(new TestCaseWithAnnotatedMethod.Foo("123")), arguments(new TestCaseWithAnnotatedMethod.Foo("456"))); + } + } + } + + static class MustacheAdapter implements ExpressionLanguageAdapter { + + @Override + public String evaluate(String template, Map context) { + return "hello!"; + } + } +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java index 638cdbf95e1f..5a4a72b64c5d 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java @@ -285,7 +285,7 @@ public void publishFile(String fileName, ThrowingConsumer action) { public Store getStore(Namespace namespace) { var store = new NamespaceAwareStore(this.store, namespace); method // - .map(it -> new ParameterizedTestMethodContext(it, it.getAnnotation(ParameterizedTest.class))) // + .map(it -> new ParameterizedTestMethodContext(it, it.getAnnotation(ParameterizedTest.class), Optional.empty())) // .ifPresent(ctx -> store.put(METHOD_CONTEXT_KEY, ctx)); return store; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java index e2ce097ca4c5..e0554ef91d30 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java @@ -13,6 +13,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Optional; + import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.params.aggregator.AggregatorIntegrationTests.CsvToPerson; import org.junit.jupiter.params.aggregator.AggregatorIntegrationTests.Person; @@ -42,7 +44,8 @@ void invalidSignatures(String methodName) { private ParameterizedTestMethodContext createMethodContext(Class testClass, String methodName) { var method = ReflectionUtils.findMethods(testClass, m -> m.getName().equals(methodName)).getFirst(); - return new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class)); + return new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class), + Optional.empty()); } @SuppressWarnings("JUnitMalformedDeclaration") diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java index c95332c171bb..7906e2d8ab1b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java @@ -35,6 +35,7 @@ import java.time.ZoneId; import java.util.Arrays; import java.util.Locale; +import java.util.Optional; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Nested; @@ -331,7 +332,7 @@ private static ParameterizedTestNameFormatter formatter(String pattern, String d } private static ParameterizedTestNameFormatter formatter(String pattern, String displayName, Method method) { - var context = new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class)); + var context = new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class), Optional.empty()); return new ParameterizedTestNameFormatter(pattern, displayName, context, 512); } From e9c16190e2fde8964fc00a4a08eb69ca99894ef6 Mon Sep 17 00:00:00 2001 From: Jean Gauthier Date: Fri, 6 Dec 2024 14:40:56 +0100 Subject: [PATCH 2/7] Reformat code with eclipse formatter --- .../org/junit/jupiter/params/ExpressionLanguage.java | 1 + .../jupiter/params/ExpressionLanguageAdapter.java | 1 + .../jupiter/params/ParameterizedTestExtension.java | 6 ++++-- .../jupiter/params/ParameterizedTestMethodContext.java | 6 ++++-- .../junit/jupiter/params/ExpressionLanguageTests.java | 10 ++++++---- .../params/ParameterizedTestExtensionTests.java | 3 ++- .../params/ParameterizedTestMethodContextTests.java | 2 +- .../params/ParameterizedTestNameFormatterTests.java | 3 ++- 8 files changed, 21 insertions(+), 11 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguage.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguage.java index b4287fa9908c..289df8af5d79 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguage.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguage.java @@ -1,3 +1,4 @@ + package org.junit.jupiter.params; import java.lang.annotation.Retention; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java index afeb81c8dac0..a4f5bb611208 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java @@ -1,3 +1,4 @@ + package org.junit.jupiter.params; import java.util.Map; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java index 41dbd19a574c..99eb07c448e5 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java @@ -46,8 +46,10 @@ public boolean supportsTestTemplate(ExtensionContext context) { } Method templateMethod = context.getTestMethod().get(); - Optional expressionLanguageAnnotation = findAnnotation(templateMethod, ExpressionLanguage.class); - Optional parameterizedTestAnnotation = findAnnotation(templateMethod, ParameterizedTest.class); + Optional expressionLanguageAnnotation = findAnnotation(templateMethod, + ExpressionLanguage.class); + Optional parameterizedTestAnnotation = findAnnotation(templateMethod, + ParameterizedTest.class); if (!parameterizedTestAnnotation.isPresent()) { return false; } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java index 10c0d624509f..04d4bfdc0772 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java @@ -51,9 +51,11 @@ class ParameterizedTestMethodContext { private final Resolver[] resolvers; private final List resolverTypes; - ParameterizedTestMethodContext(Method method, ParameterizedTest parameterizedTestAnnotation, Optional expressionLanguageAnnotation) { + ParameterizedTestMethodContext(Method method, ParameterizedTest parameterizedTestAnnotation, + Optional expressionLanguageAnnotation) { this.method = Preconditions.notNull(method, "method must not be null"); - this.parameterizedTestAnnotation = Preconditions.notNull(parameterizedTestAnnotation, "parameterizedTestAnnotation must not be null"); + this.parameterizedTestAnnotation = Preconditions.notNull(parameterizedTestAnnotation, + "parameterizedTestAnnotation must not be null"); this.expressionLanguageAnnotation = expressionLanguageAnnotation; this.parameters = method.getParameters(); this.resolvers = new Resolver[this.parameters.length]; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java index 32b80421fb60..591eb29af78f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java @@ -1,3 +1,4 @@ + package org.junit.jupiter.params; import static org.assertj.core.api.Assertions.assertThat; @@ -25,7 +26,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; -import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; @@ -49,7 +49,7 @@ private ExtensionContext getExtensionContextReturningSingleMethod(Object testCas Function> configurationSupplier) { var method = ReflectionUtils.findMethods(testCase.getClass(), - it -> "method".equals(it.getName())).stream().findFirst(); + it -> "method".equals(it.getName())).stream().findFirst(); return new ExtensionContext() { @@ -137,7 +137,8 @@ public void publishFile(String fileName, ThrowingConsumer action) { public Store getStore(Namespace namespace) { var store = new NamespaceAwareStore(this.store, namespace); method // - .map(it -> new ParameterizedTestMethodContext(it, it.getAnnotation(ParameterizedTest.class), Optional.of(it.getAnnotation(ExpressionLanguage.class)))) // + .map(it -> new ParameterizedTestMethodContext(it, it.getAnnotation(ParameterizedTest.class), + Optional.of(it.getAnnotation(ExpressionLanguage.class)))) // .ifPresent(ctx -> store.put(METHOD_CONTEXT_KEY, ctx)); return store; } @@ -184,7 +185,8 @@ static class FooArgumentsProvider implements ArgumentsProvider { @Override public Stream provideArguments(ExtensionContext context) { - return Stream.of(arguments(new TestCaseWithAnnotatedMethod.Foo("123")), arguments(new TestCaseWithAnnotatedMethod.Foo("456"))); + return Stream.of(arguments(new TestCaseWithAnnotatedMethod.Foo("123")), + arguments(new TestCaseWithAnnotatedMethod.Foo("456"))); } } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java index 5a4a72b64c5d..90a468cdf57f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java @@ -285,7 +285,8 @@ public void publishFile(String fileName, ThrowingConsumer action) { public Store getStore(Namespace namespace) { var store = new NamespaceAwareStore(this.store, namespace); method // - .map(it -> new ParameterizedTestMethodContext(it, it.getAnnotation(ParameterizedTest.class), Optional.empty())) // + .map(it -> new ParameterizedTestMethodContext(it, it.getAnnotation(ParameterizedTest.class), + Optional.empty())) // .ifPresent(ctx -> store.put(METHOD_CONTEXT_KEY, ctx)); return store; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java index e0554ef91d30..e1f989ef306a 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java @@ -45,7 +45,7 @@ void invalidSignatures(String methodName) { private ParameterizedTestMethodContext createMethodContext(Class testClass, String methodName) { var method = ReflectionUtils.findMethods(testClass, m -> m.getName().equals(methodName)).getFirst(); return new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class), - Optional.empty()); + Optional.empty()); } @SuppressWarnings("JUnitMalformedDeclaration") diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java index 7906e2d8ab1b..0dcb214e1382 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java @@ -332,7 +332,8 @@ private static ParameterizedTestNameFormatter formatter(String pattern, String d } private static ParameterizedTestNameFormatter formatter(String pattern, String displayName, Method method) { - var context = new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class), Optional.empty()); + var context = new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class), + Optional.empty()); return new ParameterizedTestNameFormatter(pattern, displayName, context, 512); } From ab3ed379433d77dd509ae46b7996052fd7834a4d Mon Sep 17 00:00:00 2001 From: Jean Gauthier Date: Fri, 6 Dec 2024 14:45:56 +0100 Subject: [PATCH 3/7] Extract getExtensionContextReturningSingleMethod from ParameterizedTestExtensionTests to TestExtensionContext --- .../params/ExpressionLanguageTests.java | 141 +--------------- .../ParameterizedTestExtensionTests.java | 140 +--------------- .../jupiter/params/TestExtensionContext.java | 153 ++++++++++++++++++ 3 files changed, 155 insertions(+), 279 deletions(-) create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/params/TestExtensionContext.java diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java index 591eb29af78f..eb4ea28e554b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java @@ -2,32 +2,17 @@ package org.junit.jupiter.params; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.params.ParameterizedTestExtension.METHOD_CONTEXT_KEY; +import static org.junit.jupiter.params.TestExtensionContext.getExtensionContextReturningSingleMethod; import static org.junit.jupiter.params.provider.Arguments.arguments; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.nio.file.Path; import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; import java.util.stream.Stream; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.api.function.ThrowingConsumer; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.jupiter.engine.execution.NamespaceAwareStore; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; public class ExpressionLanguageTests { @@ -41,130 +26,6 @@ void throwsExceptionWhenParameterizedTestIsNotInvokedAtLeastOnce() { assertThat(stream.findFirst().get().getDisplayName(0)).isEqualTo("hello!"); } - private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase) { - return getExtensionContextReturningSingleMethod(testCase, ignored -> Optional.empty()); - } - - private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase, - Function> configurationSupplier) { - - var method = ReflectionUtils.findMethods(testCase.getClass(), - it -> "method".equals(it.getName())).stream().findFirst(); - - return new ExtensionContext() { - - private final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>(null); - - @Override - public Optional getTestMethod() { - return method; - } - - @Override - public Optional getParent() { - return Optional.empty(); - } - - @Override - public ExtensionContext getRoot() { - return this; - } - - @Override - public String getUniqueId() { - return null; - } - - @Override - public String getDisplayName() { - return null; - } - - @Override - public Set getTags() { - return null; - } - - @Override - public Optional getElement() { - return Optional.empty(); - } - - @Override - public Optional> getTestClass() { - return Optional.empty(); - } - - @Override - public Optional getTestInstanceLifecycle() { - return Optional.empty(); - } - - @Override - public java.util.Optional getTestInstance() { - return Optional.empty(); - } - - @Override - public Optional getTestInstances() { - return Optional.empty(); - } - - @Override - public Optional getExecutionException() { - return Optional.empty(); - } - - @Override - public Optional getConfigurationParameter(String key) { - return configurationSupplier.apply(key); - } - - @Override - public Optional getConfigurationParameter(String key, Function transformer) { - return configurationSupplier.apply(key).map(transformer); - } - - @Override - public void publishReportEntry(Map map) { - } - - @Override - public void publishFile(String fileName, ThrowingConsumer action) { - } - - @Override - public Store getStore(Namespace namespace) { - var store = new NamespaceAwareStore(this.store, namespace); - method // - .map(it -> new ParameterizedTestMethodContext(it, it.getAnnotation(ParameterizedTest.class), - Optional.of(it.getAnnotation(ExpressionLanguage.class)))) // - .ifPresent(ctx -> store.put(METHOD_CONTEXT_KEY, ctx)); - return store; - } - - @Override - public ExecutionMode getExecutionMode() { - return ExecutionMode.SAME_THREAD; - } - - @Override - public ExecutableInvoker getExecutableInvoker() { - return new ExecutableInvoker() { - @Override - public Object invoke(Method method, Object target) { - return null; - } - - @Override - public T invoke(Constructor constructor, Object outerInstance) { - return ReflectionUtils.newInstance(constructor); - } - }; - } - }; - } - static class TestCaseWithAnnotatedMethod { @ExpressionLanguage(MustacheAdapter.class) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java index 90a468cdf57f..eaa8c43f4327 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java @@ -15,36 +15,22 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.params.ParameterizedTestExtension.METHOD_CONTEXT_KEY; import static org.junit.jupiter.params.ParameterizedTestExtension.arguments; +import static org.junit.jupiter.params.TestExtensionContext.getExtensionContextReturningSingleMethod; import java.io.FileNotFoundException; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.nio.file.Path; -import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Stream; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.api.function.ThrowingConsumer; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.jupiter.engine.execution.NamespaceAwareStore; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * Unit tests for {@link ParameterizedTestExtension}. @@ -189,130 +175,6 @@ void throwsExceptionWhenArgumentsProviderDoesNotContainUnambiguousConstructor() className)); } - private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase) { - return getExtensionContextReturningSingleMethod(testCase, ignored -> Optional.empty()); - } - - private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase, - Function> configurationSupplier) { - - var method = ReflectionUtils.findMethods(testCase.getClass(), - it -> "method".equals(it.getName())).stream().findFirst(); - - return new ExtensionContext() { - - private final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>(null); - - @Override - public Optional getTestMethod() { - return method; - } - - @Override - public Optional getParent() { - return Optional.empty(); - } - - @Override - public ExtensionContext getRoot() { - return this; - } - - @Override - public String getUniqueId() { - return null; - } - - @Override - public String getDisplayName() { - return null; - } - - @Override - public Set getTags() { - return null; - } - - @Override - public Optional getElement() { - return Optional.empty(); - } - - @Override - public Optional> getTestClass() { - return Optional.empty(); - } - - @Override - public Optional getTestInstanceLifecycle() { - return Optional.empty(); - } - - @Override - public java.util.Optional getTestInstance() { - return Optional.empty(); - } - - @Override - public Optional getTestInstances() { - return Optional.empty(); - } - - @Override - public Optional getExecutionException() { - return Optional.empty(); - } - - @Override - public Optional getConfigurationParameter(String key) { - return configurationSupplier.apply(key); - } - - @Override - public Optional getConfigurationParameter(String key, Function transformer) { - return configurationSupplier.apply(key).map(transformer); - } - - @Override - public void publishReportEntry(Map map) { - } - - @Override - public void publishFile(String fileName, ThrowingConsumer action) { - } - - @Override - public Store getStore(Namespace namespace) { - var store = new NamespaceAwareStore(this.store, namespace); - method // - .map(it -> new ParameterizedTestMethodContext(it, it.getAnnotation(ParameterizedTest.class), - Optional.empty())) // - .ifPresent(ctx -> store.put(METHOD_CONTEXT_KEY, ctx)); - return store; - } - - @Override - public ExecutionMode getExecutionMode() { - return ExecutionMode.SAME_THREAD; - } - - @Override - public ExecutableInvoker getExecutableInvoker() { - return new ExecutableInvoker() { - @Override - public Object invoke(Method method, Object target) { - return null; - } - - @Override - public T invoke(Constructor constructor, Object outerInstance) { - return ReflectionUtils.newInstance(constructor); - } - }; - } - }; - } - static class TestCaseWithoutMethod { } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/TestExtensionContext.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/TestExtensionContext.java new file mode 100644 index 000000000000..f825edded8d3 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/TestExtensionContext.java @@ -0,0 +1,153 @@ + +package org.junit.jupiter.params; + +import static org.junit.jupiter.params.ParameterizedTestExtension.METHOD_CONTEXT_KEY; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.engine.execution.NamespaceAwareStore; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; + +public class TestExtensionContext { + + private TestExtensionContext() { + } + + static ExtensionContext getExtensionContextReturningSingleMethod(Object testCase) { + return getExtensionContextReturningSingleMethod(testCase, ignored -> Optional.empty()); + } + + static ExtensionContext getExtensionContextReturningSingleMethod(Object testCase, + Function> configurationSupplier) { + + var method = ReflectionUtils.findMethods(testCase.getClass(), + it -> "method".equals(it.getName())).stream().findFirst(); + + return new ExtensionContext() { + + private final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>(null); + + @Override + public Optional getTestMethod() { + return method; + } + + @Override + public Optional getParent() { + return Optional.empty(); + } + + @Override + public ExtensionContext getRoot() { + return this; + } + + @Override + public String getUniqueId() { + return null; + } + + @Override + public String getDisplayName() { + return null; + } + + @Override + public Set getTags() { + return null; + } + + @Override + public Optional getElement() { + return Optional.empty(); + } + + @Override + public Optional> getTestClass() { + return Optional.empty(); + } + + @Override + public Optional getTestInstanceLifecycle() { + return Optional.empty(); + } + + @Override + public java.util.Optional getTestInstance() { + return Optional.empty(); + } + + @Override + public Optional getTestInstances() { + return Optional.empty(); + } + + @Override + public Optional getExecutionException() { + return Optional.empty(); + } + + @Override + public Optional getConfigurationParameter(String key) { + return configurationSupplier.apply(key); + } + + @Override + public Optional getConfigurationParameter(String key, Function transformer) { + return configurationSupplier.apply(key).map(transformer); + } + + @Override + public void publishReportEntry(Map map) { + } + + @Override + public void publishFile(String fileName, ThrowingConsumer action) { + } + + @Override + public Store getStore(Namespace namespace) { + var store = new NamespaceAwareStore(this.store, namespace); + method // + .map(it -> new ParameterizedTestMethodContext(it, it.getAnnotation(ParameterizedTest.class), + Optional.empty())) // + .ifPresent(ctx -> store.put(METHOD_CONTEXT_KEY, ctx)); + return store; + } + + @Override + public ExecutionMode getExecutionMode() { + return ExecutionMode.SAME_THREAD; + } + + @Override + public ExecutableInvoker getExecutableInvoker() { + return new ExecutableInvoker() { + @Override + public Object invoke(Method method, Object target) { + return null; + } + + @Override + public T invoke(Constructor constructor, Object outerInstance) { + return ReflectionUtils.newInstance(constructor); + } + }; + } + }; + } +} From ba7b30c61233c78a972ecd392b46f1586f5b3322 Mon Sep 17 00:00:00 2001 From: Jean Gauthier Date: Fri, 20 Dec 2024 16:30:03 +0100 Subject: [PATCH 4/7] Replace logic in ParameterizedTestNameFormatter.determineNonPlaceholderFormatter so that a custom expression language formatter can be plugged in --- .../ParameterizedTestNameFormatter.java | 64 +++++++++++++++---- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java index ecbea24fd04c..e7804d735efb 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java @@ -46,6 +46,7 @@ class ParameterizedTestNameFormatter { private final PartialFormatter[] partialFormatters; + private final boolean useExpressionLanguageFormatter = false; // TODO jgauthier ParameterizedTestNameFormatter(String pattern, String displayName, ParameterizedTestMethodContext methodContext, int argumentMaxLength) { @@ -129,10 +130,10 @@ else if (minimum == null || index < minimum.index) { return minimum; } - private static PartialFormatter determineNonPlaceholderFormatter(String segment, int argumentMaxLength) { - return segment.contains("{") // - ? new MessageFormatPartialFormatter(segment, argumentMaxLength) // - : (context, result) -> result.append(segment); + private NonPlaceholderFormatter determineNonPlaceholderFormatter(String segment, int argumentMaxLength) { + return useExpressionLanguageFormatter + ? new ExpressionLanguageNonPlaceholderFormatter(argumentsContext -> "") // TODO jgauthier + : new DefaultNonPlaceholderFormatter(segment, argumentMaxLength); } private PartialFormatters createPartialFormatters(String displayName, ParameterizedTestMethodContext methodContext, @@ -143,15 +144,15 @@ private PartialFormatters createPartialFormatters(String displayName, Parameteri argumentMaxLength)); PartialFormatters formatters = new PartialFormatters(); - formatters.put(INDEX_PLACEHOLDER, PartialFormatter.INDEX); + formatters.put(INDEX_PLACEHOLDER, PlaceholderFormatter.INDEX); formatters.put(DISPLAY_NAME_PLACEHOLDER, (context, result) -> result.append(displayName)); - formatters.put(ARGUMENT_SET_NAME_PLACEHOLDER, PartialFormatter.ARGUMENT_SET_NAME); + formatters.put(ARGUMENT_SET_NAME_PLACEHOLDER, PlaceholderFormatter.ARGUMENT_SET_NAME); formatters.put(ARGUMENTS_WITH_NAMES_PLACEHOLDER, argumentsWithNamesFormatter); formatters.put(ARGUMENTS_PLACEHOLDER, new CachingByArgumentsLengthPartialFormatter( length -> new MessageFormatPartialFormatter(argumentsPattern(length), argumentMaxLength))); formatters.put(ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER, (context, result) -> { PartialFormatter formatterToUse = context.arguments instanceof ArgumentSet // - ? PartialFormatter.ARGUMENT_SET_NAME // + ? PlaceholderFormatter.ARGUMENT_SET_NAME // : argumentsWithNamesFormatter; formatterToUse.append(context, result); }); @@ -199,21 +200,26 @@ private static class ArgumentsContext { @FunctionalInterface private interface PartialFormatter { + void append(ArgumentsContext context, StringBuffer result); + } + + private interface PlaceholderFormatter extends PartialFormatter { + PartialFormatter INDEX = (context, result) -> result.append(context.invocationIndex); PartialFormatter ARGUMENT_SET_NAME = (context, result) -> { if (!(context.arguments instanceof ArgumentSet)) { throw new ExtensionConfigurationException( - String.format("When the display name pattern for a @ParameterizedTest contains %s, " - + "the arguments must be supplied as an ArgumentSet.", - ARGUMENT_SET_NAME_PLACEHOLDER)); + String.format("When the display name pattern for a @ParameterizedTest contains %s, " + + "the arguments must be supplied as an ArgumentSet.", + ARGUMENT_SET_NAME_PLACEHOLDER)); } result.append(((ArgumentSet) context.arguments).getName()); }; - - void append(ArgumentsContext context, StringBuffer result); } + private interface NonPlaceholderFormatter extends PartialFormatter {} + private static class MessageFormatPartialFormatter implements PartialFormatter { @SuppressWarnings("UnnecessaryUnicodeEscape") @@ -289,4 +295,38 @@ Set placeholders() { } } + private static class DefaultNonPlaceholderFormatter implements NonPlaceholderFormatter { + + private final PartialFormatter delegate; + + public DefaultNonPlaceholderFormatter(String segment, int argumentMaxLength) { + this.delegate = segment.contains("{") // + ? new MessageFormatPartialFormatter(segment, argumentMaxLength) // + : (context, result) -> result.append(segment); + } + + @Override + public void append(ArgumentsContext context, StringBuffer result) { + delegate.append(context, result); + } + } + + private interface ExpressionLanguageAdapter { + + String format(ArgumentsContext argumentsContext); + } + + private static class ExpressionLanguageNonPlaceholderFormatter implements NonPlaceholderFormatter { + + private final ExpressionLanguageAdapter expressionLanguageAdapter; + + public ExpressionLanguageNonPlaceholderFormatter(ExpressionLanguageAdapter expressionLanguageAdapter) { + this.expressionLanguageAdapter = expressionLanguageAdapter; + } + + @Override + public void append(ArgumentsContext argumentsContext, StringBuffer result) { + result.append(expressionLanguageAdapter.format(argumentsContext)); + } + } } From 22c697b1b0c06c9903e5f773c23ddde560d82458 Mon Sep 17 00:00:00 2001 From: Jean Gauthier Date: Fri, 31 Jan 2025 13:33:49 +0100 Subject: [PATCH 5/7] Wire up logic to compile & parse EL expressions in ParameterizedTestNameFormatter, pull ArgumentsContext up to separate class --- ...ameterizedTestNameFormatterBenchmarks.java | 3 +- .../jupiter/params/ArgumentsContext.java | 16 ++++++ .../params/ExpressionLanguageAdapter.java | 6 +- .../ParameterizedTestNameFormatter.java | 56 ++++++++++--------- jupiter-tests/jupiter-tests.gradle.kts | 2 +- .../params/ExpressionLanguageTests.java | 27 +++++++-- .../ParameterizedTestMethodContextTests.java | 2 +- .../ParameterizedTestNameFormatterTests.java | 2 +- .../jupiter/params/TestExtensionContext.java | 2 +- 9 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentsContext.java diff --git a/junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedTestNameFormatterBenchmarks.java b/junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedTestNameFormatterBenchmarks.java index 8c6605490ab8..782d183f01ff 100644 --- a/junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedTestNameFormatterBenchmarks.java +++ b/junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedTestNameFormatterBenchmarks.java @@ -11,6 +11,7 @@ package org.junit.jupiter.params; import java.util.List; +import java.util.Optional; import java.util.stream.IntStream; import org.junit.jupiter.params.provider.Arguments; @@ -47,7 +48,7 @@ public void formatTestNames(Blackhole blackhole) throws Exception { var method = TestCase.class.getDeclaredMethod("parameterizedTest", int.class); var formatter = new ParameterizedTestNameFormatter( ParameterizedTest.DISPLAY_NAME_PLACEHOLDER + " " + ParameterizedTest.DEFAULT_DISPLAY_NAME + " ({0})", - "displayName", new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class)), + "displayName", new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class), Optional.ofNullable(method.getAnnotation(ExpressionLanguage.class))), 512); for (int i = 0; i < argumentsList.size(); i++) { Arguments arguments = argumentsList.get(i); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentsContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentsContext.java new file mode 100644 index 000000000000..a1320684cd88 --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentsContext.java @@ -0,0 +1,16 @@ +package org.junit.jupiter.params; + +import org.junit.jupiter.params.provider.Arguments; + +public class ArgumentsContext { + + final int invocationIndex; + final Arguments arguments; + final Object[] consumedArguments; + + ArgumentsContext(int invocationIndex, Arguments arguments, Object[] consumedArguments) { + this.invocationIndex = invocationIndex; + this.arguments = arguments; + this.consumedArguments = consumedArguments; + } +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java index a4f5bb611208..1abb23625ae6 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java @@ -1,9 +1,9 @@ package org.junit.jupiter.params; -import java.util.Map; - public interface ExpressionLanguageAdapter { - String evaluate(String template, Map context); + void compile(String template); + + void format(ArgumentsContext argumentsContext, StringBuffer stringBuffer); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java index e7804d735efb..8d65c85af529 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java @@ -19,6 +19,7 @@ import static org.junit.jupiter.params.ParameterizedTest.INDEX_PLACEHOLDER; import static org.junit.platform.commons.util.StringUtils.isNotBlank; +import java.lang.reflect.InvocationTargetException; import java.text.FieldPosition; import java.text.Format; import java.text.MessageFormat; @@ -27,12 +28,14 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; import java.util.stream.IntStream; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.params.provider.Arguments; @@ -46,7 +49,6 @@ class ParameterizedTestNameFormatter { private final PartialFormatter[] partialFormatters; - private final boolean useExpressionLanguageFormatter = false; // TODO jgauthier ParameterizedTestNameFormatter(String pattern, String displayName, ParameterizedTestMethodContext methodContext, int argumentMaxLength) { @@ -97,12 +99,12 @@ private PartialFormatter[] parse(String pattern, String displayName, Parameteriz while (isNotBlank(unparsedSegment)) { PlaceholderPosition position = findFirstPlaceholder(formatters, unparsedSegment); if (position == null) { - result.add(determineNonPlaceholderFormatter(unparsedSegment, argumentMaxLength)); + result.add(determineNonPlaceholderFormatter(methodContext, unparsedSegment, argumentMaxLength)); break; } if (position.index > 0) { String before = unparsedSegment.substring(0, position.index); - result.add(determineNonPlaceholderFormatter(before, argumentMaxLength)); + result.add(determineNonPlaceholderFormatter(methodContext, before, argumentMaxLength)); } result.add(formatters.get(position.placeholder)); unparsedSegment = unparsedSegment.substring(position.index + position.placeholder.length()); @@ -111,6 +113,24 @@ private PartialFormatter[] parse(String pattern, String displayName, Parameteriz return result.toArray(new PartialFormatter[0]); } + @NotNull + private static Optional createExpressionLanguageAdapter( + ParameterizedTestMethodContext methodContext, + String segment + ) { + return methodContext.expressionLanguageAnnotation.map(ExpressionLanguage::value).map(adapterClass -> { + try { + ExpressionLanguageAdapter adapterInstance = adapterClass.getDeclaredConstructor().newInstance(); + adapterInstance.compile(segment); + return adapterInstance; + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { + String message = "Failed to initialize expression language for parameterized test. " + + "See nested exception for further details."; + throw new JUnitException(message, ex); + } + }); + } + private static PlaceholderPosition findFirstPlaceholder(PartialFormatters formatters, String segment) { if (segment.length() < formatters.minimumPlaceholderLength) { return null; @@ -130,10 +150,11 @@ else if (minimum == null || index < minimum.index) { return minimum; } - private NonPlaceholderFormatter determineNonPlaceholderFormatter(String segment, int argumentMaxLength) { - return useExpressionLanguageFormatter - ? new ExpressionLanguageNonPlaceholderFormatter(argumentsContext -> "") // TODO jgauthier - : new DefaultNonPlaceholderFormatter(segment, argumentMaxLength); + private NonPlaceholderFormatter determineNonPlaceholderFormatter( + ParameterizedTestMethodContext methodContext, String segment, int argumentMaxLength) { + return createExpressionLanguageAdapter(methodContext, segment) + .map(expressionLanguageAdapter -> (NonPlaceholderFormatter) new ExpressionLanguageNonPlaceholderFormatter(expressionLanguageAdapter)) + .orElseGet(() -> new DefaultNonPlaceholderFormatter(segment, argumentMaxLength)); } private PartialFormatters createPartialFormatters(String displayName, ParameterizedTestMethodContext methodContext, @@ -184,19 +205,6 @@ private static class PlaceholderPosition { } - private static class ArgumentsContext { - - private final int invocationIndex; - private final Arguments arguments; - private final Object[] consumedArguments; - - ArgumentsContext(int invocationIndex, Arguments arguments, Object[] consumedArguments) { - this.invocationIndex = invocationIndex; - this.arguments = arguments; - this.consumedArguments = consumedArguments; - } - } - @FunctionalInterface private interface PartialFormatter { @@ -311,22 +319,18 @@ public void append(ArgumentsContext context, StringBuffer result) { } } - private interface ExpressionLanguageAdapter { - - String format(ArgumentsContext argumentsContext); - } - private static class ExpressionLanguageNonPlaceholderFormatter implements NonPlaceholderFormatter { private final ExpressionLanguageAdapter expressionLanguageAdapter; public ExpressionLanguageNonPlaceholderFormatter(ExpressionLanguageAdapter expressionLanguageAdapter) { this.expressionLanguageAdapter = expressionLanguageAdapter; + } @Override public void append(ArgumentsContext argumentsContext, StringBuffer result) { - result.append(expressionLanguageAdapter.format(argumentsContext)); + expressionLanguageAdapter.format(argumentsContext, result); } } } diff --git a/jupiter-tests/jupiter-tests.gradle.kts b/jupiter-tests/jupiter-tests.gradle.kts index 8721fdc11811..c3ba633a873f 100644 --- a/jupiter-tests/jupiter-tests.gradle.kts +++ b/jupiter-tests/jupiter-tests.gradle.kts @@ -21,11 +21,11 @@ dependencies { testImplementation(libs.kotlinx.coroutines) testImplementation(libs.groovy4) testImplementation(libs.memoryfilesystem) - testImplementation(libs.mustache) testImplementation(testFixtures(projects.junitJupiterApi)) testImplementation(testFixtures(projects.junitJupiterEngine)) testImplementation(testFixtures(projects.junitPlatformLauncher)) testImplementation(testFixtures(projects.junitPlatformReporting)) + implementation(libs.mustache) } tasks { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java index eb4ea28e554b..9e5fd2aff208 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java @@ -5,9 +5,14 @@ import static org.junit.jupiter.params.TestExtensionContext.getExtensionContextReturningSingleMethod; import static org.junit.jupiter.params.provider.Arguments.arguments; -import java.util.Map; +import java.io.StringReader; +import java.io.StringWriter; import java.util.stream.Stream; +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; @@ -29,7 +34,7 @@ void throwsExceptionWhenParameterizedTestIsNotInvokedAtLeastOnce() { static class TestCaseWithAnnotatedMethod { @ExpressionLanguage(MustacheAdapter.class) - @ParameterizedTest(name = "foo") + @ParameterizedTest(name = "foo {{name}}") @ArgumentsSource(FooArgumentsProvider.class) void method() { } @@ -54,9 +59,23 @@ public Stream provideArguments(ExtensionContext context) { static class MustacheAdapter implements ExpressionLanguageAdapter { + MustacheFactory mustacheFactory; + Mustache mustache; + + MustacheAdapter() { + mustacheFactory = new DefaultMustacheFactory(); + } + + @Override + public void compile(String template) { + mustache = mustacheFactory.compile(new StringReader(template), template); + } + @Override - public String evaluate(String template, Map context) { - return "hello!"; + public void format(ArgumentsContext argumentsContext, StringBuffer stringBuffer) { + StringWriter stringWriter = new StringWriter(); + mustache.execute(stringWriter, argumentsContext); + stringBuffer.append(stringWriter); } } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java index e1f989ef306a..65414d0c0ba1 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java @@ -45,7 +45,7 @@ void invalidSignatures(String methodName) { private ParameterizedTestMethodContext createMethodContext(Class testClass, String methodName) { var method = ReflectionUtils.findMethods(testClass, m -> m.getName().equals(methodName)).getFirst(); return new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class), - Optional.empty()); + Optional.ofNullable(method.getAnnotation(ExpressionLanguage.class))); } @SuppressWarnings("JUnitMalformedDeclaration") diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java index 0dcb214e1382..c3396b99975b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java @@ -333,7 +333,7 @@ private static ParameterizedTestNameFormatter formatter(String pattern, String d private static ParameterizedTestNameFormatter formatter(String pattern, String displayName, Method method) { var context = new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class), - Optional.empty()); + Optional.ofNullable(method.getAnnotation(ExpressionLanguage.class))); return new ParameterizedTestNameFormatter(pattern, displayName, context, 512); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/TestExtensionContext.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/TestExtensionContext.java index f825edded8d3..31a222e77740 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/TestExtensionContext.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/TestExtensionContext.java @@ -124,7 +124,7 @@ public Store getStore(Namespace namespace) { var store = new NamespaceAwareStore(this.store, namespace); method // .map(it -> new ParameterizedTestMethodContext(it, it.getAnnotation(ParameterizedTest.class), - Optional.empty())) // + Optional.ofNullable(it.getAnnotation(ExpressionLanguage.class)))) // .ifPresent(ctx -> store.put(METHOD_CONTEXT_KEY, ctx)); return store; } From e3963d9f9d43e0a8ffa91d812b18634da6715893 Mon Sep 17 00:00:00 2001 From: Jean Gauthier Date: Fri, 31 Jan 2025 14:06:16 +0100 Subject: [PATCH 6/7] Fix logic to extract EL arguments --- .../params/ExpressionLanguageAdapter.java | 2 +- .../ParameterizedTestNameFormatter.java | 2 +- .../params/ExpressionLanguageTests.java | 22 +++++++++++-------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java index 1abb23625ae6..43a3b5caf8c2 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ExpressionLanguageAdapter.java @@ -5,5 +5,5 @@ public interface ExpressionLanguageAdapter { void compile(String template); - void format(ArgumentsContext argumentsContext, StringBuffer stringBuffer); + void format(Object scope, StringBuffer result); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java index 8d65c85af529..1b8674be8335 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java @@ -330,7 +330,7 @@ public ExpressionLanguageNonPlaceholderFormatter(ExpressionLanguageAdapter expre @Override public void append(ArgumentsContext argumentsContext, StringBuffer result) { - expressionLanguageAdapter.format(argumentsContext, result); + expressionLanguageAdapter.format(argumentsContext.arguments.get()[0], result); } } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java index 9e5fd2aff208..913041392888 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java @@ -15,6 +15,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -27,23 +28,26 @@ public class ExpressionLanguageTests { void throwsExceptionWhenParameterizedTestIsNotInvokedAtLeastOnce() { var extensionContext = getExtensionContextReturningSingleMethod(new TestCaseWithAnnotatedMethod()); this.parameterizedTestExtension.supportsTestTemplate(extensionContext); - var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext); - assertThat(stream.findFirst().get().getDisplayName(0)).isEqualTo("hello!"); + var testTemplateInvocationContexts = this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext).toList(); + assertThat(testTemplateInvocationContexts.get(0).getDisplayName(0)).isEqualTo("foo 123"); + assertThat(testTemplateInvocationContexts.get(1).getDisplayName(0)).isEqualTo("foo 456"); } static class TestCaseWithAnnotatedMethod { @ExpressionLanguage(MustacheAdapter.class) - @ParameterizedTest(name = "foo {{name}}") + @ParameterizedTest(name = "foo {{bar}}") @ArgumentsSource(FooArgumentsProvider.class) void method() { } static class Foo { String bar; + String baz; - public Foo(String bar) { + public Foo(String bar, String baz) { this.bar = bar; + this.baz = baz; } } @@ -51,8 +55,8 @@ static class FooArgumentsProvider implements ArgumentsProvider { @Override public Stream provideArguments(ExtensionContext context) { - return Stream.of(arguments(new TestCaseWithAnnotatedMethod.Foo("123")), - arguments(new TestCaseWithAnnotatedMethod.Foo("456"))); + return Stream.of(arguments(new TestCaseWithAnnotatedMethod.Foo("123", "abc")), + arguments(new TestCaseWithAnnotatedMethod.Foo("456", "def"))); } } } @@ -72,10 +76,10 @@ public void compile(String template) { } @Override - public void format(ArgumentsContext argumentsContext, StringBuffer stringBuffer) { + public void format(Object scope, StringBuffer result) { StringWriter stringWriter = new StringWriter(); - mustache.execute(stringWriter, argumentsContext); - stringBuffer.append(stringWriter); + mustache.execute(stringWriter, scope); + result.append(stringWriter); } } } From fdbe111b4119e5a86696d9a9002a28a7eca7d90e Mon Sep 17 00:00:00 2001 From: Jean Gauthier Date: Fri, 31 Jan 2025 15:03:45 +0100 Subject: [PATCH 7/7] Add additional tests for mustache EL --- .../ExpressionLanguageMustacheTests.java | 223 ++++++++++++++++++ .../params/ExpressionLanguageTests.java | 85 ------- 2 files changed, 223 insertions(+), 85 deletions(-) create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageMustacheTests.java delete mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageMustacheTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageMustacheTests.java new file mode 100644 index 000000000000..bd32d1a63b35 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageMustacheTests.java @@ -0,0 +1,223 @@ + +package org.junit.jupiter.params; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.TestExtensionContext.getExtensionContextReturningSingleMethod; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import com.github.mustachejava.DefaultMustacheFactory; +import com.github.mustachejava.Mustache; +import com.github.mustachejava.MustacheFactory; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +public class ExpressionLanguageMustacheTests { + + private final ParameterizedTestExtension parameterizedTestExtension = new ParameterizedTestExtension(); + + @Test + void correctlyComputesDisplayNameTemplateWithoutPlaceholders() { + var testTemplateInvocationContexts = testTemplateInvocationContextsFor(TestCaseTemplateWithoutPlaceholders::new); + assertThat(testTemplateInvocationContexts.get(0).getDisplayName(0)).isEqualTo("foo"); + } + + private List testTemplateInvocationContextsFor(Supplier testCaseFactory) { + var extensionContext = getExtensionContextReturningSingleMethod(testCaseFactory.get()); + this.parameterizedTestExtension.supportsTestTemplate(extensionContext); + return this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext).toList(); + } + + static class TestCaseTemplateWithoutPlaceholders { + + @ExpressionLanguage(MustacheAdapter.class) + @ParameterizedTest(name = "foo") + @ArgumentsSource(FooArgumentsProvider.class) + void method() { + } + + static class FooArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of(arguments(new FooArgument("123", "abc"))); + } + } + } + + @Test + void correctlyComputesDisplayNameTemplateSimplePlaceholder() { + var testTemplateInvocationContexts = testTemplateInvocationContextsFor(TestCaseSimplePlaceholder::new); + assertThat(testTemplateInvocationContexts.get(0).getDisplayName(0)).isEqualTo("foo 123"); + assertThat(testTemplateInvocationContexts.get(1).getDisplayName(0)).isEqualTo("foo 456"); + } + + static class TestCaseSimplePlaceholder { + + @ExpressionLanguage(MustacheAdapter.class) + @ParameterizedTest(name = "foo {{bar}}") + @ArgumentsSource(FooArgumentsProvider.class) + void method() { + } + + static class FooArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of(arguments(new FooArgument("123", "abc")), + arguments(new FooArgument("456", "def"))); + } + } + } + + @Test + void correctlyComputesDisplayNameTemplateInvalidPlaceholder() { + var testTemplateInvocationContexts = testTemplateInvocationContextsFor(TestCaseInvalidPlaceholder::new); + assertThat(testTemplateInvocationContexts.get(0).getDisplayName(0)).isEqualTo("foo "); + } + + static class TestCaseInvalidPlaceholder { + + @ExpressionLanguage(MustacheAdapter.class) + @ParameterizedTest(name = "foo {{barbaz}}") + @ArgumentsSource(FooArgumentsProvider.class) + void method() { + } + + static class FooArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of(arguments(new FooArgument("123", "abc"))); + } + } + } + + @Test + void correctlyComputesDisplayNameTemplateMultiplePlaceholders() { + var testTemplateInvocationContexts = testTemplateInvocationContextsFor(TestCaseMultiplePlaceholders::new); + assertThat(testTemplateInvocationContexts.get(0).getDisplayName(0)).isEqualTo("foo 123 abc foo"); + } + + static class TestCaseMultiplePlaceholders { + + @ExpressionLanguage(MustacheAdapter.class) + @ParameterizedTest(name = "foo {{bar}} {{baz}} foo") + @ArgumentsSource(FooArgumentsProvider.class) + void method() { + } + + static class FooArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of(arguments(new FooArgument("123", "abc"))); + } + } + } + + @Test + void correctlyComputesDisplayNameTemplateNestedPlaceholders() { + var testTemplateInvocationContexts = testTemplateInvocationContextsFor(TestCaseNestedPlaceholders::new); + assertThat(testTemplateInvocationContexts.get(0).getDisplayName(0)).isEqualTo("123: abc"); + } + + static class TestCaseNestedPlaceholders { + + @ExpressionLanguage(MustacheAdapter.class) + @ParameterizedTest(name = "{{foo.bar}}: {{foo.baz}}") + @ArgumentsSource(FooArgumentsProvider.class) + void method() { + } + + static class FooArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of(arguments(new FooArgumentWrapper(new FooArgument("123", "abc")))); + } + } + } + + @Test + void correctlyComputesDisplayNameTemplatePlaceholderList() { + var testTemplateInvocationContexts = testTemplateInvocationContextsFor(TestCasePlaceholderList::new); + assertThat(testTemplateInvocationContexts.get(0).getDisplayName(0)).isEqualTo("(123, abc)(456, def)"); + } + + static class TestCasePlaceholderList { + + @ExpressionLanguage(MustacheAdapter.class) + @ParameterizedTest(name = "{{#foos}}({{bar}}, {{baz}}){{/foos}}") + @ArgumentsSource(FooArgumentsProvider.class) + void method() { + } + + static class FooArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of(arguments(new FooArgumentList(new FooArgument("123", "abc"), new FooArgument("456", "def")))); + } + } + } + + static class FooArgument { + String bar; + String baz; + + public FooArgument(String bar, String baz) { + this.bar = bar; + this.baz = baz; + } + } + + static class FooArgumentWrapper { + FooArgument foo; + + public FooArgumentWrapper(FooArgument foo) { + this.foo = foo; + } + } + + static class FooArgumentList { + List foos; + + public FooArgumentList(FooArgument... foos) { + this.foos = Arrays.asList(foos); + } + } + + static class MustacheAdapter implements ExpressionLanguageAdapter { + + MustacheFactory mustacheFactory; + Mustache mustache; + + MustacheAdapter() { + mustacheFactory = new DefaultMustacheFactory(); + } + + @Override + public void compile(String template) { + mustache = mustacheFactory.compile(new StringReader(template), template); + } + + @Override + public void format(Object scope, StringBuffer result) { + StringWriter stringWriter = new StringWriter(); + mustache.execute(stringWriter, scope); + result.append(stringWriter); + } + } +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java deleted file mode 100644 index 913041392888..000000000000 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ExpressionLanguageTests.java +++ /dev/null @@ -1,85 +0,0 @@ - -package org.junit.jupiter.params; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.params.TestExtensionContext.getExtensionContextReturningSingleMethod; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -import java.io.StringReader; -import java.io.StringWriter; -import java.util.stream.Stream; - -import com.github.mustachejava.DefaultMustacheFactory; -import com.github.mustachejava.Mustache; -import com.github.mustachejava.MustacheFactory; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; - -public class ExpressionLanguageTests { - - private final ParameterizedTestExtension parameterizedTestExtension = new ParameterizedTestExtension(); - - @Test - void throwsExceptionWhenParameterizedTestIsNotInvokedAtLeastOnce() { - var extensionContext = getExtensionContextReturningSingleMethod(new TestCaseWithAnnotatedMethod()); - this.parameterizedTestExtension.supportsTestTemplate(extensionContext); - var testTemplateInvocationContexts = this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext).toList(); - assertThat(testTemplateInvocationContexts.get(0).getDisplayName(0)).isEqualTo("foo 123"); - assertThat(testTemplateInvocationContexts.get(1).getDisplayName(0)).isEqualTo("foo 456"); - } - - static class TestCaseWithAnnotatedMethod { - - @ExpressionLanguage(MustacheAdapter.class) - @ParameterizedTest(name = "foo {{bar}}") - @ArgumentsSource(FooArgumentsProvider.class) - void method() { - } - - static class Foo { - String bar; - String baz; - - public Foo(String bar, String baz) { - this.bar = bar; - this.baz = baz; - } - } - - static class FooArgumentsProvider implements ArgumentsProvider { - - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of(arguments(new TestCaseWithAnnotatedMethod.Foo("123", "abc")), - arguments(new TestCaseWithAnnotatedMethod.Foo("456", "def"))); - } - } - } - - static class MustacheAdapter implements ExpressionLanguageAdapter { - - MustacheFactory mustacheFactory; - Mustache mustache; - - MustacheAdapter() { - mustacheFactory = new DefaultMustacheFactory(); - } - - @Override - public void compile(String template) { - mustache = mustacheFactory.compile(new StringReader(template), template); - } - - @Override - public void format(Object scope, StringBuffer result) { - StringWriter stringWriter = new StringWriter(); - mustache.execute(stringWriter, scope); - result.append(stringWriter); - } - } -}