Mercurial > projects > dstress
changeset 879:cf77fcf67454
added log.d (mail:Pine.LNX.4.64.0603012315520.30259@bellevue.puremagic.com)
author | thomask |
---|---|
date | Thu, 02 Mar 2006 08:52:59 +0000 |
parents | ab1736f2e47e |
children | fdb649fc5b8d |
files | log.d |
diffstat | 1 files changed, 1020 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/log.d Thu Mar 02 08:52:59 2006 +0000 @@ -0,0 +1,1020 @@ +module cn.kuehne.dstress.log; + +private import std.string; +private import std.conv; +private import std.stdio; +private import std.stream; +private import std.file; +private import std.c.stdlib; +private import std.date; + +static char[][] TORTURE_FLAGS = [ + "", + "-g", + "-inline", + "-fPIC", + "-O", + "-release", + "-g -inline", + "-g -fPIC", + "-g -O", + "-g -release", + "-inline -fPIC", + "-inline -O", + "-inline -release", + "-fPIC -O", + "-fPIC -release", + "-O -release", + "-g -inline -fPIC", + "-g -inline -O", + "-g -inline -release", + "-g -fPIC -O", + "-g -fPIC -release", + "-g -O -release", + "-inline -fPIC -O", + "-inline -fPIC -release", + "-inline -O -release", + "-fPIC -O -release", + "-g -inline -fPIC -O", + "-g -inline -fPIC -release", + "-g -fPIC -O -release", + "-inline -fPIC -O -release", + "-g -inline -fPIC -O -release" +]; + +enum Result{ + UNTESTED = 0, + PASS = 1 << 2, + XFAIL = 2 << 2, + XPASS = 3 << 2, + FAIL = 4 << 2, + ERROR = 5 << 2, + BASE_MASK = 7 << 2, + + EXT_MASK = 3, + BAD_MSG = 1, + BAD_GDB = 2, + + MAX = BAD_GDB + BASE_MASK +} + +char[] toString(Result r){ + switch(r & Result.BASE_MASK){ + case Result.PASS: return "PASS"; + case Result.XPASS: return "XPASS"; + case Result.FAIL: return "FAIL"; + case Result.XFAIL: return "XFAIL"; + case Result.ERROR: return "ERROR"; + case Result.UNTESTED: return "UNTESTED"; + default: + break; + } + throw new Exception(format("unhandled Result value %s", cast(int)r)); +} + +char[] dateString(){ + static char[] date; + if(date is null){ + auto time = getUTCtime(); + auto year = YearFromTime(time); + auto month = MonthFromTime(time); + auto day = DateFromTime(time); + date = format("%d-%02d-%02d", year, month+1, day); + } + return date; +} + +char[][] unique(char[][] a){ + char[][] b = a.sort; + char[][] back; + + back ~= b[0]; + + size_t ii=0; + for(size_t i=0; i<b.length; i++){ + if(back[ii]!=b[i]){ + back~=b[i]; + ii++; + } + } + + return back; +} + +private{ + version(Windows){ + import std.c.windows.windows; + extern(Windows) BOOL GetFileTime(HANDLE hFile, LPFILETIME lpCreationTime, LPFILETIME lpLastAccessTime, LPFILETIME lpLastWriteTime); + }else version(linux){ + import std.c.linux.linux; + version = Unix; + }else version(Unix){ + import std.c.unix.unix; + }else{ + static assert(0); + } + + alias ulong FStime; + + FStime getFStime(char[] fileName){ + version(Windows){ + HANDLE h; + + if (useWfuncs){ + wchar* namez = std.utf.toUTF16z(fileName); + h = CreateFileW(namez,GENERIC_WRITE,0,null,OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null); + }else{ + char* namez = toMBSz(fileName); + h = CreateFileA(namez,GENERIC_WRITE,0,null,OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null); + } + + if (h == INVALID_HANDLE_VALUE) + goto err; + + FILETIME creationTime; + FILETIME accessTime; + FILETIME writeTime; + + BOOL b = GetFileTime(h, &creationTime, &accessTime, &writeTime); + if(b==1){ + long modA = writeTime.dwLowDateTime; + long modB = writeTime.dwHighDateTime; + return modA | (modB << (writeTime.dwHighDateTime.sizeof*8)); + } + +err: + CloseHandle(h); + throw new Exception("failed to query file modification : "~fileName); + }else version(Unix){ + char* namez = toStringz(fileName); + struct_stat statbuf; + + if(stat(namez, &statbuf)){ + throw new FileException(fileName, getErrno()); + } + + return statbuf.st_mtime; + }else{ + static assert(0); + } + } +} + +char[] cleanFileName(char[] file){ + char[] back; + bool hadSep; + + foreach(char c; file){ + if(c == '/' || c == '\\'){ + if(!hadSep){ + back ~= '/'; + hadSep = true; + } + }else{ + back ~= c; + hadSep = false; + } + } + + size_t start = 0; + while(back[start] <= ' ' && start < back.length){ + start++; + } + + size_t end = back.length-1; + while(back[end] <= ' ' && end >= start){ + end--; + } + + back = back[start .. end+1]; + + return back; +} + +abstract class Test{ + char[] name; + char[] file; + + abstract Result condensed(); + + this(char[] file){ + this.file = file; + + int start = rfind(file, "/"); + if(start<0){ + start = 0; + }else{ + start += 1; + } + + int end = rfind(file, "."); + if(end < start){ + end = file.length; + } + + name = file[start .. end]; + } +} + +class PlainTest : Test{ + Result r; + + Result condensed(){ + return r; + } + + this(char[] file, Result result){ + super(file); + r = result; + } +} + +class TortureTest : Test{ + Result[] r; + + Result condensed(){ + Result back; + foreach(Result res; r){ + if(res > back){ + back = res; + } + } + + return back; + } + + this(char[] file){ + super(file); + r.length = TORTURE_FLAGS.length; + } + + this(char[] file, Result[] result){ + if(result.length != TORTURE_FLAGS.length){ + throw new Exception(format("expected %s results but got %s (%s)", TORTURE_FLAGS.length, result.length, file)); + } + + super(file); + r = result.dup; + } +} + +class Log{ + PlainTest[char[]] plain; + TortureTest[char[]] torture; + + char[] id; + + this(char[] id){ + this.id = id; + } + + Regression[] findGlobalRegressions(Log[] logs){ + Regression[] back; + + if(logs.length < 1){ + return back; + } + + foreach(TortureTest currentTest; torture.values){ + bool hadOldData = false; + Result oldResults[]; + oldResults.length = TORTURE_FLAGS.length; + oldResults[] = Result.MAX; + + foreach(Log l; logs){ + TortureTest* test = currentTest.file in l.torture; + if(test !is null){ + hadOldData = true; + foreach(size_t i, Result r; test.r){ + if(r != Result.UNTESTED && r < oldResults[i]){ + oldResults[i] = r; + } + } + } + } + + if(hadOldData){ + foreach(size_t i, Result r; oldResults){ + if((currentTest.r[i] != Result.UNTESTED) && (r != Result.MAX) && (currentTest.r[i] > r)){ + back ~= new Regression(currentTest.name, currentTest.file, r, currentTest.r[i], TORTURE_FLAGS[i]); + } + } + } + } + return back; + } + + Regression[] findRegressions(Log oldLog){ + Regression[] back; + + foreach(PlainTest t; plain.values){ + PlainTest* oldT = t.file in oldLog.plain; + if(oldT !is null){ + if(oldT.r < t.r){ + back ~= new Regression(oldT.r, t); + } + }else{ + TortureTest* oldT = t.file in oldLog.torture; + if(oldT !is null){ + Result r = oldT.condensed(); + if(r < t.r){ + back ~= new Regression(r, t); + } + } + } + } + + foreach(TortureTest t; torture.values){ + TortureTest* oldT = t.file in oldLog.torture; + + if(oldT !is null){ + foreach(size_t i, Result r; t.r){ + if(oldT.r[i] < r){ + back ~= new Regression(t.name, t.file, oldT.r[i], r, TORTURE_FLAGS[i]); + } + } + }else{ + PlainTest* oldT = t.file in oldLog.plain; + if(oldT !is null){ + Result r = t.condensed(); + if(oldT.r < r){ + back ~= new Regression(t.name, t.file, oldT.r, r); + } + } + } + } + + return back; + } + + char[][] genUpdateList(char[] testRoot){ + char[][] updateList; + const char[][] statusList = ["compile", "nocompile", "run", "norun"]; + char[] status; + + void list(char[] path){ + if(isdir(path)){ + foreach(char[] entry; listdir(path)){ + if(entry.length>0 && entry[0] != '.' && entry[0] != '~'){ + list(path~std.path.sep~entry); + } + } + }else if(isfile(path)){ + char[] file = path[testRoot.length + std.path.sep.length .. $]; + if(!(file in plain) && !(file in torture)){ + char[] output = "dstress torture-" ~ status ~ " " ~ file; + updateList ~= output; + } + } + } + + foreach(char[] s; statusList){ + status = s; + list(testRoot ~ std.path.sep ~ s); + } + + return updateList; + } + + void dropBogusResults(FStime recordTime, char[] testRoot){ + uint totalCount = plain.length + torture.length; + + char[][] sourcesPlain = plain.keys; + foreach(char[] source; sourcesPlain){ + try{ + FStime caseTime = getFStime(testRoot~std.path.sep~source); + if(caseTime > recordTime){ + debug(drop) fwritefln(stderr, "dropped: %s", source); + plain.remove(source); + } + }catch(Exception e){ + debug(drop) fwritefln(stderr, "dropped: %s", source); + plain.remove(source); + } + } + plain.rehash; + + char[][] sourcesTorture = torture.keys; + foreach(char[] source; sourcesTorture){ + try{ + FStime caseTime = getFStime(testRoot~std.path.sep~source); + if(caseTime > recordTime){ + debug(drop) fwritefln(stderr, "dropped: %s", source); + torture.remove(source); + } + }catch(Exception e){ + debug(drop) fwritefln(stderr, "dropped: %s", source); + torture.remove(source); + } + } + torture.rehash; + + writefln("dropped %s outdated tests (%s remaining)", totalCount - (plain.length + torture.length), plain.length + torture.length); + } + + + bool add(char[] line){ + const char[] SUB = "Torture-Sub-"; + const char[] TORTURE = "Torture:"; + + line = strip(line); + int id = -1; + Result r = Result.UNTESTED; + + if(line.length > SUB.length && line[0 .. SUB.length] == SUB){ + line = line[SUB.length .. $]; + id = 0; + while(line[id] >= '0' && line[id] <= '9'){ + id++; + } + int start = id; + id = std.conv.toUint(line[0 .. id]); + + while(line[start] != '-'){ + start++; + } + line = line[start+1 .. $]; + } + + char[][] token = split(line); + if(token.length < 2){ + return false; + } + char[] file = strip(token[1]); + + switch(token[0]){ + case "PASS:": + r = Result.PASS; break; + case "FAIL:": + r = Result.FAIL; break; + case "XPASS:": + r = Result.XPASS; break; + case "XFAIL:": + r = Result.XFAIL; break; + case "ERROR:": + r = Result.ERROR; break; + default:{ + if(token[0] == TORTURE){ + throw new Exception("not yet handled: "~line); + }else if(id > -1){ + throw new Exception(format("bug in SUB line: (%s) %s", id, line)); + } + } + } + + if(r != Result.UNTESTED){ + if(std.string.find(line, "bad error message") > -1){ + r |= Result.BAD_MSG; + } + if(std.string.find(line, "bad debugger message") > -1){ + r |= Result.BAD_MSG; + } + + file = cleanFileName(file); + + if(id < 0){ + // update plain + PlainTest* test = file in plain; + + if(test is null){ + if((file in torture) !is null){ + torture.remove(file); + } + + plain[file] = new PlainTest(file, r); + }else{ + test.r = r; + } + }else{ + // update sub + id--; + + TortureTest* test = file in torture; + + if(test is null){ + if((file in plain) !is null){ + plain.remove(file); + } + + TortureTest t = new TortureTest(file); + torture[file] = t; + t.r[id] = r; + }else{ + if(test.r[id] != Result.UNTESTED){ + test.r[] = Result.UNTESTED; + } + test.r[id] = r; + } + } + return true; + } + return false; + } +} + +class Regression{ + Result before; + Result after; + char[] file; + char[] name; + char[] extInfo; + + this(Result oldResult, PlainTest b){ + before = oldResult; + after = b.r; + file = b.file; + name = b.name; + } + + this(char[] name, char[] file, Result oldR, Result newR, char[] addonInfo=null){ + this.file = file; + this.name = name; + before = oldR; + after = newR; + extInfo = addonInfo; + } + + char[] toString(){ + char[] back = .toString(before) ~" -> "~.toString(after)~": "~file; + if(extInfo.length > 0){ + back ~= " ("~extInfo~")"; + } + return back; + } +} + +class Report{ + char[] root; + Log[] log; + static const char[] header = + "<th> </th><th>-g</th><th>-inline</th>" + "<th>-fPIC</th><th>-O</th><th>-release</th>" + "<th>-g -inline</th><th>-g -fPIC</th><th>-g -O</th>" + "<th>-g -release</th><th>-inline -fPIC</th>" + "<th>-inline -O</th><th>-inline -release</th>" + "<th>-fPIC -O</th><th>-fPIC -release</th>" + "<th>-O -release</th><th>-g -inline -fPIC</th>" + "<th>-g -inline -O</th><th>-g -inline -release</th>" + "<th>-g -fPIC -O</th><th>-g -fPIC -release</th>" + "<th>-g -O -release</th><th>-inline -fPIC -O</th>" + "<th>-inline -fPIC -release</th><th>-inline -O -release</th>" + "<th>-fPIC -O -release</th><th>-g -inline -fPIC -O</th>" + "<th>-g -inline -fPIC -release</th>" + "<th>-g -fPIC -O -release</th>" + "<th>-inline -fPIC -O -release</th>" + "<th>-g -inline -fPIC -O -release</th>"; + + this(char[] root, Log[] log){ + this.root = root; + this.log = log; + } + + void toHtml(OutputStream summary, OutputStream[] cases, bool[] hotspot){ + + if(cases.length != log.length || cases.length != hotspot.length){ + throw new Exception("unexpected argument length"); + } + foreach(size_t i, Log l; log){ + toHtml(l, cases[i]); + } + toHtmlSummary(summary, hotspot); + } + + static char[] cleanFileName(char[] name){ + int i = rfind(name, "_"); + if(i > -1){ + name = name[i+1 .. $]; + } + i = rfind(name, "."); + if(i > -1){ + name = name[0 .. i]; + } + return name; + } + + static char[] streamLine(uint[] stats){ + char[] buffer; + foreach(uint i; stats){ + buffer ~= "<td>"~std.string.toString(i)~"</td>"; + } + return buffer; + } + + static void toHtml(Log log, OutputStream stream){ + char[] cleanName = cleanFileName(log.id); + { // header + char[] name = toupper(cleanName); + + stream.writeLine("<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"); + stream.writeLine("<html xmlns='http://www.w3.org/1999/xhtml'>"); + + stream.writeLine("<head><title>DStress - Torture: "~name~"</title><link rel='stylesheet' type='text/css' href='formate.css' /><meta name='author' content='Thomas Kühne' /><meta name='date' content='" ~ dateString() ~ "' /></head>"); + stream.writeLine("<body><center><h1>DStress - Torture: "~name~"</h1></center><center><small>by Thomas Kühne <thomas-at-kuehne.cn></small></center>"); + stream.writeLine("<h2><a name='note' id='note'></a>Note</h2><blockquote>A detailed description of the testing and the used symbols can be found on the <a href='./dstress.html'>main page</a>.</blockquote>"); + } + + { // stats + stream.writeLine("<h2><a name='summary' id='summary'></a>Summary</h2>"); + uint[][] stats; + stats.length = 6; + foreach(inout uint[] array; stats){ + array.length = TORTURE_FLAGS.length; + } + + foreach(TortureTest t; log.torture){ + foreach(int i, Result r; t.r){ + stats[r >> 2][i]++; + } + } + + + { // total + uint total = 0; + + foreach(uint[] cases; stats){ + total += cases[0]; + } + + uint config = 0; + foreach(uint[] a; stats[1 .. $]){ + foreach(uint b; a){ + config += b; + } + } + + stream.writeLine(format("<blockquote><dl><dt><strong>test cases:</strong></dt><dd>%d</dd><dt><strong>tested configurations:</strong></dt><dd>%d</dd></dl></blockquote>", total, config)); + } + + + stream.writeLine("<table border='1' summary='nummeric summary of the test results'>"); + stream.writeLine("\t<tr><td> </td>"~header~"</tr>"); + stream.writeLine("\t<tr class='" ~ cast(char)('A'+Result.PASS)~"'><th>PASS</th>" ~ streamLine(stats[Result.PASS >> 2])~"</tr>"); + stream.writeLine("\t<tr class='" ~ cast(char)('A'+Result.XFAIL)~"'><th>XFAIL</th>" ~ streamLine(stats[Result.XFAIL >> 2])~"</tr>"); + stream.writeLine("\t<tr class='" ~ cast(char)('A'+Result.XPASS)~"'><th>XPASS</th>" ~ streamLine(stats[Result.XPASS >> 2])~"</tr>"); + stream.writeLine("\t<tr class='" ~ cast(char)('A'+Result.FAIL)~"'><th>FAIL</th>" ~ streamLine(stats[Result.FAIL >> 2])~"</tr>"); + stream.writeLine("\t<tr class='" ~ cast(char)('A'+Result.ERROR)~"'><th>ERROR</th>" ~ streamLine(stats[Result.ERROR >> 2])~"</tr>"); + stream.writeLine("\t<tr class='" ~ cast(char)('A'+Result.UNTESTED)~"'><th>untested</th>" ~ streamLine(stats[Result.UNTESTED >> 2])~"</tr>"); + stream.writeLine("</table>"); + } + + { // details + stream.writeLine("<h2><a name='details' id='details'></a>Details</h2>"); + stream.writeLine("<table border='1' summary='detailed listing of all test cases with unexpected results'>"); + stream.writeLine("<tr><td> </td>"~header~"</tr>"); + + char[][] keys; + { + char[][char[]] k; + foreach(char[] org; log.torture.keys){ + char[] z = org; + int i = rfind(z, "/"); + if(i > -1){ + z = z[i+1 .. $]; + } + i = rfind(z, "."); + if(i > -1){ + z = z[0 .. i]; + } + if(z in k){ + throw new Exception("dublicate key "~org); + } + k[z] = org; + } + + foreach(char[] x; k.keys.sort){ + keys ~= k[x]; + } + + } + foreach(char[] key; keys){ + TortureTest t = log.torture[key]; + Result plainR = t.condensed(); + + if(plainR == Result.PASS + || plainR == Result.XFAIL + || plainR == Result.UNTESTED) + { + continue; + }else{ + char[] name = replace(t.name, "_", " "); + char[] back = "<tr><th><a href=\"../"~t.file~"\" id='"~t.name~"'>"~name~"</a></th>"; + foreach(Result r; t.r){ + back ~= "<td class='" ~ cast(char)(r+'A') ~ "'>"; + if(r == Result.UNTESTED){ + back ~= "-"; + }else{ + try{ + back ~= .toString(r & Result.BASE_MASK); + }catch(Exception e){ + throw new Exception(t.toString()~" ["~e.toString()~"]"); + } + } + back ~= "</td>"; + } + stream.writeLine(back ~ "</tr>"); + } + } + + stream.writeLine("<tr><td> </td>"~header~"</tr>"); + stream.writeLine("</table>"); + } + + { // footer + stream.writeLine("<div><br /><br /><hr /><a href='http://dstress.kuehne.cn/www/"~cleanName~".html'>http://dstress.kuehne.cn/www/"~cleanName~".html</a> " ~ dateString() ~ "</div>"); + stream.writeLine("<!-- Start of StatCounter Code -->"); + stream.writeLine("<script type='text/javascript'><!-- var sc_project=1337754; var sc_invisible=1; var sc_partition=12; var sc_security=\"a4a998fe\"; var sc_remove_link=1; //--> </script>"); + stream.writeLine("<script type='text/javascript' src='http://www.statcounter.com/counter/counter_xhtml.js'></script><noscript><div class='statcounter'><img src='http://c13.statcounter.com/counter.php?sc_project=1337754&amp;java=0&amp;security=a4a998fe&amp;invisible=1' class='statcounter' alt='counter' /></div></noscript>"); + stream.writeLine("<!-- End of StatCounter Code -->"); + + stream.writeLine("</body></html>"); + } + } + + void toHtmlSummary(OutputStream stream, bool[] hotspot){ + if(hotspot.length != log.length){ + throw new Exception("illegal hotspot length"); + } + + char[][] names; + + foreach(Log l; log){ + names ~= l.torture.keys; + } + + uint[][] stats; + stats.length = 6; + foreach(inout uint[] array; stats){ + array.length = log.length; + } + + + char[][char[]] keys; + { + foreach(char[] org; unique(names)){ + char[] z = org; + int i = rfind(z, "/"); + if(i > -1){ + z = z[i+1 .. $]; + } + i = rfind(z, "."); + if(i > -1){ + z = z[0 .. i]; + } + if(z in keys){ + throw new Exception("dublicate key "~org); + } + keys[z] = org; + } + + } + + { // total + uint total = keys.keys.length; + for(size_t i = 0; i < stats[0].length; i++){ + stats[0][i] = total; + } + } + + char[][] badLines; + foreach(char[] name; keys.keys.sort){ + char[] file = keys[name]; + Result[] result = new Result[log.length]; + bool isBadLine; + foreach(size_t i, Log l; log){ + auto t = file in l.torture; + + if(t){ + Result r = t.condensed(); + result[i] = r; + + if(r != Result.UNTESTED){ + stats[result[i] >> 2][i]++; + stats[0][i]--; + if(hotspot[i] && r>= Result.XPASS){ + isBadLine = true; + } + } + } + + } + + if(isBadLine){ + char[] cleanName = replace(name, "_", " "); + char[] back = "<tr><th><a href=\"../"~file~"\" id='"~name~"'>"~cleanName~"</a></th>"; + foreach(Result r; result){ + back ~= "<td class='" ~ cast(char)(r+'A') ~ "'>"; + if(r == Result.UNTESTED){ + back ~= "-"; + }else{ + try{ + back ~= .toString(r & Result.BASE_MASK); + }catch(Exception e){ + throw new Exception("name:" ~name~" ["~e.toString()~"]"); + } + } + back ~= "</td>"; + } + badLines ~= back ~ "</tr>"; + } + } + + + // output + stream.writeLine("<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"); + stream.writeLine("<html xmlns='http://www.w3.org/1999/xhtml'>"); + stream.writeLine("<head><title>DStress Report</title><link rel='stylesheet' type='text/css' href='formate.css' /><meta name='author' content='Thomas Kühne' /><meta name='date' content='"~dateString()~"' /></head>"); + stream.writeLine("<body><center><h1>DStress Report</h1></center>"); + stream.writeLine("<h2><a name='note' id='note'></a>Note</h2>"); + stream.writeLine("<blockquote>A detailed description of the testing and the used symbols can be found on the <a href='./dstress.html'>main page</a>.</blockquote>"); + stream.writeLine("<h2><a name='summary' id='summary'></a>Summary</h2>"); + + stream.writeLine("<table border='1' summary='nummeric summary of the test results'>"); + char[] versionHeader = "<tr><td> </td>"; + { // version header + foreach(Log l; log){ + char[] name = l.id; + int i = rfind(name, "/"); + if(i > -1){ + name = name[i+1 .. $]; + } + i = rfind(name, ".log"); + if(i + ".log".length == name.length){ + name = name[0 .. i]; + } + versionHeader ~= "<th><a href='./" ~ cleanFileName(l.id) ~ ".html'>"~replace(name, "_", " ")~"</a></th>"; + } + stream.writeLine("\t"~versionHeader); + } + + stream.writeLine("\t<tr class='" ~ cast(char)('A'+Result.PASS)~"'><th>PASS</th>" ~ streamLine(stats[Result.PASS >> 2])~"</tr>"); + stream.writeLine("\t<tr class='" ~ cast(char)('A'+Result.XFAIL)~"'><th>XFAIL</th>" ~ streamLine(stats[Result.XFAIL >> 2])~"</tr>"); + stream.writeLine("\t<tr class='" ~ cast(char)('A'+Result.XPASS)~"'><th>XPASS</th>" ~ streamLine(stats[Result.XPASS >> 2])~"</tr>"); + stream.writeLine("\t<tr class='" ~ cast(char)('A'+Result.FAIL)~"'><th>FAIL</th>" ~ streamLine(stats[Result.FAIL >> 2])~"</tr>"); + stream.writeLine("\t<tr class='" ~ cast(char)('A'+Result.ERROR)~"'><th>ERROR</th>" ~ streamLine(stats[Result.ERROR >> 2])~"</tr>"); + stream.writeLine("\t<tr class='" ~ cast(char)('A'+Result.UNTESTED)~"'><th>untested</th>" ~ streamLine(stats[Result.UNTESTED >> 2])~"</tr>"); + stream.writeLine("</table>"); + + stream.writeLine("<h2><a name='details' id='details'></a>Details</h2>"); + stream.writeLine("<table border='1'>"); + stream.writeLine(versionHeader); + foreach(char[] line; badLines){ + stream.writeLine(line); + } + stream.writeLine(versionHeader); + stream.writeLine("</table>"); + + stream.writeLine("<div><br /><br /><hr /><a href='http://dstress.kuehne.cn/www/results.html'>http://dstress.kuehne.cn/www/results.html</a> "~dateString()~"</div>"); + + stream.writeLine("<!-- Start of StatCounter Code -->"); + stream.writeLine("<script type='text/javascript'><!-- var sc_project=1337754; var sc_invisible=1; var sc_partition=12; var sc_security=\"a4a998fe\"; var sc_remove_link=1; //--> </script>"); + stream.writeLine("<script type='text/javascript' src='http://www.statcounter.com/counter/counter_xhtml.js'></script><noscript><div class='statcounter'><img src='http://c13.statcounter.com/counter.php?sc_project=1337754&amp;java=0&amp;security=a4a998fe&amp;invisible=1' class='statcounter' alt='counter' /></div></noscript>"); + stream.writeLine("<!-- End of StatCounter Code -->"); + + stream.writeLine("</body></html>"); + } +} + +int main(char[][] args){ + + if(args.length < 4){ + fwritefln(stderr, "%s <command> <root> <log.1> [<log.2> ...]", args[0]); + fwritefln(stderr, "known commands: genUpdateList findRegressions genReport"); + return 1; + } + + + char[] command = args[1]; + char[] root = args[2]; + if(root.length < 1){ + root = "."; + } + debug fwritefln(stderr, "command: %s", command); + debug fwritefln(stderr, "root: %s", root); + + Report report = new Report(root, null); + bool[] hotspot; + + switch(command){ + case "genUpdateList", "findRegressions", "genReport": break; + default:{ + fwritefln(stderr, "unknown command: %s", command); + return -1; + } + } + + foreach(size_t id, char[] file; args[3 .. $]){ + if(file.length > "--".length && file[0 .. 2] == "--"){ + file = file[2..$]; + hotspot ~= true; + }else{ + hotspot ~= false; + } + + writefln("parsing: %s", file); + FStime logTime = getFStime(file); + debug fwritefln(stderr, "sourceTime: %s", logTime); + + Log l= new Log(file); + Stream source = new BufferedFile(file, FileMode.In); + while(!source.eof()){ + l.add(source.readLine()); + } + + l.dropBogusResults(logTime, root); + + report.log ~= l; + } + + switch(command){ + case "genUpdateList":{ + foreach(Log l; report.log){ + char[][] update = l.genUpdateList(root); + writefln("%s updates required (%s still up to date)", update.length, l.plain.length + l.torture.length); + + char[] updateFile = l.id ~ ".update"; + try{std.file.remove(updateFile);}catch{} + + if(update.length){ + File f = new File(updateFile, FileMode.OutNew); + foreach(char[] line; update){ + f.writeLine(line); + } + f.close(); + } + } + break; + }case "findRegressions":{ + foreach(size_t i, Log newLog; report.log[1..$]){ + Regression[] newReg = newLog.findRegressions(report.log[i]); + writefln("identified %s new regressions for %s", newReg.length, newLog.id); + + Regression[] oldReg; + { + Regression[] oldRegT = newLog.findGlobalRegressions(report.log[0 .. i]); + foreach(Regression a; oldRegT){ + foreach(Regression b; newReg){ + if(a.file == b.file && (!a.extInfo || a.extInfo == b.extInfo)){ + goto handled; + } + } + + oldReg ~= a; + handled: {} + } + } + + writefln("identified %s old regressions for %s", oldReg.length, newLog.id); + + char[] regressionFile = newLog.id ~ ".regression"; + try{std.file.remove(regressionFile);}catch{} + + if(newReg.length + oldReg.length){ + File f = new File(regressionFile, FileMode.OutNew); + + if(newReg.length){ + f.writeLine(format("%s new regressions", newReg.length)); + foreach(Regression r; newReg){ + f.writeLine(r.toString()); + } + } + + if(oldReg.length){ + if(newReg.length){ + f.writeLine(""); + } + f.writeLine(format("%s old regressions", oldReg.length)); + foreach(Regression r; oldReg){ + f.writeLine(r.toString()); + } + } + + f.close(); + } + } + break; + }case "genReport":{ + OutputStream[] html; + OutputStream o = new File("./www/results.html", FileMode.OutNew); + foreach(Log l; report.log){ + html ~= new File("./www/"~Report.cleanFileName(l.id)~".html", FileMode.OutNew); + } + + report.toHtml(o, html, hotspot); + + o.close(); + foreach(OutputStream o; html){ + o.close(); + } + + break; + }default:{ + fwritefln(stderr, "unknown command: %s", command); + return -1; + } + } + + return 0; +} +