package org.mitre.test.impl; import org.mitre.test.Loader; import org.mitre.test.TestUnit; import org.slf4j.LoggerFactory; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; import org.apache.log4j.spi.ThrowableInformation; import org.apache.commons.lang.StringUtils; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.Date; import java.util.List; import java.util.Set; /** * HTML output report writer. * * Output writer is integrated with the logger system such that all logged events * are written to output file as well. * * @author Jason Mathews, MITRE Corp. * Date: 6/28/12 8:47 AM */ public class HtmlReporter extends AbstractReporter { private static final org.slf4j.Logger log = LoggerFactory.getLogger(HtmlReporter.class); private boolean inPreBlock; private boolean htmlMode; // flag to disable HTML escaping in inPreBlock mode private MetaAppender appender; /** * Sets up the reporter. * This method is called before any other methods are called with the exception of {@link #setOutputFile}. * * @throws IllegalStateException if executeStart(), startTest(), or startGroup() are called before setup() or * if setup is called more than once */ @Override public void setup() { if (inPreBlock || startTime != 0 || appender != null) throw new IllegalStateException(); System.out.println("<html>"); System.out.println("<head><title>Interoperability Conformance Test Report</title></head>"); System.out.println("<body>"); System.out.println("<h1>Interoperability Conformance Test Report</h1>"); System.out.println("<h2><a name='toc'>Table of contents</a></h2>"); System.out.println("<ol>"); System.out.println("<li><B><a href='#configuration'>Configuration</a></B>"); System.out.println("<li><B><a href='#build'>Build Execution Plan</a></B>"); System.out.println("<li><B><a href='#exec'>Execution</a></B>"); System.out.println("<li><B><a href='#summary'>Conformance Test Report</a></B>"); System.out.println("</ol>"); // <HR> // Setup Appender programmatically Logger root = Logger.getRootLogger(); //Get the root logger if (root != null) { appender = new MetaAppender(); appender.setThreshold(Level.DEBUG); root.addAppender(appender); } } /** * Start a new group or section in the report * @param title Title of group, value can be null if not relevant */ @Override public void startGroup(String title) { assert(!inPreBlock); System.out.println("<hr>"); if (title != null) { int ind = title.indexOf(' '); String name = ind < 1 ? title : title.substring(0,ind); System.out.printf("<h2><a name='%s'>%s</a></h2>%n", name.toLowerCase(), title); } System.out.println("<pre>"); inPreBlock = true; } /** * End last group or section in the report */ @Override public void endGroup() { assert(inPreBlock); inPreBlock = false; System.out.println("</pre>"); } /** * start execution */ @Override public void executeStart() { assert(!inPreBlock); startTime = System.currentTimeMillis(); final Loader loader = Loader.getInstance(); System.out.println("<hr><h2><a name='exec'>Execution</a></h2>"); System.out.printf("<h2>Exec Tests (%d) on %s</h2>%n", loader.getCount(), new Date(startTime)); //System.out.println("<pre>"); System.out.println("<ol>"); } /** * stop execution */ @Override public void executeStop() { assert(!inPreBlock); elapsedTime = System.currentTimeMillis() - startTime; System.out.println("</ol>"); //System.out.println("</pre>"); } @Override public void startTest(TestUnit test) { assert(!inPreBlock); // System.out.println("<hr width='50%'>"); System.out.printf("<li><b><a name='%s'>Run test: %s [<a href='#s%s'>%s</a>]</a></b>%n", test.getId(), test.getClass().getName(), test.getId(), test.getId()); System.out.print("<pre>"); inPreBlock = true; } @Override public void stopTest(TestUnit test) { assert(inPreBlock); inPreBlock = false; System.out.println("</pre>"); TestUnit.StatusEnumType status = test.getStatus(); String color = null; String statusDesc = null; if (status == TestUnit.StatusEnumType.SUCCESS) { color = "green"; statusDesc = "Test: OK"; } else if (status == TestUnit.StatusEnumType.FAILED) { color = test.isRequired() ? "red" : "orange"; statusDesc = "<B>XXX: Test *failed*</B>"; if (!test.isRequired() && test.getWarnings().isEmpty()) { test.addWarning("Recommended feature not implemented"); System.out.println("WARN: Recommended feature not implemented"); } } else if (status == TestUnit.StatusEnumType.PREREQ_FAILED) { color = "purple"; statusDesc = "<B>Test: prereq failed</B>"; } else if (status == TestUnit.StatusEnumType.SKIPPED) { color = "blue"; statusDesc = "<B>Test: Skipped</B>"; } else { log.warn("unexpected status: " + status); } if (statusDesc != null) { System.out.printf("<div style='background-color: %s; width: 600px'>%n", color); System.out.println(statusDesc); System.out.println("</div>"); } String desc = test.getStatusDescription(); if (desc != null) { // check if "test xxx" appears in reason text and make hyperlink // e.g. Prerequisite test 6.3.1.1 failed int ind = desc.indexOf("test "); //System.out.println("<BR>XXX: " + ind); if (ind > 0) { int endp = desc.indexOf(' ', ind+5); //System.out.println("<BR>XXX: " + endp); if (endp > ind) { String name = desc.substring(ind+5, endp); if (Loader.getInstance().getIdSet().contains(name)) { desc = String.format("%s <a href='#%s'>%s</a>%s", desc.substring(0,ind+5), name, name, desc.substring(endp)); } //System.out.printf("<BR>[%s]%n",name); } } System.out.println("<BR>Reason: " + desc); // debug } System.out.println("<P>"); } /** * Generate summary report after all tests are run * @return error status 0 = all passed, 1 = some failed */ @Override public int generateSummary() { assert(!inPreBlock); int failed = 0; try { int testsRun = 0; int successCount = 0; int warningCount = 0 ; System.out.println("<HR>"); System.out.println("<h2><a name='summary'>Conformance Test Report</a></h2>"); System.out.println("<table>"); final Loader loader = Loader.getInstance(); Set<TestUnit> tests = loader.getSortedSet(); for (TestUnit test : tests) { TestUnit.StatusEnumType status = test.getStatus(); System.out.println("<TR>"); String warning = null; if (status == null) { warning = "Null status for test " + test.getClass().getName() + " id=" + test.getId(); // REVIEW: assume skipped, error condition or warning (assertion failed) status = TestUnit.StatusEnumType.SKIPPED; } else if (status != TestUnit.StatusEnumType.SKIPPED && status != TestUnit.StatusEnumType.PREREQ_FAILED) { // only count PASSED and FAILED tests testsRun++; } final Set<String> warnings = test.getWarnings(); String outStatus; String color; if (status == TestUnit.StatusEnumType.PREREQ_FAILED) { outStatus = "Prerequsite<BR>Failed"; color = "purple"; // failed++; // ?? } else if (status == TestUnit.StatusEnumType.FAILED) { if (test.isRequired()) { outStatus = "*FAILED*"; color = "red"; failed++; } else { outStatus = "Failed<BR>recommendation"; color = "orange"; /* * Failed to meet recommended element of the specification (SHOULD, RECOMMENDED, etc.) * Treated as a "warning" such that a test assertion failed, but the type attribute for * the test assertion indicated that it was 'recommended', not 'required'. * This type of failure will not affect the overall conformance result. */ } } else if (status == TestUnit.StatusEnumType.SUCCESS) { successCount++; if (warnings.isEmpty()) { outStatus = "Passed"; color = "green"; } else { outStatus = "Passed<BR>with warnings"; color = "yellow"; } } else { assert(status == TestUnit.StatusEnumType.SKIPPED); outStatus = status.toString(); // SKIPPED color = "blue"; } System.out.printf("<TD valign='top'><a name='s%s'/><a href='#%s'>%s</a>", test.getId(), test.getId(), test.getId()); System.out.printf("<TD bgcolor='%s'>%s%n", color, outStatus); String name = test.getName(); System.out.println("<TD valign='top'>" + name); System.out.println("<TR><TD colspan='3'>"); final Set<? extends TestUnit> dependencies = test.getDependencies(); System.out.print("<P>Prerequisites:"); if (!dependencies.isEmpty()) { int count = 0; for(TestUnit aTest : dependencies) { if (count++ != 0) System.out.print(','); System.out.printf(" <a href='#%s'>%s</a>", aTest.getId(), aTest.getId()); } System.out.println(""); } else { List<Class<? extends TestUnit>> depends = test.getDependencyClasses(); if (depends.isEmpty()) { System.out.println(" None"); } else { int count = 0; for(Class<? extends TestUnit> aTest : depends) { if (count++ != 0) System.out.print(','); String classname = aTest.getName(); int ind = classname.lastIndexOf('.'); if (ind > 0) classname = classname.substring(ind + 1); // strip off full package name System.out.printf(" <i>%s</i>", classname); } System.out.println(""); } } String desc = test.getStatusDescription(); if (StringUtils.isNotBlank(desc)) { System.out.printf("<P><b>Reason:</b> %s%n", desc); } if (!warnings.isEmpty()) { warningCount += warnings.size(); System.out.println("<p><b>Warnings</b><ul>"); for (String s : warnings) { System.out.printf("<li>%s%n", escapeHtml(s)); } System.out.println("</ul>"); } // else if (status == TestUnit.StatusEnumType.FAILED && !test.isRequired()) { //warningCount++; // treat failed recommendation as a warning //} if (warning != null) log.warn(warning); System.out.println("<P/>"); System.out.println("</tr>"); } System.out.println("</table>"); System.out.println("<h3>Summary</h3>\n<blockquote><table><tr><td>Tests run:<td>"); if (tests.size() != testsRun) { System.out.printf("%d (%d)%n", testsRun, tests.size()); } else { System.out.printf("%d%n", testsRun); } System.out.printf("<tr><td>Passed:<td>%d%n", successCount); System.out.printf("<tr><td>Failures:<td>%d<tr><td>Warnings:<td>%d", failed, warningCount); System.out.printf("<tr><td>Time elapsed:<td>%.1f sec%n", elapsedTime / 1000.0); System.out.println("</table></blockquote><P>Return to <a href='#toc'>Table of Contents</a>"); System.out.println("</body>"); System.out.println("</html>"); } finally { close(); } return failed; } public void close() { super.close(); if (appender != null) { Logger root = Logger.getRootLogger(); if (root != null) { root.removeAppender(appender); } appender.clearFilters(); appender = null; } } private static String escapeHtml(String s) { StringBuilder buf = new StringBuilder(s.length() + 16); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == '<') buf.append("<"); else if (c == '>') buf.append(">"); else if (c == '&') buf.append("&"); else buf.append(c); } return buf.toString(); } protected PrintStream createPrintStream(OutputStream out) { return new HtmlPrintStream(out); } private class MetaAppender extends AppenderSkeleton { @Override protected void append(LoggingEvent event) { String msg = StringUtils.trimToNull(event.getRenderedMessage()); ThrowableInformation ti = event.getThrowableInformation(); if (msg == null || ti != null) { String oldMsg = msg; LocationInfo location = event.getLocationInformation(); if (location != null) { msg = StringUtils.trimToNull(location.getClassName()); if(msg != null) { String linenum = location.getLineNumber(); if (linenum != null) { msg = "(" + msg + ":" + linenum + ")"; if (oldMsg != null) msg += " - " + oldMsg; } } } } // make ERROR bold if (event.getLevel() == Level.ERROR || event.getLevel() == Level.FATAL) { htmlMode = true; System.out.printf("<b><font color='red'>%s</font></b>", event.getLevel()); htmlMode = false; } else System.out.print(event.getLevel()); if (msg != null) { System.out.print(msg.startsWith("(") ? " " : ": "); System.out.println(msg); } else { System.out.println(); } if (ti != null) { Throwable throwable = ti.getThrowable(); if (throwable != null) { if (!inPreBlock) System.out.print("<pre>"); throwable.printStackTrace(System.out); if (!inPreBlock) System.out.println("</pre>"); } } } public void close() { // nothing to do } public boolean requiresLayout() { return false; } } private class HtmlPrintStream extends PrintStream { public HtmlPrintStream(OutputStream os) { super(os); } public void print(String s) { // escape string as safe HTML if inPreBlock flag is true and htmlMode is false if (s != null && inPreBlock && !htmlMode && s.indexOf('<') > -1) { s = escapeHtml(s); } super.print(s); } } }