// Regress.java package net.sf.gogui.tools.regress; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.io.Reader; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sf.gogui.gtp.GtpClient; import net.sf.gogui.gtp.GtpError; import net.sf.gogui.gtp.GtpUtil; import net.sf.gogui.util.ErrorMessage; import net.sf.gogui.util.FileUtil; import net.sf.gogui.util.HtmlUtil; import net.sf.gogui.util.Platform; import net.sf.gogui.util.StringUtil; /** Runs GTP regression tests. */ public class Regress implements GtpClient.IOCallback { /** Constructor. @param gtpFile File with GTP commands to send at startup or <code>null</code> for no file. */ public Regress(String program, ArrayList<String> tests, String output, boolean longOutput, boolean verbose, File gtpFile) throws Exception { tests = RegressUtil.expandTestSuites(tests); RegressUtil.checkFiles(tests); m_result = true; m_program = program; m_longOutput = longOutput; m_verbose = verbose; m_gtpFile = gtpFile; if (output.equals("")) m_prefix = ""; else { File file = new File(output); if (! file.exists()) if (! file.mkdir()) throw new ErrorMessage("Could not create output directory '" + output + "'"); m_prefix = output + File.separator; } initOutNames(tests); for (int i = 0; i < tests.size(); ++i) { String test = tests.get(i); if (tests.size() > 1) m_outPrefix = test + " "; else m_outPrefix = ""; runTest(test); } writeSummary(); writeData(); } /** Return true if tests completed with no unexpected failures. */ public boolean getResult() { return m_result; } public void receivedInvalidResponse(String s) { printOutLine("invalid", "Invalid response: " + s); } public void receivedResponse(boolean error, String s) { } public void receivedStdErr(String s) { printOut("stderr", s, -1); } public void sentCommand(String s) { } /** Exception thrown if Go program died. */ private static class ProgramIsDeadException extends Exception { public String getMessage() { return "Program died"; } } /** Information about one test and its result. */ private static class Test { public int m_id; public int m_lastSgfMove; public boolean m_expectedFail; public boolean m_fail; public String m_command; public String m_required; public String m_response; public String m_lastSgf; public Test(int id, String command, boolean fail, boolean expectedFail, String required, String response, String lastSgf, int lastSgfMove) { m_id = id; m_fail = fail; m_expectedFail = expectedFail; m_command = command; m_required = required; m_response = response; m_lastSgf = lastSgf; m_lastSgfMove = lastSgfMove; } } /** Information about test results of one test file. */ private static class TestSummary { public File m_file; /** See Regress#m_outName */ public String m_outName; public int m_numberTests; public int m_otherErrors; public int m_unexpectedFails; public int m_expectedFails; public int m_expectedPasses; public int m_unexpectedPasses; public long m_timeMillis; public double m_cpuTime; public int getNumberPasses() { return m_expectedPasses + m_unexpectedPasses; } } private boolean m_lastError; private final boolean m_longOutput; private boolean m_result; private final boolean m_verbose; private int m_lastCommandId; private int m_lastSgfMove; private int m_otherErrors; private File m_testFile; private PrintStream m_out; private static final String COLOR_ERROR = "#ffa954"; private static final String COLOR_HEADER = "#91aee8"; private static final String COLOR_INFO = "#e0e0e0"; private static final String COLOR_BG_LIGHT = "#e0e0e0"; private static final String COLOR_BG_GRAY = "#e0e0e0"; private static final String COLOR_GREEN = "#5eaf5e"; private static final String COLOR_RED = "#ff5454"; /** Output file of the current test. The file contains an HTML formatted log of the GTP streams and the standard error of Go program. */ private File m_outFile; private final File m_gtpFile; private String m_currentStyle; private String m_lastCommand; private String m_lastFullResponse; private String m_lastResponse; private String m_lastSgf; private String m_name; /** Name of m_outFile and the summary file of the test without directory and file extension for the current test. */ private String m_outName; private String m_outFileRelativeName; private String m_outPrefix; private final String m_prefix; private final String m_program; /** Relative URI path between m_outFile and the directory of the current test. */ private String m_relativePath; private String m_version; /** Name of m_outFile and the summary file of the test without directory and file extension for the all tests. */ private TreeMap<String,String> m_outNames; private final ArrayList<Test> m_tests = new ArrayList<Test>(); private final ArrayList<TestSummary> m_testSummaries = new ArrayList<TestSummary>(); private GtpClient m_gtp; private void checkLastSgf(String line) { String regex = "[0-9]*\\s*loadsgf\\s+(\\S+\\.[Ss][Gg][Ff])\\s+([0-9]+)\\s*"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(line); if (matcher.matches()) { m_lastSgf = matcher.group(1); try { m_lastSgfMove = Integer.parseInt(matcher.group(2)); return; } catch (NumberFormatException e) { assert false; } } regex = "[0-9]*\\s*loadsgf\\s+(\\S+\\.[Ss][Gg][Ff])\\s*"; pattern = Pattern.compile(regex); matcher = pattern.matcher(line); if (matcher.matches()) { m_lastSgf = matcher.group(1); m_lastSgfMove = -1; } } private void finishOutFile() { if (m_currentStyle != null) m_out.print("</span>"); m_out.print("</pre>\n" + HtmlUtil.getFooter("gogui-regress") + "</body>\n" + "</html>\n"); m_out.close(); } private int getId(String line) { line = line.replaceAll("\\t", "\n"); int index = line.indexOf(' '); if (index < 0) return -1; try { return Integer.parseInt(line.substring(0, index)); } catch (NumberFormatException e) { return -1; } } private TestSummary getTestSummary(long timeMillis, double cpuTime) { TestSummary summary = new TestSummary(); summary.m_file = m_testFile; summary.m_outName = m_outName; summary.m_timeMillis = timeMillis; summary.m_cpuTime = cpuTime; summary.m_otherErrors = m_otherErrors; for (int i = 0; i < m_tests.size(); ++i) { Test t = m_tests.get(i); ++summary.m_numberTests; if (t.m_fail && ! t.m_expectedFail) ++summary.m_unexpectedFails; else if (t.m_fail && t.m_expectedFail) ++summary.m_expectedFails; else if (! t.m_fail && ! t.m_expectedFail) ++summary.m_expectedPasses; else if (! t.m_fail && t.m_expectedFail) ++summary.m_unexpectedPasses; } return summary; } private synchronized void handleLastResponse() { if (m_lastCommandId >= 0) { boolean fail = false; if (m_lastError) { printOutLine("fail", m_lastFullResponse); if (m_lastResponse.equals("")) System.out.println(m_outPrefix + Integer.toString(m_lastCommandId) + " unexpected FAIL"); else System.out.println(m_outPrefix + Integer.toString(m_lastCommandId) + " unexpected FAIL: '" + m_lastResponse + "'"); fail = true; } else printOutLine("test", m_lastFullResponse); m_tests.add(new Test(m_lastCommandId, m_lastCommand, fail, false, "", m_lastResponse, m_lastSgf, m_lastSgfMove)); } else { if (m_lastError) { printOutLine("error", m_lastFullResponse); ++m_otherErrors; } else printOutLine(null, m_lastFullResponse); } } private void handleLine(String line) throws ErrorMessage, ProgramIsDeadException { line = line.trim(); if (line.startsWith("#?")) { if (m_lastFullResponse == null) throw new ErrorMessage(m_testFile + ": Response pattern" + " without preceding test command: " + line); printOutLine("test", line); handleTest(line.substring(2).trim()); m_lastFullResponse = null; return; } if (m_lastFullResponse != null) { handleLastResponse(); m_lastFullResponse = null; } if (line.equals("")) printOutLine(null, line); else if (line.startsWith("#")) printOutLine("comment", line); else { line = line.replaceAll("\\t", " "); m_lastCommandId = getId(line); if (m_lastCommandId < 0) m_lastCommand = line; else { int index = line.indexOf(' '); m_lastCommand = line.substring(index + 1); } printOutLine(m_lastCommandId >= 0 ? "test" : "command", line, m_lastCommandId); checkLastSgf(line); m_lastError = false; assert m_lastFullResponse == null; try { m_lastResponse = m_gtp.send(line); } catch (GtpError error) { m_lastError = true; m_lastResponse = error.getMessage(); if (m_gtp.isProgramDead()) throw new ProgramIsDeadException(); } m_lastFullResponse = m_gtp.getFullResponse(); } } private void handleTest(String patternString) throws ErrorMessage { boolean expectedFail = false; if (StringUtil.isEmpty(patternString)) { handleLastResponse(); return; } if (patternString.endsWith("*")) { expectedFail = true; patternString = patternString.substring(0, patternString.length() - 1); } if (! patternString.startsWith("[")) throw new ErrorMessage(m_testFile + ": Pattern has no opening bracket: " + patternString); if (! patternString.endsWith("]")) throw new ErrorMessage(m_testFile + ": Pattern has no closing bracket: " + patternString); patternString = patternString.substring(1, patternString.length() - 1).trim(); String expectedResponse = patternString; boolean notPattern = false; if (patternString.startsWith("!")) { notPattern = true; patternString = patternString.substring(1); } boolean fail = false; String response = ""; int index = m_lastFullResponse.indexOf(' '); if (index >= 0) response = m_lastFullResponse.substring(index).trim(); if (m_lastError) fail = true; else { Pattern pattern = Pattern.compile(patternString, Pattern.MULTILINE | Pattern.DOTALL); Matcher matcher = pattern.matcher(response); if ((! matcher.matches() && ! notPattern) || (matcher.matches() && notPattern)) fail = true; } if (fail && ! expectedFail) m_result = false; String style = null; if (fail && ! expectedFail) style = "fail"; else if (! fail && expectedFail) style = "pass"; else style = "test"; printOutLine(style, m_lastFullResponse); if (m_longOutput) { // Output compatible with eval.sh in GNU Go if (fail && ! expectedFail) System.out.println(m_outPrefix + Integer.toString(m_lastCommandId) + " FAILED: Correct '" + expectedResponse + "', got '" + response + "'"); else if (fail && expectedFail) System.out.println(m_outPrefix + Integer.toString(m_lastCommandId) + " failed: Correct '" + expectedResponse + "', got '" + response + "'"); else if (! fail && expectedFail) System.out.println(m_outPrefix + Integer.toString(m_lastCommandId) + " PASSED"); else if (! fail && ! expectedFail) System.out.println(m_outPrefix + Integer.toString(m_lastCommandId) + " passed"); } else { // Output compatible with regress.sh in GNU Go if (fail && ! expectedFail) System.out.println(m_outPrefix + Integer.toString(m_lastCommandId) + " unexpected FAIL: Correct '" + expectedResponse + "', got '" + response + "'"); else if (! fail && expectedFail) System.out.println(m_outPrefix + Integer.toString(m_lastCommandId) + " unexpected PASS!"); } m_tests.add(new Test(m_lastCommandId, m_lastCommand, fail, expectedFail, expectedResponse, response, m_lastSgf, m_lastSgfMove)); } /** Compute unique names for output directory. Appends a number, if tests with same name in different directories exist. */ private void initOutNames(ArrayList<String> tests) { m_outNames = new TreeMap<String,String>(); for (int i = 0; i < tests.size(); ++i) { String test = tests.get(i); File testFile = new File(test); String name = FileUtil.removeExtension(new File(testFile.getName()), "tst"); if (m_outNames.containsValue(name)) for (int j = 2; ; ++j) { String testName = name + "_" + j; if (! m_outNames.containsValue(testName)) { name = testName; break; } } m_outNames.put(test, name); } } private void initOutFile() throws Exception { m_outFileRelativeName = m_outName + ".out.html"; m_outFile = new File(m_prefix + m_outFileRelativeName); File parent = m_outFile.getParentFile(); if (parent != null && ! parent.exists()) if (! parent.mkdir()) throw new ErrorMessage("Could not create directory '" + parent + "'"); m_currentStyle = null; m_out = new PrintStream(m_outFile); m_out.print("<html>\n" + "<head>\n" + "<title>Output: " + m_testFile + "</title>\n" + HtmlUtil.getMeta("gogui-regress") + "<style type=\"text/css\">\n" + "<!--\n" + "body { margin:0; }\n" + "span.comment { color:#999999; }\n" + "span.fail { font-weight:bold; color:" + COLOR_RED + "; }\n" + "span.error { font-weight:bold; color:" + COLOR_ERROR + "; }\n" + "span.stderr { color:#666666; }\n" + "span.invalid { background:" + COLOR_RED + ";}\n" + "span.pass { font-weight:bold; color:" + COLOR_GREEN + "; }\n" + "span.test { font-weight:bold; }\n" + "-->\n" + "</style>\n" + "</head>\n" + "<body bgcolor=\"white\" text=\"black\"" + " link=\"#0000ee\" vlink=\"#551a8b\">\n" + "<table border=\"0\" width=\"100%\" bgcolor=\"" + COLOR_HEADER + "\" border=\"0\">\n" + "<tr><td>\n" + "<h1>Output: " + m_testFile + "</h1>\n" + "</td></tr>\n" + "</table>\n" + "<table width=\"100%\" bgcolor=\"" + COLOR_INFO + "\">\n"); writeInfo(m_out, false); m_out.print("</table>\n" + "<pre style=\"margin:1em\">\n"); } private synchronized void printOut(String style, String line, int id) { if (line == null || line.length() == 0) return; line = line.replaceAll("&", "&"); line = line.replaceAll(">", ">"); line = line.replaceAll("<", "<"); if (style != null && (style.equals("command") || style.equals("test"))) { Pattern pattern = Pattern.compile("\\S*\\.[Ss][Gg][Ff]"); Matcher matcher = pattern.matcher(line); if (matcher.find()) { String sgf = matcher.group(); StringBuilder stringBuffer = new StringBuilder(); stringBuffer.append(line.substring(0, matcher.start())); stringBuffer.append("<a href=\""); stringBuffer.append(m_relativePath); stringBuffer.append(sgf); stringBuffer.append("\">"); stringBuffer.append(sgf); stringBuffer.append("</a>"); stringBuffer.append(line.substring(matcher.end())); line = stringBuffer.toString(); } } if ((style == null && m_currentStyle != null) || (style != null && m_currentStyle == null) || (style != null && m_currentStyle != null && ! style.equals(m_currentStyle))) { if (m_currentStyle != null) m_out.print("</span>"); if (style != null) m_out.print("<span class=\"" + style + "\">"); m_currentStyle = style; } if (id >= 0) m_out.print("<a name=\"" + id + "\">"); m_out.print(line); if (id >= 0) m_out.print("</a>"); } private synchronized void printOutLine(String style, String line, int id) { if (line == null) return; if (! line.endsWith("\n")) line = line + "\n"; printOut(style, line, id); } private synchronized void printOutSeparator() { if (m_currentStyle != null) m_out.print("</span>"); m_out.println("</pre>\n" + "<hr style=\"margin:1em\" size=\"1\">\n" + "<pre style=\"margin:1em\">"); } private synchronized void printOutLine(String style, String line) { printOutLine(style, line, -1); } private String send(String command) throws GtpError { printOutLine(null, command); try { return m_gtp.send(command); } finally { printOutLine(null, m_gtp.getFullResponse()); } } private double getCpuTime() { try { return Double.parseDouble(send("cputime")); } catch (GtpError e) { return 0; } catch (NumberFormatException e) { return 0; } } private String getTimeString(double seconds) { NumberFormat format1 = StringUtil.getNumberFormat(1); StringBuilder buffer = new StringBuilder(16); buffer.append(format1.format(seconds)); buffer.append(" ("); buffer.append(StringUtil.formatTime((long)seconds)); buffer.append(')'); return buffer.toString(); } private TestSummary getTotalSummary() { TestSummary total = new TestSummary(); for (int i = 0; i < m_testSummaries.size(); ++i) { TestSummary summary = m_testSummaries.get(i); total.m_numberTests += summary.m_numberTests; total.m_otherErrors += summary.m_otherErrors; total.m_unexpectedFails += summary.m_unexpectedFails; total.m_expectedFails += summary.m_expectedFails; total.m_expectedPasses += summary.m_expectedPasses; total.m_unexpectedPasses += summary.m_unexpectedPasses; total.m_timeMillis += summary.m_timeMillis; total.m_cpuTime += summary.m_cpuTime; } return total; } private void queryNameAndVersion() throws GtpError { try { m_name = send("name"); } catch (GtpError e) { m_name = ""; if (m_gtp.isProgramDead()) throw e; } try { m_version = send("version"); } catch (GtpError e) { m_version = ""; } } private void runTest(String test) throws Exception { m_tests.clear(); m_otherErrors = 0; m_testFile = new File(test); m_outName = m_outNames.get(test); initOutFile(); File testFileDir = m_testFile.getAbsoluteFile().getParentFile(); m_relativePath = FileUtil.getRelativeURI(m_outFile, testFileDir); if (! m_relativePath.equals("") && ! m_relativePath.endsWith("/")) m_relativePath = m_relativePath + "/"; FileReader fileReader = new FileReader(m_testFile); BufferedReader reader = new BufferedReader(fileReader); try { m_gtp = new GtpClient(m_program, testFileDir, m_verbose, this); if (m_gtpFile != null) sendGtpFile(); m_lastSgf = null; queryNameAndVersion(); double cpuTime = getCpuTime(); long timeMillis = System.currentTimeMillis(); printOutSeparator(); String line; while (true) { line = reader.readLine(); if (line == null) break; handleLine(line); } timeMillis = System.currentTimeMillis() - timeMillis; if (m_lastFullResponse != null) { handleLastResponse(); m_lastFullResponse = null; } printOutSeparator(); cpuTime = getCpuTime() - cpuTime; if (m_lastFullResponse != null) { handleLastResponse(); m_lastFullResponse = null; } if (! m_gtp.isProgramDead()) { send("quit"); m_gtp.close(); } m_gtp.waitForExit(); finishOutFile(); TestSummary testSummary = getTestSummary(timeMillis, cpuTime); m_testSummaries.add(testSummary); writeTestSummary(testSummary); } finally { reader.close(); } } private void sendGtpFile() throws ErrorMessage { Reader reader; try { reader = new FileReader(m_gtpFile); } catch (FileNotFoundException e) { throw new ErrorMessage("GTP file not found: " + m_gtpFile); } java.io.BufferedReader in; in = new BufferedReader(reader); try { while (true) { try { String line = in.readLine(); if (line == null) break; if (! GtpUtil.isCommand(line)) continue; send(line); } catch (IOException e) { throw new ErrorMessage("Error reading GTP file: " + e.getMessage()); } catch (GtpError e) { throw new ErrorMessage("GTP command '" + e.getCommand() + "' from file " + m_gtpFile + " failed: " + e.getMessage()); } } printOutSeparator(); } finally { try { in.close(); } catch (IOException e) { } } } private String truncate(String string) { int maxLength = 25; if (string.length() < maxLength) return string.trim(); return string.substring(0, maxLength).trim() + "..."; } private void writeInfo(PrintStream out, boolean withName) { String host = Platform.getHostInfo(); if (withName) out.print("<tr><th align=\"left\">Name:</th><td>" + m_name + "</td></tr>\n" + "<tr><th align=\"left\">Version:</th><td>" + m_version + "</td></tr>\n"); out.print("<tr><th align=\"left\">Date:</th><td>" + StringUtil.getDate() + "</td></tr>\n" + "<tr><th align=\"left\">Host:</th><td>" + host + "</td></tr>\n" + "<tr><th align=\"left\" valign=\"top\">Command:</th>\n" + "<td valign=\"top\"><tt>" + m_program + "</tt></td></tr>\n"); if (m_gtpFile != null) out.print("<tr><th align=\"left\">GtpFile:</th><td>" + m_gtpFile + "</td></tr>\n"); } /** Write text based data file with summary information. */ private void writeData() throws FileNotFoundException { File file = new File(m_prefix + "summary.dat"); PrintStream out = new PrintStream(file); NumberFormat format1 = StringUtil.getNumberFormat(1); TestSummary s = getTotalSummary(); double time = ((double)s.m_timeMillis) / 1000F; out.print("#Tests\tFAIL\tfail\tPASS\tpass\tError\tTime\tCpuTime\n" + + s.m_numberTests + "\t" + s.m_unexpectedFails + "\t" + s.m_expectedFails + "\t" + s.m_unexpectedPasses + "\t" + s.m_expectedPasses + "\t" + s.m_otherErrors + "\t" + format1.format(time) + "\t" + format1.format(s.m_cpuTime) + "\t" + "\n"); out.close(); } private void writeSummary() throws FileNotFoundException { File file = new File(m_prefix + "index.html"); PrintStream out = new PrintStream(file); out.print("<html>\n" + "<head>\n" + "<title>Regression Test Summary</title>\n" + HtmlUtil.getMeta("gogui-regress") + "<style type=\"text/css\">\n" + "<!--\n" + "body { margin:0; }\n" + "-->\n" + "</style>\n" + "</head>\n" + "<body bgcolor=\"white\" text=\"black\" link=\"blue\"" + " vlink=\"purple\" alink=\"red\">\n" + "<table border=\"0\" width=\"100%\" bgcolor=\"" + COLOR_HEADER + "\">\n" + "<tr><td>\n" + "<h1>Regression Test Summary</h1>\n" + "</td></tr>\n" + "</table>\n" + "<table width=\"100%\" bgcolor=\"" + COLOR_INFO + "\">\n"); writeInfo(out, true); out.print("</table>\n" + "<table width=\"100%\" border=\"0\" cellpadding=\"0\"" + "cellspacing=\"1\">\n" + "<colgroup>\n" + "<col width=\"20%\">\n" + "<col width=\"10%\">\n" + "<col width=\"10%\">\n" + "<col width=\"10%\">\n" + "<col width=\"10%\">\n" + "<col width=\"10%\">\n" + "<col width=\"10%\">\n" + "<col width=\"10%\">\n" + "<col width=\"10%\">\n" + "</colgroup>\n" + "<thead align=\"center\">\n" + "<tr bgcolor = \"" + COLOR_HEADER + "\">\n" + "<th>File</th>\n" + "<th>Tests</th>\n" + "<th>FAIL</th>\n" + "<th>fail</th>\n" + "<th>PASS</th>\n" + "<th>pass</th>\n" + "<th>Error</th>\n" + "<th>Time</th>\n" + "<th>CpuTime</th>\n" + "</tr>\n" + "</thead>\n"); for (int i = 0; i < m_testSummaries.size(); ++i) { TestSummary summary = m_testSummaries.get(i); writeSummaryRow(out, summary, true, false); } writeSummaryRow(out, getTotalSummary(), true, true); out.print("</table>\n" + HtmlUtil.getFooter("gogui-regress") + "</body>\n" + "</html>\n"); out.close(); } private void writeSummaryRow(PrintStream out, TestSummary summary, boolean withFileName, boolean foot) { File file = summary.m_file; if (foot) { out.print("<tfoot align=\"center\">\n"); out.print("<tr align=\"center\" bgcolor=\"" + COLOR_HEADER + "\">\n"); } else out.print("<tr align=\"center\" bgcolor=\"" + COLOR_BG_GRAY + "\">\n"); if (withFileName) { if (foot) out.print("<td><b>Total</b></td>"); else out.print("<td><a href=\"" + summary.m_outName + ".html" + "\">" + file + "</a></td>"); } double time = ((double)summary.m_timeMillis) / 1000F; String colorAttrUnexpectedFails = ""; if (summary.m_unexpectedFails > 0) colorAttrUnexpectedFails = " bgcolor=\"" + COLOR_RED + "\""; String colorAttrUnexpectedPasses = ""; if (summary.m_unexpectedPasses > 0) colorAttrUnexpectedPasses = " bgcolor=\"" + COLOR_GREEN + "\""; String colorAttrOtherErrors = ""; if (summary.m_otherErrors > 0) colorAttrOtherErrors = " bgcolor=\"" + COLOR_ERROR + "\""; out.print("<td>" + summary.m_numberTests + "</td>\n" + "<td" + colorAttrUnexpectedFails + ">" + summary.m_unexpectedFails + "</td>\n" + "<td>" + summary.m_expectedFails + "</td>\n" + "<td" + colorAttrUnexpectedPasses + ">\n" + summary.m_unexpectedPasses + "</td>\n" + "<td>" + summary.m_expectedPasses + "</td>\n" + "<td" + colorAttrOtherErrors + ">\n" + summary.m_otherErrors + "</td>\n" + "<td align=\"right\">" + getTimeString(time) + "</td>\n" + "<td align=\"right\">" + getTimeString(summary.m_cpuTime) + "</td>\n" + "</tr>\n"); if (foot) out.print("</tfoot>\n"); } private void writeTestSummary(TestSummary summary) throws FileNotFoundException { if (m_longOutput) { // Output compatible with eval.sh in GNU Go System.out.println("Summary: " + summary.getNumberPasses() + "/" + summary.m_numberTests + " passes. " + summary.m_unexpectedPasses + " unexpected passes, " + summary.m_unexpectedFails + " unexpected failures"); } File file = new File(m_prefix + m_outName + ".html"); PrintStream out = new PrintStream(file); out.print("<html>\n" + "<head>\n" + "<title>Summary: " + m_testFile + "</title>\n" + HtmlUtil.getMeta("gogui-regress") + "<style type=\"text/css\">\n" + "<!--\n" + "body { margin:0; }\n" + "-->\n" + "</style>\n" + "</head>\n" + "<body bgcolor=\"white\" text=\"black\" link=\"blue\"" + " vlink=\"purple\" alink=\"red\">\n" + "<table border=\"0\" width=\"100%\" bgcolor=\"" + COLOR_HEADER + "\">\n" + "<tr><td>\n" + "<h1>Summary: " + m_testFile + "</h1>\n" + "</td></tr>\n" + "</table>\n" + "<table width=\"100%\" bgcolor=\"" + COLOR_INFO + "\">\n"); writeInfo(out, true); out.print("<tr><th align=\"left\">Output:</th><td><a href=\"" + m_outFileRelativeName + "\">" + m_outFileRelativeName + "</a></td></tr>\n" + "</table>\n" + "<table width=\"100%\" border=\"0\" cellpadding=\"0\"" + " cellspacing=\"1\">\n" + "<colgroup>\n" + "<col width=\"12%\">\n" + "<col width=\"12%\">\n" + "<col width=\"12%\">\n" + "<col width=\"12%\">\n" + "<col width=\"12%\">\n" + "<col width=\"12%\">\n" + "<col width=\"12%\">\n" + "<col width=\"12%\">\n" + "</colgroup>\n" + "<thead align=\"center\">\n" + "<tr bgcolor=\"" + COLOR_HEADER + "\">\n" + "<th>Tests</th>\n" + "<th>FAIL</th>\n" + "<th>fail</th>\n" + "<th>PASS</th>\n" + "<th>pass</th>\n" + "<th>Error</th>\n" + "<th>Time</th>\n" + "<th>CpuTime</th>\n" + "</tr>\n" + "</thead>\n"); writeSummaryRow(out, summary, false, false); out.print("</table>\n" + "<table width=\"100%\" border=\"0\" cellpadding=\"0\"" + " cellspacing=\"1\">\n" + "<thead>\n" + "<tr bgcolor=\"" + COLOR_HEADER + "\">\n" + "<th>ID</th>\n" + "<th>Status</th>\n" + "<th>Command</th>\n" + "<th>Output</th>\n" + "<th>Required</th>\n" + "<th>Last SGF</th>\n" + "</tr>\n" + "</thead>\n"); for (int i = 0; i < m_tests.size(); ++i) { Test t = m_tests.get(i); String rowBackground = COLOR_BG_LIGHT; String statusColor = rowBackground; String status = null; if (t.m_fail && t.m_expectedFail) { status = "fail"; } else if (t.m_fail && ! t.m_expectedFail) { statusColor = COLOR_RED; status = "FAIL"; } else if (! t.m_fail && t.m_expectedFail) { statusColor = COLOR_GREEN; status = "PASS"; } else if (! t.m_fail && ! t.m_expectedFail) { status = "pass"; } else assert false; String lastSgf = ""; if (t.m_lastSgf != null) { lastSgf = "<a href=\"" + m_relativePath + t.m_lastSgf + "\">" + t.m_lastSgf + "</a>"; if (t.m_lastSgfMove != -1) lastSgf += " " + t.m_lastSgfMove; } String command = t.m_command.replaceAll(" ", " "); out.print("<tr bgcolor=\"" + rowBackground + "\">\n" + "<td align=\"right\"><a href=\"" + m_outFileRelativeName + "#" + t.m_id + "\">" + t.m_id + "</a></td>\n" + "<td align=\"center\" bgcolor=\"" + statusColor + "\">" + status + "</td>\n" + "<td>" + command + "</td>\n" + "<td align=\"center\">" + truncate(t.m_response) + "</td>\n" + "<td align=\"center\">" + truncate(t.m_required) + "</td>\n" + "<td>" + lastSgf + "</td>\n" + "</tr>\n"); } out.print("</table>\n" + HtmlUtil.getFooter("gogui-regress") + "</body>\n" + "</html>\n"); out.close(); } }