/* MonkeyTalk - a cross-platform functional testing tool Copyright (C) 2012 Gorilla Logic, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.gorillalogic.monkeytalk.processor.report; import java.io.IOException; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.gorillalogic.monkeytalk.CommandWorld; import com.gorillalogic.monkeytalk.utils.FileUtils; /** * JUnit-compatible XML report generator for TestSuite. */ public class Suite implements IReport { private String name; private List<IReport> tests; private static final DecimalFormat decimalFmt = new DecimalFormat("0.000"); private final String MASTER_NAME = "MTHtmlTemplate.html"; private final String TEMPLATE_NAME = "/templates/MTHtmlTemplate.html"; private int count = 1; /** * Instantiate a new suite with the given name. * * @param name * the suite name */ public Suite(String name) { this.name = name; tests = new ArrayList<IReport>(); } /** * Get the suite name minus the .mts extension. * * @return the suite name */ public String getName() { return getName(true); } /** * Get the suite name minus the .mts extension. * * @return the suite name */ public String getName(boolean removeExtension) { if (name == null) { return ""; } return (removeExtension ? FileUtils.removeExt(name, CommandWorld.SUITE_EXT) : name); } /** * Compute the duration (in seconds) of the suite by summing the duration of all child tests. * * @return the duration (in seconds) formatted into a string with three decimals places */ public String getDuration() { long sum = 0; for (IReport t : tests) { if (t instanceof Test) sum += ((Test) t).getDuration(); else if (t instanceof Suite) { Suite s = (Suite) t; for (IReport te : s.tests) { if (te instanceof Test) sum += ((Test) te).getDuration(); } } } return decimalFmt.format(sum / 1000.0); } /** * Finds the start time of the first test. If there are no tests, return 0 * * @return the time, in ms of the first test's begining */ public long getFirstStartTime() { long first = Long.MAX_VALUE; for (IReport t : tests) { if (t instanceof Test) { if (((Test) t).getStartTime() < first) { first = ((Test) t).getStartTime(); } } else if (t instanceof Suite) { Suite s = (Suite) t; if (s.getFirstStartTime() < first) { first = s.getFirstStartTime(); } } } if (first == Long.MAX_VALUE) first = 0; return first; } /** * Finds the stop time of the last test. If there are no tests, return 0 * * @return the time, in ms of the last test's end */ public long getLastStopTime() { long last = 0; for (IReport t : tests) { if (t instanceof Test) { if (((Test) t).getStopTime() > last) { last = ((Test) t).getStopTime(); } } else if (t instanceof Suite) { Suite s = (Suite) t; if (s.getLastStopTime() > last) { last = s.getLastStopTime(); } } } return last; } public String getTimestamp() { long unixtime = getFirstStartTime(); Date time = new Date(unixtime); DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); return format.format(time); } /** * Get the list of all tests in the suite. * * @return the tests */ public List<IReport> getTests() { return tests; } /** * Add a test to the suite. * * @param t * the test */ public void addTest(Test t) { tests.add(t); } /** * Add a test to the suite. * * @param t * the test */ public void addSuite(Suite t) { tests.add(t); } /** * Get a test in the suite by name. * * @param name * the test name * @return the test, or null if no match is found */ public Test getTest(String name) { if (name != null) { for (IReport t : tests) { if (t instanceof Test && t.getName().equals(name)) { return (Test) t; } } } return null; } /** * True if one or more of the tests in this suite have a screenshot, otherwise false. * * @return true if the suite has any screenshots */ public boolean hasScreenshots() { for (IReport r : tests) { if (r instanceof Test && (((Test) r).getScreenshot() != null || ((Test) r).getScreenshots() != null && ((Test) r).getScreenshots().size() > 0)) { return true; } else if (r instanceof Suite) { for (IReport ro : ((Suite) r).tests) { if (ro instanceof Test && (((Test) ro).getScreenshot() != null || ((Test) ro).getScreenshots() != null && ((Test) ro).getScreenshots().size() > 0)) { return true; } } } } return false; } @Override public String toString() { return toXML("\t", this, ""); } public String toXML() { return "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + toXML("\t", this, ""); } /** * Output the <code><testsuite></code> tag with attributes for the number of tests, errors, * failures, etc., plus all child <code><testcase></code> tags. * * @param indent * the indent string for the {@code testcase} * @return the test as xml */ public String toXML(String indent) { return toXML(indent, this, ""); } public String toXML(String indentAmount, Suite s, String currentIndent) { StringBuilder sb = new StringBuilder(); sb.append(currentIndent + "<testsuite "); if (name != null) { sb.append("name=\"").append(escapeXML(s.getName())).append("\" "); } int numTests = 0; int numErrors = 0; int numFailures = 0; int numSkipped = 0; int numSuites = 0; StringBuilder out = new StringBuilder(); for (IReport ro : s.tests) { if (ro instanceof Suite) { String innerSuite = toXML(indentAmount, ((Suite) ro), currentIndent + indentAmount); // Roll up counts to parent numTests += getValueOfFirstAttributeByName("tests", innerSuite); numErrors += getValueOfFirstAttributeByName("errors", innerSuite); numFailures += getValueOfFirstAttributeByName("failures", innerSuite); numSkipped += getValueOfFirstAttributeByName("skipped", innerSuite); numSuites += getValueOfFirstAttributeByName("suites", innerSuite); out.append(innerSuite); out.append("\n"); numSuites++; } else { Test t = (Test) ro; numTests++; if (t.getResult() == TestResult.ERROR) { numErrors++; } else if (t.getResult() == TestResult.FAILURE) { numFailures++; } else if (t.getResult() == TestResult.SKIPPED) { numSkipped++; } out.append(t.toXML(indentAmount + currentIndent)).append("\n"); } } sb.append("tests=\"").append(numTests).append("\" "); sb.append("suites=\"").append(numSuites).append("\" "); sb.append("errors=\"").append(numErrors).append("\" "); sb.append("failures=\"").append(numFailures).append("\" "); sb.append("skipped=\"").append(numSkipped).append("\" "); sb.append("starttime=\"").append(getFirstStartTime()).append("\" "); sb.append("stoptime=\"").append(getLastStopTime()).append("\" "); sb.append("timestamp=\"").append(getTimestamp()).append("\" "); sb.append("time=\"").append(getDuration()).append("\">\n"); sb.append(out).append(currentIndent + "</testsuite>"); return sb.toString(); } public String toHTML() { String html = null; count = 1; MasterReport masterReport = new MasterReport(MASTER_NAME, TEMPLATE_NAME); try { html = masterReport.getMasterContents(getName(false), toHTML(new StringBuilder(), this, 0, null)); } catch (Exception e) { e.printStackTrace(); } return html; } public String toHTML(StringBuilder sb, Suite suitero, int padding, String duration) { int numTests = 0; int numErrors = 0; int numFailures = 0; int numSkipped = 0; StringBuilder out = new StringBuilder(); String result = ""; for (IReport ro : suitero.getTests()) { if (ro instanceof Suite) { Suite s = (Suite) ro; out.append("<li>\n"); for (IReport r : s.getTests()) { if (r instanceof Test) { Test t = (Test) r; numTests++; if (t.getResult() == TestResult.ERROR) { numErrors++; } else if (t.getResult() == TestResult.FAILURE) { numFailures++; } else if (t.getResult() == TestResult.SKIPPED) { numSkipped++; } } } out.append(toHTML(out, s, padding + 50, s.getDuration())); out.append("</li>\n"); } else { Test t = (Test) ro; numTests++; if (t.getResult() == TestResult.ERROR) { numErrors++; } else if (t.getResult() == TestResult.FAILURE) { numFailures++; } else if (t.getResult() == TestResult.SKIPPED) { numSkipped++; } out.append(((Test) t).toHTML(count)); count++; } } if (duration == null) { duration = getDuration(); } SuiteReportTemplate template = new SuiteReportTemplate("SuiteHtmlTemplate.html", "/templates/SuiteHtmlTemplate.html"); try { result = template.getContents(out.toString(), "" + padding, suitero.getName(false), "" + numTests, "" + numErrors, "" + numFailures, "" + numSkipped, "" + duration); } catch (IOException e) { e.printStackTrace(); } return result; } public int getValueOfFirstAttributeByName(String name, String xmlString) { Pattern pattern = Pattern.compile("( +" + name + "=\")([0-9]*)"); Matcher matcher = pattern.matcher(xmlString); matcher.find(); if (matcher.group(2).trim().length() == 0) return 0; return Integer.parseInt(matcher.group(2)); } private String escapeXML(String s) { return com.gorillalogic.monkeytalk.processor.report.detail.XmlUtils.escapeXml(s); } }