diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/makewebstatistics.d	Thu Jun 12 18:19:54 2008 +0200
@@ -0,0 +1,522 @@
+// Based on DSTRESS code by Thomas Kühne
+
+module findregressions;
+
+private import std.string;
+private import std.conv;
+private import std.stdio;
+private import std.stream;
+private import std.file;
+private import std.c.stdlib;
+private import std.date;
+private import std.path;
+
+
+enum Result{
+	UNTESTED	= 0,
+	PASS		= 1 << 2,
+	XFAIL		= 2 << 2,
+	XPASS		= 3 << 2,
+	FAIL		= 4 << 2,
+	ERROR		= 5 << 2,
+	BASE_MASK	= 7 << 2,
+
+	EXT_MASK	= 3,
+	BAD_MSG		= 1,
+	BAD_GDB		= 2,
+	
+	MAX		= BAD_GDB + BASE_MASK
+}
+
+char[] toString(Result r){
+	switch(r & Result.BASE_MASK){
+		case Result.PASS: return "PASS";
+		case Result.XPASS: return "XPASS";
+		case Result.FAIL: return "FAIL";
+		case Result.XFAIL: return "XFAIL";
+		case Result.ERROR: return "ERROR";
+		case Result.UNTESTED: return "UNTESTED";
+		default:
+			break;
+	}
+	throw new Exception(format("unhandled Result value %s", cast(int)r));
+}
+
+char[] dateString(){
+	static char[] date;
+	if(date is null){
+		auto time = getUTCtime();
+		auto year = YearFromTime(time);
+		auto month = MonthFromTime(time);
+		auto day = DateFromTime(time);
+		date = format("%d-%02d-%02d", year, month+1, day); 
+	}
+	return date;
+}
+
+char[][] unique(char[][] a){
+	char[][] b = a.sort;
+	char[][] back;
+
+	back ~= b[0];
+
+	size_t ii=0;
+	for(size_t i=0; i<b.length; i++){
+		if(back[ii]!=b[i]){
+			back~=b[i];
+			ii++;
+		}
+	}
+
+	return back;	
+}
+
+private{
+	version(Windows){
+		import std.c.windows.windows;
+		extern(Windows) BOOL GetFileTime(HANDLE hFile, LPFILETIME lpCreationTime, LPFILETIME lpLastAccessTime, LPFILETIME lpLastWriteTime);
+	}else version(linux){
+		import std.c.linux.linux;
+		version = Unix;
+	}else version(Unix){
+		import std.c.unix.unix;
+	}else{
+		static assert(0);
+	}
+
+	alias ulong FStime;
+
+	FStime getFStime(char[] fileName){
+		version(Windows){
+			HANDLE h;
+		
+			if (useWfuncs){
+				wchar* namez = std.utf.toUTF16z(fileName);
+				h = CreateFileW(namez,GENERIC_WRITE,0,null,OPEN_ALWAYS,
+					FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null);
+			}else{
+				char* namez = toMBSz(fileName);
+				h = CreateFileA(namez,GENERIC_WRITE,0,null,OPEN_ALWAYS,
+				FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null);
+			}
+
+			if (h == INVALID_HANDLE_VALUE)
+				goto err;
+
+			FILETIME creationTime;
+			FILETIME accessTime;
+			FILETIME writeTime;
+		
+			BOOL b = GetFileTime(h, &creationTime, &accessTime, &writeTime);
+			if(b==1){
+				long modA = writeTime.dwLowDateTime;
+				long modB = writeTime.dwHighDateTime;
+				return modA  | (modB << (writeTime.dwHighDateTime.sizeof*8));
+			}
+
+err:
+			CloseHandle(h);
+			throw new Exception("failed to query file modification : "~fileName);
+		}else version(Unix){
+			char* namez = toStringz(fileName);
+			struct_stat statbuf;
+		
+			if(stat(namez, &statbuf)){
+				throw new FileException(fileName, getErrno());
+			}
+
+			return statbuf.st_mtime;
+		}else{
+			static assert(0);
+		}
+	}
+}
+
+char[] cleanFileName(char[] file){
+	char[] back;
+	bool hadSep;
+
+	foreach(char c; file){
+		if(c == '/' || c == '\\'){
+			if(!hadSep){
+				back ~= '/';
+				hadSep = true;
+			}
+		}else{
+			back ~= c;
+			hadSep = false;
+		}
+	}
+
+	size_t start = 0;
+	while(back[start] <= ' ' && start < back.length){
+		start++;
+	}
+
+	size_t end = back.length-1;
+	while(back[end] <= ' ' && end >= start){
+		end--;
+	}
+
+	back = back[start .. end+1];
+
+	return back;
+}
+
+class Test{
+	char[] name;
+	char[] file;
+	Result r;
+
+	this(char[] file){
+		this.file = file;
+
+		int start = rfind(file, "/");
+		if(start<0){
+			start = 0;
+		}else{
+			start += 1;
+		}
+		
+		int end = rfind(file, ".");
+		if(end < start){
+			end = file.length;
+		}
+
+		name = file[start .. end];
+	}
+}
+
+
+class Log{
+	Test[char[]] tests;
+
+	char[] id;
+
+	int[Result] counts;
+
+	this(char[] id, char[] file){
+		this.id = id;
+		counts = [
+			Result.PASS: 0,
+			Result.FAIL: 0,
+			Result.XPASS: 0,
+			Result.XFAIL: 0,
+			Result.ERROR: 0 ];
+
+		writefln("parsing: %s", file);
+		FStime logTime  = getFStime(file);
+		Stream source = new BufferedFile(file, FileMode.In);
+		while(!source.eof()){
+			add(source.readLine());
+		}		
+		dropBogusResults(logTime, "dstress");
+	}
+
+
+	void dropBogusResults(FStime recordTime, char[] testRoot){
+		uint totalCount = tests.length; 
+		
+		char[][] sourcesTests = tests.keys;
+		foreach(char[] source; sourcesTests){
+			if(find(source, "complex/") < 0){
+				try{
+					FStime caseTime = getFStime(testRoot~std.path.sep~source);
+					if(caseTime > recordTime){
+						debug(drop) fwritefln(stderr, "dropped: %s", source);
+						counts[tests[source].r & Result.BASE_MASK]--;
+						tests.remove(source);
+					}
+				}catch(Exception e){
+					debug(drop) fwritefln(stderr, "dropped: %s", source);
+					counts[tests[source].r & Result.BASE_MASK]--;
+					tests.remove(source);
+				}
+			}
+			// asm-filter
+			int i = find(source, "asm_p");
+			if(i >= 0){
+				counts[tests[source].r & Result.BASE_MASK]--;
+				tests.remove(source);
+			}
+		}
+		tests.rehash;
+		
+		writefln("dropped %s outdated tests (%s remaining)", totalCount - tests.length, tests.length); 
+	}
+
+	
+	bool add(char[] line){
+		const char[] SUB = "Torture-Sub-";
+		const char[] TORTURE = "Torture:";
+
+		line = strip(line);
+		int id = -1;
+		Result r = Result.UNTESTED;
+
+		if(line.length > SUB.length && line[0 .. SUB.length] == SUB){
+			line = line[SUB.length .. $];
+			id = 0;
+			while(line[id] >= '0' && line[id] <= '9'){
+				id++;
+			}
+			int start = id;
+			id = std.conv.toUint(line[0 .. id]);
+
+			while(line[start] != '-'){
+				start++;
+			}
+			line = line[start+1 .. $];
+		}
+
+		char[][] token = split(line);
+		if(token.length < 2){
+			return false;
+		}
+		char[] file = strip(token[1]);
+
+		switch(token[0]){
+			case "PASS:":
+				r = Result.PASS; break;
+			case "FAIL:":
+				r = Result.FAIL; break;
+			case "XPASS:":
+				r = Result.XPASS; break;
+			case "XFAIL:":
+				r = Result.XFAIL; break;
+			case "ERROR:":
+				r = Result.ERROR; break;
+			default:{
+				if(token[0] == TORTURE){
+					throw new Exception("not yet handled: "~line);
+				}else if(id > -1){
+					throw new Exception(format("bug in SUB line: (%s) %s", id, line));
+				}
+			}
+		}
+
+		if(r != Result.UNTESTED){
+			if(std.string.find(line, "bad error message") > -1){
+				r |= Result.BAD_MSG;	
+			}
+			if(std.string.find(line, "bad debugger message") > -1){
+				r |= Result.BAD_MSG;	
+			}
+			
+			file = cleanFileName(file);
+			
+			if(id >= 0){
+				// update sub
+				id--;
+		
+				Test* test = file in tests;
+
+				if(test is null){
+					Test t = new Test(file);
+					tests[file] = t;
+					t.r = r;
+					counts[r & Result.BASE_MASK]++;
+				}else{
+					if(test.r != Result.UNTESTED){
+						test.r = Result.UNTESTED;
+					}
+					test.r = r;
+				}
+			}
+			return true;
+		}
+		return false;
+	}
+}
+
+
+int main(char[][] args){
+
+	if(args.length < 2){
+		fwritefln(stderr, "%s <log> <log> ...", args[0]);
+		fwritefln(stderr, "bash example: %s $(ls reference/llvmdc*)", args[0]);
+		return 1;
+	}
+
+	// make sure base path exists
+	char[] basedir = "web";
+	if(std.file.exists(basedir) && !std.file.isdir(basedir))
+		throw new Exception(basedir ~ " is not a directory!");
+	else if(!std.file.exists(basedir))
+		std.file.mkdir(basedir);
+	
+	
+	Log[char[]] logs;
+
+	// parse log and emit per-log data if necessary
+	foreach(char[] file; args[1 .. $]){
+		char[] id = std.path.getBaseName(file);
+		char[] dirname = std.path.join(basedir, id);
+
+		if(std.file.exists(dirname)) {
+			if(std.file.isdir(dirname)) {
+				writefln("Directory ", dirname, " already exists, skipping...");
+				continue;
+			}
+			else
+				throw new Exception(dirname ~ " is not a directory!");
+		}
+		else
+			std.file.mkdir(dirname);
+
+		// parse etc.
+		Log log = new Log(id, file);
+		logs[id] = log;
+
+		// write status
+		BufferedFile makeFile(char[] name) {
+			return new BufferedFile(std.path.join(dirname, name), FileMode.Out);
+		}
+		BufferedFile[Result] resultsfile = [
+			Result.PASS: makeFile("pass.html"),
+			Result.FAIL: makeFile("fail.html"),
+			Result.XPASS: makeFile("xpass.html"),
+			Result.XFAIL: makeFile("xfail.html"),
+			Result.ERROR: makeFile("error.html") ];
+
+		scope(exit) {
+			foreach(file; resultsfile)
+				file.close();
+		}
+
+
+		foreach(tkey; log.tests.keys.sort) {
+			auto test = log.tests[tkey];
+			auto result = test.r & Result.BASE_MASK;
+			resultsfile[result].writefln(test.name, " in ", test.file, "<br>");
+		}
+
+
+		BufferedFile stats = new BufferedFile(std.path.join(dirname, "stats.base"), FileMode.Out);
+		scope(exit) stats.close();
+		stats.writefln(`<tr>`);
+		stats.writefln(`<td style="padding-right:1em; text-align:left;">`, id, `</td>`);
+		stats.writefln(`<td><a href="`, std.path.join(log.id, "pass.html"), `">`, log.counts[Result.PASS], `</a></td>`);
+		stats.writefln(`<td><a href="`, std.path.join(log.id, "xfail.html"), `">`, log.counts[Result.XFAIL], `</a></td>`);
+		stats.writefln(`<td><a href="`, std.path.join(log.id, "fail.html"), `">`, log.counts[Result.FAIL], `</a></td>`);
+		stats.writefln(`<td><a href="`, std.path.join(log.id, "xpass.html"), `">`, log.counts[Result.XPASS], `</a></td>`);
+		stats.writefln(`<td><a href="`, std.path.join(log.id, "error.html"), `">`, log.counts[Result.ERROR], `</a></td>`);
+		stats.writefln(`</tr>`);
+	}
+	
+	// differences between logs
+	foreach(int i, char[] file; args[2 .. $]){
+		char[] newid = std.path.getBaseName(args[2+i]);
+		char[] oldid = std.path.getBaseName(args[2+i-1]);
+
+		char[] dirname = std.path.join(basedir, oldid ~ "-to-" ~ newid);
+
+		if(std.file.exists(dirname)) {
+			if(std.file.isdir(dirname)) {
+				writefln("Directory ", dirname, " already exists, skipping...");
+				continue;
+			}
+			else
+				throw new Exception(dirname ~ " is not a directory!");
+		}
+		else
+			std.file.mkdir(dirname);
+
+		// parse etc.
+		Log newLog, oldLog;
+		Log getOrParse(char[] id, char[] file) {		
+			if(id in logs)
+				return logs[id];
+			else {
+				Log tmp = new Log(id, file);
+				logs[id] = tmp;
+				return tmp;
+			}
+		}
+		newLog = getOrParse(newid, args[2+i]);
+		oldLog = getOrParse(oldid, args[2+i-1]);
+
+		int nRegressions, nImprovements, nChanges;
+		auto regressionsFile = new BufferedFile(std.path.join(dirname, "regressions.html"), FileMode.Out);
+		scope(exit) regressionsFile.close();
+		auto improvementsFile = new BufferedFile(std.path.join(dirname, "improvements.html"), FileMode.Out);
+		scope(exit) improvementsFile.close();
+		auto changesFile = new BufferedFile(std.path.join(dirname, "changes.html"), FileMode.Out);
+		scope(exit) changesFile.close();
+		BufferedFile targetFile;
+
+		foreach(Test t; newLog.tests.values){
+			Test* oldT = t.file in oldLog.tests;
+	
+			if(oldT !is null){
+				if(oldT.r == t.r)
+					continue;
+				else if(oldT.r < t.r && oldT.r && oldT.r <= Result.XFAIL){
+					targetFile = regressionsFile;
+					nRegressions++;
+				}
+				else if(t.r < oldT.r && t.r && t.r <= Result.XFAIL){
+					targetFile = improvementsFile;
+					nImprovements++;
+				}
+				else {
+					targetFile = changesFile;
+					nChanges++;
+				}
+				targetFile.writefln(toString(oldT.r), " -> ", toString(t.r), " : ", t.name, " in ", t.file, "<br>");
+			}
+		}		
+
+		BufferedFile stats = new BufferedFile(std.path.join(dirname, "stats.base"), FileMode.Out);
+		scope(exit) stats.close();
+		auto dir = oldid ~ "-to-" ~ newid;
+		stats.writefln(`<tr><td></td>`);
+		stats.writefln(`<td style="background-color:white;" colspan="5">`);
+		stats.writefln(`<a href="`, std.path.join(dir, "improvements.html"), `">Improvements: `, nImprovements, `</a>, `);
+		stats.writefln(`<a href="`, std.path.join(dir, "regressions.html"), `">Regressions: `, nRegressions, `</a>, `);
+		stats.writefln(`<a href="`, std.path.join(dir, "changes.html"), `">Changes: `, nChanges, `</a></td>`);
+		stats.writefln(`</tr>`);
+	}
+
+	// collect all the stats.base files into a large table
+	BufferedFile index = new BufferedFile(std.path.join(basedir, "index.html"), FileMode.Out);
+	scope(exit) index.close();
+	index.writefln(`
+		<table style="border-collapse:collapse; text-align:center;">
+		<colgroup>
+			<col style="border-right: medium solid black;">
+			<col style="background-color: #AAFFAA;">
+			<col style="background-color: #AAFFAA; border-right: thin solid black;">
+			<col style="background-color: #FFAAAA;">
+			<col style="background-color: #FFAAAA;">
+			<col style="background-color: #FFAAAA;">
+		</colgroup>
+		<tr style="border-bottom: medium solid black;">
+			<th>name</th>
+			<th style="padding-left:1em;padding-right:1em;">PASS</th>
+			<th style="padding-left:1em;padding-right:1em;">XFAIL</th>
+			<th style="padding-left:1em;padding-right:1em;">FAIL</th>
+			<th style="padding-left:1em;padding-right:1em;">XPASS</th>
+			<th style="padding-left:1em;padding-right:1em;">ERROR</th>
+		</tr>
+	`);
+
+	for(int i = args.length - 1; i >= 1; --i) {
+		auto file = args[i];
+		char[] id = std.path.getBaseName(file);
+		char[] statsname = std.path.join(std.path.join(basedir, id), "stats.base");
+		index.writef(cast(char[])std.file.read(statsname));
+
+		if(i == 1) 
+			continue;
+
+		char[] newid = std.path.getBaseName(args[i-1]);
+		statsname = std.path.join(std.path.join(basedir, newid ~ "-to-" ~ id), "stats.base");
+		index.writef(cast(char[])std.file.read(statsname));
+	}
+
+	index.writefln(`</table>`);
+	
+	return 0;
+}
+