/* * Copyright to the original author or authors. * * Licensed 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. */ package org.rioproject.test; import org.junit.Assert; import org.junit.Test; import org.junit.internal.runners.model.MultipleFailureException; import org.junit.internal.runners.statements.FailOnTimeout; 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.rioproject.RioVersion; import org.rioproject.logging.LoggingSystem; import org.rioproject.url.artifact.ArtifactURLStreamHandlerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.logging.LogManager; /** * RioTestRunner is a custom extension of {@link BlockJUnit4ClassRunner} * * <p> * <em>NOTE</em>: RioTestRunner requires JUnit 4.5. or later */ public class RioTestRunner extends BlockJUnit4ClassRunner { static final Logger logger = LoggerFactory.getLogger(RioTestRunner.class.getName()); static { if (System.getSecurityManager() == null) { Utils.checkSecurityPolicy(); System.setSecurityManager(new SecurityManager()); } if(LoggingSystem.usingJUL()) { if(System.getProperty("java.util.logging.config.file")==null) { LogManager logManager = LogManager.getLogManager(); try { logManager.readConfiguration(Thread.currentThread().getClass().getResourceAsStream("/default-logging.properties")); } catch (IOException e) { e.printStackTrace(); } } } /* If the artifact URL has not been configured, set it up */ try { new URL("artifact:foo"); } catch (MalformedURLException e) { URL.setURLStreamHandlerFactory(new ArtifactURLStreamHandlerFactory()); } } TestManager testManager; TestConfig testConfig; /** * Constructs a new <code>RioTestRunner</code> * * @param clazz the Class object corresponding to the test class to be run * * @throws org.junit.runners.model.InitializationError If the class * cannot be created */ public RioTestRunner(Class<?> clazz) throws InitializationError { super(clazz); logger.debug("TestRunner constructor called with [{}].", clazz); } @Override protected Statement withAfterClasses(Statement statement) { Statement afterStatement = super.withAfterClasses(statement); return new TestCaseShutdownStatement(afterStatement, testManager); } /** * Check whether the test is enabled in the first place. This prevents classes with * a non-matching <code>@IfPropertySet</code> annotation from running altogether, * even skipping the execution of <code>prepareTestInstance</code> listener methods. * * This method also creates and initializes a {@link TestManager} providing * Rio testing functionality to standard JUnit tests. * * @see #createTestConfig(String) */ @Override public void run(RunNotifier notifier) { if (!isTestClassEnabled(getTestClass().getJavaClass())) { logger.info("Test class {} is not enabled, skipping", getTestClass().getJavaClass().getName()); notifier.fireTestIgnored(getDescription()); return; } Utils.setEnvironment(); testConfig = createTestConfig(getTestClass().getName()); testManager = testConfig.getTestManager(); testManager.init(testConfig); for (FrameworkMethod fMethod : getTestClass().getAnnotatedMethods(SetTestManager.class)) { Method method = fMethod.getMethod(); if(Modifier.isStatic(method.getModifiers())) { method.setAccessible(true); try { method.invoke(null, getManager()); } catch (Exception e) { logger.warn("Invoking static method [{}] with declared annotation {}", method.getName(), SetTestManager.class.getName(), e); } //break; } } for(Field field : getAnnotatedFields(getTestClass().getJavaClass(), SetTestManager.class)) { field.setAccessible(true); if(Modifier.isStatic(field.getModifiers())) { try { field.set(null, getManager()); } catch (IllegalAccessException e) { logger.warn("Invoking static field [{}] with declared annotation {}", field.getName(), SetTestManager.class.getName(), e); } //break; } } super.run(notifier); } /** * Delegates to {@link BlockJUnit4ClassRunner#createTest()} to create the test * {@link BlockJUnit4ClassRunner#createTest()}, first checks for TestManager injection */ @Override protected Object createTest() throws Exception { Object testInstance = super.createTest(); for (FrameworkMethod fMethod : getTestClass().getAnnotatedMethods(SetTestManager.class)) { Method method = fMethod.getMethod(); if(!Modifier.isStatic(method.getModifiers())) { method.setAccessible(true); try { method.invoke(null, getManager()); } catch (Exception e) { logger.warn("Invoking method [{}] with declared annotation {}", method.getName(), SetTestManager.class.getName(), e); } break; } } for(Field field : getAnnotatedFields(testInstance.getClass(), SetTestManager.class)) { field.setAccessible(true); if(!Modifier.isStatic(field.getModifiers())) { try { field.set(testInstance, getManager()); } catch (IllegalAccessException e) { logger.warn("Invoking field [{}] with declared annotation {}", field.getName(), SetTestManager.class.getName(), e); } break; } } return testInstance; } @Override protected Statement withPotentialTimeout(FrameworkMethod method, Object target, Statement next) { Test testAnnotation = method.getAnnotation(Test.class); long junitTimeout = 0; if(testAnnotation != null && testAnnotation.timeout() > 0) junitTimeout = testAnnotation.timeout(); long testTimeout = testManager.getTestConfig().getTimeout(); if (junitTimeout > 0) { if(testTimeout > 0) { logger.info("Test method [{}] has a default test configuration timeout value of {} "+ "and JUnit's @Test(timeout={}) annotation. The JUnit declaration takes precedence " + "over the configured default timeout.", method.getMethod(), testTimeout, junitTimeout); } return super.withPotentialTimeout(method, target, next); } if(testTimeout>0) return new FailOnTimeout(next, testTimeout); return super.withPotentialTimeout(method, target, next); } /** * Creates A testConfig. Can be overridden by subclasses. * * @param testClassName The name of the test class to be run * * @return A TestConfig */ protected TestConfig createTestConfig(String testClassName) { TestConfig testConfig = new TestConfig(testClassName); String testConfigLocation = System.getProperty("org.rioproject.test.config"); /* If the property isnt declared look for the test configuration in the expected place. If found, use it */ if(testConfigLocation==null) { File tc = new File(System.getProperty("user.dir"), "src"+File.separator+ "test"+File.separator+ "conf"+File.separator+ "test-config.groovy"); if(tc.exists()) testConfigLocation = tc.getPath(); } StringBuilder sb = new StringBuilder(); sb.append("\n"); sb.append("Test Configuration for ").append(testClassName); sb.append("\n"); sb.append("=========================================="); sb.append("\n"); sb.append("Rio "); sb.append(RioVersion.VERSION); sb.append(" build "); sb.append(RioVersion.getBuildNumber()); sb.append("\n"); sb.append("=========================================="); sb.append("\n"); sb.append("JAVA_HOME: "); sb.append(System.getProperty("JAVA_HOME")); sb.append("\n"); sb.append("RIO_HOME: "); sb.append(System.getProperty("rio.home")); sb.append("\n"); if(testConfigLocation!=null) { sb.append("test-config: ").append(testConfigLocation); sb.append("\n"); testConfig.loadConfig(testConfigLocation); sb.append("groups: ").append((testConfig.getGroups()==null? "<not declared>":testConfig.getGroups())); sb.append("\n"); sb.append("locators: ").append((testConfig.getLocators()==null? "<not declared>":testConfig.getLocators())); sb.append("\n"); sb.append("numCybernodes: ").append(testConfig.getNumCybernodes()); sb.append("\n"); sb.append("numMonitors: ").append(testConfig.getNumMonitors()); sb.append("\n"); if(testConfig.getNumLookups()>0) { sb.append("numLookups: ").append(testConfig.getNumLookups()); sb.append("\n"); } sb.append("opstring: ").append((testConfig.getOpString()==null? "<not declared>":testConfig.getOpString())); sb.append("\n"); sb.append("autoDeploy: ").append(testConfig.autoDeploy()); sb.append("\n"); if(testConfig.autoDeploy()) Assert.assertNotNull("You have declared that the test case " + "["+testClassName+"] have it's " + "OperationalString automatically " + "deployed, but have not declared an " + "OperationalString. You must declare " + "an OperationalString using the " + "opstring property for the test case " + "configuration", testConfig.getOpString()); } sb.append("testManager: ").append(testConfig.getTestManager().getClass().getName()); sb.append("\n"); sb.append("harvest: ").append(testConfig.runHarvester()); sb.append("\n"); sb.append("loggingSystem: ").append(testConfig.getLoggingSystem().name().toLowerCase()); sb.append("\n"); sb.append("timeout: ").append((testConfig.getTimeout()==0? "<not declared>":testConfig.getTimeout())); sb.append("\n"); sb.append("=========================================="); logger.info(sb.toString()); return testConfig; } /** * Get the {@link TestManager} associated with this runner. * @return The created Manager */ protected final TestManager getManager() { return this.testManager; } /* * Invokes the supplied {@link Method test method} and notifies the supplied * {@link RunNotifier} of the appropriate events. * @see #createTest() * @see BlockJUnit4ClassRunner#invokeTestMethod(Method, RunNotifier) */ @Override protected void runChild(FrameworkMethod method, RunNotifier notifier) { logger.debug("Invoking test method [{}]", method.getMethod().toGenericString()); if (!isTestMethodEnabled(method.getMethod())) { logger.info("Test method {} is not enabled, skipping", method.getMethod().toGenericString()); notifier.fireTestIgnored(getDescription()); return; } super.runChild(method, notifier); } boolean isTestClassEnabled(Class<?> testClass) { return isPropertySet(testClass.getAnnotation(IfPropertySet.class)); } boolean isTestMethodEnabled(Method testMethod) { return isPropertySet(testMethod.getAnnotation(IfPropertySet.class)); } boolean isPropertySet(IfPropertySet ifPropertySet) { if (ifPropertySet == null) { return true; } boolean isSet = false; String value = System.getProperty(ifPropertySet.name()); if(value==null) return false; String propertyValue = ifPropertySet.value(); String notPropertyValue = ifPropertySet.notvalue(); boolean wildcard = false; if(propertyValue.length()>0) { if(propertyValue.endsWith("*")) { propertyValue = propertyValue.substring(0, propertyValue.length()-1); wildcard = true; } isSet = (wildcard?value.startsWith(propertyValue):value.equals(propertyValue)); } if(notPropertyValue.length()>0) { if(notPropertyValue.endsWith("*")) { notPropertyValue = notPropertyValue.substring(0, notPropertyValue.length()-1); wildcard = true; } isSet = !(wildcard?value.startsWith(notPropertyValue):value.equals(notPropertyValue)); } return isSet; } List<Field> getAnnotatedFields(Class<?> tClass, Class<? extends Annotation> annotationClass) { List<Field> results= new ArrayList<Field>(); for (Class<?> eachClass : getSuperClasses(tClass)) { Field[] fields= eachClass.getDeclaredFields(); for (Field field : fields) { Annotation annotation= field.getAnnotation(annotationClass); if (annotation != null) results.add(field); } } return results; } private List<Class<?>> getSuperClasses(Class< ?> testClass) { ArrayList<Class<?>> results= new ArrayList<Class<?>>(); Class<?> current= testClass; while (current != null) { results.add(current); current= current.getSuperclass(); } return results; } class InitStatement extends Statement { Statement next; TestConfig testConfig; InitStatement(Statement next, TestConfig testConfig) { this.next = next; this.testConfig = testConfig; } @Override public void evaluate() throws Throwable { testManager.init(testConfig); next.evaluate(); } } /** * A custom JUnit 4.5+ {@link Statement} that first checks if Harvester * should be run. This allows Harvester to be plugged ino the JUnit test * execution chain, executing after all <tt>@AfterClass</tt> invocations * have been run. This statement also undeploys deployed opstrings prior to * returning. */ class TestCaseShutdownStatement extends Statement { Statement next; TestManager testManager; TestCaseShutdownStatement(Statement next, TestManager testManager) { this.next = next; this.testManager = testManager; } @Override public void evaluate() throws Throwable { List<Throwable> errors = new ArrayList<Throwable>(); try { next.evaluate(); } catch (Throwable e) { errors.add(e); } try { testManager.maybeRunHarvester(); } catch (Exception e) { errors.add(e); } if(testManager.getDeployedOperationalStringManager()!=null) { try { String undeploy = testManager.getDeployedOperationalStringManager() .getOperationalString().getName(); testManager.undeploy(undeploy); } catch (Throwable t) { System.err.println("Failed automated undeployment as part of test case shutdown for " + "["+testManager.getOpStringToDeploy()+"], "+ t.getClass().getName()+": "+t.getMessage()); } } testManager.shutdown(); if (errors.isEmpty()) return; if (errors.size() == 1) throw errors.get(0); throw new MultipleFailureException(errors); } } }