| package org.apache.maven.test; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| import org.junit.jupiter.api.extension.Extension; |
| import org.junit.jupiter.api.extension.ExtensionContext; |
| import org.junit.jupiter.api.extension.TestInstancePostProcessor; |
| import org.junit.jupiter.api.extension.TestInstantiationException; |
| import org.junit.jupiter.api.extension.TestTemplateInvocationContext; |
| import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; |
| import org.junit.jupiter.params.converter.DefaultArgumentConverter; |
| import org.junit.jupiter.params.provider.Arguments; |
| import org.junit.platform.commons.util.CollectionUtils; |
| import org.junit.platform.commons.util.ReflectionUtils; |
| |
| import static org.junit.jupiter.params.provider.Arguments.arguments; |
| import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; |
| |
| /** |
| * ParameterizedExtension |
| */ |
| public class ParameterizedExtension implements TestTemplateInvocationContextProvider |
| { |
| |
| @Override |
| public boolean supportsTestTemplate( ExtensionContext context ) |
| { |
| return context.getTestMethod() |
| .map( m -> isAnnotated( m, Test.class ) ) |
| .orElse( false ); |
| } |
| |
| @Override |
| public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts( |
| ExtensionContext extensionContext ) |
| { |
| Class<?> testClass = extensionContext.getRequiredTestClass(); |
| try |
| { |
| List<Method> parameters = getParametersMethods( testClass ); |
| if ( parameters.size() != 1 ) |
| { |
| throw new IllegalStateException( |
| "Class " + testClass.getName() + " should provide a single method annotated with @" |
| + Parameters.class.getSimpleName() ); |
| } |
| Object params = parameters.iterator().next().invoke( null ); |
| return CollectionUtils.toStream( params ) |
| .map( ParameterizedExtension::toArguments ) |
| .map( Arguments::get ) |
| .map( ParameterizedTemplate::new ); |
| } |
| catch ( Exception e ) |
| { |
| throw new IllegalStateException( "Unable to generate test templates for class " + testClass.getName(), e ); |
| } |
| } |
| |
| private List<Method> getParametersMethods( Class<?> testClass ) |
| { |
| List<Method> parameters = Stream.of( testClass.getDeclaredMethods() ) |
| .filter( m -> Modifier.isStatic( m.getModifiers() ) ) |
| .filter( m -> m.getAnnotation( Parameters.class ) != null ) |
| .collect( Collectors.toList() ); |
| if ( parameters.isEmpty() && testClass != null ) |
| { |
| return getParametersMethods( testClass.getSuperclass() ); |
| } |
| else |
| { |
| return parameters; |
| } |
| } |
| |
| private static Arguments toArguments( Object item ) |
| { |
| // Nothing to do except cast. |
| if ( item instanceof Arguments ) |
| { |
| return ( Arguments ) item; |
| } |
| // Pass all multidimensional arrays "as is", in contrast to Object[]. |
| // See https://github.com/junit-team/junit5/issues/1665 |
| if ( ReflectionUtils.isMultidimensionalArray( item ) ) |
| { |
| return arguments( item ); |
| } |
| // Special treatment for one-dimensional reference arrays. |
| // See https://github.com/junit-team/junit5/issues/1665 |
| if ( item instanceof Object[] ) |
| { |
| return arguments( ( Object[] ) item ); |
| } |
| // Pass everything else "as is". |
| return arguments( item ); |
| } |
| |
| /** |
| * ParameterizedTemplate |
| */ |
| public static class ParameterizedTemplate implements TestTemplateInvocationContext |
| { |
| |
| private final Object[] params; |
| |
| public ParameterizedTemplate( Object[] params ) |
| { |
| this.params = params; |
| } |
| |
| @Override |
| public String getDisplayName( int invocationIndex ) |
| { |
| return "[" + invocationIndex + "] " |
| + Stream.of( params ).map( Object::toString ).collect( Collectors.joining( ", " ) ); |
| } |
| |
| @Override |
| public List<Extension> getAdditionalExtensions() |
| { |
| return Arrays.asList( |
| ( TestInstancePostProcessor ) this::postProcessTestInstance ); |
| } |
| |
| protected void postProcessTestInstance( Object testInstance, ExtensionContext context ) throws Exception |
| { |
| Class<?> clazz = testInstance.getClass(); |
| List<Field> fields = hierarchy( clazz ) |
| .map( Class::getDeclaredFields ) |
| .flatMap( Stream::of ) |
| .filter( f -> isAnnotated( f, Parameter.class ) ) |
| .sorted( Comparator.comparing( f -> ( Integer ) f.getAnnotation( Parameter.class ).value() ) ) |
| .collect( Collectors.toList() ); |
| if ( params.length != fields.size() ) |
| { |
| throw new TestInstantiationException( |
| "Expected " + fields.size() + " parameters bug got " + params.length + " when instantiating " |
| + clazz.getName() ); |
| } |
| for ( int i = 0; i < fields.size(); i++ ) |
| { |
| Field f = fields.get( i ); |
| f.setAccessible( true ); |
| f.set( testInstance, DefaultArgumentConverter.INSTANCE.convert( params[i], f.getType() ) ); |
| } |
| } |
| |
| protected Stream<Class<?>> hierarchy( Class<?> clazz ) |
| { |
| Class<?> superclass = clazz.getSuperclass(); |
| return Stream.concat( Stream.of( clazz ), superclass != null ? hierarchy( superclass ) : Stream.empty() ); |
| } |
| |
| } |
| |
| } |