/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.junit.runners;
import java.util.LinkedList;
import java.util.List;
import org.junit.BeforeClass;
import org.junit.experimental.categories.Category;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.internal.runners.statements.RunBefores;
import org.junit.runner.Description;
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;
/**
* This is a custom JUnit runner that allows to determine the set of tests to be
* run based on the VM that is used for running the tests. It also supports
* examination of VM features if the VM is Jikes RVM.
* <p>
* For example, some tests can only be run on Jikes RVM (e.g. because they use
* Magic or other Jikes RVM features) and some tests can only be run on a "normal"
* VM (e.g. HotSpot) because they use libraries such as Mockito that don't support
* GNU Classpath or Jikes RVM. Note that the bootstrap VM can never by Jikes RVM
* because Jikes RVM isn't able to build itself right now (Feb 2016). This makes
* the implementation of the runner easy because it can just assume that the
* bootstrap VM supports the full set of Java features and libraries.
* <p>
* The set of tests to run is determined via classes for JUnit categories,
* e.g. {@link RequiresBuiltJikesRVM} or {@link RequiresBootstrapVM}.
*/
public final class VMRequirements extends BlockJUnit4ClassRunner {
private static final boolean RUNNING_ON_BOOTSTRAP_VM;
private static final boolean RUNNING_ON_BUILT_JIKES_RVM;
private static final boolean RUNNING_ON_IA32;
private static final boolean RUNNING_ON_POWERPC;
private static final boolean VM_HAS_OPT_COMPILER;
private static final boolean VM_USES_32_BIT_ADDRESSING;
private static final boolean VM_USES_64_BIT_ADDRESSING;
static {
String runnerVM = System.getProperty("jikesrvm.junit.runner.vm");
RUNNING_ON_BOOTSTRAP_VM = "bootstrap".equals(runnerVM);
RUNNING_ON_BUILT_JIKES_RVM = "built-jikes-rvm".equals(runnerVM);
if (RUNNING_ON_BUILT_JIKES_RVM) {
String arch = System.getProperty("jikesrvm.target.arch");
RUNNING_ON_IA32 = "ia32".equals(arch);
RUNNING_ON_POWERPC = "ppc".equals(arch);
String opt = System.getProperty("jikesrvm.include.opt");
VM_HAS_OPT_COMPILER = "true".equals(opt);
String addressingMode = System.getProperty("jikesrvm.addressing.mode");
VM_USES_32_BIT_ADDRESSING = "32-bit".equals(addressingMode);
VM_USES_64_BIT_ADDRESSING = "64-bit".equals(addressingMode);
} else {
RUNNING_ON_IA32 = false;
RUNNING_ON_POWERPC = false;
VM_HAS_OPT_COMPILER = false;
VM_USES_32_BIT_ADDRESSING = false;
VM_USES_64_BIT_ADDRESSING = false;
}
}
/**
* By default, JUnit always runs methods annotated with {@code @BeforeClass},
* even if all the tests in the test case are skipped. This is not desirable
* for Jikes RVM tests because test setup code might (have to) rely on Jikes
* RVM internals. This method skips execution of methods annotated with
* {@code @BeforeClass} if necessary.
*/
@Override
protected Statement withBeforeClasses(Statement statement) {
List<FrameworkMethod> befores = getTestClass().getAnnotatedMethods(BeforeClass.class);
LinkedList<FrameworkMethod> newBefores = new LinkedList<FrameworkMethod>();
for (FrameworkMethod method : befores) {
if (methodIsNotSuitableForExecutionInCurrentVMEnvironment(method)) {
// skip the method
} else {
newBefores.addLast(method);
}
}
return new RunBefores(statement, newBefores, null);
}
public VMRequirements(Class<?> klass) throws InitializationError {
super(klass);
}
@Override
protected void runChild(FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
boolean ignoreTest =
methodIsNotSuitableForExecutionInCurrentVMEnvironment(method);
if (ignoreTest) {
ignoreTest(method, notifier, description);
return;
}
super.runChild(method, notifier);
}
private boolean methodIsNotSuitableForExecutionInCurrentVMEnvironment(
FrameworkMethod method) {
return !isRunningOnBuiltJikesRVM() && annotatedWith(method, RequiresBuiltJikesRVM.class) ||
!isRunningOnBootstrapVM() && annotatedWith(method, RequiresBootstrapVM.class) ||
!VM_HAS_OPT_COMPILER && annotatedWith(method, RequiresOptCompiler.class) ||
VM_HAS_OPT_COMPILER && (annotatedWith(method, RequiresLackOfOptCompiler.class)) ||
RUNNING_ON_IA32 && annotatedWith(method, RequiresPowerPC.class) ||
RUNNING_ON_POWERPC && annotatedWith(method, RequiresIA32.class) ||
VM_USES_32_BIT_ADDRESSING && annotatedWith(method, Requires64BitAddressing.class) ||
VM_USES_64_BIT_ADDRESSING && annotatedWith(method, Requires32BitAddressing.class);
}
public static boolean isRunningOnBootstrapVM() {
return RUNNING_ON_BOOTSTRAP_VM;
}
public static boolean isRunningOnBuiltJikesRVM() {
return RUNNING_ON_BUILT_JIKES_RVM;
}
private void ignoreTest(FrameworkMethod method, RunNotifier notifier, Description description) {
EachTestNotifier eachTestNotifier = new EachTestNotifier(notifier, description);
eachTestNotifier.fireTestIgnored();
}
private boolean annotatedWith(FrameworkMethod method, Class<?> category) {
boolean methodRequiresVM = false;
boolean classRequiresVM = false;
Category methodAnnotation = method.getAnnotation(Category.class);
Category classAnnotation = getTestClass().getJavaClass().getAnnotation(Category.class);
methodRequiresVM = checkAnnotation(methodAnnotation, category);
classRequiresVM = checkAnnotation(classAnnotation, category);
return methodRequiresVM || classRequiresVM;
}
private boolean checkAnnotation(Category annotation, Class<?> category) {
if (annotation == null) return false;
Class<?>[] categories = annotation.value();
if (categories == null) return false;
for (Class<?> c : categories) {
if (c.equals(category)) {
return true;
}
}
return false;
}
}