comparison tests/makewebstatistics.d @ 255:4d14a1c84be7 trunk

[svn r272] some rough html statistics for the tests
author ChristianK
date Thu, 12 Jun 2008 18:19:54 +0200
parents
children 26127a48bc09
comparison
equal deleted inserted replaced
254:8187884566fa 255:4d14a1c84be7
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 }
230 }catch(Exception e){
231 debug(drop) fwritefln(stderr, "dropped: %s", source);
232 counts[tests[source].r & Result.BASE_MASK]--;
233 tests.remove(source);
234 }
235 }
236 // asm-filter
237 int i = find(source, "asm_p");
238 if(i >= 0){
239 counts[tests[source].r & Result.BASE_MASK]--;
240 tests.remove(source);
241 }
242 }
243 tests.rehash;
244
245 writefln("dropped %s outdated tests (%s remaining)", totalCount - tests.length, tests.length);
246 }
247
248
249 bool add(char[] line){
250 const char[] SUB = "Torture-Sub-";
251 const char[] TORTURE = "Torture:";
252
253 line = strip(line);
254 int id = -1;
255 Result r = Result.UNTESTED;
256
257 if(line.length > SUB.length && line[0 .. SUB.length] == SUB){
258 line = line[SUB.length .. $];
259 id = 0;
260 while(line[id] >= '0' && line[id] <= '9'){
261 id++;
262 }
263 int start = id;
264 id = std.conv.toUint(line[0 .. id]);
265
266 while(line[start] != '-'){
267 start++;
268 }
269 line = line[start+1 .. $];
270 }
271
272 char[][] token = split(line);
273 if(token.length < 2){
274 return false;
275 }
276 char[] file = strip(token[1]);
277
278 switch(token[0]){
279 case "PASS:":
280 r = Result.PASS; break;
281 case "FAIL:":
282 r = Result.FAIL; break;
283 case "XPASS:":
284 r = Result.XPASS; break;
285 case "XFAIL:":
286 r = Result.XFAIL; break;
287 case "ERROR:":
288 r = Result.ERROR; break;
289 default:{
290 if(token[0] == TORTURE){
291 throw new Exception("not yet handled: "~line);
292 }else if(id > -1){
293 throw new Exception(format("bug in SUB line: (%s) %s", id, line));
294 }
295 }
296 }
297
298 if(r != Result.UNTESTED){
299 if(std.string.find(line, "bad error message") > -1){
300 r |= Result.BAD_MSG;
301 }
302 if(std.string.find(line, "bad debugger message") > -1){
303 r |= Result.BAD_MSG;
304 }
305
306 file = cleanFileName(file);
307
308 if(id >= 0){
309 // update sub
310 id--;
311
312 Test* test = file in tests;
313
314 if(test is null){
315 Test t = new Test(file);
316 tests[file] = t;
317 t.r = r;
318 counts[r & Result.BASE_MASK]++;
319 }else{
320 if(test.r != Result.UNTESTED){
321 test.r = Result.UNTESTED;
322 }
323 test.r = r;
324 }
325 }
326 return true;
327 }
328 return false;
329 }
330 }
331
332
333 int main(char[][] args){
334
335 if(args.length < 2){
336 fwritefln(stderr, "%s <log> <log> ...", args[0]);
337 fwritefln(stderr, "bash example: %s $(ls reference/llvmdc*)", args[0]);
338 return 1;
339 }
340
341 // make sure base path exists
342 char[] basedir = "web";
343 if(std.file.exists(basedir) && !std.file.isdir(basedir))
344 throw new Exception(basedir ~ " is not a directory!");
345 else if(!std.file.exists(basedir))
346 std.file.mkdir(basedir);
347
348
349 Log[char[]] logs;
350
351 // parse log and emit per-log data if necessary
352 foreach(char[] file; args[1 .. $]){
353 char[] id = std.path.getBaseName(file);
354 char[] dirname = std.path.join(basedir, id);
355
356 if(std.file.exists(dirname)) {
357 if(std.file.isdir(dirname)) {
358 writefln("Directory ", dirname, " already exists, skipping...");
359 continue;
360 }
361 else
362 throw new Exception(dirname ~ " is not a directory!");
363 }
364 else
365 std.file.mkdir(dirname);
366
367 // parse etc.
368 Log log = new Log(id, file);
369 logs[id] = log;
370
371 // write status
372 BufferedFile makeFile(char[] name) {
373 return new BufferedFile(std.path.join(dirname, name), FileMode.Out);
374 }
375 BufferedFile[Result] resultsfile = [
376 Result.PASS: makeFile("pass.html"),
377 Result.FAIL: makeFile("fail.html"),
378 Result.XPASS: makeFile("xpass.html"),
379 Result.XFAIL: makeFile("xfail.html"),
380 Result.ERROR: makeFile("error.html") ];
381
382 scope(exit) {
383 foreach(file; resultsfile)
384 file.close();
385 }
386
387
388 foreach(tkey; log.tests.keys.sort) {
389 auto test = log.tests[tkey];
390 auto result = test.r & Result.BASE_MASK;
391 resultsfile[result].writefln(test.name, " in ", test.file, "<br>");
392 }
393
394
395 BufferedFile stats = new BufferedFile(std.path.join(dirname, "stats.base"), FileMode.Out);
396 scope(exit) stats.close();
397 stats.writefln(`<tr>`);
398 stats.writefln(`<td style="padding-right:1em; text-align:left;">`, id, `</td>`);
399 stats.writefln(`<td><a href="`, std.path.join(log.id, "pass.html"), `">`, log.counts[Result.PASS], `</a></td>`);
400 stats.writefln(`<td><a href="`, std.path.join(log.id, "xfail.html"), `">`, log.counts[Result.XFAIL], `</a></td>`);
401 stats.writefln(`<td><a href="`, std.path.join(log.id, "fail.html"), `">`, log.counts[Result.FAIL], `</a></td>`);
402 stats.writefln(`<td><a href="`, std.path.join(log.id, "xpass.html"), `">`, log.counts[Result.XPASS], `</a></td>`);
403 stats.writefln(`<td><a href="`, std.path.join(log.id, "error.html"), `">`, log.counts[Result.ERROR], `</a></td>`);
404 stats.writefln(`</tr>`);
405 }
406
407 // differences between logs
408 foreach(int i, char[] file; args[2 .. $]){
409 char[] newid = std.path.getBaseName(args[2+i]);
410 char[] oldid = std.path.getBaseName(args[2+i-1]);
411
412 char[] dirname = std.path.join(basedir, oldid ~ "-to-" ~ newid);
413
414 if(std.file.exists(dirname)) {
415 if(std.file.isdir(dirname)) {
416 writefln("Directory ", dirname, " already exists, skipping...");
417 continue;
418 }
419 else
420 throw new Exception(dirname ~ " is not a directory!");
421 }
422 else
423 std.file.mkdir(dirname);
424
425 // parse etc.
426 Log newLog, oldLog;
427 Log getOrParse(char[] id, char[] file) {
428 if(id in logs)
429 return logs[id];
430 else {
431 Log tmp = new Log(id, file);
432 logs[id] = tmp;
433 return tmp;
434 }
435 }
436 newLog = getOrParse(newid, args[2+i]);
437 oldLog = getOrParse(oldid, args[2+i-1]);
438
439 int nRegressions, nImprovements, nChanges;
440 auto regressionsFile = new BufferedFile(std.path.join(dirname, "regressions.html"), FileMode.Out);
441 scope(exit) regressionsFile.close();
442 auto improvementsFile = new BufferedFile(std.path.join(dirname, "improvements.html"), FileMode.Out);
443 scope(exit) improvementsFile.close();
444 auto changesFile = new BufferedFile(std.path.join(dirname, "changes.html"), FileMode.Out);
445 scope(exit) changesFile.close();
446 BufferedFile targetFile;
447
448 foreach(Test t; newLog.tests.values){
449 Test* oldT = t.file in oldLog.tests;
450
451 if(oldT !is null){
452 if(oldT.r == t.r)
453 continue;
454 else if(oldT.r < t.r && oldT.r && oldT.r <= Result.XFAIL){
455 targetFile = regressionsFile;
456 nRegressions++;
457 }
458 else if(t.r < oldT.r && t.r && t.r <= Result.XFAIL){
459 targetFile = improvementsFile;
460 nImprovements++;
461 }
462 else {
463 targetFile = changesFile;
464 nChanges++;
465 }
466 targetFile.writefln(toString(oldT.r), " -> ", toString(t.r), " : ", t.name, " in ", t.file, "<br>");
467 }
468 }
469
470 BufferedFile stats = new BufferedFile(std.path.join(dirname, "stats.base"), FileMode.Out);
471 scope(exit) stats.close();
472 auto dir = oldid ~ "-to-" ~ newid;
473 stats.writefln(`<tr><td></td>`);
474 stats.writefln(`<td style="background-color:white;" colspan="5">`);
475 stats.writefln(`<a href="`, std.path.join(dir, "improvements.html"), `">Improvements: `, nImprovements, `</a>, `);
476 stats.writefln(`<a href="`, std.path.join(dir, "regressions.html"), `">Regressions: `, nRegressions, `</a>, `);
477 stats.writefln(`<a href="`, std.path.join(dir, "changes.html"), `">Changes: `, nChanges, `</a></td>`);
478 stats.writefln(`</tr>`);
479 }
480
481 // collect all the stats.base files into a large table
482 BufferedFile index = new BufferedFile(std.path.join(basedir, "index.html"), FileMode.Out);
483 scope(exit) index.close();
484 index.writefln(`
485 <table style="border-collapse:collapse; text-align:center;">
486 <colgroup>
487 <col style="border-right: medium solid black;">
488 <col style="background-color: #AAFFAA;">
489 <col style="background-color: #AAFFAA; border-right: thin solid black;">
490 <col style="background-color: #FFAAAA;">
491 <col style="background-color: #FFAAAA;">
492 <col style="background-color: #FFAAAA;">
493 </colgroup>
494 <tr style="border-bottom: medium solid black;">
495 <th>name</th>
496 <th style="padding-left:1em;padding-right:1em;">PASS</th>
497 <th style="padding-left:1em;padding-right:1em;">XFAIL</th>
498 <th style="padding-left:1em;padding-right:1em;">FAIL</th>
499 <th style="padding-left:1em;padding-right:1em;">XPASS</th>
500 <th style="padding-left:1em;padding-right:1em;">ERROR</th>
501 </tr>
502 `);
503
504 for(int i = args.length - 1; i >= 1; --i) {
505 auto file = args[i];
506 char[] id = std.path.getBaseName(file);
507 char[] statsname = std.path.join(std.path.join(basedir, id), "stats.base");
508 index.writef(cast(char[])std.file.read(statsname));
509
510 if(i == 1)
511 continue;
512
513 char[] newid = std.path.getBaseName(args[i-1]);
514 statsname = std.path.join(std.path.join(basedir, newid ~ "-to-" ~ id), "stats.base");
515 index.writef(cast(char[])std.file.read(statsname));
516 }
517
518 index.writefln(`</table>`);
519
520 return 0;
521 }
522