// Copyright © 2011-2014, Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0
package fi.jumi.core.suite;
import fi.jumi.actors.*;
import fi.jumi.actors.eventizers.ComposedEventizerProvider;
import fi.jumi.actors.listeners.*;
import fi.jumi.core.api.SuiteListener;
import fi.jumi.core.config.*;
import fi.jumi.core.discovery.*;
import fi.jumi.core.drivers.*;
import fi.jumi.core.events.*;
import fi.jumi.core.runs.RunIdSequence;
import fi.jumi.core.stdout.OutputCapturer;
import fi.jumi.core.util.*;
import javax.annotation.concurrent.NotThreadSafe;
import java.io.PrintStream;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
@NotThreadSafe
public class SuiteFactory implements AutoCloseable {
private final DaemonConfiguration config;
private final OutputCapturer outputCapturer;
private final PrintStream logOutput;
private final MessageListener messageListener;
// some fields are package-private for testing purposes
private ExecutorService actorThreadPool;
ExecutorService testThreadPool;
ClassLoader testClassLoader;
private TestFileFinder testFileFinder;
private CompositeDriverFinder driverFinder;
private RunIdSequence runIdSequence;
MultiThreadedActors actors;
public SuiteFactory(DaemonConfiguration daemonConfiguration, OutputCapturer outputCapturer, PrintStream logOutput, MessageListener messageListener) {
this.config = daemonConfiguration;
this.outputCapturer = outputCapturer;
this.logOutput = logOutput;
this.messageListener = messageListener;
}
public void configure(SuiteConfiguration suite) {
testClassLoader = createClassLoader(suite.getClasspath());
testFileFinder = createTestFileFinder(suite);
driverFinder = DriverFinderFactory.createDriverFinder(testClassLoader, logOutput);
runIdSequence = new RunIdSequence();
// thread pool configuration
actorThreadPool = Executors.newCachedThreadPool(new PrefixedThreadFactory("jumi-actor-"));
testThreadPool = Executors.newFixedThreadPool(config.getTestThreadsCountCalculated(),
new ContextClassLoaderThreadFactory(testClassLoader, new PrefixedThreadFactory("jumi-test-")));
}
public void start(final SuiteListener suiteListener) {
// logging configuration
FailureHandler failureHandler = new InternalErrorReportingFailureHandler(suiteListener, logOutput);
// actor messages are already logged by the actors container, but the test thread pool must be hooked separately
Executor testExecutor = messageListener.getListenedExecutor(testThreadPool);
// actors configuration
// TODO: not all of these eventizers might be needed - create a statistics gathering EventizerProvider
actors = new MultiThreadedActors(
actorThreadPool,
new ComposedEventizerProvider(
new StartableEventizer(),
new RunnableEventizer(),
new WorkerListenerEventizer(),
new TestFileFinderListenerEventizer(),
new SuiteListenerEventizer(),
new RequestListenerEventizer(),
new RunListenerEventizer()
),
failureHandler,
messageListener
);
// bootstrap the system
ActorThread actorThread = actors.startActorThread();
ActorRef<TestFileFinderListener> suiteRunner = actorThread.bindActor(TestFileFinderListener.class,
new SuiteRunner(
new DriverFactory(suiteListener, actorThread, outputCapturer, driverFinder, runIdSequence, testClassLoader),
suiteListener,
actorThread,
testExecutor,
logOutput
));
suiteListener.onSuiteStarted();
actorThreadPool.execute(new TestFileFinderRunner(testFileFinder, suiteRunner));
}
@Override
public void close() {
if (actorThreadPool != null) {
actorThreadPool.shutdownNow();
}
if (testThreadPool != null) {
testThreadPool.shutdownNow();
}
}
private static ClassLoader createClassLoader(List<URI> classpath) {
try {
return new URLClassLoader(asUrls(classpath));
} catch (MalformedURLException e) {
throw new RuntimeException("Failed to create class loader for classpath " + classpath, e);
}
}
private static URL[] asUrls(List<URI> uris) throws MalformedURLException {
URL[] urls = new URL[uris.size()];
for (int i = 0, filesLength = uris.size(); i < filesLength; i++) {
urls[i] = uris.get(i).toURL();
}
return urls;
}
private static TestFileFinder createTestFileFinder(SuiteConfiguration suite) {
List<Path> classDirectories = getClassDirectories(suite);
List<TestFileFinder> finders = new ArrayList<>();
for (Path dir : classDirectories) {
PathMatcher matcher = suite.createTestFileMatcher(dir.getFileSystem());
finders.add(new PathMatcherTestFileFinder(matcher, dir));
}
return new CompositeTestFileFinder(finders);
}
public static List<Path> getClassDirectories(SuiteConfiguration suite) {
ArrayList<Path> dirs = new ArrayList<>();
for (URI uri : suite.getClasspath()) {
Path path = Paths.get(uri);
if (Files.isDirectory(path)) {
dirs.add(path);
}
}
return dirs;
}
}