PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/makewebstatistics.d

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