You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/10/18 23:28:34 UTC

[sling-org-apache-sling-junit-performance] 01/14: SLING-3756 - Create an improved JUnit test runner for performance tests

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-junit-performance.git

commit 918b6403c2d293a8c257a91549a557a5691e16b6
Author: Antonio Sanso <as...@apache.org>
AuthorDate: Thu Jul 10 14:09:33 2014 +0000

    SLING-3756 - Create an improved JUnit test runner for performance tests
    
    * applied patch from Francesco Mari (Thanks!!)
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1609464 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  70 ++++++
 .../performance/impl/InvokePerformanceBlock.java   |  85 +++++++
 .../performance/impl/InvokePerformanceMethod.java  |  48 ++++
 .../sling/junit/performance/impl/Listeners.java    | 119 ++++++++++
 .../junit/performance/impl/PerformanceMethod.java  |  61 +++++
 .../performance/listener/StatisticsListener.java   |  68 ++++++
 .../sling/junit/performance/runner/Listen.java     |  30 +++
 .../sling/junit/performance/runner/Listener.java   |  58 +++++
 .../performance/runner/PerformanceRunner.java      | 145 ++++++++++++
 .../junit/performance/runner/PerformanceTest.java  |  37 +++
 .../performance/runner/PerformanceRunnerTest.java  | 247 +++++++++++++++++++++
 11 files changed, 968 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..cc2f17e
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+    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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>19</version>
+        <relativePath>../../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.junit.performance</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <name>Apache Sling JUnit Performance</name>
+    <description>Provides utilities for JUnit to run performance tests and report results</description>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.11</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+            <version>1.3</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-math</artifactId>
+            <version>2.2</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/junit/performance/impl/InvokePerformanceBlock.java b/src/main/java/org/apache/sling/junit/performance/impl/InvokePerformanceBlock.java
new file mode 100644
index 0000000..8af19e5
--- /dev/null
+++ b/src/main/java/org/apache/sling/junit/performance/impl/InvokePerformanceBlock.java
@@ -0,0 +1,85 @@
+/*
+ * 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.junit.performance.impl;
+
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+public class InvokePerformanceBlock extends Statement {
+
+    private final PerformanceMethod method;
+
+    private final Statement inner;
+
+    private final Listeners listeners;
+
+    private final TestClass testClass;
+
+    public InvokePerformanceBlock(TestClass testClass, FrameworkMethod method, Statement inner, Listeners listeners) {
+        this.testClass = testClass;
+        this.method = new PerformanceMethod(method);
+        this.inner = inner;
+        this.listeners = listeners;
+    }
+
+    @Override
+    public void evaluate() throws Throwable {
+
+        // Run warm-up invocations
+
+        listeners.warmUpStarted(testClass.getName(), method.getName());
+        run(method.getWarmUpInvocations(), method.getWarmUpTime());
+        listeners.warmUpFinished(testClass.getName(), method.getName());
+
+        // Run performance invocations
+
+        listeners.executionStarted(testClass.getName(), method.getName());
+        run(method.getRunInvocations(), method.getRunTime());
+        listeners.executionFinished(testClass.getName(), method.getName());
+    }
+
+    private void run(int invocations, int time) throws Throwable {
+        if (invocations > 0) {
+            runByInvocations(invocations);
+            return;
+        }
+
+        if (time > 0) {
+            runByTime(time);
+            return;
+        }
+
+        throw new IllegalArgumentException("no time or number of invocations specified");
+    }
+
+    private void runByInvocations(int invocations) throws Throwable {
+        for (int i = 0; i < invocations; i++) {
+            inner.evaluate();
+        }
+    }
+
+    private void runByTime(int seconds) throws Throwable {
+        long end = System.currentTimeMillis() + seconds * 1000;
+
+        while (System.currentTimeMillis() < end) {
+            inner.evaluate();
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/junit/performance/impl/InvokePerformanceMethod.java b/src/main/java/org/apache/sling/junit/performance/impl/InvokePerformanceMethod.java
new file mode 100644
index 0000000..ed8b5c7
--- /dev/null
+++ b/src/main/java/org/apache/sling/junit/performance/impl/InvokePerformanceMethod.java
@@ -0,0 +1,48 @@
+/*
+ * 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.junit.performance.impl;
+
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+public class InvokePerformanceMethod extends Statement {
+
+    private final TestClass testClass;
+
+    private final PerformanceMethod method;
+
+    private final Statement inner;
+
+    private final Listeners listeners;
+
+    public InvokePerformanceMethod(TestClass testClass, FrameworkMethod method, Statement inner, Listeners listeners) {
+        this.testClass = testClass;
+        this.method = new PerformanceMethod(method);
+        this.inner = inner;
+        this.listeners = listeners;
+    }
+
+    @Override
+    public void evaluate() throws Throwable {
+        listeners.iterationStarted(testClass.getName(), method.getName());
+        inner.evaluate();
+        listeners.iterationFinished(testClass.getName(), method.getName());
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/junit/performance/impl/Listeners.java b/src/main/java/org/apache/sling/junit/performance/impl/Listeners.java
new file mode 100644
index 0000000..1a7b861
--- /dev/null
+++ b/src/main/java/org/apache/sling/junit/performance/impl/Listeners.java
@@ -0,0 +1,119 @@
+/*
+ * 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.junit.performance.impl;
+
+import org.apache.sling.junit.performance.runner.Listener;
+
+import java.util.List;
+
+public class Listeners {
+
+    private boolean isWarmUp;
+
+    private final List<Listener> listeners;
+
+    public Listeners(List<Listener> listeners) {
+        this.listeners = listeners;
+    }
+
+    private interface Invoker {
+
+        void invoke(Listener listener) throws Exception;
+
+    }
+
+    private void invoke(Invoker invoker) throws Exception {
+        for (Listener listener : listeners) {
+            invoker.invoke(listener);
+        }
+
+    }
+
+    public void warmUpStarted(final String className, final String testName) throws Exception {
+        isWarmUp = true;
+
+        invoke(new Invoker() {
+
+            public void invoke(Listener listener) throws Exception {
+                listener.warmUpStarted(className, testName);
+            }
+
+        });
+    }
+
+    public void warmUpFinished(final String className, final String testName) throws Exception {
+        isWarmUp = false;
+
+        invoke(new Invoker() {
+
+            public void invoke(Listener listener) throws Exception {
+                listener.warmUpFinished(className, testName);
+            }
+
+        });
+    }
+
+    public void executionStarted(final String className, final String testName) throws Exception {
+        invoke(new Invoker() {
+
+            public void invoke(Listener listener) throws Exception {
+                listener.executionStarted(className, testName);
+            }
+
+        });
+    }
+
+    public void executionFinished(final String className, final String testName) throws Exception {
+        invoke(new Invoker() {
+
+            public void invoke(Listener listener) throws Exception {
+                listener.executionFinished(className, testName);
+            }
+
+        });
+    }
+
+    public void iterationStarted(final String className, final String testName) throws Exception {
+        invoke(new Invoker() {
+
+            public void invoke(Listener listener) throws Exception {
+                if (isWarmUp) {
+                    listener.warmUpIterationStarted(className, testName);
+                } else {
+                    listener.executionIterationStarted(className, testName);
+                }
+            }
+
+        });
+    }
+
+    public void iterationFinished(final String className, final String testName) throws Exception {
+        invoke(new Invoker() {
+
+            public void invoke(Listener listener) throws Exception {
+                if (isWarmUp) {
+                    listener.warmUpIterationFinished(className, testName);
+                } else {
+                    listener.executionIterationFinished(className, testName);
+                }
+            }
+
+        });
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/junit/performance/impl/PerformanceMethod.java b/src/main/java/org/apache/sling/junit/performance/impl/PerformanceMethod.java
new file mode 100644
index 0000000..296a030
--- /dev/null
+++ b/src/main/java/org/apache/sling/junit/performance/impl/PerformanceMethod.java
@@ -0,0 +1,61 @@
+/*
+ * 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.junit.performance.impl;
+
+import org.apache.sling.junit.performance.runner.PerformanceTest;
+import org.junit.runners.model.FrameworkMethod;
+
+public class PerformanceMethod {
+
+    private final FrameworkMethod method;
+
+    public PerformanceMethod(FrameworkMethod method) {
+        this.method = method;
+    }
+
+    private PerformanceTest getPerformanceTestAnnotation() {
+        PerformanceTest performanceTest = method.getAnnotation(PerformanceTest.class);
+
+        if (performanceTest == null) {
+            throw new IllegalStateException("a performance method should be annotated with @PerformanceTest");
+        }
+
+        return performanceTest;
+    }
+
+    public int getWarmUpTime() {
+        return getPerformanceTestAnnotation().warmUpTime();
+    }
+
+    public int getWarmUpInvocations() {
+        return getPerformanceTestAnnotation().warmUpInvocations();
+    }
+
+    public int getRunTime() {
+        return getPerformanceTestAnnotation().runTime();
+    }
+
+    public int getRunInvocations() {
+        return getPerformanceTestAnnotation().runInvocations();
+    }
+
+    public String getName() {
+        return method.getName();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/junit/performance/listener/StatisticsListener.java b/src/main/java/org/apache/sling/junit/performance/listener/StatisticsListener.java
new file mode 100644
index 0000000..3bd7ad9
--- /dev/null
+++ b/src/main/java/org/apache/sling/junit/performance/listener/StatisticsListener.java
@@ -0,0 +1,68 @@
+/*
+ * 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.junit.performance.listener;
+
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+import org.apache.sling.junit.performance.runner.Listener;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A performance test listener which computes statistics about the execution of a test and makes them available in form
+ * of a {@link org.apache.commons.math.stat.descriptive.DescriptiveStatistics} object.
+ * <p/>
+ * Clients of this listener are supposed to subclass it and to override the {@link #executionStatistics} method to react
+ * when new statistics for a method are available.
+ */
+public abstract class StatisticsListener extends Listener {
+
+    private DescriptiveStatistics statistics;
+
+    private long begin;
+
+    @Override
+    public void executionStarted(String className, String testName) throws Exception {
+        statistics = new DescriptiveStatistics();
+    }
+
+    @Override
+    public void executionIterationStarted(String className, String testName) throws Exception {
+        begin = System.nanoTime();
+    }
+
+    @Override
+    public void executionIterationFinished(String className, String testName) throws Exception {
+        statistics.addValue(TimeUnit.MILLISECONDS.convert(System.nanoTime() - begin, TimeUnit.NANOSECONDS));
+    }
+
+    @Override
+    public void executionFinished(String className, String testName) throws Exception {
+        executionStatistics(className, testName, statistics);
+    }
+
+    /**
+     * This method is called when new statistics are available for a performance method.
+     *
+     * @param className  Name of the class containing the performance test.
+     * @param testName   Name of the method implementing the performance test.
+     * @param statistics Statistics about the executions of the performance test.
+     * @throws Exception
+     */
+    protected abstract void executionStatistics(String className, String testName, DescriptiveStatistics statistics) throws Exception;
+
+}
diff --git a/src/main/java/org/apache/sling/junit/performance/runner/Listen.java b/src/main/java/org/apache/sling/junit/performance/runner/Listen.java
new file mode 100644
index 0000000..5d9b422
--- /dev/null
+++ b/src/main/java/org/apache/sling/junit/performance/runner/Listen.java
@@ -0,0 +1,30 @@
+/*
+ * 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.junit.performance.runner;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Mark a static method or a static instance variable as a listener for a performance test. The variable or the result
+ * type of the method must be a {@link Listener} or a subclass of it.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Listen {
+
+}
diff --git a/src/main/java/org/apache/sling/junit/performance/runner/Listener.java b/src/main/java/org/apache/sling/junit/performance/runner/Listener.java
new file mode 100644
index 0000000..a5d859c
--- /dev/null
+++ b/src/main/java/org/apache/sling/junit/performance/runner/Listener.java
@@ -0,0 +1,58 @@
+/*
+ * 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.junit.performance.runner;
+
+/**
+ * Base class for Listener classes with empty methods for every possible event. A listener is made available to the
+ * {@link PerformanceRunner} using the {@link Listen} annotation.
+ */
+public class Listener {
+
+    public void warmUpStarted(String className, String testName) throws Exception {
+
+    }
+
+    public void warmUpFinished(String className, String testName) throws Exception {
+
+    }
+
+    public void executionStarted(String className, String testName) throws Exception {
+
+    }
+
+    public void executionFinished(String className, String testName) throws Exception {
+
+    }
+
+    public void warmUpIterationStarted(String className, String testName) throws Exception {
+
+    }
+
+    public void executionIterationStarted(String className, String testName) throws Exception {
+
+    }
+
+    public void warmUpIterationFinished(String className, String testName) throws Exception {
+
+    }
+
+    public void executionIterationFinished(String className, String testName) throws Exception {
+
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/junit/performance/runner/PerformanceRunner.java b/src/main/java/org/apache/sling/junit/performance/runner/PerformanceRunner.java
new file mode 100644
index 0000000..d9aa3de
--- /dev/null
+++ b/src/main/java/org/apache/sling/junit/performance/runner/PerformanceRunner.java
@@ -0,0 +1,145 @@
+/*
+ * 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.junit.performance.runner;
+
+import org.apache.sling.junit.performance.impl.InvokePerformanceBlock;
+import org.apache.sling.junit.performance.impl.InvokePerformanceMethod;
+import org.apache.sling.junit.performance.impl.Listeners;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkField;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Custom runner to execute performance tests using JUnit.
+ * <p/>
+ * For a method to be executed as a performance test, it must be annotated with {@link PerformanceTest}. Every time this
+ * annotation is specified, the user must also specify the warm up and execution strategy, because these information are
+ * mandatory for the runner to work properly. The warm up and execution strategy can be provided in two ways: by
+ * specifying the number of executions to run, or by specifying the amount of time the method should run.
+ * <p/>
+ * The runner can also invoke one or more {@link Listener}. The listener is specified as a static variable of the test
+ * class or as the result of a static method. The listeners are made available to the runner by annotating them with the
+ * {@link Listen} annotation.
+ */
+public class PerformanceRunner extends BlockJUnit4ClassRunner {
+
+    private Listeners listeners;
+
+    public PerformanceRunner(Class<?> testClass) throws InitializationError {
+        super(testClass);
+
+        try {
+            listeners = new Listeners(readListeners());
+        } catch (Exception e) {
+            throw new InitializationError(e);
+        }
+    }
+
+    @Override
+    protected List<FrameworkMethod> computeTestMethods() {
+        return getTestClass().getAnnotatedMethods(PerformanceTest.class);
+    }
+
+    @Override
+    protected Statement methodBlock(FrameworkMethod method) {
+        return new InvokePerformanceBlock(getTestClass(), method, super.methodBlock(method), listeners);
+    }
+
+    @Override
+    protected Statement methodInvoker(FrameworkMethod method, Object test) {
+        return new InvokePerformanceMethod(getTestClass(), method, super.methodInvoker(method, test), listeners);
+    }
+
+    private List<Listener> readListeners() throws Exception {
+        List<Listener> listeners = new ArrayList<Listener>();
+
+        listeners.addAll(readListenersFromStaticFields());
+        listeners.addAll(readListenersFromStaticMethods());
+
+        return listeners;
+    }
+
+    private List<Listener> readListenersFromStaticMethods() throws Exception {
+        List<Listener> listeners = new ArrayList<Listener>();
+
+        for (FrameworkMethod method : getTestClass().getAnnotatedMethods(Listen.class)) {
+            if (!method.isPublic()) {
+                throw new IllegalArgumentException("a @Listen method must be public");
+            }
+
+            if (!method.isStatic()) {
+                throw new IllegalArgumentException("a @Listen method must be static");
+            }
+
+            if (!Listener.class.isAssignableFrom(method.getReturnType())) {
+                throw new IllegalArgumentException("a @Listen method must be of type Listener");
+            }
+
+            Listener listener = null;
+
+            try {
+                listener = (Listener) method.invokeExplosively(null);
+            } catch (Throwable throwable) {
+                throw new RuntimeException("error while invoking the @Listen method", throwable);
+            }
+
+            if (listener == null) {
+                throw new IllegalArgumentException("a @Listen method must return a non-null value");
+            }
+
+            listeners.add(listener);
+        }
+
+        return listeners;
+    }
+
+    private List<Listener> readListenersFromStaticFields() throws Exception {
+        List<Listener> reporters = new ArrayList<Listener>();
+
+        for (FrameworkField field : getTestClass().getAnnotatedFields(Listen.class)) {
+            if (!field.isPublic()) {
+                throw new IllegalArgumentException("a @Listen field must be public");
+            }
+
+            if (!field.isStatic()) {
+                throw new IllegalArgumentException("a @Listen field must be static");
+            }
+
+            if (!Listener.class.isAssignableFrom(field.getType())) {
+                throw new IllegalArgumentException("a @Listen field must be of type Listener");
+            }
+
+            Listener listener = (Listener) field.get(null);
+
+            if (listener == null) {
+                throw new IllegalArgumentException("a @Listen field must not be null");
+            }
+
+            reporters.add(listener);
+        }
+
+        return reporters;
+    }
+
+}
+
diff --git a/src/main/java/org/apache/sling/junit/performance/runner/PerformanceTest.java b/src/main/java/org/apache/sling/junit/performance/runner/PerformanceTest.java
new file mode 100644
index 0000000..19442a4
--- /dev/null
+++ b/src/main/java/org/apache/sling/junit/performance/runner/PerformanceTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.junit.performance.runner;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Annotate a method to mark it as a performance test. It also specifies the warm up and execution strategy.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PerformanceTest {
+
+    int warmUpTime() default 0;
+
+    int runTime() default 0;
+
+    int runInvocations() default 0;
+
+    int warmUpInvocations() default 0;
+
+}
diff --git a/src/test/java/org/apache/sling/junit/performance/runner/PerformanceRunnerTest.java b/src/test/java/org/apache/sling/junit/performance/runner/PerformanceRunnerTest.java
new file mode 100644
index 0000000..5141b5a
--- /dev/null
+++ b/src/test/java/org/apache/sling/junit/performance/runner/PerformanceRunnerTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.junit.performance.runner;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Result;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.Failure;
+
+public class PerformanceRunnerTest {
+
+    private Result runTest(Class<?> testClass) {
+        return JUnitCore.runClasses(testClass);
+    }
+
+    private void assertTestFails(Class<?> testClass, String message) {
+        Result result = runTest(testClass);
+
+        boolean isInitializationError = false;
+
+        for (Failure failure : result.getFailures()) {
+            isInitializationError = isInitializationError || failure.getException().getMessage().equals(message);
+        }
+
+        Assert.assertEquals(true, isInitializationError);
+    }
+
+    @RunWith(PerformanceRunner.class)
+    public static class PrivateListenerField {
+
+        @Listen
+        private static Listener listener = new Listener();
+
+        @PerformanceTest
+        public void test() {
+
+        }
+
+    }
+
+    @Test
+    public void testPrivateListenerField() {
+        assertTestFails(PrivateListenerField.class, "a @Listen field must be public");
+    }
+
+    @RunWith(PerformanceRunner.class)
+    public static class InstanceListenerField {
+
+        @Listen
+        public Listener listener = new Listener();
+
+        @PerformanceTest
+        public void test() {
+
+        }
+
+    }
+
+    @Test
+    public void testInstanceListenerField() {
+        assertTestFails(InstanceListenerField.class, "a @Listen field must be static");
+    }
+
+    @RunWith(PerformanceRunner.class)
+    public static class WrongTypeListenerField {
+
+        @Listen
+        public static Integer listener = 42;
+
+        @PerformanceTest
+        public void test() {
+
+        }
+
+    }
+
+    @Test
+    public void testWrongTypeListenerField() {
+        assertTestFails(WrongTypeListenerField.class, "a @Listen field must be of type Listener");
+    }
+
+    @RunWith(PerformanceRunner.class)
+    public static class NullListenerField {
+
+        @Listen
+        public static Listener listener = null;
+
+        @PerformanceTest
+        public void test() {
+
+        }
+
+    }
+
+    @Test
+    public void testNullListenerField() {
+        assertTestFails(NullListenerField.class, "a @Listen field must not be null");
+    }
+
+    @RunWith(PerformanceRunner.class)
+    public static class PrivateListenerMethod {
+
+        @Listen
+        private static Listener listener() {
+            return new Listener();
+        }
+
+        @PerformanceTest
+        public void test() {
+
+        }
+
+    }
+
+    @Test
+    public void testPrivateListenerMethod() {
+        assertTestFails(PrivateListenerMethod.class, "a @Listen method must be public");
+    }
+
+    @RunWith(PerformanceRunner.class)
+    public static class InstanceListenerMethod {
+
+        @Listen
+        public Listener listener() {
+            return new Listener();
+        }
+
+        @PerformanceTest
+        public void test() {
+
+        }
+
+    }
+
+    @Test
+    public void testInstanceListenerMethod() {
+        assertTestFails(InstanceListenerMethod.class, "a @Listen method must be static");
+    }
+
+    @RunWith(PerformanceRunner.class)
+    public static class WrongTypeListenerMethod {
+
+        @Listen
+        public static Integer listener() {
+            return 42;
+        }
+
+        @PerformanceTest
+        public void test() {
+
+        }
+
+    }
+
+    @Test
+    public void testWrongTypeListenerMethod() {
+        assertTestFails(WrongTypeListenerMethod.class, "a @Listen method must be of type Listener");
+    }
+
+    @RunWith(PerformanceRunner.class)
+    public static class BuggyListenerMethod {
+
+        @Listen
+        public static Listener listener() {
+            throw new RuntimeException();
+        }
+
+        @PerformanceTest
+        public void test() {
+
+        }
+
+    }
+
+    @Test
+    public void testBuggyListenerMethod() {
+        assertTestFails(BuggyListenerMethod.class, "error while invoking the @Listen method");
+    }
+
+    @RunWith(PerformanceRunner.class)
+    public static class NullListenerMethod {
+
+        @Listen
+        public static Listener listener() {
+            return null;
+        }
+
+        @PerformanceTest
+        public void test() {
+
+        }
+
+    }
+
+    @Test
+    public void testNullListenerMethod() {
+        assertTestFails(NullListenerMethod.class, "a @Listen method must return a non-null value");
+    }
+
+    @RunWith(PerformanceRunner.class)
+    public static class ExecutionStrategyNotSpecified {
+
+        @PerformanceTest(warmUpInvocations = 10)
+        public void test() {
+
+        }
+
+    }
+
+    @Test
+    public void testExecutionStrategyNotSpecified() {
+        assertTestFails(ExecutionStrategyNotSpecified.class, "no time or number of invocations specified");
+    }
+
+    @RunWith(PerformanceRunner.class)
+    public static class WarmUpStrategyNotSpecified {
+
+        @PerformanceTest(runInvocations = 10)
+        public void test() {
+
+        }
+
+    }
+
+    @Test
+    public void testWarmUpStrategyNotSpecified() {
+        assertTestFails(WarmUpStrategyNotSpecified.class, "no time or number of invocations specified");
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.