/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* 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.hazelcast.test;
import com.hazelcast.test.annotation.ConfigureParallelRunnerWith;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Runtime.getRuntime;
import static java.lang.String.format;
/**
* Runs the test methods in parallel with multiple threads.
*/
public class HazelcastParallelClassRunner extends AbstractHazelcastClassRunner {
private static final boolean SPAWN_MULTIPLE_THREADS = TestEnvironment.isMockNetwork();
private static final int DEFAULT_MAX_THREADS = getDefaultMaxThreads();
private static int getDefaultMaxThreads() {
int cpuWorkers = max(getRuntime().availableProcessors(), 8);
//the parallel profile can spawn multiple JVMs
boolean multipleJVM = Boolean.getBoolean("multipleJVM");
if (multipleJVM) {
// when running tests in multiple JVMs in parallel then we want to put a cap
// on parallelism inside each JVM. otherwise it's easy to use too much resource
// and the test duration is actually longer and not shorter.
cpuWorkers = min(4, cpuWorkers);
}
return cpuWorkers;
}
private final AtomicInteger numThreads = new AtomicInteger(0);
private final int maxThreads;
public HazelcastParallelClassRunner(Class<?> clazz) throws InitializationError {
super(clazz);
maxThreads = getMaxThreads(clazz);
}
public HazelcastParallelClassRunner(Class<?> clazz, Object[] parameters, String name) throws InitializationError {
super(clazz, parameters, name);
maxThreads = getMaxThreads(clazz);
}
private int getMaxThreads(Class<?> clazz) throws InitializationError {
if (!SPAWN_MULTIPLE_THREADS) {
return 1;
}
ConfigureParallelRunnerWith annotation = clazz.getAnnotation(ConfigureParallelRunnerWith.class);
if (annotation != null) {
try {
Class<? extends ParallelRunnerOptions> optionsClass = annotation.value();
Constructor constructor = optionsClass.getConstructor();
ParallelRunnerOptions parallelRunnerOptions = (ParallelRunnerOptions) constructor.newInstance();
return parallelRunnerOptions.maxParallelTests();
} catch (Exception e) {
throw new InitializationError(e);
}
} else {
return DEFAULT_MAX_THREADS;
}
}
@Override
protected void runChild(final FrameworkMethod method, final RunNotifier notifier) {
while (numThreads.get() >= maxThreads) {
try {
Thread.sleep(25);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
numThreads.incrementAndGet();
new Thread(new TestRunner(method, notifier)).start();
}
@Override
protected Statement childrenInvoker(final RunNotifier notifier) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
// save the current system properties
Properties currentSystemProperties = System.getProperties();
try {
// use thread-local based system properties so parallel tests don't effect each other
System.setProperties(new ThreadLocalProperties(currentSystemProperties));
HazelcastParallelClassRunner.super.childrenInvoker(notifier).evaluate();
// wait for all child threads (tests) to complete
while (numThreads.get() > 0) {
Thread.sleep(25);
}
} finally {
// restore the system properties
System.setProperties(currentSystemProperties);
}
}
};
}
private class TestRunner implements Runnable {
private final FrameworkMethod method;
private final RunNotifier notifier;
TestRunner(final FrameworkMethod method, final RunNotifier notifier) {
this.method = method;
this.notifier = notifier;
}
@Override
public void run() {
String testName = testName(method);
setThreadLocalTestMethodName(testName);
try {
long start = System.currentTimeMillis();
System.out.println("Started Running Test: " + testName);
HazelcastParallelClassRunner.super.runChild(method, notifier);
numThreads.decrementAndGet();
float took = (float) (System.currentTimeMillis() - start) / 1000;
System.out.println(format("Finished Running Test: %s in %.3f seconds.", testName, took));
} finally {
removeThreadLocalTestMethodName();
}
}
}
@SuppressWarnings({"deprecation", "NullableProblems"})
private static class ThreadLocalProperties extends Properties {
private final Properties globalProperties;
private final ThreadLocal<Properties> localProperties = new InheritableThreadLocal<Properties>();
private ThreadLocalProperties(Properties properties) {
this.globalProperties = properties;
}
private Properties init(Properties properties) {
for (Map.Entry entry : globalProperties.entrySet()) {
properties.put(entry.getKey(), entry.getValue());
}
return properties;
}
private Properties getThreadLocal() {
Properties properties = localProperties.get();
if (properties == null) {
properties = init(new Properties());
localProperties.set(properties);
}
return properties;
}
@Override
public String getProperty(String key) {
return getThreadLocal().getProperty(key);
}
@Override
public Object setProperty(String key, String value) {
return getThreadLocal().setProperty(key, value);
}
@Override
public Enumeration<?> propertyNames() {
return getThreadLocal().propertyNames();
}
@Override
public Set<String> stringPropertyNames() {
return getThreadLocal().stringPropertyNames();
}
@Override
public int size() {
return getThreadLocal().size();
}
@Override
public boolean isEmpty() {
return getThreadLocal().isEmpty();
}
@Override
public Enumeration<Object> keys() {
return getThreadLocal().keys();
}
@Override
public Enumeration<Object> elements() {
return getThreadLocal().elements();
}
@Override
public boolean contains(Object value) {
return getThreadLocal().contains(value);
}
@Override
public boolean containsValue(Object value) {
return getThreadLocal().containsValue(value);
}
@Override
public boolean containsKey(Object key) {
return getThreadLocal().containsKey(key);
}
@Override
public Object get(Object key) {
return getThreadLocal().get(key);
}
@Override
public Object put(Object key, Object value) {
return getThreadLocal().put(key, value);
}
@Override
public Object remove(Object key) {
return getThreadLocal().remove(key);
}
@Override
public void putAll(Map<?, ?> t) {
getThreadLocal().putAll(t);
}
@Override
public void clear() {
getThreadLocal().clear();
}
@Override
public Set<Object> keySet() {
return getThreadLocal().keySet();
}
@Override
public Set<Map.Entry<Object, Object>> entrySet() {
return getThreadLocal().entrySet();
}
@Override
public Collection<Object> values() {
return getThreadLocal().values();
}
@Override
public void load(Reader reader) throws IOException {
getThreadLocal().load(reader);
}
@Override
public void load(InputStream inStream) throws IOException {
getThreadLocal().load(inStream);
}
@Override
public void save(OutputStream out, String comments) {
getThreadLocal().save(out, comments);
}
@Override
public void store(Writer writer, String comments) throws IOException {
getThreadLocal().store(writer, comments);
}
@Override
public void store(OutputStream out, String comments) throws IOException {
getThreadLocal().store(out, comments);
}
@Override
public void loadFromXML(InputStream in) throws IOException {
getThreadLocal().loadFromXML(in);
}
@Override
public void storeToXML(OutputStream os, String comment) throws IOException {
getThreadLocal().storeToXML(os, comment);
}
@Override
public void storeToXML(OutputStream os, String comment, String encoding) throws IOException {
getThreadLocal().storeToXML(os, comment, encoding);
}
@Override
public String getProperty(String key, String defaultValue) {
return getThreadLocal().getProperty(key, defaultValue);
}
@Override
public void list(PrintStream out) {
getThreadLocal().list(out);
}
@Override
public void list(PrintWriter out) {
getThreadLocal().list(out);
}
}
}