| 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 |
|
|---|
| 13 |
|
|---|
| 14 |
enum Result{ |
|---|
| 15 |
UNTESTED = 0, |
|---|
| 16 |
PASS = 1 << 2, |
|---|
| 17 |
XFAIL = 2 << 2, |
|---|
| 18 |
XPASS = 3 << 2, |
|---|
| 19 |
FAIL = 4 << 2, |
|---|
| 20 |
ERROR = 5 << 2, |
|---|
| 21 |
BASE_MASK = 7 << 2, |
|---|
| 22 |
|
|---|
| 23 |
EXT_MASK = 3, |
|---|
| 24 |
BAD_MSG = 1, |
|---|
| 25 |
BAD_GDB = 2, |
|---|
| 26 |
|
|---|
| 27 |
MAX = BAD_GDB + BASE_MASK |
|---|
| 28 |
} |
|---|
| 29 |
|
|---|
| 30 |
char[] toString(Result r){ |
|---|
| 31 |
switch(r & Result.BASE_MASK){ |
|---|
| 32 |
case Result.PASS: return "PASS"; |
|---|
| 33 |
case Result.XPASS: return "XPASS"; |
|---|
| 34 |
case Result.FAIL: return "FAIL"; |
|---|
| 35 |
case Result.XFAIL: return "XFAIL"; |
|---|
| 36 |
case Result.ERROR: return "ERROR"; |
|---|
| 37 |
case Result.UNTESTED: return "UNTESTED"; |
|---|
| 38 |
default: |
|---|
| 39 |
break; |
|---|
| 40 |
} |
|---|
| 41 |
throw new Exception(format("unhandled Result value %s", cast(int)r)); |
|---|
| 42 |
} |
|---|
| 43 |
|
|---|
| 44 |
char[] dateString(){ |
|---|
| 45 |
static char[] date; |
|---|
| 46 |
if(date is null){ |
|---|
| 47 |
auto time = getUTCtime(); |
|---|
| 48 |
auto year = YearFromTime(time); |
|---|
| 49 |
auto month = MonthFromTime(time); |
|---|
| 50 |
auto day = DateFromTime(time); |
|---|
| 51 |
date = format("%d-%02d-%02d", year, month+1, day); |
|---|
| 52 |
} |
|---|
| 53 |
return date; |
|---|
| 54 |
} |
|---|
| 55 |
|
|---|
| 56 |
char[][] unique(char[][] a){ |
|---|
| 57 |
char[][] b = a.sort; |
|---|
| 58 |
char[][] back; |
|---|
| 59 |
|
|---|
| 60 |
back ~= b[0]; |
|---|
| 61 |
|
|---|
| 62 |
size_t ii=0; |
|---|
| 63 |
for(size_t i=0; i<b.length; i++){ |
|---|
| 64 |
if(back[ii]!=b[i]){ |
|---|
| 65 |
back~=b[i]; |
|---|
| 66 |
ii++; |
|---|
| 67 |
} |
|---|
| 68 |
} |
|---|
| 69 |
|
|---|
| 70 |
return back; |
|---|
| 71 |
} |
|---|
| 72 |
|
|---|
| 73 |
private{ |
|---|
| 74 |
version(Windows){ |
|---|
| 75 |
import std.c.windows.windows; |
|---|
| 76 |
extern(Windows) BOOL GetFileTime(HANDLE hFile, LPFILETIME lpCreationTime, LPFILETIME lpLastAccessTime, LPFILETIME lpLastWriteTime); |
|---|
| 77 |
}else version(linux){ |
|---|
| 78 |
import std.c.linux.linux; |
|---|
| 79 |
version = Unix; |
|---|
| 80 |
}else version(Unix){ |
|---|
| 81 |
import std.c.unix.unix; |
|---|
| 82 |
}else{ |
|---|
| 83 |
static assert(0); |
|---|
| 84 |
} |
|---|
| 85 |
|
|---|
| 86 |
alias ulong FStime; |
|---|
| 87 |
|
|---|
| 88 |
FStime getFStime(char[] fileName){ |
|---|
| 89 |
version(Windows){ |
|---|
| 90 |
HANDLE h; |
|---|
| 91 |
|
|---|
| 92 |
if (useWfuncs){ |
|---|
| 93 |
wchar* namez = std.utf.toUTF16z(fileName); |
|---|
| 94 |
h = CreateFileW(namez,GENERIC_WRITE,0,null,OPEN_ALWAYS, |
|---|
| 95 |
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null); |
|---|
| 96 |
}else{ |
|---|
| 97 |
char* namez = toMBSz(fileName); |
|---|
| 98 |
h = CreateFileA(namez,GENERIC_WRITE,0,null,OPEN_ALWAYS, |
|---|
| 99 |
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,cast(HANDLE)null); |
|---|
| 100 |
} |
|---|
| 101 |
|
|---|
| 102 |
if (h == INVALID_HANDLE_VALUE) |
|---|
| 103 |
goto err; |
|---|
| 104 |
|
|---|
| 105 |
FILETIME creationTime; |
|---|
| 106 |
FILETIME accessTime; |
|---|
| 107 |
FILETIME writeTime; |
|---|
| 108 |
|
|---|
| 109 |
BOOL b = GetFileTime(h, &creationTime, &accessTime, &writeTime); |
|---|
| 110 |
if(b==1){ |
|---|
| 111 |
long modA = writeTime.dwLowDateTime; |
|---|
| 112 |
long modB = writeTime.dwHighDateTime; |
|---|
| 113 |
return modA | (modB << (writeTime.dwHighDateTime.sizeof*8)); |
|---|
| 114 |
} |
|---|
| 115 |
|
|---|
| 116 |
err: |
|---|
| 117 |
CloseHandle(h); |
|---|
| 118 |
throw new Exception("failed to query file modification : "~fileName); |
|---|
| 119 |
}else version(Unix){ |
|---|
| 120 |
char* namez = toStringz(fileName); |
|---|
| 121 |
struct_stat statbuf; |
|---|
| 122 |
|
|---|
| 123 |
if(stat(namez, &statbuf)){ |
|---|
| 124 |
throw new FileException(fileName, getErrno()); |
|---|
| 125 |
} |
|---|
| 126 |
|
|---|
| 127 |
return statbuf.st_mtime; |
|---|
| 128 |
}else{ |
|---|
| 129 |
static assert(0); |
|---|
| 130 |
} |
|---|
| 131 |
} |
|---|
| 132 |
} |
|---|
| 133 |
|
|---|
| 134 |
char[] cleanFileName(char[] file){ |
|---|
| 135 |
char[] back; |
|---|
| 136 |
bool hadSep; |
|---|
| 137 |
|
|---|
| 138 |
foreach(char c; file){ |
|---|
| 139 |
if(c == '/' || c == '\\'){ |
|---|
| 140 |
if(!hadSep){ |
|---|
| 141 |
back ~= '/'; |
|---|
| 142 |
hadSep = true; |
|---|
| 143 |
} |
|---|
| 144 |
}else{ |
|---|
| 145 |
back ~= c; |
|---|
| 146 |
hadSep = false; |
|---|
| 147 |
} |
|---|
| 148 |
} |
|---|
| 149 |
|
|---|
| 150 |
size_t start = 0; |
|---|
| 151 |
while(back[start] <= ' ' && start < back.length){ |
|---|
| 152 |
start++; |
|---|
| 153 |
} |
|---|
| 154 |
|
|---|
| 155 |
size_t end = back.length-1; |
|---|
| 156 |
while(back[end] <= ' ' && end >= start){ |
|---|
| 157 |
end--; |
|---|
| 158 |
} |
|---|
| 159 |
|
|---|
| 160 |
back = back[start .. end+1]; |
|---|
| 161 |
|
|---|
| 162 |
return back; |
|---|
| 163 |
} |
|---|
| 164 |
|
|---|
| 165 |
class Test{ |
|---|
| 166 |
char[] name; |
|---|
| 167 |
char[] file; |
|---|
| 168 |
Result r; |
|---|
| 169 |
|
|---|
| 170 |
this(char[] file){ |
|---|
| 171 |
this.file = file; |
|---|
| 172 |
|
|---|
| 173 |
int start = rfind(file, "/"); |
|---|
| 174 |
if(start<0){ |
|---|
| 175 |
start = 0; |
|---|
| 176 |
}else{ |
|---|
| 177 |
start += 1; |
|---|
| 178 |
} |
|---|
| 179 |
|
|---|
| 180 |
int end = rfind(file, "."); |
|---|
| 181 |
if(end < start){ |
|---|
| 182 |
end = file.length; |
|---|
| 183 |
} |
|---|
| 184 |
|
|---|
| 185 |
name = file[start .. end]; |
|---|
| 186 |
} |
|---|
| 187 |
} |
|---|
| 188 |
|
|---|
| 189 |
|
|---|
| 190 |
class Log{ |
|---|
| 191 |
Test[char[]] tests; |
|---|
| 192 |
|
|---|
| 193 |
char[] id; |
|---|
| 194 |
|
|---|
| 195 |
this(char[] id){ |
|---|
| 196 |
this.id = id; |
|---|
| 197 |
} |
|---|
| 198 |
|
|---|
| 199 |
|
|---|
| 200 |
void dropBogusResults(FStime recordTime, char[] testRoot){ |
|---|
| 201 |
uint totalCount = tests.length; |
|---|
| 202 |
|
|---|
| 203 |
char[][] sourcesTests = tests.keys; |
|---|
| 204 |
foreach(char[] source; sourcesTests){ |
|---|
| 205 |
if(find(source, "complex/") < 0){ |
|---|
| 206 |
try{ |
|---|
| 207 |
FStime caseTime = getFStime(testRoot~std.path.sep~source); |
|---|
| 208 |
if(caseTime > recordTime){ |
|---|
| 209 |
debug(drop) fwritefln(stderr, "dropped: %s", source); |
|---|
| 210 |
tests.remove(source); |
|---|
| 211 |
} |
|---|
| 212 |
}catch(Exception e){ |
|---|
| 213 |
debug(drop) fwritefln(stderr, "dropped: %s", source); |
|---|
| 214 |
tests.remove(source); |
|---|
| 215 |
} |
|---|
| 216 |
} |
|---|
| 217 |
// asm-filter |
|---|
| 218 |
int i = find(source, "asm_p"); |
|---|
| 219 |
if(i >= 0){ |
|---|
| 220 |
tests.remove(source); |
|---|
| 221 |
} |
|---|
| 222 |
} |
|---|
| 223 |
tests.rehash; |
|---|
| 224 |
|
|---|
| 225 |
writefln("dropped %s outdated tests (%s remaining)", totalCount - tests.length, tests.length); |
|---|
| 226 |
} |
|---|
| 227 |
|
|---|
| 228 |
|
|---|
| 229 |
bool add(char[] line){ |
|---|
| 230 |
const char[] SUB = "Torture-Sub-"; |
|---|
| 231 |
const char[] TORTURE = "Torture:"; |
|---|
| 232 |
|
|---|
| 233 |
line = strip(line); |
|---|
| 234 |
int id = -1; |
|---|
| 235 |
Result r = Result.UNTESTED; |
|---|
| 236 |
|
|---|
| 237 |
if(line.length > SUB.length && line[0 .. SUB.length] == SUB){ |
|---|
| 238 |
line = line[SUB.length .. $]; |
|---|
| 239 |
id = 0; |
|---|
| 240 |
while(line[id] >= '0' && line[id] <= '9'){ |
|---|
| 241 |
id++; |
|---|
| 242 |
} |
|---|
| 243 |
int start = id; |
|---|
| 244 |
id = std.conv.toUint(line[0 .. id]); |
|---|
| 245 |
|
|---|
| 246 |
while(line[start] != '-'){ |
|---|
| 247 |
start++; |
|---|
| 248 |
} |
|---|
| 249 |
line = line[start+1 .. $]; |
|---|
| 250 |
} |
|---|
| 251 |
|
|---|
| 252 |
char[][] token = split(line); |
|---|
| 253 |
if(token.length < 2){ |
|---|
| 254 |
return false; |
|---|
| 255 |
} |
|---|
| 256 |
char[] file = strip(token[1]); |
|---|
| 257 |
|
|---|
| 258 |
switch(token[0]){ |
|---|
| 259 |
case "PASS:": |
|---|
| 260 |
r = Result.PASS; break; |
|---|
| 261 |
case "FAIL:": |
|---|
| 262 |
r = Result.FAIL; break; |
|---|
| 263 |
case "XPASS:": |
|---|
| 264 |
r = Result.XPASS; break; |
|---|
| 265 |
case "XFAIL:": |
|---|
| 266 |
r = Result.XFAIL; break; |
|---|
| 267 |
case "ERROR:": |
|---|
| 268 |
r = Result.ERROR; break; |
|---|
| 269 |
default:{ |
|---|
| 270 |
if(token[0] == TORTURE){ |
|---|
| 271 |
throw new Exception("not yet handled: "~line); |
|---|
| 272 |
}else if(id > -1){ |
|---|
| 273 |
throw new Exception(format("bug in SUB line: (%s) %s", id, line)); |
|---|
| 274 |
} |
|---|
| 275 |
} |
|---|
| 276 |
} |
|---|
| 277 |
|
|---|
| 278 |
if(r != Result.UNTESTED){ |
|---|
| 279 |
if(std.string.find(line, "bad error message") > -1){ |
|---|
| 280 |
r |= Result.BAD_MSG; |
|---|
| 281 |
} |
|---|
| 282 |
if(std.string.find(line, "bad debugger message") > -1){ |
|---|
| 283 |
r |= Result.BAD_MSG; |
|---|
| 284 |
} |
|---|
| 285 |
|
|---|
| 286 |
file = cleanFileName(file); |
|---|
| 287 |
|
|---|
| 288 |
if(id >= 0){ |
|---|
| 289 |
// update sub |
|---|
| 290 |
id--; |
|---|
| 291 |
|
|---|
| 292 |
Test* test = file in tests; |
|---|
| 293 |
|
|---|
| 294 |
if(test is null){ |
|---|
| 295 |
Test t = new Test(file); |
|---|
| 296 |
tests[file] = t; |
|---|
| 297 |
t.r = r; |
|---|
| 298 |
}else{ |
|---|
| 299 |
if(test.r != Result.UNTESTED){ |
|---|
| 300 |
test.r = Result.UNTESTED; |
|---|
| 301 |
} |
|---|
| 302 |
test.r = r; |
|---|
| 303 |
} |
|---|
| 304 |
} |
|---|
| 305 |
return true; |
|---|
| 306 |
} |
|---|
| 307 |
return false; |
|---|
| 308 |
} |
|---|
| 309 |
} |
|---|
| 310 |
|
|---|
| 311 |
|
|---|
| 312 |
int main(char[][] args){ |
|---|
| 313 |
|
|---|
| 314 |
if(args.length < 2){ |
|---|
| 315 |
fwritefln(stderr, "%s <old log> <new log>", args[0]); |
|---|
| 316 |
return 1; |
|---|
| 317 |
} |
|---|
| 318 |
|
|---|
| 319 |
|
|---|
| 320 |
Log[] logs; |
|---|
| 321 |
|
|---|
| 322 |
foreach(size_t id, char[] file; args[1 .. $]){ |
|---|
| 323 |
writefln("parsing: %s", file); |
|---|
| 324 |
FStime logTime = getFStime(file); |
|---|
| 325 |
debug fwritefln(stderr, "sourceTime: %s", logTime); |
|---|
| 326 |
|
|---|
| 327 |
Log l= new Log(file); |
|---|
| 328 |
Stream source = new BufferedFile(file, FileMode.In); |
|---|
| 329 |
while(!source.eof()){ |
|---|
| 330 |
l.add(source.readLine()); |
|---|
| 331 |
} |
|---|
| 332 |
|
|---|
| 333 |
l.dropBogusResults(logTime, "dstress"); |
|---|
| 334 |
|
|---|
| 335 |
logs ~= l; |
|---|
| 336 |
} |
|---|
| 337 |
|
|---|
| 338 |
Log oldLog = logs[0]; |
|---|
| 339 |
Log newLog = logs[1]; |
|---|
| 340 |
|
|---|
| 341 |
foreach(Test t; newLog.tests.values){ |
|---|
| 342 |
Test* oldT = t.file in oldLog.tests; |
|---|
| 343 |
|
|---|
| 344 |
if(oldT !is null){ |
|---|
| 345 |
if(oldT.r == t.r) |
|---|
| 346 |
continue; |
|---|
| 347 |
else if(t.r >= Result.XPASS && oldT.r && oldT.r <= Result.XFAIL){ |
|---|
| 348 |
writef("Regression "); |
|---|
| 349 |
} |
|---|
| 350 |
else if(t.r && t.r <= Result.XFAIL && oldT.r >= Result.XPASS){ |
|---|
| 351 |
writef("Improvement "); |
|---|
| 352 |
} |
|---|
| 353 |
else { |
|---|
| 354 |
writef("Change "); |
|---|
| 355 |
} |
|---|
| 356 |
writefln(toString(oldT.r), " -> ", toString(t.r), " : ", t.name, " in ", t.file); |
|---|
| 357 |
} |
|---|
| 358 |
} |
|---|
| 359 |
|
|---|
| 360 |
|
|---|
| 361 |
return 0; |
|---|
| 362 |
} |
|---|