view tests/makewebstatistics.d @ 270:d9d5d59873d8 trunk

[svn r291] Fixed a bunch of the old Phobos tests to work with Tango. Branch statements now emit a new block after it. Fixed the _adSort runtime function had a bad signature. Added a missing dot prefix on compiler generated string tables for string switch. Fixed, PTRSIZE seems like it was wrong on 64bit, now it definitely gets set properly.
author lindquist
date Mon, 16 Jun 2008 16:01:19 +0200
parents 5d2f4814bb2e
children c9d5c711d65a
line wrap: on
line source

// 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;
	}
}


char[] basedir = "web";
bool regenerate = false;

int main(char[][] args){

	if(args.length < 3 || (args[1] == "--regenerate" && args.length < 4)){
		fwritefln(stderr, "%s [--regenerate] <reference-log> <log> <log> ...", args[0]);
		fwritefln(stderr, "bash example: %s reference/dmd-something $(ls reference/llvmdc*)", args[0]);
		return 1;
	}

	char[] reference;
	char[][] files;
	if(args[1] == "--regenerate") {
		regenerate = true;
		reference = args[2];
		files = args[3..$] ~ reference;
	} else {
		reference = args[1];
		files = args[2..$] ~ reference;
	}

	// make sure base path exists
	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;

	// emit per-log data
	foreach(char[] file; files) 
		generateLogStatistics(file, logs);
	
	// differences between logs
	foreach(int i, char[] file; files[1 .. $])
		generateChangeStatistics(files[1+i], files[1+i-1], logs);

	// differences between reference and logs
	foreach(char[] file; files[0..$-1])
		generateChangeStatistics(file, reference, logs);

	// collect all the stats.base files into a large table
	BufferedFile index = new BufferedFile(std.path.join(basedir, "index.html"), FileMode.OutNew);
	scope(exit) index.close();
	index.writefln(`
		<html><body>
		<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;">
			<col style="border-left: medium solid black;">
		</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>
			<th style="padding-left:1em;padding-right:1em;">comparison to ` ~ std.path.getBaseName(reference) ~ `</th>
		</tr>
	`);

	for(int i = files.length - 1; i >= 0; --i) {
		auto file = files[i];
		index.writefln(`<tr>`);
		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 != files.length - 1) {
			index.writefln(`<td>`);
			char[] refid = std.path.getBaseName(reference);
			statsname = std.path.join(std.path.join(basedir, refid ~ "-to-" ~ id), "stats.base");
			index.writef(cast(char[])std.file.read(statsname));
			index.writefln(`</td></tr>`);
		} else {
			index.writefln(`<td></td></tr>`);
		}

		if(i == 0) {
			continue;
		}

		index.writefln(`<tr><td></td>`);
		index.writefln(`<td style="background-color:white;" colspan="5">`);
		char[] newid = std.path.getBaseName(files[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(`</td><td></td></tr>`);
	}

	index.writefln(`</table></body></html>`);
	
	return 0;
}

void generateLogStatistics(char[] file, ref Log[char[]] logs)
{
	char[] id = std.path.getBaseName(file);
	char[] dirname = std.path.join(basedir, id);

	if(std.file.exists(dirname)) {
		if(std.file.isdir(dirname)) {
			if(!regenerate) {
				writefln("Directory ", dirname, " already exists, skipping...");
				return;
			}
		}
		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.OutNew);
		}
		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(file; resultsfile)
			file.writefln(`<html><body>`);
	
		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>");
		}
	
		foreach(file; resultsfile)
			file.writefln(`</body></html>`);
	}

	BufferedFile stats = new BufferedFile(std.path.join(dirname, "stats.base"), FileMode.OutNew);
	scope(exit) stats.close();
	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>`);
}

void generateChangeStatistics(char[] file1, char[] file2, ref Log[char[]] logs)
{
	char[] newid = std.path.getBaseName(file1);
	char[] oldid = std.path.getBaseName(file2);

	char[] dirname = std.path.join(basedir, oldid ~ "-to-" ~ newid);

	if(std.file.exists(dirname)) {
		if(std.file.isdir(dirname)) {
			if(!regenerate) {
				writefln("Directory ", dirname, " already exists, skipping...");
				return;
			}
		}
		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, file1);
	oldLog = getOrParse(oldid, file2);

	int nRegressions, nImprovements, nChanges;

	{
		auto regressionsFile = new BufferedFile(std.path.join(dirname, "regressions.html"), FileMode.OutNew);
		scope(exit) regressionsFile.close();
		regressionsFile.writefln(`<html><body>`);

		auto improvementsFile = new BufferedFile(std.path.join(dirname, "improvements.html"), FileMode.OutNew);
		scope(exit) improvementsFile.close();
		improvementsFile.writefln(`<html><body>`);

		auto changesFile = new BufferedFile(std.path.join(dirname, "changes.html"), FileMode.OutNew);
		scope(exit) changesFile.close();
		changesFile.writefln(`<html><body>`);

		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>");
			}
		}

		regressionsFile.writefln(`</body></html>`);
		improvementsFile.writefln(`</body></html>`);
		changesFile.writefln(`</body></html>`);
	}

	BufferedFile stats = new BufferedFile(std.path.join(dirname, "stats.base"), FileMode.OutNew);
	scope(exit) stats.close();
	auto dir = oldid ~ "-to-" ~ newid;
	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>`);
}