
com.github.nfalco79.junit4osgi.runner.internal.JUnitRunner Maven / Gradle / Ivy
/*
* Copyright 2017 Nikolas Falco
* 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 com.github.nfalco79.junit4osgi.runner.internal;
import java.io.File;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.junit.runner.JUnitCore;
import org.junit.runner.notification.RunListener;
import org.osgi.service.log.LogService;
import com.github.nfalco79.junit4osgi.registry.TestRegistryUtils;
import com.github.nfalco79.junit4osgi.registry.spi.TestBean;
import com.github.nfalco79.junit4osgi.registry.spi.TestRegistry;
import com.github.nfalco79.junit4osgi.registry.spi.TestRegistryChangeListener;
import com.github.nfalco79.junit4osgi.registry.spi.TestRegistryEvent;
import com.github.nfalco79.junit4osgi.runner.internal.AntGlobPattern.IncludeExcludePattern;
import com.github.nfalco79.junit4osgi.runner.internal.jmx.JMXServer;
import com.github.nfalco79.junit4osgi.runner.spi.TestRunner;
import com.github.nfalco79.junit4osgi.runner.spi.TestRunnerNotifier;
import com.j256.simplejmx.common.JmxAttributeMethod;
import com.j256.simplejmx.common.JmxOperation;
import com.j256.simplejmx.common.JmxOperationInfo.OperationAction;
import com.j256.simplejmx.common.JmxResource;
@JmxResource(domainName = "org.osgi.junit4osgi", folderNames = "type=runner", beanName = "JUnitRunner", description = "The JUnit4 runner, executes JUnit3/4 test case in any OSGi bundle in the current system")
public class JUnitRunner implements TestRunner {
private final class QueeueTestListener implements TestRegistryChangeListener {
private final Queue tests;
private QueeueTestListener(Queue tests) {
this.tests = tests;
}
@Override
public void registryChanged(TestRegistryEvent event) {
TestBean testBean = event.getTest();
if (testBean == null) {
throw new IllegalArgumentException("event has a null test bean");
}
switch (event.getType()) {
case ADD:
tests.add(testBean);
break;
case REMOVE:
tests.remove(testBean);
break;
default:
logger.log(LogService.LOG_WARNING, "Test registry event type " + event.getType() + " not supported");
break;
}
}
}
public static final String RUNNER_REGISTY = "org.osgi.junit.runner.registry";
public static final String RUNNER_AUTOSTART = "org.osgi.junit.runner.autostart";
public static final String REPORT_PATH = "org.osgi.junit.reportsPath";
public static final String RERUN_COUNT = "org.osgi.junit.rerunFailingTestsCount";
public static final String PATH_INCLUDES = "org.osgi.junit.include";
public static final String PATH_EXCLUDE = "org.osgi.junit.exclude";
private TestRegistry registry;
private boolean stop;
private boolean running;
private LogService logger;
private Set includes;
private Set excludes;
private TestRegistryChangeListener testListener;
private ScheduledThreadPoolExecutor executor;
private final Integer reRunCount;
private final File defaultReportsDirectory;
public JUnitRunner() {
defaultReportsDirectory = new File(System.getProperty(REPORT_PATH, "surefire-reports"));
reRunCount = Integer.getInteger(RERUN_COUNT, 5);
stop = true;
setIncludes(new LinkedHashSet());
setExcludes(new LinkedHashSet());
}
/* (non-Javadoc)
* @see com.github.nfalco79.junit4osgi.runner.internal.TestRunner#setRegistry(com.github.nfalco79.junit4osgi.registry.spi.TestRegistry)
*/
@Override
public void setRegistry(final TestRegistry registry) {
bindRegistry(registry, null);
}
/**
* Binds the registry service reference.
*
* @param registry
* an implementation of {@link TestRegistry}
* @param properties
* component declaration properties registered for the given
* registry instance.
*/
public void bindRegistry(final TestRegistry registry, final Map properties) {
final String defaultRegistry = System.getProperty(RUNNER_REGISTY, "auto");
if (properties == null || defaultRegistry.equals(properties.get("discovery"))) {
this.registry = registry;
}
}
/**
* Remove binds of the registry service instance if matches the current.
*
* @param registry
* the implementation of {@link TestRegistry} that is being
* disabled.
*/
public void unbindRegistry(final TestRegistry registry) {
if (this.registry == registry) {
this.registry = null;
}
}
/* (non-Javadoc)
* @see com.github.nfalco79.junit4osgi.runner.internal.TestRunner#setLog(org.osgi.service.log.LogService)
*/
@Override
public void setLog(final LogService logger) {
this.logger = logger;
}
/* (non-Javadoc)
* @see com.github.nfalco79.junit4osgi.runner.internal.TestRunner#start()
*/
@Override
public void start() {
start(null, null, null);
}
@JmxOperation(description = "Start the runner that execute the test with the specified id collected by the JUnit registry", operationAction = OperationAction.ACTION)
public void start(String[] testIds, String reportsPath) {
start(testIds, reportsPath, null);
}
@Override
public void start(String[] testIds, String reportsPath, TestRunnerNotifier notifier) {
if (logger == null || registry == null) {
return;
}
final File reportsDirectory;
if (reportsPath != null) {
reportsDirectory = new File(reportsPath);
} else {
reportsDirectory = defaultReportsDirectory;
}
if (!isRunning()) {
final Queue tests;
if (testIds == null) {
// create a queue collecting all registry tests
tests = new ConcurrentLinkedQueue();
testListener = new QueeueTestListener(tests);
registry.addTestRegistryListener(testListener);
tests.addAll(registry.getTests());
} else {
// create a queue with only the specified tests
tests = new ArrayDeque(registry.getTests(testIds));
}
stop = false;
running = true;
executor = new ScheduledThreadPoolExecutor(1);
if (testIds != null) {
Runnable testRunnable = getSingleRunnable(reportsDirectory, tests, notifier);
executor.schedule(testRunnable, 0l, TimeUnit.MILLISECONDS);
} else {
Runnable testRunnable = getInfiniteRunnable(reportsDirectory, tests);
executor.scheduleAtFixedRate(testRunnable, 0l, getRepeatTime(), TimeUnit.MILLISECONDS);
}
}
}
/**
* For test purpose only
*
* @return the delay time before reschedule the job runner
*/
protected long getRepeatTime() {
return 5000l;
}
protected Runnable getSingleRunnable(final File reportsDirectory, final Queue tests, final TestRunnerNotifier notifier) {
return getTestRunnable(reportsDirectory, tests, notifier, true);
}
protected Runnable getInfiniteRunnable(final File reportsDirectory, final Queue tests) {
return getTestRunnable(reportsDirectory, tests, null, false);
}
private Runnable getTestRunnable(final File reportsDirectory, final Queue tests, final TestRunnerNotifier notifier, final boolean singleRun) {
final TestRunnerNotifier safeNotifier = new SafeTestRunnerNotifier(notifier, logger);
return new Runnable() {
@Override
public void run() {
try {
safeNotifier.start();
runTests(tests, reportsDirectory, safeNotifier);
} finally {
if (singleRun) {
running = false;
}
safeNotifier.stop();
}
}
};
}
private void runTests(final Queue tests, final File reportsDirectory, TestRunnerNotifier notifier) {
TestBean testBean;
try {
RunListener customListener = notifier.getRunListener();
ReportListener listener = null;
JUnitCore core = new JUnitCore();
while (!isStopped() && (testBean = tests.poll()) != null) {
try {
Class> testClass = testBean.getTestClass();
if (!TestRegistryUtils.isValidTestClass(testClass) || !accept(testClass)) {
logger.log(LogService.LOG_DEBUG, "Skip test class " + testBean.getName());
continue;
}
// initialise the report listener
XMLReport report = new XMLReport();
listener = new ReportListener(report);
core.addListener(listener);
if (customListener != null) {
core.addListener(listener);
}
logger.log(LogService.LOG_INFO, "Running test " + testBean.getId());
runTest(core, testClass);
// write test result
report.generateReport(reportsDirectory);
} catch (ClassNotFoundException e) {
logger.log(LogService.LOG_ERROR, "Cannot load class " + testBean.getId(), e);
} catch (NoClassDefFoundError e) {
logger.log(LogService.LOG_ERROR, "Cannot load class " + testBean.getId(), e);
} finally {
if (customListener != null) {
core.removeListener(customListener);
}
if (listener != null) {
core.removeListener(listener);
}
}
}
} catch (Exception e) {
logger.log(LogService.LOG_ERROR, null, e);
}
}
private void runTest(JUnitCore core, Class> testClass) {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(testClass.getClassLoader());
core.run(testClass);
} finally {
Thread.currentThread().setContextClassLoader(ccl);
}
}
/* (non-Javadoc)
* @see com.github.nfalco79.junit4osgi.runner.internal.TestRunner#stop()
*/
@Override
@JmxOperation(description = "Stop any active runner", operationAction = OperationAction.ACTION)
public void stop() {
stop = true;
if (registry != null && testListener != null) {
registry.removeTestRegistryListener(testListener);
}
if (executor != null) {
running = false;
executor.shutdownNow();
}
}
/* (non-Javadoc)
* @see com.github.nfalco79.junit4osgi.runner.internal.TestRunner#isStopped()
*/
@Override
@JmxAttributeMethod(description = "Returns if the runner is stopped or is plan to be stopped")
public boolean isStopped() {
return stop;
}
/* (non-Javadoc)
* @see com.github.nfalco79.junit4osgi.runner.internal.TestRunner#isRunning()
*/
@Override
@JmxAttributeMethod(description = "Returns the actual state of JUnit runner")
public boolean isRunning() {
return running;
}
public void setIncludes(Set includes) {
this.includes = new LinkedHashSet(includes.size());
for (String include : includes) {
this.includes.add(AntGlobPattern.include(include));
}
}
public void setExcludes(Set excludes) {
this.excludes = new LinkedHashSet(excludes.size());
for (String exclude : excludes) {
this.excludes.add(AntGlobPattern.exclude(exclude));
}
}
public boolean accept(Class> testClass) {
boolean matches = includes.isEmpty(); // by default accepts all
String suiteName = testClass.getName();
Iterator include = includes.iterator();
while (include.hasNext() && !matches) {
matches = include.next().matches(suiteName);
}
Iterator exclude = excludes.iterator();
while (exclude.hasNext() && matches) {
if (exclude.next().matches(suiteName)) {
matches = false;
logger.log(LogService.LOG_DEBUG, "Test class: " + testClass.getName() + " excluded by exclude pattern");
}
}
return matches;
}
private JMXServer jmxServer = newJMXServer();
protected JMXServer newJMXServer() {
return new JMXServer();
}
public void activate() {
jmxServer.start();
jmxServer.register(registry);
jmxServer.register(this);
if (Boolean.getBoolean(RUNNER_AUTOSTART)) {
start();
}
}
public void deactivate() {
stop();
jmxServer.unregister(this);
jmxServer.unregister(registry);
jmxServer.stop();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy