Wiki Roadmap Timeline Tickets New Ticket Source Search Help / Guide About Trac Login

root/tests/makewebstatistics.d

Revision 1127:3e98925bcc39, 16.3 kB (checked in by Frits van Bommel <fvbommel wxs.nl>, 3 years ago)

Fix makewebstatistics: don't try to drop a test multiple times.

Line 
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 }
Note: See TracBrowser for help on using the browser.
Copyright © 2008, LDC Development Team.