blob: 42ee110f4ac9f5e9c70b9c8767e8cb784d5b9a36 [file] [log] [blame]
/*
* 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 java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.naming.directory.InvalidAttributesException;
import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
import org.apache.sling.performance.annotation.AfterMethodInvocation;
import org.apache.sling.performance.annotation.BeforeMethodInvocation;
import org.apache.sling.performance.annotation.PerformanceTest;
import org.junit.After;
import org.junit.Before;
import org.junit.runners.model.FrameworkMethod;
class FrameworkPerformanceMethod extends FrameworkMethod {
private Object target;
private PerformanceSuiteState performanceSuiteState;
private PerformanceRunner.ReportLevel reportLevel = PerformanceRunner.ReportLevel.ClassLevel;
private String testCaseName = "";
private String className;
public FrameworkPerformanceMethod(Method method, Object target,
PerformanceSuiteState performanceSuiteState, PerformanceRunner.ReportLevel reportLevel) {
super(method);
this.target = target;
this.performanceSuiteState = performanceSuiteState;
this.reportLevel = reportLevel;
if (target instanceof IdentifiableTestCase) {
this.testCaseName = ((IdentifiableTestCase) target).testCaseName();
}
// Name of the test class, as the report logger needs it
// This can be overwritten by tests by implementing IdentifiableTestClass
String longClassName = this.target.getClass().getName();
className = longClassName.substring(longClassName.lastIndexOf(".") + 1);
if (target instanceof IdentifiableTestClass) {
this.className = ((IdentifiableTestClass) target).testClassName();
}
}
@Override
public Object invokeExplosively(Object target, Object... params)
throws Throwable {
// Executes the test method on the supplied target
// Check if this is the first test running from this specific
// PerformanceSuite
// and run the BeforeSuite methods
if ((performanceSuiteState != null)
&& (performanceSuiteState.getBeforeSuiteMethod() != null)
&& (performanceSuiteState.getTargetObjectSuite() != null)
&& (performanceSuiteState.getNumberOfExecutedMethods() == 0)
&& !performanceSuiteState.testSuiteName
.equals(ParameterizedTestList.TEST_CASE_ONLY)) {
performanceSuiteState.getBeforeSuiteMethod().invoke(
performanceSuiteState.getTargetObjectSuite());
}
// In case of a PerformanceSuite we need to run the methods annotated
// with Before and After
// ourselves as JUnit can't find them (JUnit is looking for them in the
// test suite class);
// in case we don't have to deal with a PerformanceSuite just skip this
// as JUnit will run the methods itself
if ((performanceSuiteState != null)
&& !performanceSuiteState.testSuiteName.equals(ParameterizedTestList.TEST_CASE_ONLY)) {
recursiveCallSpecificMethod(this.target.getClass(), this.target, Before.class);
}
// Need to count the number of tests run from the PerformanceSuite
// so that we can call the AfterSuite method after the last test from
// the suite
// has run and the AfterSuite needs to run
performanceSuiteState.incrementNumberOfExecutedTestMethods();
Object response = null;
Method testMethodToInvoke = this.getMethod();
PerformanceTest performanceAnnotation = testMethodToInvoke
.getAnnotation(PerformanceTest.class);
// retrieve the test configuration options
int warmuptime = performanceAnnotation.warmuptime();
int runtime = performanceAnnotation.runtime();
int warmupinvocations = performanceAnnotation.warmupinvocations();
int runinvocations = performanceAnnotation.runinvocations();
DescriptiveStatistics statistics = new DescriptiveStatistics();
if (warmupinvocations != 0) {
// Run the number of invocation specified in the annotation
// for warming up the system
for (int invocationIndex = 0; invocationIndex < warmupinvocations; invocationIndex++) {
recursiveCallSpecificMethod(this.target.getClass(), this.target, BeforeMethodInvocation.class);
// TODO: implement the method to run a before a specific test
// method
// recursiveCallSpecificMethod(this.target.getClass(),
// this.target, BeforeSpecificTest.class);
response = super.invokeExplosively(this.target, params);
// TODO: implement the method to run a after a specific test
// method
// recursiveCallSpecificMethod(this.target.getClass(),
// this.target, AfterSpecificTest.class);
recursiveCallSpecificMethod(this.target.getClass(),
this.target, AfterMethodInvocation.class);
}
} else {
// Run a few iterations to warm up the system
long warmupEnd = System.currentTimeMillis() + warmuptime * 1000;
while (System.currentTimeMillis() < warmupEnd) {
recursiveCallSpecificMethod(this.target.getClass(),
this.target, BeforeMethodInvocation.class);
// TODO: implement the method to run a before a specific test
// method
// recursiveCallSpecificMethod(this.target.getClass(),
// this.target, BeforeSpecificTest.class);
response = super.invokeExplosively(this.target, params);
// recursiveCallSpecificMethod(this.target.getClass(),
// this.target, AfterSpecificTest.class);
// TODO: implement the method to run a after a specific test
// method
recursiveCallSpecificMethod(this.target.getClass(),
this.target, AfterMethodInvocation.class);
}
}
// System.out.println("Warmup ended - test :" +
// testMethodToInvoke.getName());
if (runinvocations != 0) {
// Run the specified number of iterations and capture the execution
// times
for (int invocationIndex = 0; invocationIndex < runinvocations; invocationIndex++) {
response = this.invokeTimedTestMethod(testMethodToInvoke,
statistics, params);
}
} else {
// Run test iterations and capture the execution times
long runtimeEnd = System.currentTimeMillis() + runtime * 1000;
while (System.currentTimeMillis() < runtimeEnd) {
response = this.invokeTimedTestMethod(testMethodToInvoke,
statistics, params);
}
}
if (statistics.getN() > 0) {
ReportLogger.writeReport(this.performanceSuiteState.testSuiteName, testCaseName, className, getMethod().getName(),
statistics, ReportLogger.ReportType.TXT, reportLevel);
}
// In case of a PerformanceSuite we need to run the methods annotated
// with Before and After
// ourselves as JUnit can't find them; in case we don't have to deal
// with a PerformanceSuite
// just skip this as JUnit will run the methods itself
if ((performanceSuiteState != null)
&& !performanceSuiteState.testSuiteName.equals(ParameterizedTestList.TEST_CASE_ONLY)) {
recursiveCallSpecificMethod(this.target.getClass(), this.target, After.class);
}
// Check if this is the last test running from a PerformanceSuite
// and run the AfterSuite method
if ((performanceSuiteState != null)
&& (performanceSuiteState.getAfterSuiteMethod() != null)
&& (performanceSuiteState.getTargetObjectSuite() != null)
&& (performanceSuiteState.getNumberOfExecutedMethods() == performanceSuiteState.getNumberOfMethodsInSuite())
&& !performanceSuiteState.testSuiteName.equals(ParameterizedTestList.TEST_CASE_ONLY)) {
performanceSuiteState.getAfterSuiteMethod().invoke(performanceSuiteState.getTargetObjectSuite());
}
return response;
}
/**
* Method that runs 1 invocation of the timed test method
*
* @param testMethodToInvoke
* the test method to invoke
* @param statistics
* the statistics object that collects the results
* @param params
* the parameters for the invocation of the test method
* @return the response from the method invocation
* @throws Throwable
*/
private Object invokeTimedTestMethod(Method testMethodToInvoke,
DescriptiveStatistics statistics, Object... params)
throws Throwable {
Object response = null;
recursiveCallSpecificMethod(this.target.getClass(), this.target,
BeforeMethodInvocation.class);
// TODO: implement the method to run a before a specific test method
// recursiveCallSpecificMethod(this.target.getClass(), this.target,
// BeforeSpecificTest.class);
// timing the test method execution
// System.out.println("Start test: " + testMethodToInvoke.getName());
long start = System.nanoTime();
response = super.invokeExplosively(this.target, params);
long timeMilliseconds = TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS);
statistics.addValue(timeMilliseconds);
// System.out.println("End test: " + testMethodToInvoke.getName());
// System.out.println("Test execution time (ms): " + timeMilliseconds);
// TODO: implement the method to run a after a specific test method
// recursiveCallSpecificMethod(this.target.getClass(), this.target,
// AfterSpecificTest.class);
recursiveCallSpecificMethod(this.target.getClass(), this.target, AfterMethodInvocation.class);
return response;
}
/**
* Recursively call a specific method annotated with a custom annotation
*
* @param test
* the test class that contains the method
* @param instance
* the instance on which will run the method
* @param methodAnnotation
* the method annotation to look for
* @throws InvocationTargetException
* @throws InvalidAttributesException
* @throws IllegalAccessException
* @throws InstantiationException
*/
@SuppressWarnings({ "rawtypes" })
private void recursiveCallSpecificMethod(Class test, Object instance,
Class<? extends Annotation> methodAnnotation)
throws InvocationTargetException, InvalidAttributesException,
IllegalAccessException, InstantiationException {
if (test.getSuperclass() != null) {
recursiveCallSpecificMethod(test.getSuperclass(), instance,
methodAnnotation);
}
Method testMethod = getSpecificTestMethod(test, methodAnnotation);
if (testMethod != null) {
if (!testMethod.isAccessible()) {
testMethod.setAccessible(true);
}
testMethod.invoke(instance);
}
}
/**
* Get the method annotated with the custom annotation
*
* @param testClass
* the test class on which to look for the method
* @param methodAnnotation
* the method annotation to look for
* @return
* @throws InvalidAttributesException
* @throws IllegalAccessException
* @throws InstantiationException
*/
@SuppressWarnings({ "rawtypes" })
private Method getSpecificTestMethod(Class testClass,
Class<? extends Annotation> methodAnnotation)
throws InvalidAttributesException, IllegalAccessException,
InstantiationException {
Method[] methodsToReturn = getSpecificMethods(testClass, methodAnnotation);
Method methodToReturn = null;
if (methodsToReturn.length == 1) {
methodToReturn = methodsToReturn[0];
} else if (methodsToReturn.length > 1) {
throw new InvalidAttributesException("Only 1 non parameterized before method accepted");
}
return methodToReturn;
}
/**
* Retrieve all the specific methods 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[] {});
}
@Override
public String getName() {
if (testCaseName == null || "".equals(testCaseName.trim())) { return super.getName(); }
return String.format("%s [%s.%s]", testCaseName, target.getClass().getSimpleName(),
getMethod().getName());
}
}