/* * Copyright (c) 2011 Obeo. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Obeo - initial API and implementation */ package fr.obeo.performance.api; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import org.junit.Test; import org.junit.internal.runners.statements.Fail; import org.junit.internal.runners.statements.InvokeMethod; import org.junit.rules.MethodRule; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.Parameterized.Parameters; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; import fr.obeo.performance.api.annotation.Monitor; import fr.obeo.performance.api.annotation.Scenario; public class PerformanceRunner extends BlockJUnit4ClassRunner { private static class CompositeStatement extends Statement { private final List<Statement> steps; public CompositeStatement(List<Statement> steps) { this.steps = steps; } @Override public void evaluate() throws Throwable { for (Statement s : steps) { s.evaluate(); } } } private static final class PerformanceStatement extends Statement { /** * The non-monitored statement, used for the optional warm-up execution. */ private Statement basicStatement; private Scenario scenario; private Statement[] monitoredStatements; private PerformanceMonitor monitor; private PerformanceStatement(Statement basicStatement, Scenario scenario) { this.basicStatement = basicStatement; this.scenario = scenario; } public void setMonitoredStatements(Statement[] monitoredStatements) { this.monitoredStatements = monitoredStatements; } public PerformanceMonitor getMonitor() { return monitor; } @Override public void evaluate() throws Throwable { if (scenario.warmup()) { basicStatement.evaluate(); } Performance currentPerf = Performance.getCurrent(); if (currentPerf != null) { monitor = currentPerf.createMonitor(scenario.value()); } for (int i = 0; i < scenario.iterations(); i++) { monitoredStatements[i].evaluate(); } if (monitor != null) { monitor.commit(); } } } public PerformanceRunner(Class<?> klass) throws InitializationError { super(klass); } @Override protected void validateConstructor(List<Throwable> errors) { validateOnlyOneConstructor(errors); if (getParametersMethod(getTestClass()) != null) { List<Object[]> params = getParametersList(getTestClass()); if (params != null && !params.isEmpty()) { int nbParams = params.get(0).length; if (getTestClass().getJavaClass().getConstructors().length == 1 && !(getTestClass().getOnlyConstructor().getParameterTypes().length == nbParams)) { String gripe = "Test class should have exactly one public constructor taking " + nbParams + " arguments"; errors.add(new Exception(gripe)); } } else { validateZeroArgConstructor(errors); } } else { validateZeroArgConstructor(errors); } } @Override protected List<FrameworkMethod> computeTestMethods() { List<FrameworkMethod> result = new ArrayList<FrameworkMethod>(); result.addAll(getTestClass().getAnnotatedMethods(Scenario.class)); for (FrameworkMethod rawTest : getTestClass().getAnnotatedMethods(Test.class)) { if (!result.contains(rawTest)) { result.add(rawTest); } } return result; } @Override protected Statement methodBlock(FrameworkMethod method) { final List<Object[]> parameters = getParametersList(getTestClass()); if (parameters == null) { return createStatement(method, null); } else { List<Statement> steps = new ArrayList<Statement>(); for (Object[] params : parameters) { steps.add(createStatement(method, params)); } return new CompositeStatement(steps); } } private Statement createStatement(FrameworkMethod method, Object[] params) { final Scenario scenario = method.getAnnotation(Scenario.class); final int testsNeeded = scenario == null ? 1 : ((scenario.warmup() ? 1 : 0) + scenario.iterations()); Object[] tests = new Object[testsNeeded]; for (int i = 0; i < tests.length; i++) { try { tests[i] = createTest(params); } catch (Throwable e) { return new Fail(e); } } final Statement basicStatement = createBasicStatement(method, tests[0]); if (scenario == null) { return basicStatement; } else { PerformanceStatement ss = new PerformanceStatement(basicStatement, scenario); final Statement[] monitoredStatement = new Statement[scenario.iterations()]; for (int i = 0; i < scenario.iterations(); i++) { monitoredStatement[i] = createMonitoredStatement(method, tests[i + (scenario.warmup() ? 1 : 0)], ss); } ss.setMonitoredStatements(monitoredStatement); return ss; } } protected Object createTest(Object[] params) throws Exception { if (params == null) { return getTestClass().getOnlyConstructor().newInstance(); } else { return getTestClass().getOnlyConstructor().newInstance(params); } } private Statement createBasicStatement(FrameworkMethod method, Object test) { Statement basicStatement = methodInvoker(method, test); basicStatement = decorate(method, test, basicStatement); return basicStatement; } private Statement createMonitoredStatement(FrameworkMethod method, Object test, PerformanceStatement ss) { Statement monitoredStatement = monitoredMethodInvoker(method, test, ss); monitoredStatement = decorate(method, test, monitoredStatement); return monitoredStatement; } private Statement decorate(FrameworkMethod method, Object test, Statement statement) { Statement result = possiblyExpectingExceptions(method, test, statement); result = withPotentialTimeout(method, test, result); result = withBefores(method, test, result); result = withAfters(method, test, result); result = withRules(method, test, result); return result; } private Statement withRules(FrameworkMethod method, Object target, Statement statement) { Statement result = statement; for (MethodRule each : rules(target)) result = each.apply(result, method, target); return result; } protected Statement monitoredMethodInvoker(FrameworkMethod method, final Object test, final PerformanceStatement ss) { return new InvokeMethod(method, test) { @Override public void evaluate() throws Throwable { PerformanceMonitor monitor = ss.getMonitor(); if (monitor != null) { injectMonitor(test, monitor); monitor.start(); } super.evaluate(); if (monitor != null) { monitor.stop(); } } private void injectMonitor(final Object test, PerformanceMonitor monitor) throws IllegalAccessException { List<FrameworkMethod> annotatedMethods = getTestClass().getAnnotatedMethods(Monitor.class); for (FrameworkMethod method : annotatedMethods) { Class<?>[] params = method.getMethod().getParameterTypes(); if (params.length == 1 && params[0].isAssignableFrom(PerformanceMonitor.class)) { try { method.invokeExplosively(test, new Object[] { monitor }); } catch (Throwable e) { e.printStackTrace(); } } } } }; } @SuppressWarnings("unchecked") private List<Object[]> getParametersList(TestClass klass) { try { FrameworkMethod parametersMethod = getParametersMethod(klass); if (parametersMethod != null) { return (List<Object[]>) parametersMethod.invokeExplosively(null); } else { return null; } } catch (Exception e) { return null; } catch (Throwable e) { return null; } } private FrameworkMethod getParametersMethod(TestClass testClass) { for (FrameworkMethod method : testClass.getAnnotatedMethods(Parameters.class)) { int modifiers = method.getMethod().getModifiers(); if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers)) return method; } return null; } }