/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.sling.performance; import org.apache.sling.performance.annotation.AfterSuite; import org.apache.sling.performance.annotation.BeforeSuite; import org.apache.sling.performance.annotation.PerformanceTest; import org.apache.sling.performance.annotation.PerformanceTestFactory; import org.apache.sling.performance.annotation.PerformanceTestSuite; import org.junit.After; import org.junit.Before; import org.junit.runner.notification.Failure; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.annotation.Annotation; 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.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; /** * The custom JUnit runner that collects the performance tests * */ public class PerformanceRunner extends BlockJUnit4ClassRunner { private static final Logger logger = LoggerFactory.getLogger(ReportLogger.class); protected LinkedList<FrameworkMethod> tests = new LinkedList<FrameworkMethod>(); private List<PerformanceSuiteState> suitesState = new ArrayList<PerformanceSuiteState>(); public ReportLevel reportLevel = ReportLevel.ClassLevel; public String referenceMethod = null; public static enum ReportLevel{ ClassLevel, MethodLevel } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Parameters { public ReportLevel reportLevel() default ReportLevel.ClassLevel; /** This is the name of the reference method used to compute statistics */ public String referenceMethod() default ""; } public PerformanceRunner(Class<?> clazz) throws InitializationError { super(clazz); // set the report level for the tests that are run with the PerformanceRunner // by default set to class level for legacy tests compatibility if (clazz.getAnnotation(Parameters.class) != null){ reportLevel = clazz.getAnnotation(Parameters.class).reportLevel(); referenceMethod = clazz.getAnnotation(Parameters.class).referenceMethod(); if ("".equals(referenceMethod)) { referenceMethod = null; } else { boolean found = false; for (Method method : clazz.getMethods()) { if (method.getName().equals(referenceMethod) && method.getParameterTypes().length == 0) { found = true; break; } } if (!found) { referenceMethod = null; } } } try { computeTests(); } catch (Exception e) { throw new InitializationError(e); } } @Override public void run(RunNotifier notifier) { super.run(notifier); try { ReportLogger.writeAllResults(); } catch (Exception e) { logger.error(e.getMessage(), e); } try { for (Failure failure : ReportLogger.checkAllThresholds()) { notifier.fireTestFailure(failure); } } catch (Exception e) { logger.error(e.getMessage(), e); } } /** * Compute the tests that will be run * * @throws Exception */ protected void computeTests() throws Exception { // add normal JUnit tests tests.addAll(super.computeTestMethods()); // add the performance tests tests.addAll(computePerformanceTests()); // This is called here to ensure the test class constructor is called at // least // once during testing. createTest(); } /** * Compute performance tests * * @return the list containing the performance test methods * @throws Exception */ protected Collection<? extends FrameworkMethod> computePerformanceTests() throws Exception { List<FrameworkMethod> tests = new LinkedList<FrameworkMethod>(); List<Object> testObjects = new ArrayList<Object>(); List<Object> testObjectsTmp = new ArrayList<Object>(); ParameterizedTestList testCenter = new ParameterizedTestList(); // Retrieve the test objects included in the Performance test suite for (FrameworkMethod method : getTestClass().getAnnotatedMethods( PerformanceTestSuite.class)) { Object targetObject = getTestClass().getJavaClass().newInstance(); if (method.getMethod().getReturnType() .equals(ParameterizedTestList.class)) { testCenter = (ParameterizedTestList) method.getMethod().invoke( targetObject); testObjectsTmp = testCenter.getTestObjectList(); // Iterate through all the test cases and see if they have a factory for (Object testObject : testObjectsTmp) { Method[] factoryMethods = getSpecificMethods( testObject.getClass(), PerformanceTestFactory.class); // If we have a factory method, get all the instance objects returned by this factory if (factoryMethods.length > 0) { // Make sure there's only one factory method if (factoryMethods.length > 1) { throw new IllegalStateException( "There should be at most one PerformanceTestFactory method"); } Method factoryMethod = factoryMethods[0]; // Execute the method (statically) Object instances = factoryMethod.invoke(testObject); // If the factory returned an array, make a list if (instances.getClass().isArray()) { instances = Arrays.asList((Object[]) instances); } // If the factory returned a single element, put it in a list if (!(instances instanceof Iterable<?>)) { instances = Collections.singletonList(instances); } testObjects.addAll((List) instances); } else { testObjects.add(testObject); } } } else { throw new InitializationError( "Wrong signature for the @PerformanceTestSuite method"); } } // Retrieve the methods before running the methods from the test suite List<FrameworkMethod> beforeSuiteMethods = getTestClass() .getAnnotatedMethods(BeforeSuite.class); if (beforeSuiteMethods.size() > 1) { throw new InitializationError( "Only one @BeforeSuite method is allowed for a @PerformanceSuite"); } // Retrieve the methods before running the methods from the test suite List<FrameworkMethod> afterSuiteMethods = getTestClass() .getAnnotatedMethods(AfterSuite.class); if (afterSuiteMethods.size() > 1) { throw new InitializationError( "Only one @AfterSuite method is allowed for a @PerformanceSuite"); } PerformanceSuiteState current = null; boolean suiteAlreadyRegistered = false; for (PerformanceSuiteState suiteState : suitesState) { if (suiteState.testSuiteName.equals(testCenter.getTestSuiteName())) { suiteAlreadyRegistered = true; suiteState.incrementNumberOfTestMethodsInSuite(); current = suiteState; break; } } // Create a new PerformanceSuiteState object PerformanceSuiteState newSuite = new PerformanceSuiteState( testCenter.getTestSuiteName()); if (!suiteAlreadyRegistered) { if (beforeSuiteMethods.size() == 1) { newSuite.setBeforeSuiteMethod(beforeSuiteMethods.get(0).getMethod()); } if (afterSuiteMethods.size() == 1) { newSuite.setAfterSuiteMethod(afterSuiteMethods.get(0).getMethod()); } current = newSuite; newSuite.setTargetObjectSuite(getTestClass().getJavaClass().newInstance()); } // In case there are any objects retrieved from the Performance Suite // we should add them to the tests that will be run and increase the // number of methods // contained in the PerformanceSuite if (!testObjects.isEmpty()) { for (Object testObject : testObjects) { // retrieve the test methods from the test classes Method[] testMethods = getSpecificMethods(testObject.getClass(), PerformanceTest.class); for (Method method : testMethods) { FrameworkPerformanceMethod performaceTestMethod = new FrameworkPerformanceMethod(method, testObject, current, reportLevel, referenceMethod); tests.add(performaceTestMethod); } if (!suiteAlreadyRegistered) { newSuite.incrementNumberOfTestMethodsInSuite(); } } // add the new suite to the list of suites suitesState.add(newSuite); } // Retrieve the performance tests in the case we don't have a // performance test suite for (FrameworkMethod method : getTestClass().getAnnotatedMethods(PerformanceTest.class)) { Object targetObject = getTestClass().getJavaClass().newInstance(); FrameworkPerformanceMethod performanceTestMethod = new FrameworkPerformanceMethod( method.getMethod(), targetObject, current, reportLevel, referenceMethod); tests.add(performanceTestMethod); } return tests; } /** * Retrieve specific method from test class * * @param testClass * the test class that we need to search in * @param annotation * the annotation that we should look for * @return the list with the methods that have the specified annotation */ @SuppressWarnings({ "rawtypes" }) private Method[] getSpecificMethods(Class testClass, Class<? extends Annotation> annotation) { Method[] allMethods = testClass.getDeclaredMethods(); List<Method> methodListResult = new ArrayList<Method>(); for (Method testMethod : allMethods) { if (testMethod.isAnnotationPresent(annotation)) { methodListResult.add(testMethod); } } return methodListResult.toArray(new Method[] {}); } /** * {@inheritDoc} * * @see org.junit.runners.BlockJUnit4ClassRunner#computeTestMethods() */ @Override protected List<FrameworkMethod> computeTestMethods() { return tests; } /** * Need to override method otherwise the validation will fail because of * some hardcoded conditions in JUnit */ @Override protected void validateInstanceMethods(List<Throwable> errors) { validatePublicVoidNoArgMethods(After.class, false, errors); validatePublicVoidNoArgMethods(Before.class, false, errors); validateTestMethods(errors); } }