// 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 java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import fitnesse.FitNesseContext; import fitnesse.authentication.SecureOperation; import fitnesse.authentication.SecureResponder; import fitnesse.authentication.SecureTestOperation; import fitnesse.components.TraversalListener; import fitnesse.html.template.HtmlPage; import fitnesse.html.template.PageTitle; import fitnesse.http.Request; import fitnesse.http.Response; import fitnesse.reporting.BaseFormatter; import fitnesse.reporting.Formatter; import fitnesse.reporting.InteractiveFormatter; import fitnesse.reporting.SuiteHtmlFormatter; import fitnesse.reporting.TestTextFormatter; import fitnesse.reporting.history.HistoryPurger; import fitnesse.reporting.history.JunitReFormatter; import fitnesse.reporting.history.PageHistory; import fitnesse.reporting.history.SuiteHistoryFormatter; import fitnesse.reporting.history.SuiteXmlReformatter; import fitnesse.reporting.history.TestXmlFormatter; import fitnesse.responders.ChunkingResponder; import fitnesse.responders.WikiImporter; import fitnesse.responders.WikiImportingResponder; import fitnesse.responders.WikiImportingTraverser; import fitnesse.responders.WikiPageActions; import fitnesse.testrunner.MultipleTestsRunner; import fitnesse.testrunner.PagesByTestSystem; import fitnesse.testrunner.RunningTestingTracker; import fitnesse.testrunner.SuiteContentsFinder; import fitnesse.testrunner.SuiteFilter; import fitnesse.testsystems.ConsoleExecutionLogListener; import fitnesse.testsystems.ExecutionLogListener; import fitnesse.testsystems.TestExecutionException; import fitnesse.testsystems.TestSummary; import fitnesse.wiki.PageCrawler; import fitnesse.wiki.PageData; import fitnesse.wiki.PageType; import fitnesse.wiki.PathParser; import fitnesse.wiki.WikiImportProperty; import fitnesse.wiki.WikiPage; import fitnesse.wiki.WikiPagePath; import fitnesse.wiki.WikiPageUtil; import org.apache.commons.lang.StringUtils; import util.FileUtil; import static fitnesse.responders.WikiImportingTraverser.ImportError; import static fitnesse.wiki.WikiImportProperty.isAutoUpdated; public class SuiteResponder extends ChunkingResponder implements SecureResponder { private static final Logger LOG = Logger.getLogger(SuiteResponder.class.getName()); private static final String NOT_FILTER_ARG = "excludeSuiteFilter"; private static final String AND_FILTER_ARG = "runTestsMatchingAllTags"; private static final String OR_FILTER_ARG_1 = "runTestsMatchingAnyTag"; private static final String OR_FILTER_ARG_2 = "suiteFilter"; static final RunningTestingTracker runningTestingTracker = new RunningTestingTracker(); private final WikiImporter wikiImporter; private SuiteHistoryFormatter suiteHistoryFormatter; private PageData data; private String testRunId; private BaseFormatter mainFormatter; private volatile boolean isClosed = false; private boolean debug = false; private boolean remoteDebug = false; private boolean includeHtml = false; private int exitCode; public SuiteResponder() { this(new WikiImporter()); } public SuiteResponder(WikiImporter wikiImporter) { super(); this.wikiImporter = wikiImporter; } private boolean isInteractive() { return mainFormatter instanceof InteractiveFormatter; } @Override public Response makeResponse(FitNesseContext context, Request request) throws Exception { Response result = super.makeResponse(context, request); if (result != response){ return result; } testRunId = runningTestingTracker.generateNextTicket(); response.addHeader("X-FitNesse-Test-Id", testRunId); return response; } @Override protected void doSending() throws Exception { debug |= request.hasInput("debug"); remoteDebug |= request.hasInput("remote_debug"); includeHtml |= request.hasInput("includehtml"); data = page.getData(); createMainFormatter(); if (isInteractive()) { makeHtml().render(response.getWriter()); } else { doExecuteTests(); } closeHtmlResponse(exitCode); cleanHistoryForSuite(); } private void cleanHistoryForSuite() { String testHistoryDays = context.getProperty("test.history.days"); if (withSuiteHistoryFormatter() && StringUtils.isNumeric(testHistoryDays)) { new HistoryPurger(context.getTestHistoryDirectory(), Integer.parseInt(testHistoryDays)) .deleteTestHistoryOlderThanDays(path); } } public void doExecuteTests() { if (WikiImportProperty.isImported(data)) { importWikiPages(); } try { performExecution(); } catch (Exception e) { LOG.log(Level.INFO, "Test system terminated with exception", e); } exitCode = mainFormatter.getErrorCount(); } public void importWikiPages() { if (response.isXmlFormat() || !isAutoUpdated(data != null ? data : page.getData())) { return; } try { addToResponse("<span class=\"meta\">Updating imported content...</span><span class=\"meta\">"); new WikiImportingTraverser(wikiImporter, page).traverse(new TraversalListener<Object>() { @Override public void process(Object pageOrError) { if (pageOrError instanceof ImportError) { ImportError error = (ImportError) pageOrError; addToResponse(" " + error.toString() + "."); } } }); addToResponse(" Done."); // Refresh data, since it might have changed. data = page.getData(); } catch (IOException e) { addToResponse(" Import failed: " + e.toString() + "."); } finally { addToResponse("</span>"); } } private HtmlPage makeHtml() { PageCrawler pageCrawler = page.getPageCrawler(); WikiPagePath fullPath = pageCrawler.getFullPath(); String fullPathName = PathParser.render(fullPath); HtmlPage htmlPage = context.pageFactory.newPage(); htmlPage.setTitle(getTitle() + ": " + fullPathName); htmlPage.setPageTitle(new PageTitle(getTitle(), fullPath, data.getAttribute(PageData.PropertySUITES))); htmlPage.setNavTemplate("testNav.vm"); htmlPage.put("actions", new WikiPageActions(page)); htmlPage.setMainTemplate(mainTemplate()); htmlPage.put("testExecutor", new TestExecutor()); htmlPage.setFooterTemplate("wikiFooter.vm"); htmlPage.put("headerContent", new WikiPageHeaderRenderer()); htmlPage.put("footerContent", new WikiPageFooterRenderer()); htmlPage.setErrorNavTemplate("errorNavigator"); htmlPage.put("multipleTestsRun", isMultipleTestsRun()); WikiImportingResponder.handleImportProperties(htmlPage, page); return htmlPage; } public boolean isDebug() { return debug; } public void setDebug(boolean debug) { this.debug = debug; } public class WikiPageHeaderRenderer { public String render() { return WikiPageUtil.getHeaderPageHtml(page); } } public class WikiPageFooterRenderer { public String render() { return WikiPageUtil.getFooterPageHtml(page); } } public class TestExecutor { public void execute() { doExecuteTests(); } } private boolean isMultipleTestsRun() { return PageType.fromWikiPage(page) == PageType.SUITE; } protected void addFormatters(MultipleTestsRunner runner) { runner.addTestSystemListener(mainFormatter); if (withSuiteHistoryFormatter()) { addHistoryFormatter(runner); } else { runner.addExecutionLogListener(new ConsoleExecutionLogListener()); } if (mainFormatter instanceof ExecutionLogListener) { runner.addExecutionLogListener((ExecutionLogListener) mainFormatter); } for (Formatter formatter : context.formatterFactory.createFormatters()) { runner.addTestSystemListener(formatter); } if (context.testSystemListener != null) { runner.addTestSystemListener(context.testSystemListener); } } private boolean withSuiteHistoryFormatter() { return !request.hasInput("nohistory"); } protected void addHistoryFormatter(MultipleTestsRunner runner) { SuiteHistoryFormatter historyFormatter = getSuiteHistoryFormatter(); runner.addTestSystemListener(historyFormatter); runner.addExecutionLogListener(historyFormatter); } private void createMainFormatter() { if (response.isXmlFormat()) { mainFormatter = newXmlFormatter(); } else if (response.isTextFormat()) { mainFormatter = newTextFormatter(); } else if (response.isJunitFormat()) { mainFormatter = newJunitFormatter(); } else { mainFormatter = newHtmlFormatter(); } } protected String getTitle() { return "Test Results"; } protected String mainTemplate() { return "testPage"; } protected BaseFormatter newXmlFormatter() { SuiteXmlReformatter xmlFormatter = new SuiteXmlReformatter(context, page, response.getWriter(), getSuiteHistoryFormatter()); if (includeHtml) xmlFormatter.includeHtml(); if (!isMultipleTestsRun()) xmlFormatter.includeInstructions(); return xmlFormatter; } protected BaseFormatter newTextFormatter() { return new TestTextFormatter(response); } protected BaseFormatter newJunitFormatter() { return new JunitReFormatter(context, page, response.getWriter(), getSuiteHistoryFormatter()); } protected BaseFormatter newHtmlFormatter() { return new SuiteHtmlFormatter(page, response.getWriter()); } protected void performExecution() throws TestExecutionException { MultipleTestsRunner runner = newMultipleTestsRunner(getPagesToRun()); runningTestingTracker.addStartedProcess(testRunId, runner); if (isInteractive()) { ((InteractiveFormatter) mainFormatter).setTrackingId(testRunId); } try { runner.executeTestPages(); } finally { runningTestingTracker.removeEndedProcess(testRunId); } } protected List<WikiPage> getPagesToRun() { SuiteFilter filter = createSuiteFilter(request, page.getPageCrawler().getFullPath().toString()); SuiteContentsFinder suiteTestFinder = new SuiteContentsFinder(page, filter, root); return suiteTestFinder.getAllPagesToRunForThisSuite(); } protected MultipleTestsRunner newMultipleTestsRunner(List<WikiPage> pages) { // Add test url inputs to context's variableSource. final PagesByTestSystem pagesByTestSystem = new PagesByTestSystem(pages, root); MultipleTestsRunner runner = new MultipleTestsRunner(pagesByTestSystem, context.testSystemFactory); runner.setRunInProcess(debug); runner.setEnableRemoteDebug(remoteDebug); addFormatters(runner); return runner; } @Override public SecureOperation getSecureOperation() { return new SecureTestOperation(); } public void addToResponse(String output) { if (!isClosed()) { try { response.add(output); } catch (IOException e) { LOG.log(Level.WARNING, "Unable to send output", e); } } } synchronized boolean isClosed() { return isClosed; } synchronized void setClosed() { isClosed = true; } void closeHtmlResponse(int exitCode) throws IOException { if (!isClosed()) { setClosed(); response.closeChunks(); response.addTrailingHeader("Exit-Code", String.valueOf(exitCode)); response.closeTrailer(); response.close(); } } public Response getResponse() { return response; } public static SuiteFilter createSuiteFilter(Request request, String suitePath) { return new SuiteFilter(getOrTagFilter(request), getNotSuiteFilter(request), getAndTagFilters(request), getSuiteFirstTest(request, suitePath)); } private static String getOrTagFilter(Request request) { return request != null ? getOrFilterString(request) : null; } private static String getOrFilterString(Request request) { //request already confirmed not-null String orFilterString = null; if(request.getInput(OR_FILTER_ARG_1) != null){ orFilterString = request.getInput(OR_FILTER_ARG_1); } else { orFilterString = request.getInput(OR_FILTER_ARG_2); } return orFilterString; } private static String getNotSuiteFilter(Request request) { return request != null ? request.getInput(NOT_FILTER_ARG) : null; } private static String getAndTagFilters(Request request) { return request != null ? request.getInput(AND_FILTER_ARG) : null; } private static String getSuiteFirstTest(Request request, String suiteName) { String startTest = null; if (request != null) { startTest = request.getInput("firstTest"); } if (startTest != null) { if (startTest.indexOf(suiteName) != 0) { startTest = suiteName + "." + startTest; } } return startTest; } public static class HistoryWriterFactory implements TestXmlFormatter.WriterFactory { @Override public Writer getWriter(FitNesseContext context, WikiPage page, TestSummary counts, long time) throws IOException { File resultPath = new File(makePageHistoryFileName(context, page, counts, time)); File resultDirectory = new File(resultPath.getParent()); if (!resultDirectory.exists()) { resultDirectory.mkdirs(); } File resultFile = new File(resultDirectory, resultPath.getName()); return new PrintWriter(resultFile, FileUtil.CHARENCODING); } } public static String makePageHistoryFileName(FitNesseContext context, WikiPage page, TestSummary counts, long time) { return String.format("%s/%s/%s", context.getTestHistoryDirectory(), page.getPageCrawler().getFullPath().toString(), makeResultFileName(counts, time)); } public static String makeResultFileName(TestSummary summary, long time) { SimpleDateFormat format = new SimpleDateFormat(PageHistory.TEST_RESULT_FILE_DATE_PATTERN); String datePart = format.format(new Date(time)); return String.format("%s_%d_%d_%d_%d.xml", datePart, summary.getRight(), summary.getWrong(), summary.getIgnores(), summary.getExceptions()); } private SuiteHistoryFormatter getSuiteHistoryFormatter() { if (suiteHistoryFormatter == null) { HistoryWriterFactory source = new HistoryWriterFactory(); suiteHistoryFormatter = new SuiteHistoryFormatter(context, page, source); } return suiteHistoryFormatter; } }