Skip to content

Commit

Permalink
HSEARCH-3654 Attempt for reusable mapper replacement
Browse files Browse the repository at this point in the history
  • Loading branch information
marko-bekhta committed Sep 27, 2023
1 parent c5fbffc commit 7f50d40
Show file tree
Hide file tree
Showing 2 changed files with 291 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.util.impl.test.extension;

import static java.util.Spliterators.spliteratorUnknownSize;
import static java.util.stream.StreamSupport.stream;
import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations;
import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
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.support.AnnotationConsumerInitializer;
import org.junit.platform.commons.util.AnnotationUtils;
import org.junit.platform.commons.util.ReflectionUtils;

import org.opentest4j.AssertionFailedError;

public final class MultiRunExtension
implements TestTemplateInvocationContextProvider, AfterEachCallback, ParameterResolver {
private List<Object[]> envArguments;
private int envIndex = 0;
private boolean envInitialized = false;
private Method initMethod;

@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return true;
}

@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
if ( envArguments == null ) {
envArguments = new ArrayList<>();
Method testMethod = extensionContext.getRequiredTestMethod();

for ( ArgumentsSource source : findRepeatableAnnotations( testMethod, ArgumentsSource.class ) ) {
ArgumentsProvider argumentsProvider =
AnnotationConsumerInitializer.initialize( testMethod, ReflectionUtils.newInstance( source.value() ) );
try {
envArguments.addAll(
argumentsProvider.provideArguments( extensionContext )
.map( Arguments::get )
.collect( Collectors.toList() ) );
}
catch (Exception e) {
throw new IllegalStateException( "unable to read arguments.", e );
}
}
}
return envArguments.get( envIndex )[parameterContext.getIndex()];
}

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@TestTemplate
@ExtendWith(MultiRunExtension.class)
public @interface EnvironmentTest {
String init() default "init";
}

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface TestToExecute {
}

@Override
public boolean supportsTestTemplate(ExtensionContext context) {
return isAnnotated( context.getTestMethod(), EnvironmentTest.class );
}

@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
EnvironmentTest environment = context.getTestMethod()
.flatMap( method -> AnnotationUtils.findAnnotation( method, EnvironmentTest.class ) )
.orElseThrow( IllegalStateException::new );

String init = environment.init();
Class<?> testClass = context.getTestClass().orElseThrow();
try {
initMethod = testClass.getDeclaredMethod( init, context.getRequiredTestMethod().getParameterTypes() );
initMethod.setAccessible( true );
}
catch (NoSuchMethodException e) {
throw new IllegalStateException( "Cannot locate init method.", e );
}


// find actual "tests" that we'll invoke via reflection:
List<Method> testMethods = new ArrayList<>();
for ( Method method : testClass.getDeclaredMethods() ) {
if ( AnnotationUtils.isAnnotated( method, TestToExecute.class ) ) {
testMethods.add( method );
}
}

if ( testMethods.isEmpty() ) {
throw new IllegalStateException( "No tests to execute were found." );
}

return stream( spliteratorUnknownSize(
new Iterator<TestTemplateInvocationContext>() {

Iterator<Method> test = testMethods.iterator();

@Override
public boolean hasNext() {
if ( Boolean.TRUE.equals( read( context, StoreKey.STOP_RUNNING, Boolean.class ) ) ) {
return false;
}
if ( test.hasNext() ) {
return true;
}
else {
envIndex++;
envInitialized = false;
}

if ( envIndex < envArguments.size() ) {
test = testMethods.iterator();
return test.hasNext();
}
return false;
}

@Override
public TestTemplateInvocationContext next() {
if ( envInitialized ) {
Method testMethod = test.next();
write( context, StoreKey.TEST_TO_RUN, testMethod );

return new TestTemplateInvocationContext() {

@Override
public String getDisplayName(int invocationIndex) {
return "Env #" + envIndex + ": " + testMethod.getName();
}

@Override
public List<Extension> getAdditionalExtensions() {
return TestTemplateInvocationContext.super.getAdditionalExtensions();
}
};
}
else {
return new TestTemplateInvocationContext() {

@Override
public String getDisplayName(int invocationIndex) {
return "Env #" + envIndex + ": Initializing";
}

@Override
public List<Extension> getAdditionalExtensions() {
return TestTemplateInvocationContext.super.getAdditionalExtensions();
}
};
}
}
}, Spliterator.NONNULL
), false );
}

@Override
public void afterEach(ExtensionContext extensionContext) throws Exception {
// that's where we actually execute the test or init the env
if ( envInitialized ) {
Method testMethod = read( extensionContext, StoreKey.TEST_TO_RUN, Method.class );
testMethod.setAccessible( true );
testMethod.invoke( extensionContext.getRequiredTestInstance() );
}
else {
try {
initMethod.invoke( extensionContext.getRequiredTestInstance(), envArguments.get( envIndex ) );
}
catch (Exception e) {
envIndex++;
if ( envIndex >= envArguments.size() ) {
write( extensionContext, StoreKey.STOP_RUNNING, Boolean.TRUE );
}
throw new AssertionFailedError( "Unable to init the env, stopping further execution", e );
}
envInitialized = true;
}
}

private void write(ExtensionContext context, StoreKey key, Object value) {
ExtensionContext.Store store = context.getRoot().getStore(
ExtensionContext.Namespace.create( context.getRequiredTestMethod() )
);
store.put( key, value );
}

private <T> T read(ExtensionContext context, StoreKey key, Class<T> clazz) {
ExtensionContext.Store store = context.getRoot().getStore(
ExtensionContext.Namespace.create( context.getRequiredTestMethod() )
);
return store.get( key, clazz );
}

private enum StoreKey {
TEST_TO_RUN,
STOP_RUNNING
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.util.impl.test.reflect;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Arrays;
import java.util.List;

import org.hibernate.search.util.impl.test.extension.MultiRunExtension;

import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

class ExperimentsTest {

public static List<? extends Arguments> params() {
return Arrays.asList(
Arguments.of( "string1", 1, true ),
Arguments.of( "string2", 2, true ),
Arguments.of( "string3", 3, true )
);
}

@MultiRunExtension.EnvironmentTest(init = "init")
@MethodSource("params")
void env(String string, int number, boolean bool) {
System.err.println( "env" );
// this one is really ignored, and should be empty. alternatively it can be treated as "@BeforeEach"
// since this method will be executed before both for the init and each @TestToExecute

// input parameters are here so we can use default argument sources and feed these arguments to the init method when needed.
// note init method *MUST* match the same type/order/amount of parameters.
}

public void init(String string, int number, boolean bool) {
System.err.println( "init" );
System.err.println( "\t" + string );
System.err.println( "\t" + number );
System.err.println( "\t" + bool );
}

@MultiRunExtension.TestToExecute
void test1() {
System.err.println( "test1" );
assertThat( 1 ).isPositive();
}

@MultiRunExtension.TestToExecute
void test2() {
System.err.println( "test2" );
assertThat( 1 ).isPositive();
}
}

0 comments on commit 7f50d40

Please sign in to comment.