PageRenderTime 30ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/makewebstatistics.d

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