package org.xtest.runner;
import java.util.Collection;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.xtest.runner.events.Events;
import org.xtest.runner.external.ITestRunner;
import org.xtest.runner.external.ITestType;
import org.xtest.runner.external.TestResult;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
* Re-usable job that runs all scheduled tests
*
* @author Michael Barry
*/
@Singleton
public class RunAllJob extends Job {
private static final Logger logger = Logger.getLogger(RunAllJob.class);
@Inject
private Events events;
private final PriorityBlockingQueue<RunnableTest> files;
/**
* Constructs a new {@link RunAllJob}, should only be called by Guice.
*/
public RunAllJob() {
super("Xtest");
files = new PriorityBlockingQueue<RunnableTest>();
}
/**
* Schedules the job if it has pending transactions and is not running
*/
public void scheduleIfNecessary() {
if (!files.isEmpty() && getState() != Job.RUNNING) {
schedule();
}
}
/**
* Submits a set of {@link RunnableTest}s to the queue
*
* @param toRun
* The list of tests to schedule
* @return True if any of the tests were not already scheduled, false if no change was made to
* scheduled test queue
*/
public boolean submit(Collection<RunnableTest> toRun) {
boolean scheduled = false;
for (RunnableTest file : toRun) {
if (file != null && !files.contains(file)) {
file.setPending();
files.offer(file);
scheduled = true;
}
}
return scheduled;
}
@Override
protected void canceling() {
super.canceling();
getThread().interrupt();
}
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
getJobManager().join(ResourcesPlugin.FAMILY_MANUAL_BUILD,
new SubProgressMonitor(monitor, 1));
getJobManager().join(ResourcesPlugin.FAMILY_AUTO_BUILD,
new SubProgressMonitor(monitor, 1));
} catch (OperationCanceledException e) {
} catch (InterruptedException e) {
}
long start = System.nanoTime();
int size = files.size();
SubMonitor convert = SubMonitor.convert(monitor, "Running Tests", size);
events.startTests(Lists.newArrayList(files));
logger.info("==========> Starting " + size + " tests");
Cache<ITestType, ITestRunner> runnerCache = CacheBuilder.newBuilder().build(
new CacheLoader<ITestType, ITestRunner>() {
@Override
public ITestRunner load(ITestType arg0) throws Exception {
return arg0.provideNewRunner();
}
});
// Run each test
while (!monitor.isCanceled() && !files.isEmpty()) {
RunnableTest peek = files.peek();
TestResult result = invokeAndRecord(peek, runnerCache, convert.newChild(1));
files.remove(peek);
if (!monitor.isCanceled()) {
events.finishTest(result, peek.getFile());
}
}
if (monitor.isCanceled()) {
logger.info("!!!!!!!!!!! Canceled with "
+ files.size()
+ " tests left took "
+ TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS)
+ " ms");
// Post a "canceled" event where the number of events scheduled is the size of the queue
events.cancelAndSchedule(Lists.newArrayList(files));
} else {
logger.info("==========> Finished "
+ size
+ " tests took "
+ TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS)
+ " ms");
// Only post finished event if not canceled
events.finishTests();
}
monitor.done();
return Status.OK_STATUS;
}
private TestResult invokeAndRecord(RunnableTest peek,
Cache<ITestType, ITestRunner> runnerCache, SubMonitor convert) {
String name = peek.getName();
convert.subTask(name);
return peek.invoke(convert, runnerCache);
}
}