| 1 |
// Based on DSTRESS code by Thomas KÃŒhne |
|---|
| 2 |
|
|---|
| 3 |
module findregressions; |
|---|
| 4 |
|
|---|
| 5 |
private import std.string; |
|---|
| 6 |
private import std.conv; |
|---|
| 7 |
private import std.stdio; |
|---|
| 8 |
private import std.stream; |
|---|
| 9 |
private import std.file; |
|---|
| 10 |
private import std.c.stdlib; |
|---|
| 11 |
private import std.date; |
|---|
| 12 |
private import std.path; |
|---|
| 13 |
|
|---|
| 14 |
|
|---|
| 15 |
enum Result{ |
|---|
| 16 |
UNTESTED = 0, |
|---|
| 17 |
PASS = 1 << 2, |
|---|
| 18 |
XFAIL = 2 << 2, |
|---|
| 19 |
XPASS = 3 << 2, |
|---|
| 20 |
FAIL = 4 << 2, |
|---|
| 21 |
ERROR = 5 << 2, |
|---|
| 22 |
BASE_MASK = 7 << 2, |
|---|
| 23 |
|
|---|
| 24 |
EXT_MASK = 3, |
|---|
| 25 |
BAD_MSG = 1, |
|---|
| 26 |
BAD_GDB = 2, |
|---|
| 27 |
|
|---|
| 28 |
MAX = BAD_GDB + BASE_MASK |
|---|
| 29 |
} |
|---|
| 30 |
|
|---|
| 31 |
char[] toString(Result r){ |
|---|
| 32 |
switch(r & Result.BASE_MASK){ |
|---|
| 33 |
case Result.PASS: return "PASS"; |
|---|
| 34 |
case Result.XPASS: return "XPASS"; |
|---|
| 35 |
case Result.FAIL: return "FAIL"; |
|---|
| 36 |
case Result.XFAIL: return "XFAIL"; |
|---|
| 37 |
case Result.ERROR: return "ERROR"; |
|---|
| 38 |
case Result.UNTESTED: return "UNTESTED"; |
|---|
| 39 |
default: |
|---|
| 40 |
break; |
|---|
| 41 |
} |
|---|
| 42 |
throw new Exception(format("unhandled Result value %s", cast(int)r)); |
|---|
| 43 |
} |
|---|
| 44 |
|
|---|
| 45 |
char[] dateString(){ |
|---|
| 46 |
static char[] date; |
|---|
| 47 |
if(date is null){ |
|---|
| 48 |
auto time = getUTCtime(); |
|---|
| 49 |
auto year = YearFromTime(time); |
|---|
| 50 |
auto month = MonthFromTime(time); |
|---|
| 51 |
auto day = DateFromTime(time); |
|---|
| 52 |
date = format("%d-%02d-%02d", year, month+1, day); |
|---|
| 53 |
} |
|---|
| 54 |
return date; |
|---|
| 55 |
} |
|---|
| 56 |
|
|---|
| 57 |
char[][] unique(char[][] a){ |
|---|
| 58 |
char[][] b = a.sort; |
|---|
| 59 |
char[][] back; |
|---|
| 60 |
|
|---|
| 61 |
back ~= b[0]; |
|---|
| 62 |
|
|---|
| 63 |
size_t ii=0; |
|---|
| 64 |
for(size_t i=0; i<b.length; i++){ |
|---|
| 65 |
if(back[ii]!=b[i]){ |
|---|
| 66 |
back~=b[i]; |
|---|
| 67 |
ii++; |
|---|
| 68 |
} |
|---|
| 69 |
} |
|---|
| 70 |
|
|---|
| 71 |
return back; |
|---|
| 72 |
} |
|---|
| 73 |
|
|---|
| 74 |
private{ |
|---|
| 75 |
version(Windows){ |
|---|
| 76 |
import std.c.windows.windows; |
|---|
| 77 |
extern(Windows) BOOL GetFileTime(HANDLE hFile, LPFILETIME lpCreationTime, LPFILETIME lpLastAccessTime, LPFILETIME lpLastWriteTime); |
|---|
| 78 |
}else version(linux){ |
|---|
| 79 |
import std.c.linux.linux; |
|---|
| 80 |
version = Unix; |
|---|
| 81 |
}else version(Unix){ |
|---|
| 82 |
import std.c.unix.unix; |
|---|
| 83 |
}else{ |
|---|
| 84 |
static assert(0); |
|---|
| 85 |
} |
|---|
| 86 |
|
|---|
| 87 |
alias ulong FStime; |
|---|
| 88 |
|
|---|
| 89 |
FStime getFStime(char[] fileName){ |
|---|
| 90 |
version(Windows){ |
|---|
| 91 |
HANDLE h; |
|---|
| 92 |
|
|---|
| 93 |
if (useWfuncs){ |
|---|
| 94 |
wchar* namez = std.utf.toUTF16z(fileName); |
|---|
| 95 |
h = CreateFileW(namez,GENERIC_WRITE,0,null,OPEN_ALWAYS, |
|---|
| 96 |
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null); |
|---|
| 97 |
}else{ |
|---|
| 98 |
char* namez = toMBSz(fileName); |
|---|
| 99 |
h = CreateFileA(namez,GENERIC_WRITE,0,null,OPEN_ALWAYS, |
|---|
| 100 |
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null); |
|---|
| 101 |
} |
|---|
| 102 |
|
|---|
| 103 |
if (h == INVALID_HANDLE_VALUE) |
|---|
| 104 |
goto err; |
|---|
| 105 |
|
|---|
| 106 |
FILETIME creationTime; |
|---|
| 107 |
FILETIME accessTime; |
|---|
| 108 |
FILETIME writeTime; |
|---|
| 109 |
|
|---|
| 110 |
BOOL b = GetFileTime(h, &creationTime, &accessTime, &writeTime); |
|---|
| 111 |
if(b==1){ |
|---|
| 112 |
long modA = writeTime.dwLowDateTime; |
|---|
| 113 |
long modB = writeTime.dwHighDateTime; |
|---|
| 114 |
return modA | (modB << (writeTime.dwHighDateTime.sizeof*8)); |
|---|
| 115 |
} |
|---|
| 116 |
|
|---|
| 117 |
err: |
|---|
| 118 |
CloseHandle(h); |
|---|
| 119 |
throw new Exception("failed to query file modification : "~fileName); |
|---|
| 120 |
}else version(Unix){ |
|---|
| 121 |
char* namez = toStringz(fileName); |
|---|
| 122 |
struct_stat statbuf; |
|---|
| 123 |
|
|---|
| 124 |
if(stat(namez, &statbuf)){ |
|---|
| 125 |
throw new FileException(fileName, getErrno()); |
|---|
| 126 |
} |
|---|
| 127 |
|
|---|
| 128 |
return statbuf.st_mtime; |
|---|
| 129 |
}else{ |
|---|
| 130 |
static assert(0); |
|---|
| 131 |
} |
|---|
| 132 |
} |
|---|
| 133 |
} |
|---|
| 134 |
|
|---|
| 135 |
char[] cleanFileName(char[] file){ |
|---|
| 136 |
char[] back; |
|---|
| 137 |
bool hadSep; |
|---|
| 138 |
|
|---|
| 139 |
foreach(char c; file){ |
|---|
| 140 |
if(c == '/' || c == '\\'){ |
|---|
| 141 |
if(!hadSep){ |
|---|
| 142 |
back ~= '/'; |
|---|
| 143 |
hadSep = true; |
|---|
| 144 |
} |
|---|
| 145 |
}else{ |
|---|
| 146 |
back ~= c; |
|---|
| 147 |
hadSep = false; |
|---|
| 148 |
} |
|---|
| 149 |
} |
|---|
| 150 |
|
|---|
| 151 |
size_t start = 0; |
|---|
| 152 |
while(back[start] <= ' ' && start < back.length){ |
|---|
| 153 |
start++; |
|---|
| 154 |
} |
|---|
| 155 |
|
|---|
| 156 |
size_t end = back.length-1; |
|---|
| 157 |
while(back[end] <= ' ' && end >= start){ |
|---|
| 158 |
end--; |
|---|
| 159 |
} |
|---|
| 160 |
|
|---|
| 161 |
back = back[start .. end+1]; |
|---|
| 162 |
|
|---|
| 163 |
return back; |
|---|
| 164 |
} |
|---|
| 165 |
|
|---|
| 166 |
class Test{ |
|---|
| 167 |
char[] name; |
|---|
| 168 |
char[] file; |
|---|
| 169 |
Result r; |
|---|
| 170 |
|
|---|
| 171 |
this(char[] file){ |
|---|
| 172 |
this.file = file; |
|---|
| 173 |
|
|---|
| 174 |
int start = rfind(file, "/"); |
|---|
| 175 |
if(start<0){ |
|---|
| 176 |
start = 0; |
|---|
| 177 |
}else{ |
|---|
| 178 |
start += 1; |
|---|
| 179 |
} |
|---|
| 180 |
|
|---|
| 181 |
int end = rfind(file, "."); |
|---|
| 182 |
if(end < start){ |
|---|
| 183 |
end = file.length; |
|---|
| 184 |
} |
|---|
| 185 |
|
|---|
| 186 |
name = file[start .. end]; |
|---|
| 187 |
} |
|---|
| 188 |
} |
|---|
| 189 |
|
|---|
| 190 |
|
|---|
| 191 |
class Log{ |
|---|
| 192 |
Test[char[]] tests; |
|---|
| 193 |
|
|---|
| 194 |
char[] id; |
|---|
| 195 |
|
|---|
| 196 |
int[Result] counts; |
|---|
| 197 |
|
|---|
| 198 |
this(char[] id, char[] file){ |
|---|
| 199 |
this.id = id; |
|---|
| 200 |
counts = [ |
|---|
| 201 |
Result.PASS: 0, |
|---|
| 202 |
Result.FAIL: 0, |
|---|
| 203 |
Result.XPASS: 0, |
|---|
| 204 |
Result.XFAIL: 0, |
|---|
| 205 |
Result.ERROR: 0 ]; |
|---|
| 206 |
|
|---|
| 207 |
writefln("parsing: %s", file); |
|---|
| 208 |
FStime logTime = getFStime(file); |
|---|
| 209 |
Stream source = new BufferedFile(file, FileMode.In); |
|---|
| 210 |
while(!source.eof()){ |
|---|
| 211 |
add(source.readLine()); |
|---|
| 212 |
} |
|---|
| 213 |
dropBogusResults(logTime, "dstress"); |
|---|
| 214 |
} |
|---|
| 215 |
|
|---|
| 216 |
|
|---|
| 217 |
void dropBogusResults(FStime recordTime, char[] testRoot){ |
|---|
| 218 |
uint totalCount = tests.length; |
|---|
| 219 |
|
|---|
| 220 |
char[][] sourcesTests = tests.keys; |
|---|
| 221 |
foreach(char[] source; sourcesTests){ |
|---|
| 222 |
if(find(source, "complex/") < 0){ |
|---|
| 223 |
try{ |
|---|
| 224 |
FStime caseTime = getFStime(testRoot~std.path.sep~source); |
|---|
| 225 |
if(caseTime > recordTime){ |
|---|
| 226 |
debug(drop) fwritefln(stderr, "dropped: %s", source); |
|---|
| 227 |
counts[tests[source].r & Result.BASE_MASK]--; |
|---|
| 228 |
tests.remove(source); |
|---|
| 229 |
continue; |
|---|
| 230 |
} |
|---|
| 231 |
}catch(Exception e){ |
|---|
| 232 |
debug(drop) fwritefln(stderr, "dropped: %s", source); |
|---|
| 233 |
counts[tests[source].r & Result.BASE_MASK]--; |
|---|
| 234 |
tests.remove(source); |
|---|
| 235 |
continue; |
|---|
| 236 |
} |
|---|
| 237 |
} |
|---|
| 238 |
// asm-filter |
|---|
| 239 |
int i = find(source, "asm_p"); |
|---|
| 240 |
if(i >= 0){ |
|---|
| 241 |
counts[tests[source].r & Result.BASE_MASK]--; |
|---|
| 242 |
tests.remove(source); |
|---|
| 243 |
continue; |
|---|
| 244 |
} |
|---|
| 245 |
} |
|---|
| 246 |
tests.rehash; |
|---|
| 247 |
|
|---|
| 248 |
writefln("dropped %s outdated tests (%s remaining)", totalCount - tests.length, tests.length); |
|---|
| 249 |
} |
|---|
| 250 |
|
|---|
| 251 |
|
|---|
| 252 |
bool add(char[] line){ |
|---|
| 253 |
const char[] SUB = "Torture-Sub-"; |
|---|
| 254 |
const char[] TORTURE = "Torture:"; |
|---|
| 255 |
|
|---|
| 256 |
line = strip(line); |
|---|
| 257 |
int id = -1; |
|---|
| 258 |
Result r = Result.UNTESTED; |
|---|
| 259 |
|
|---|
| 260 |
if(line.length > SUB.length && line[0 .. SUB.length] == SUB){ |
|---|
| 261 |
line = line[SUB.length .. $]; |
|---|
| 262 |
id = 0; |
|---|
| 263 |
while(line[id] >= '0' && line[id] <= '9'){ |
|---|
| 264 |
id++; |
|---|
| 265 |
} |
|---|
| 266 |
int start = id; |
|---|
| 267 |
id = std.conv.toUint(line[0 .. id]); |
|---|
| 268 |
|
|---|
| 269 |
while(line[start] != '-'){ |
|---|
| 270 |
start++; |
|---|
| 271 |
} |
|---|
| 272 |
line = line[start+1 .. $]; |
|---|
| 273 |
} |
|---|
| 274 |
|
|---|
| 275 |
char[][] token = split(line); |
|---|
| 276 |
if(token.length < 2){ |
|---|
| 277 |
return false; |
|---|
| 278 |
} |
|---|
| 279 |
char[] file = strip(token[1]); |
|---|
| 280 |
|
|---|
| 281 |
switch(token[0]){ |
|---|
| 282 |
case "PASS:": |
|---|
| 283 |
r = Result.PASS; break; |
|---|
| 284 |
case "FAIL:": |
|---|
| 285 |
r = Result.FAIL; break; |
|---|
| 286 |
case "XPASS:": |
|---|
| 287 |
r = Result.XPASS; break; |
|---|
| 288 |
case "XFAIL:": |
|---|
| 289 |
r = Result.XFAIL; break; |
|---|
| 290 |
case "ERROR:": |
|---|
| 291 |
r = Result.ERROR; break; |
|---|
| 292 |
default:{ |
|---|
| 293 |
if(token[0] == TORTURE){ |
|---|
| 294 |
throw new Exception("not yet handled: "~line); |
|---|
| 295 |
}else if(id > -1){ |
|---|
| 296 |
throw new Exception(format("bug in SUB line: (%s) %s", id, line)); |
|---|
| 297 |
} |
|---|
| 298 |
} |
|---|
| 299 |
} |
|---|
| 300 |
|
|---|
| 301 |
if(r != Result.UNTESTED){ |
|---|
| 302 |
if(std.string.find(line, "bad error message") > -1){ |
|---|
| 303 |
r |= Result.BAD_MSG; |
|---|
| 304 |
} |
|---|
| 305 |
if(std.string.find(line, "bad debugger message") > -1){ |
|---|
| 306 |
r |= Result.BAD_MSG; |
|---|
| 307 |
} |
|---|
| 308 |
|
|---|
| 309 |
file = cleanFileName(file); |
|---|
| 310 |
|
|---|
| 311 |
if(id >= 0){ |
|---|
| 312 |
// update sub |
|---|
| 313 |
id--; |
|---|
| 314 |
|
|---|
| 315 |
Test* test = file in tests; |
|---|
| 316 |
|
|---|
| 317 |
if(test is null){ |
|---|
| 318 |
Test t = new Test(file); |
|---|
| 319 |
tests[file] = t; |
|---|
| 320 |
t.r = r; |
|---|
| 321 |
counts[r & Result.BASE_MASK]++; |
|---|
| 322 |
}else{ |
|---|
| 323 |
if(test.r != Result.UNTESTED){ |
|---|
| 324 |
test.r = Result.UNTESTED; |
|---|
| 325 |
} |
|---|
| 326 |
test.r = r; |
|---|
| 327 |
} |
|---|
| 328 |
} |
|---|
| 329 |
return true; |
|---|
| 330 |
} |
|---|
| 331 |
return false; |
|---|
| 332 |
} |
|---|
| 333 |
} |
|---|
| 334 |
|
|---|
| 335 |
|
|---|
| 336 |
char[] basedir = "web"; |
|---|
| 337 |
bool regenerate = false; |
|---|
| 338 |
|
|---|
| 339 |
int main(char[][] args){ |
|---|
| 340 |
|
|---|
| 341 |
if(args.length < 3 || (args[1] == "--regenerate" && args.length < 4)){ |
|---|
| 342 |
fwritefln(stderr, "%s [--regenerate] <reference-log> <log> <log> ...", args[0]); |
|---|
| 343 |
fwritefln(stderr, "bash example: %s reference/dmd-something $(ls reference/ldc*)", args[0]); |
|---|
| 344 |
return 1; |
|---|
| 345 |
} |
|---|
| 346 |
|
|---|
| 347 |
char[] reference; |
|---|
| 348 |
char[][] files; |
|---|
| 349 |
if(args[1] == "--regenerate") { |
|---|
| 350 |
regenerate = true; |
|---|
| 351 |
reference = args[2]; |
|---|
| 352 |
files = args[3..$] ~ reference; |
|---|
| 353 |
} else { |
|---|
| 354 |
reference = args[1]; |
|---|
| 355 |
files = args[2..$] ~ reference; |
|---|
| 356 |
} |
|---|
| 357 |
|
|---|
| 358 |
// make sure base path exists |
|---|
| 359 |
if(std.file.exists(basedir) && !std.file.isdir(basedir)) |
|---|
| 360 |
throw new Exception(basedir ~ " is not a directory!"); |
|---|
| 361 |
else if(!std.file.exists(basedir)) |
|---|
| 362 |
std.file.mkdir(basedir); |
|---|
| 363 |
|
|---|
| 364 |
|
|---|
| 365 |
Log[char[]] logs; |
|---|
| 366 |
|
|---|
| 367 |
// emit per-log data |
|---|
| 368 |
foreach(char[] file; files) |
|---|
| 369 |
generateLogStatistics(file, logs); |
|---|
| 370 |
|
|---|
| 371 |
// differences between logs |
|---|
| 372 |
foreach(int i, char[] file; files[1 .. $]) |
|---|
| 373 |
generateChangeStatistics(files[1+i], files[1+i-1], logs); |
|---|
| 374 |
|
|---|
| 375 |
// differences between reference and logs |
|---|
| 376 |
foreach(char[] file; files[0..$-1]) |
|---|
| 377 |
generateChangeStatistics(file, reference, logs); |
|---|
| 378 |
|
|---|
| 379 |
// collect all the stats.base files into a large table |
|---|
| 380 |
BufferedFile index = new BufferedFile(std.path.join(basedir, "index.html"), FileMode.OutNew); |
|---|
| 381 |
scope(exit) index.close(); |
|---|
| 382 |
index.writefln(` |
|---|
| 383 |
<!DOCTYPE html> |
|---|
| 384 |
<html> |
|---|
| 385 |
<head> |
|---|
| 386 |
<title>DStress results for x86-32 Linux</title> |
|---|
| 387 |
<style type="text/css"> |
|---|
| 388 |
body { |
|---|
| 389 |
font-family: Arial, Helvetica, sans-serif; |
|---|
| 390 |
font-size: 0.8em; |
|---|
| 391 |
} |
|---|
| 392 |
a { |
|---|
| 393 |
text-decoration: none; |
|---|
| 394 |
} |
|---|
| 395 |
a:hover { |
|---|
| 396 |
border-bottom: 1px dotted blue; |
|---|
| 397 |
} |
|---|
| 398 |
table { |
|---|
| 399 |
border-collapse: collapse; |
|---|
| 400 |
} |
|---|
| 401 |
tr { |
|---|
| 402 |
border-bottom: 1px solid #CCC; |
|---|
| 403 |
} |
|---|
| 404 |
tr.odd { |
|---|
| 405 |
background: #e0e0e0; |
|---|
| 406 |
} |
|---|
| 407 |
tr.head { |
|---|
| 408 |
border-bottom: none; |
|---|
| 409 |
} |
|---|
| 410 |
td,th { |
|---|
| 411 |
padding: 2px 10px 2px 10px; |
|---|
| 412 |
} |
|---|
| 413 |
.result:hover { |
|---|
| 414 |
background: #C3DFFF; |
|---|
| 415 |
} |
|---|
| 416 |
.pass,.xfail,.xpass,.fail,.xpass,.error,.generic { |
|---|
| 417 |
text-align: center; |
|---|
| 418 |
} |
|---|
| 419 |
.generic { |
|---|
| 420 |
background: #EEE; |
|---|
| 421 |
color: gray; |
|---|
| 422 |
} |
|---|
| 423 |
.pass { |
|---|
| 424 |
background: #98FF90; |
|---|
| 425 |
color: green; |
|---|
| 426 |
} |
|---|
| 427 |
tr:hover .pass { |
|---|
| 428 |
background: #83E67B; |
|---|
| 429 |
} |
|---|
| 430 |
.xfail { |
|---|
| 431 |
background: #BDFFB8; |
|---|
| 432 |
color: #0CAE00; |
|---|
| 433 |
} |
|---|
| 434 |
tr:hover .xfail { |
|---|
| 435 |
background: #98FF90; |
|---|
| 436 |
} |
|---|
| 437 |
.fail { |
|---|
| 438 |
background: #FF6E7A; |
|---|
| 439 |
color: maroon; |
|---|
| 440 |
} |
|---|
| 441 |
.xpass { |
|---|
| 442 |
background: #FF949D; |
|---|
| 443 |
color: maroon; |
|---|
| 444 |
} |
|---|
| 445 |
.error { |
|---|
| 446 |
background: #FFB3B9; |
|---|
| 447 |
color: maroon; |
|---|
| 448 |
} |
|---|
| 449 |
.borderleft { |
|---|
| 450 |
border-left: 1px solid #CCC; |
|---|
| 451 |
} |
|---|
| 452 |
</style> |
|---|
| 453 |
</head> |
|---|
| 454 |
|
|---|
| 455 |
<body> |
|---|
| 456 |
<h1>DStress results for x86-32 Linux</h1> |
|---|
| 457 |
|
|---|
| 458 |
<h2>Legend</h2> |
|---|
| 459 |
<table id="legend"> |
|---|
| 460 |
<tr> |
|---|
| 461 |
<th>Color</th> |
|---|
| 462 |
<th>Description</th> |
|---|
| 463 |
</tr> |
|---|
| 464 |
<tr class="result"> |
|---|
| 465 |
<td class="pass">PASS</td> |
|---|
| 466 |
<td>Test passed and was expected to pass</td> |
|---|
| 467 |
</tr> |
|---|
| 468 |
<tr class="result"> |
|---|
| 469 |
<td class="xfail">XFAIL</td> |
|---|
| 470 |
<td>Test failed and expected to fail</td> |
|---|
| 471 |
</tr> |
|---|
| 472 |
<tr class="result"> |
|---|
| 473 |
<td class="fail">FAIL</td> |
|---|
| 474 |
<td>Test failed but was expected to pass</td> |
|---|
| 475 |
</tr> |
|---|
| 476 |
<tr class="result"> |
|---|
| 477 |
<td class="xpass">XPASS</td> |
|---|
| 478 |
<td>Test passed but was expected to fail</td> |
|---|
| 479 |
</tr> |
|---|
| 480 |
<tr class="result"> |
|---|
| 481 |
<td class="error">ERROR</td> |
|---|
| 482 |
<td>The compiler, linker or the test segfaulted</td> |
|---|
| 483 |
</tr> |
|---|
| 484 |
<tr class="result"> |
|---|
| 485 |
<td class="generic">+</td> |
|---|
| 486 |
<td>Changes from FAIL, XPASS or ERROR to PASS or XFAIL</td> |
|---|
| 487 |
</tr> |
|---|
| 488 |
<tr class="result"> |
|---|
| 489 |
<td class="generic">-</td> |
|---|
| 490 |
<td>Changes from PASS or XFAIL to FAIL, XPASS or ERROR</td> |
|---|
| 491 |
</tr> |
|---|
| 492 |
<tr class="result"> |
|---|
| 493 |
<td class="generic">chg</td> |
|---|
| 494 |
<td>Changed within the good or bad group without crossing over</td> |
|---|
| 495 |
</tr> |
|---|
| 496 |
</table> |
|---|
| 497 |
|
|---|
| 498 |
<h2>Results</h2> |
|---|
| 499 |
<table> |
|---|
| 500 |
<tr class="head"> |
|---|
| 501 |
<th></th> |
|---|
| 502 |
<th colspan="5" class="borderleft">Test results</th> |
|---|
| 503 |
<th colspan="3" class="borderleft">Diff to previous</th> |
|---|
| 504 |
<th colspan="3" class="borderleft">Diff to ` ~ std.path.getBaseName(reference) ~ `</th> |
|---|
| 505 |
</tr> |
|---|
| 506 |
<tr> |
|---|
| 507 |
<th>Name</th> |
|---|
| 508 |
<th class="borderleft">PASS</th> |
|---|
| 509 |
<th>XFAIL</th> |
|---|
| 510 |
<th>FAIL</th> |
|---|
| 511 |
<th>XPASS</th> |
|---|
| 512 |
<th>ERROR</th> |
|---|
| 513 |
<th class="borderleft">+</th> |
|---|
| 514 |
<th>-</th> |
|---|
| 515 |
<th>chg</th> |
|---|
| 516 |
<th class="borderleft">+</th> |
|---|
| 517 |
<th>-</th> |
|---|
| 518 |
<th>chg</th> |
|---|
| 519 |
</tr> |
|---|
| 520 |
`); |
|---|
| 521 |
|
|---|
| 522 |
for(int i = files.length - 1; i >= 0; --i) { |
|---|
| 523 |
auto file = files[i]; |
|---|
| 524 |
index.writefln(`<tr class="` ~ (i%2 ? `result` : `odd result`) ~ `">`); |
|---|
| 525 |
char[] id = std.path.getBaseName(file); |
|---|
| 526 |
char[] statsname = std.path.join(std.path.join(basedir, id), "stats.base"); |
|---|
| 527 |
index.writef(cast(char[])std.file.read(statsname)); |
|---|
| 528 |
|
|---|
| 529 |
if(i != 0) { |
|---|
| 530 |
char[] newid = std.path.getBaseName(files[i-1]); |
|---|
| 531 |
statsname = std.path.join(std.path.join(basedir, newid ~ "-to-" ~ id), "stats.base"); |
|---|
| 532 |
index.writef(cast(char[])std.file.read(statsname)); |
|---|
| 533 |
} else { |
|---|
| 534 |
index.writefln(`<td class="borderleft"></td><td></td><td></td>`); |
|---|
| 535 |
} |
|---|
| 536 |
|
|---|
| 537 |
if(i != files.length - 1) { |
|---|
| 538 |
char[] refid = std.path.getBaseName(reference); |
|---|
| 539 |
statsname = std.path.join(std.path.join(basedir, refid ~ "-to-" ~ id), "stats.base"); |
|---|
| 540 |
index.writef(cast(char[])std.file.read(statsname)); |
|---|
| 541 |
} else { |
|---|
| 542 |
index.writefln(`<td class="borderleft"></td><td></td><td></td>`); |
|---|
| 543 |
} |
|---|
| 544 |
|
|---|
| 545 |
index.writefln(`</tr>`); |
|---|
| 546 |
} |
|---|
| 547 |
|
|---|
| 548 |
index.writefln(`</table></body></html>`); |
|---|
| 549 |
|
|---|
| 550 |
return 0; |
|---|
| 551 |
} |
|---|
| 552 |
|
|---|
| 553 |
void generateLogStatistics(char[] file, ref Log[char[]] logs) |
|---|
| 554 |
{ |
|---|
| 555 |
char[] id = std.path.getBaseName(file); |
|---|
| 556 |
char[] dirname = std.path.join(basedir, id); |
|---|
| 557 |
|
|---|
| 558 |
if(std.file.exists(dirname)) { |
|---|
| 559 |
if(std.file.isdir(dirname)) { |
|---|
| 560 |
if(!regenerate) { |
|---|
| 561 |
writefln("Directory ", dirname, " already exists, skipping..."); |
|---|
| 562 |
return; |
|---|
| 563 |
} |
|---|
| 564 |
} |
|---|
| 565 |
else |
|---|
| 566 |
throw new Exception(dirname ~ " is not a directory!"); |
|---|
| 567 |
} |
|---|
| 568 |
else |
|---|
| 569 |
std.file.mkdir(dirname); |
|---|
| 570 |
|
|---|
| 571 |
// parse etc. |
|---|
| 572 |
Log log = new Log(id, file); |
|---|
| 573 |
logs[id] = log; |
|---|
| 574 |
|
|---|
| 575 |
// write status |
|---|
| 576 |
{ |
|---|
| 577 |
BufferedFile makeFile(char[] name) { |
|---|
| 578 |
return new BufferedFile(std.path.join(dirname, name), FileMode.OutNew); |
|---|
| 579 |
} |
|---|
| 580 |
BufferedFile[Result] resultsfile = [ |
|---|
| 581 |
Result.PASS: makeFile("pass.html"), |
|---|
| 582 |
Result.FAIL: makeFile("fail.html"), |
|---|
| 583 |
Result.XPASS: makeFile("xpass.html"), |
|---|
| 584 |
Result.XFAIL: makeFile("xfail.html"), |
|---|
| 585 |
Result.ERROR: makeFile("error.html") ]; |
|---|
| 586 |
|
|---|
| 587 |
scope(exit) { |
|---|
| 588 |
foreach(file; resultsfile) |
|---|
| 589 |
file.close(); |
|---|
| 590 |
} |
|---|
| 591 |
|
|---|
| 592 |
foreach(file; resultsfile) |
|---|
| 593 |
file.writefln(`<html><body>`); |
|---|
| 594 |
|
|---|
| 595 |
foreach(tkey; log.tests.keys.sort) { |
|---|
| 596 |
auto test = log.tests[tkey]; |
|---|
| 597 |
auto result = test.r & Result.BASE_MASK; |
|---|
| 598 |
resultsfile[result].writefln(test.name, " in ", test.file, "<br>"); |
|---|
| 599 |
} |
|---|
| 600 |
|
|---|
| 601 |
foreach(file; resultsfile) |
|---|
| 602 |
file.writefln(`</body></html>`); |
|---|
| 603 |
} |
|---|
| 604 |
|
|---|
| 605 |
BufferedFile stats = new BufferedFile(std.path.join(dirname, "stats.base"), FileMode.OutNew); |
|---|
| 606 |
scope(exit) stats.close(); |
|---|
| 607 |
stats.writefln(`<td>`, id, `</td>`); |
|---|
| 608 |
stats.writefln(`<td class="pass borderleft"><a href="`, std.path.join(log.id, "pass.html"), `">`, log.counts[Result.PASS], `</a></td>`); |
|---|
| 609 |
stats.writefln(`<td class="xfail"><a href="`, std.path.join(log.id, "xfail.html"), `">`, log.counts[Result.XFAIL], `</a></td>`); |
|---|
| 610 |
stats.writefln(`<td class="fail"><a href="`, std.path.join(log.id, "fail.html"), `">`, log.counts[Result.FAIL], `</a></td>`); |
|---|
| 611 |
stats.writefln(`<td class="xpass"><a href="`, std.path.join(log.id, "xpass.html"), `">`, log.counts[Result.XPASS], `</a></td>`); |
|---|
| 612 |
stats.writefln(`<td class="error"><a href="`, std.path.join(log.id, "error.html"), `">`, log.counts[Result.ERROR], `</a></td>`); |
|---|
| 613 |
} |
|---|
| 614 |
|
|---|
| 615 |
void generateChangeStatistics(char[] file1, char[] file2, ref Log[char[]] logs) |
|---|
| 616 |
{ |
|---|
| 617 |
char[] newid = std.path.getBaseName(file1); |
|---|
| 618 |
char[] oldid = std.path.getBaseName(file2); |
|---|
| 619 |
|
|---|
| 620 |
char[] dirname = std.path.join(basedir, oldid ~ "-to-" ~ newid); |
|---|
| 621 |
|
|---|
| 622 |
if(std.file.exists(dirname)) { |
|---|
| 623 |
if(std.file.isdir(dirname)) { |
|---|
| 624 |
if(!regenerate) { |
|---|
| 625 |
writefln("Directory ", dirname, " already exists, skipping..."); |
|---|
| 626 |
return; |
|---|
| 627 |
} |
|---|
| 628 |
} |
|---|
| 629 |
else |
|---|
| 630 |
throw new Exception(dirname ~ " is not a directory!"); |
|---|
| 631 |
} |
|---|
| 632 |
else |
|---|
| 633 |
std.file.mkdir(dirname); |
|---|
| 634 |
|
|---|
| 635 |
// parse etc. |
|---|
| 636 |
Log newLog, oldLog; |
|---|
| 637 |
Log getOrParse(char[] id, char[] file) { |
|---|
| 638 |
if(id in logs) |
|---|
| 639 |
return logs[id]; |
|---|
| 640 |
else { |
|---|
| 641 |
Log tmp = new Log(id, file); |
|---|
| 642 |
logs[id] = tmp; |
|---|
| 643 |
return tmp; |
|---|
| 644 |
} |
|---|
| 645 |
} |
|---|
| 646 |
newLog = getOrParse(newid, file1); |
|---|
| 647 |
oldLog = getOrParse(oldid, file2); |
|---|
| 648 |
|
|---|
| 649 |
int nRegressions, nImprovements, nChanges; |
|---|
| 650 |
|
|---|
| 651 |
{ |
|---|
| 652 |
auto regressionsFile = new BufferedFile(std.path.join(dirname, "regressions.html"), FileMode.OutNew); |
|---|
| 653 |
scope(exit) regressionsFile.close(); |
|---|
| 654 |
regressionsFile.writefln(`<html><body>`); |
|---|
| 655 |
|
|---|
| 656 |
auto improvementsFile = new BufferedFile(std.path.join(dirname, "improvements.html"), FileMode.OutNew); |
|---|
| 657 |
scope(exit) improvementsFile.close(); |
|---|
| 658 |
improvementsFile.writefln(`<html><body>`); |
|---|
| 659 |
|
|---|
| 660 |
auto changesFile = new BufferedFile(std.path.join(dirname, "changes.html"), FileMode.OutNew); |
|---|
| 661 |
scope(exit) changesFile.close(); |
|---|
| 662 |
changesFile.writefln(`<html><body>`); |
|---|
| 663 |
|
|---|
| 664 |
BufferedFile targetFile; |
|---|
| 665 |
|
|---|
| 666 |
foreach(file; newLog.tests.keys.sort){ |
|---|
| 667 |
Test* t = file in newLog.tests; |
|---|
| 668 |
Test* oldT = file in oldLog.tests; |
|---|
| 669 |
|
|---|
| 670 |
if(oldT !is null){ |
|---|
| 671 |
if(oldT.r == t.r) |
|---|
| 672 |
continue; |
|---|
| 673 |
else if(t.r >= Result.XPASS && oldT.r && oldT.r <= Result.XFAIL){ |
|---|
| 674 |
targetFile = regressionsFile; |
|---|
| 675 |
nRegressions++; |
|---|
| 676 |
} |
|---|
| 677 |
else if(t.r && t.r <= Result.XFAIL && oldT.r >= Result.XPASS){ |
|---|
| 678 |
targetFile = improvementsFile; |
|---|
| 679 |
nImprovements++; |
|---|
| 680 |
} |
|---|
| 681 |
else { |
|---|
| 682 |
targetFile = changesFile; |
|---|
| 683 |
nChanges++; |
|---|
| 684 |
} |
|---|
| 685 |
targetFile.writefln(toString(oldT.r), " -> ", toString(t.r), " : ", t.name, " in ", t.file, "<br>"); |
|---|
| 686 |
} |
|---|
| 687 |
} |
|---|
| 688 |
|
|---|
| 689 |
regressionsFile.writefln(`</body></html>`); |
|---|
| 690 |
improvementsFile.writefln(`</body></html>`); |
|---|
| 691 |
changesFile.writefln(`</body></html>`); |
|---|
| 692 |
} |
|---|
| 693 |
|
|---|
| 694 |
BufferedFile stats = new BufferedFile(std.path.join(dirname, "stats.base"), FileMode.OutNew); |
|---|
| 695 |
scope(exit) stats.close(); |
|---|
| 696 |
auto dir = oldid ~ "-to-" ~ newid; |
|---|
| 697 |
stats.writefln(`<td class="borderleft"><a href="`, std.path.join(dir, "improvements.html"), `">`, nImprovements, `</a></td>`); |
|---|
| 698 |
stats.writefln(`<td><a href="`, std.path.join(dir, "regressions.html"), `">`, nRegressions, `</a></td>`); |
|---|
| 699 |
stats.writefln(`<td><a href="`, std.path.join(dir, "changes.html"), `">`, nChanges, `</a></td>`); |
|---|
| 700 |
} |
|---|