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 c54d186 commit 4c1bae2
Show file tree
Hide file tree
Showing 3 changed files with 586 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
}
}
Loading

0 comments on commit 4c1bae2

Please sign in to comment.