// Copyright (C) 2003-2009 by Object Mentor, Inc. All rights reserved.
// Released under the terms of the CPL Common Public License version 1.0.
package fitnesse.responders.run;
import fitnesse.FitNesseContext;
import fitnesse.components.ClassPathBuilder;
import fitnesse.html.SetupTeardownAndLibraryIncluder;
import fitnesse.responders.run.TestSystem.Descriptor;
import fitnesse.wiki.WikiPage;
import java.io.IOException;
import java.util.*;
import util.TimeMeasurement;
public class MultipleTestsRunner implements TestSystemListener, Stoppable {
private final ResultsListener resultsListener;
private final FitNesseContext fitNesseContext;
private final WikiPage page;
private final List<WikiPage> testPagesToRun;
private boolean isFastTest = false;
private boolean isRemoteDebug = false;
private LinkedList<TestPage> processingQueue = new LinkedList<TestPage>();
private TestPage currentTest = null;
private TestSystemGroup testSystemGroup = null;
private TestSystem currentTestSystem = null;
private boolean isStopped = false;
private String stopId = null;
private PageListSetUpTearDownSurrounder surrounder;
TimeMeasurement currentTestTime, totalTestTime;
public MultipleTestsRunner(final List<WikiPage> testPagesToRun,
final FitNesseContext fitNesseContext,
final WikiPage page,
final ResultsListener resultsListener) {
this.testPagesToRun = testPagesToRun;
this.resultsListener = resultsListener;
this.page = page;
this.fitNesseContext = fitNesseContext;
surrounder = new PageListSetUpTearDownSurrounder(fitNesseContext.root);
}
public void setDebug(boolean isDebug) {
isRemoteDebug = isDebug;
}
public void setFastTest(boolean isFastTest) {
this.isFastTest = isFastTest;
}
public void executeTestPages() {
try {
internalExecuteTestPages();
allTestingComplete();
}
catch (Exception exception) {
//hoped to write exceptions to log file but will take some work.
exception.printStackTrace(System.out);
exceptionOccurred(exception);
}
}
void allTestingComplete() throws IOException {
TimeMeasurement completionTimeMeasurement = new TimeMeasurement().start();
resultsListener.allTestingComplete(totalTestTime.stop());
completionTimeMeasurement.stop(); // a non-trivial amount of time elapses here
}
private void internalExecuteTestPages() throws IOException, InterruptedException {
synchronized (this) {
testSystemGroup = new TestSystemGroup(fitNesseContext, page, this);
stopId = fitNesseContext.runningTestingTracker.addStartedProcess(this);
}
testSystemGroup.setFastTest(isFastTest);
testSystemGroup.setManualStart(useManualStartForTestSystem());
resultsListener.setExecutionLogAndTrackingId(stopId, testSystemGroup.getExecutionLog());
PagesByTestSystem pagesByTestSystem = makeMapOfPagesByTestSystem();
announceTotalTestsToRun(pagesByTestSystem);
for (TestSystem.Descriptor descriptor : pagesByTestSystem.keySet()) {
executePagesInTestSystem(descriptor, pagesByTestSystem);
}
fitNesseContext.runningTestingTracker.removeEndedProcess(stopId);
}
private boolean useManualStartForTestSystem() {
if (isRemoteDebug) {
String useManualStart = page.readOnlyData().getVariable("MANUALLY_START_TEST_RUNNER_ON_DEBUG");
return (useManualStart != null && useManualStart.toLowerCase().equals("true"));
}
return false;
}
private void executePagesInTestSystem(TestSystem.Descriptor descriptor,
PagesByTestSystem pagesByTestSystem) throws IOException, InterruptedException {
List<TestPage> pagesInTestSystem = pagesByTestSystem.get(descriptor);
startTestSystemAndExecutePages(descriptor, pagesInTestSystem);
}
private void startTestSystemAndExecutePages(TestSystem.Descriptor descriptor, List<TestPage> testSystemPages) throws IOException, InterruptedException {
TestSystem testSystem = null;
synchronized (this) {
if (!isStopped) {
currentTestSystem = testSystemGroup.startTestSystem(descriptor, buildClassPath());
testSystem = currentTestSystem;
resultsListener.testSystemStarted(testSystem, descriptor.testSystemName, descriptor.testRunner);
} else {
}
}
if (testSystem != null) {
if (testSystem.isSuccessfullyStarted()) {
executeTestSystemPages(testSystemPages, testSystem);
waitForTestSystemToSendResults();
} else {
}
synchronized (this) {
if (!isStopped) {
testSystem.bye();
}
currentTestSystem = null;
}
} else {
}
}
private void executeTestSystemPages(List<TestPage> pagesInTestSystem, TestSystem testSystem) throws IOException, InterruptedException {
for (TestPage testPage : pagesInTestSystem) {
addToProcessingQueue(testPage);
SetupTeardownAndLibraryIncluder.includeSetupsTeardownsAndLibrariesBelowTheSuite(testPage, page);
testSystem.runTestsAndGenerateHtml(testPage.getDecoratedData());
}
}
void addToProcessingQueue(TestPage testPage) {
processingQueue.addLast(testPage);
}
private void waitForTestSystemToSendResults() throws InterruptedException {
while ((processingQueue.size() > 0) && isNotStopped())
Thread.sleep(50);
}
PagesByTestSystem makeMapOfPagesByTestSystem() {
return addSuiteSetUpAndTearDownToAllTestSystems(mapWithAllPagesButSuiteSetUpAndTearDown());
}
private PagesByTestSystem mapWithAllPagesButSuiteSetUpAndTearDown() {
PagesByTestSystem pagesByTestSystem = new PagesByTestSystem();
for (WikiPage testPage : testPagesToRun) {
if (!SuiteContentsFinder.isSuiteSetupOrTearDown(testPage)) {
addPageToListWithinMap(pagesByTestSystem, testPage);
}
}
return pagesByTestSystem;
}
private void addPageToListWithinMap(PagesByTestSystem pagesByTestSystem, WikiPage wikiPage) {
TestPage testPage = new TestPage(wikiPage);
Descriptor descriptor = TestSystem.getDescriptor(testPage.parsedData(), fitNesseContext.pageFactory, isRemoteDebug);
getOrMakeListWithinMap(pagesByTestSystem, descriptor).add(testPage);
}
private LinkedList<TestPage> getOrMakeListWithinMap(PagesByTestSystem pagesByTestSystem, Descriptor descriptor) {
LinkedList<TestPage> pagesForTestSystem;
if (!pagesByTestSystem.containsKey(descriptor)) {
pagesForTestSystem = new LinkedList<TestPage>();
pagesByTestSystem.put(descriptor, pagesForTestSystem);
} else {
pagesForTestSystem = pagesByTestSystem.get(descriptor);
}
return pagesForTestSystem;
}
private PagesByTestSystem addSuiteSetUpAndTearDownToAllTestSystems(PagesByTestSystem pagesByTestSystem) {
if (testPagesToRun.size() == 0)
return pagesByTestSystem;
for (LinkedList<TestPage> pagesForTestSystem : pagesByTestSystem.values())
surrounder.surroundGroupsOfTestPagesWithRespectiveSetUpAndTearDowns(pagesForTestSystem);
return pagesByTestSystem;
}
void announceTotalTestsToRun(PagesByTestSystem pagesByTestSystem) {
int tests = 0;
for (LinkedList<TestPage> listOfPagesToRun : pagesByTestSystem.values()) {
tests += listOfPagesToRun.size();
}
resultsListener.announceNumberTestsToRun(tests);
totalTestTime = new TimeMeasurement().start();
}
public String buildClassPath() {
final ClassPathBuilder classPathBuilder = new ClassPathBuilder();
final String pathSeparator = classPathBuilder.getPathSeparator(page);
List<String> classPathElements = new ArrayList<String>();
Set<WikiPage> visitedPages = new HashSet<WikiPage>();
for (WikiPage testPage : testPagesToRun) {
addClassPathElements(testPage, classPathElements, visitedPages);
}
return classPathBuilder.createClassPathString(classPathElements, pathSeparator);
}
private void addClassPathElements(WikiPage page, List<String> classPathElements, Set<WikiPage> visitedPages)
{
List<String> pathElements = new ClassPathBuilder().getInheritedPathElements(page, visitedPages);
classPathElements.addAll(pathElements);
}
public void acceptOutputFirst(String output) throws IOException {
TestPage firstInQueue = processingQueue.isEmpty() ? null : processingQueue.getFirst();
boolean isNewTest = firstInQueue != null && firstInQueue != currentTest;
if (isNewTest) {
startingNewTest(firstInQueue);
}
resultsListener.testOutputChunk(output);
}
void startingNewTest(TestPage test) throws IOException {
currentTest = test;
currentTestTime = new TimeMeasurement().start();
resultsListener.newTestStarted(currentTest, currentTestTime);
}
public void testComplete(TestSummary testSummary) throws IOException {
TestPage testPage = processingQueue.removeFirst();
resultsListener.testComplete(testPage, testSummary, currentTestTime.stop());
}
public void exceptionOccurred(Throwable e) {
try {
resultsListener.errorOccured();
stop();
}
catch (Exception e1) {
if (isNotStopped()) {
e1.printStackTrace();
}
}
}
private synchronized boolean isNotStopped() {
return !isStopped;
}
public void stop() throws IOException {
boolean wasNotStopped = isNotStopped();
synchronized (this) {
isStopped = true;
if (stopId != null) {
fitNesseContext.runningTestingTracker.removeEndedProcess(stopId);
}
}
if (wasNotStopped) {
testSystemGroup.kill();
}
}
}
class PagesByTestSystem extends HashMap<TestSystem.Descriptor, LinkedList<TestPage>> {
private static final long serialVersionUID = 1L;
}