// 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.formatters; import fitnesse.FitNesseContext; import fitnesse.responders.run.*; import fitnesse.responders.run.slimResponder.SlimTestSystem; import fitnesse.slimTables.HtmlTable; import fitnesse.slimTables.SlimTable; import fitnesse.slimTables.Table; import fitnesse.wiki.PageData; import fitnesse.wiki.WikiPage; import fitnesse.wiki.WikiPageUtil; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import util.TimeMeasurement; import java.io.IOException; import java.io.Writer; import java.util.List; import java.util.Map; public class XmlFormatter extends BaseFormatter { public interface WriterFactory { Writer getWriter(FitNesseContext context, WikiPage page, TestSummary counts, long time) throws IOException; } private WriterFactory writerFactory; private long currentTestStartTime; private StringBuilder outputBuffer; private TestSystem testSystem; protected TestExecutionReport testResponse = new TestExecutionReport(); protected TestSummary finalSummary = new TestSummary(); public XmlFormatter(FitNesseContext context, final WikiPage page, WriterFactory writerFactory) { super(context, page); this.writerFactory = writerFactory; } @Override public void newTestStarted(TestPage test, TimeMeasurement timeMeasurement) { currentTestStartTime = timeMeasurement.startedAt(); appendHtmlToBuffer(WikiPageUtil.getHeaderPageHtml(getPage())); } @Override public void testSystemStarted(TestSystem testSystem, String testSystemName, String testRunner) { this.testSystem = testSystem; } @Override public void testOutputChunk(String output) { appendHtmlToBuffer(output); } @Override public void testComplete(TestPage test, TestSummary testSummary, TimeMeasurement timeMeasurement) throws IOException { super.testComplete(test, testSummary, timeMeasurement); processTestResults(test.getName(), testSummary, timeMeasurement); } public void processTestResults(final String relativeTestName, TestSummary testSummary, TimeMeasurement timeMeasurement) { finalSummary = new TestSummary(testSummary); TestExecutionReport.TestResult currentResult = newTestResult(); testResponse.results.add(currentResult); currentResult.startTime = currentTestStartTime; currentResult.content = outputBuffer == null ? null : outputBuffer.toString(); outputBuffer = null; addCountsToResult(currentResult, testSummary); currentResult.runTimeInMillis = String.valueOf(timeMeasurement.elapsed()); currentResult.relativePageName = relativeTestName; currentResult.tags = page.readOnlyData().getAttribute(PageData.PropertySUITES); if (testSystem instanceof SlimTestSystem) { SlimTestSystem slimSystem = (SlimTestSystem) testSystem; new SlimTestXmlFormatter(currentResult, slimSystem).invoke(); } } protected TestExecutionReport.TestResult newTestResult() { return new TestExecutionReport.TestResult(); } @Override public void setExecutionLogAndTrackingId(String stopResponderId, CompositeExecutionLog log) { } protected void setPage(WikiPage testPage) { this.page = testPage; testResponse.rootPath = testPage.getName(); } @Override public void allTestingComplete(TimeMeasurement totalTimeMeasurement) throws IOException { super.allTestingComplete(totalTimeMeasurement); setTotalRunTimeOnReport(totalTimeMeasurement); writeResults(); } protected void setTotalRunTimeOnReport(TimeMeasurement totalTimeMeasurement) { testResponse.setTotalRunTimeInMillis(totalTimeMeasurement); } protected void writeResults() throws IOException { writeResults(writerFactory.getWriter(context, getPageForHistory(), finalSummary, currentTestStartTime)); } protected WikiPage getPageForHistory() { return page; } @Override public int getErrorCount() { return finalSummary.wrong + finalSummary.exceptions; } protected void writeResults(Writer writer) throws IOException { VelocityContext velocityContext = new VelocityContext(); velocityContext.put("response", testResponse); Template template = context.pageFactory.getVelocityEngine().getTemplate("testResults.vm"); template.merge(velocityContext, writer); writer.close(); } protected TestSummary getFinalSummary() { return finalSummary; } private void addCountsToResult(TestExecutionReport.TestResult currentResult, TestSummary testSummary) { currentResult.right = Integer.toString(testSummary.getRight()); currentResult.wrong = Integer.toString(testSummary.getWrong()); currentResult.ignores = Integer.toString(testSummary.getIgnores()); currentResult.exceptions = Integer.toString(testSummary.getExceptions()); } private void appendHtmlToBuffer(String output) { if (outputBuffer == null) outputBuffer = new StringBuilder(); outputBuffer.append(output); } private static class SlimTestXmlFormatter { private TestExecutionReport.TestResult testResult; private List<Object> instructions; private Map<String, Object> results; private List<SlimTable.Expectation> expectations; private List<SlimTable> slimTables; public SlimTestXmlFormatter(TestExecutionReport.TestResult testResult, SlimTestSystem slimSystem) { this.testResult = testResult; instructions = slimSystem.getInstructions(); results = slimSystem.getInstructionResults(); expectations = slimSystem.getExpectations(); slimTables = slimSystem.getTestTables(); } public void invoke() { addTables(); addInstructionResults(); } private void addTables() { if (slimTables.size() > 0) { for (SlimTable slimTable : slimTables) { addTable(slimTable); } } } private void addTable(SlimTable slimTable) { TestExecutionReport.Table resultTable = new TestExecutionReport.Table(slimTable.getTableName()); testResult.tables.add(resultTable); addRowsToTable(slimTable, resultTable); addChildTables(slimTable); } private void addChildTables(SlimTable slimTable) { for (SlimTable child : slimTable.getChildren()) { addTable(child); } } private void addRowsToTable(SlimTable slimTable, TestExecutionReport.Table resultTable) { Table table = slimTable.getTable(); int rows = table.getRowCount(); for (int row = 0; row < rows; row++) { addRowToTable(resultTable, table, row); } } private void addRowToTable(TestExecutionReport.Table resultTable, Table table, int row) { TestExecutionReport.Row resultRow = new TestExecutionReport.Row(); resultTable.add(resultRow); int cols = table.getColumnCountInRow(row); for (int col = 0; col < cols; col++) { String contents = table.getCellContents(col, row); if (isScenarioHtml(contents)) { addColorizedScenarioReference(resultRow, contents); } else { String colorizedContents = HtmlTable.colorize(contents); resultRow.add(colorizedContents); } } } private void addColorizedScenarioReference(TestExecutionReport.Row resultRow, String contents) { String status = getTestStatus(contents); String tableName = getTableName(contents); resultRow.add(String.format("%s(scenario:%s)", status, tableName)); } private String getTableName(String contents) { return getStringBetween(contents, "table_name=\"", "\""); } private static String getTestStatus(String contents) { return getStringBetween(contents, "<span id=\"test_status\" class=", ">Scenario</span>"); } private static String getStringBetween(String contents, String prefix, String suffix) { int start = contents.indexOf(prefix) + prefix.length(); int end = contents.indexOf(suffix, start); return contents.substring(start, end); } private boolean isScenarioHtml(String contents) { return contents.startsWith("<div class=\"collapsible\">"); } private void addInstructionResults() { for (Object instruction : instructions) { addInstructionResult(instruction); } } @SuppressWarnings("unchecked") private void addInstructionResult(Object instruction) { TestExecutionReport.InstructionResult instructionResult = new TestExecutionReport.InstructionResult(); testResult.instructions.add(instructionResult); List<Object> instructionList = (List<Object>) instruction; String id = (String) (instructionList.get(0)); Object result = results.get(id); instructionResult.instruction = instruction.toString(); instructionResult.slimResult = (result != null) ? result.toString() : ""; for (SlimTable.Expectation expectation : expectations) { if (expectation.getInstructionTag().equals(id)) { try { TestExecutionReport.Expectation expectationResult = new TestExecutionReport.Expectation(); instructionResult.addExpectation(expectationResult); expectationResult.instructionId = expectation.getInstructionTag(); expectationResult.col = Integer.toString(expectation.getCol()); expectationResult.row = Integer.toString(expectation.getRow()); expectationResult.type = expectation.getClass().getSimpleName(); expectationResult.actual = expectation.getActual(); expectationResult.expected = expectation.getExpected(); String message = expectation.getEvaluationMessage(); expectationResult.evaluationMessage = message; expectationResult.status = expectationStatus(message); } catch (Throwable e) { e.printStackTrace(); } } } } private String expectationStatus(String message) { String status; if (message.matches(".*pass(.*)")) status = "right"; else if (message.matches(".*fail(.*)")) status = "wrong"; else if (message.matches(".*__EXCEPTION__:<")) status = "exception"; else status = "ignored"; return status; } } }