org.hibernate.testing.junit4.CustomRunner Maven / Gradle / Ivy
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or .
 */
package org.hibernate.testing.junit4;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.hibernate.dialect.DatabaseVersion;
import org.hibernate.dialect.Dialect;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.testing.DialectCheck;
import org.hibernate.testing.FailureExpected;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.RequiresDialects;
import org.hibernate.testing.Skip;
import org.hibernate.testing.SkipForDialect;
import org.hibernate.testing.SkipForDialects;
import org.hibernate.testing.orm.junit.DialectContext;
import org.hibernate.testing.orm.junit.DialectFilterExtension;
import org.hibernate.testing.orm.junit.SkipForDialectGroup;
import org.hibernate.testing.orm.junit.TestingUtil;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.jboss.logging.Logger;
import static org.hibernate.dialect.SimpleDatabaseVersion.ZERO_VERSION;
/**
 * The Hibernate-specific {@link org.junit.runner.Runner} implementation which layers {@link ExtendedFrameworkMethod}
 * support on top of the standard JUnit {@link FrameworkMethod} for extra information after checking to make sure the
 * test should be run.
 *
 * @author Steve Ebersole
 */
public class CustomRunner extends BlockJUnit4ClassRunner {
	private static final Logger log = Logger.getLogger( CustomRunner.class );
	private TestClassMetadata testClassMetadata;
	public CustomRunner(Class> clazz) throws InitializationError, NoTestsRemainException {
		super( clazz );
	}
	@Override
	protected void collectInitializationErrors(List errors) {
		super.collectInitializationErrors( errors );
		this.testClassMetadata = new TestClassMetadata( getTestClass().getJavaClass() );
		testClassMetadata.validate( errors );
	}
	public TestClassMetadata getTestClassMetadata() {
		return testClassMetadata;
	}
	private Boolean isAllTestsIgnored;
	protected boolean isAllTestsIgnored() {
		if ( isAllTestsIgnored == null ) {
			if ( computeTestMethods().isEmpty() ) {
				isAllTestsIgnored = true;
			}
			else {
				isAllTestsIgnored = true;
				for ( FrameworkMethod method : computeTestMethods() ) {
					Ignore ignore = method.getAnnotation( Ignore.class );
					if ( ignore == null ) {
						isAllTestsIgnored = false;
						break;
					}
				}
			}
		}
		return isAllTestsIgnored;
	}
	@Override
	protected Statement withBeforeClasses(Statement statement) {
		if ( isAllTestsIgnored() ) {
			return super.withBeforeClasses( statement );
		}
		return new BeforeClassCallbackHandler(
				this,
				super.withBeforeClasses( statement )
		);
	}
	@Override
	protected Statement withAfterClasses(Statement statement) {
		if ( isAllTestsIgnored() ) {
			return super.withAfterClasses( statement );
		}
		return new AfterClassCallbackHandler(
				this,
				super.withAfterClasses( statement )
		);
	}
	/**
	 * {@inheritDoc}
	 *
	 * @see org.junit.runners.ParentRunner#classBlock(RunNotifier)
	 */
	@Override
	protected Statement classBlock(RunNotifier notifier) {
		log.info( BeforeClass.class.getSimpleName() + ": " + getName() );
		return super.classBlock( notifier );
	}
	@Override
	protected Statement methodBlock(FrameworkMethod method) {
		log.info( Test.class.getSimpleName() + ": " + method.getName() );
		final Statement originalMethodBlock = super.methodBlock( method );
		final ExtendedFrameworkMethod extendedFrameworkMethod = (ExtendedFrameworkMethod) method;
		return new FailureExpectedHandler(
				originalMethodBlock,
				testClassMetadata,
				extendedFrameworkMethod,
				testInstance
		);
	}
	protected Object testInstance;
	protected Object getTestInstance() throws Exception {
		if ( testInstance == null ) {
			testInstance = super.createTest();
		}
		return testInstance;
	}
	@Override
	protected Object createTest() throws Exception {
		return getTestInstance();
	}
	private List computedTestMethods;
	@Override
	protected List computeTestMethods() {
		if ( computedTestMethods == null ) {
			computedTestMethods = doComputation();
			sortMethods( computedTestMethods );
		}
		return computedTestMethods;
	}
	protected void sortMethods(List computedTestMethods) {
		if ( CollectionHelper.isEmpty( computedTestMethods ) ) {
			return;
		}
		Collections.sort(
				computedTestMethods, new Comparator() {
					@Override
					public int compare(FrameworkMethod o1, FrameworkMethod o2) {
						return o1.getName().compareTo( o2.getName() );
					}
				}
		);
	}
	protected List doComputation() {
		// Next, get all the test methods as understood by JUnit
		final List methods = super.computeTestMethods();
		// Now process that full list of test methods and build our custom result
		final List result = new ArrayList();
		final boolean doValidation = Boolean.getBoolean( Helper.VALIDATE_FAILURE_EXPECTED );
		int testCount = 0;
		Ignore virtualIgnore;
		for ( FrameworkMethod frameworkMethod : methods ) {
			// potentially ignore based on expected failure
			final FailureExpected failureExpected = Helper.locateAnnotation(
					FailureExpected.class,
					frameworkMethod,
					getTestClass()
			);
			if ( failureExpected != null && !doValidation ) {
				virtualIgnore = new IgnoreImpl( Helper.extractIgnoreMessage( failureExpected, frameworkMethod ) );
			}
			else {
				virtualIgnore = convertSkipToIgnore( frameworkMethod );
			}
			testCount++;
			log.trace( "adding test " + Helper.extractTestName( frameworkMethod ) + " [#" + testCount + "]" );
			result.add( new ExtendedFrameworkMethod( frameworkMethod, virtualIgnore, failureExpected ) );
		}
		return result;
	}
	@SuppressWarnings({"ClassExplicitlyAnnotation"})
	public static class IgnoreImpl implements Ignore {
		private final String value;
		public IgnoreImpl(String value) {
			this.value = value;
		}
		@Override
		public String value() {
			return value;
		}
		@Override
		public Class extends Annotation> annotationType() {
			return Ignore.class;
		}
	}
	private static Dialect dialect = determineDialect();
	private static Dialect determineDialect() {
		try {
			return DialectContext.getDialect();
		}
		catch (Exception e) {
			return new Dialect( ZERO_VERSION ) {};
		}
	}
	protected Ignore convertSkipToIgnore(FrameworkMethod frameworkMethod) {
		// @Skip
		Skip skip = Helper.locateAnnotation( Skip.class, frameworkMethod, getTestClass() );
		if ( skip != null ) {
			if ( isMatch( skip.condition() ) ) {
				return buildIgnore( skip );
			}
		}
		// @SkipForDialects & @SkipForDialect
		for ( SkipForDialect skipForDialectAnn : Helper.collectAnnotations(
				SkipForDialect.class, SkipForDialects.class, frameworkMethod, getTestClass()
		) ) {
			for ( Class extends Dialect> dialectClass : skipForDialectAnn.value() ) {
				if ( skipForDialectAnn.strictMatching() ) {
					if ( dialectClass.equals( dialect.getClass() ) ) {
						return buildIgnore( skipForDialectAnn );
					}
				}
				else {
					if ( dialectClass.isInstance( dialect ) ) {
						return buildIgnore( skipForDialectAnn );
					}
				}
			}
		}
		for ( org.hibernate.testing.orm.junit.SkipForDialect effectiveSkipForDialect : Helper.collectAnnotations(
				org.hibernate.testing.orm.junit.SkipForDialect.class, SkipForDialectGroup.class, frameworkMethod, getTestClass()
		) ) {
			final boolean versionsMatch;
			final int matchingMajorVersion = effectiveSkipForDialect.majorVersion();
			if ( matchingMajorVersion >= 0 ) {
				versionsMatch = DialectFilterExtension.versionsMatch(
						matchingMajorVersion,
						effectiveSkipForDialect.minorVersion(),
						effectiveSkipForDialect.microVersion(),
						dialect,
						effectiveSkipForDialect.matchSubTypes()
				);
				if ( versionsMatch ) {
					if ( effectiveSkipForDialect.matchSubTypes() ) {
						if ( effectiveSkipForDialect.dialectClass().isInstance( dialect ) ) {
							return buildIgnore( effectiveSkipForDialect );
						}
					}
					else {
						if ( effectiveSkipForDialect.dialectClass().equals( dialect.getClass() ) ) {
							return buildIgnore( effectiveSkipForDialect );
						}
					}
				}
			}
			else {
				if ( effectiveSkipForDialect.matchSubTypes() ) {
					if ( effectiveSkipForDialect.dialectClass().isInstance( dialect ) ) {
						return buildIgnore( effectiveSkipForDialect );
					}
				}
				else {
					if ( effectiveSkipForDialect.dialectClass().equals( dialect.getClass() ) ) {
						return buildIgnore( effectiveSkipForDialect );
					}
				}
			}
		}
		// @RequiresDialects & @RequiresDialect
		final List requiresDialects = Helper.collectAnnotations(
				RequiresDialect.class, RequiresDialects.class, frameworkMethod, getTestClass()
		);
		if ( !requiresDialects.isEmpty() && !isDialectMatchingRequired( requiresDialects ) ) {
			return buildIgnore( requiresDialects );
		}
		final List effectiveRequiresDialects = Helper.collectAnnotations(
				org.hibernate.testing.orm.junit.RequiresDialect.class, org.hibernate.testing.orm.junit.RequiresDialects.class, frameworkMethod, getTestClass()
		);
		if ( !effectiveRequiresDialects.isEmpty() && !isDialectMatchingRequired2( effectiveRequiresDialects ) ) {
			return buildIgnore2( effectiveRequiresDialects );
		}
		// @RequiresDialectFeature
		final List requiresDialectFeatures = Helper.locateAllAnnotations(
				RequiresDialectFeature.class,
				frameworkMethod,
				getTestClass()
		);
		if ( !requiresDialectFeatures.isEmpty() ) {
			for ( RequiresDialectFeature requiresDialectFeature : requiresDialectFeatures ) {
				try {
					for ( Class extends DialectCheck> checkClass : requiresDialectFeature.value() ) {
						if ( !checkClass.newInstance().isMatch( dialect ) ) {
							return buildIgnore( requiresDialectFeature );
						}
					}
				}
				catch (RuntimeException e) {
					throw e;
				}
				catch (Exception e) {
					throw new RuntimeException( "Unable to instantiate DialectCheck", e );
				}
			}
		}
		return null;
	}
	private boolean isDialectMatchingRequired(List requiresDialects) {
		boolean foundMatch = false;
		for ( RequiresDialect requiresDialectAnn : requiresDialects ) {
			for ( Class extends Dialect> dialectClass : requiresDialectAnn.value() ) {
				foundMatch = requiresDialectAnn.strictMatching()
						? dialectClass.equals( dialect.getClass() )
						: dialectClass.isInstance( dialect );
				if ( foundMatch ) {
					break;
				}
			}
			if ( foundMatch ) {
				break;
			}
		}
		return foundMatch;
	}
	private boolean isDialectMatchingRequired2(List effectiveRequiresDialects) {
		for ( org.hibernate.testing.orm.junit.RequiresDialect requiresDialect : effectiveRequiresDialects ) {
			final boolean versionsMatch;
			final int matchingMajorVersion = requiresDialect.majorVersion();
			if ( matchingMajorVersion >= 0 ) {
				final int matchingMinorVersion = requiresDialect.minorVersion();
				final int matchingMicroVersion = requiresDialect.microVersion();
				versionsMatch = DialectFilterExtension.versionsMatch(
						matchingMajorVersion,
						matchingMinorVersion,
						matchingMicroVersion,
						dialect,
						requiresDialect.matchSubTypes()
				);
			}
			else {
				versionsMatch = true;
			}
			if ( ! requiresDialect.value().isInstance( dialect ) ) {
				continue;
			}
			if ( ! versionsMatch ) {
				continue;
			}
			if ( requiresDialect.matchSubTypes() || requiresDialect.value().equals( dialect.getClass() ) ) {
				return true;
			}
		}
		return false;
	}
	private Ignore buildIgnore(Skip skip) {
		return new IgnoreImpl( "@Skip : " + skip.message() );
	}
	private Ignore buildIgnore(SkipForDialect skip) {
		return buildIgnore( "@SkipForDialect match", skip.comment(), skip.jiraKey() );
	}
	private Ignore buildIgnore(org.hibernate.testing.orm.junit.SkipForDialect skip) {
		return buildIgnore( "@SkipForDialect match", skip.reason(), null );
	}
	private Ignore buildIgnore(String reason, String comment, String jiraKey) {
		return new IgnoreImpl( getIgnoreMessage( reason, comment, jiraKey ) );
	}
	private String getIgnoreMessage(String reason, String comment, String jiraKey) {
		StringBuilder buffer = new StringBuilder( reason );
		if ( StringHelper.isNotEmpty( comment ) ) {
			buffer.append( "; " ).append( comment );
		}
		if ( StringHelper.isNotEmpty( jiraKey ) ) {
			buffer.append( " (" ).append( jiraKey ).append( ')' );
		}
		return buffer.toString();
	}
	private Ignore buildIgnore(RequiresDialect requiresDialect) {
		return buildIgnore( "@RequiresDialect non-match", requiresDialect.comment(), requiresDialect.jiraKey() );
	}
	private Ignore buildIgnore(List requiresDialects) {
		String ignoreMessage = "";
		for ( RequiresDialect requiresDialect : requiresDialects ) {
			ignoreMessage += getIgnoreMessage(
					"@RequiresDialect non-match",
					requiresDialect.comment(),
					requiresDialect.jiraKey()
			);
			ignoreMessage += System.lineSeparator();
		}
		return new IgnoreImpl( ignoreMessage );
	}
	private Ignore buildIgnore2(List requiresDialects) {
		String ignoreMessage = "";
		for ( org.hibernate.testing.orm.junit.RequiresDialect requiresDialect : requiresDialects ) {
			ignoreMessage += getIgnoreMessage(
					"@RequiresDialect non-match",
					requiresDialect.comment(),
					null
			);
			ignoreMessage += System.lineSeparator();
		}
		return new IgnoreImpl( ignoreMessage );
	}
	private Ignore buildIgnore(RequiresDialectFeature requiresDialectFeature) {
		return buildIgnore(
				"@RequiresDialectFeature non-match",
				requiresDialectFeature.comment(),
				requiresDialectFeature.jiraKey()
		);
	}
	private boolean isMatch(Class extends Skip.Matcher> condition) {
		try {
			Skip.Matcher matcher = condition.newInstance();
			return matcher.isMatch();
		}
		catch (Exception e) {
			throw new MatcherInstantiationException( condition, e );
		}
	}
	private static class MatcherInstantiationException extends RuntimeException {
		private MatcherInstantiationException(Class extends Skip.Matcher> matcherClass, Throwable cause) {
			super( "Unable to instantiate specified Matcher [" + matcherClass.getName(), cause );
		}
	}
}
                     © 2015 - 2025 Weber Informatics LLC | Privacy Policy