# # # Nimrod Tester # (c) Copyright 2011 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This program verifies Nimrod against the testcases. import strutils, pegs, os, osproc, streams, parsecsv, browsers const cmdTemplate = r"nimrod cc --hints:on $# $#" resultsFile = "testresults.html" type TMsg = tuple[ file: string, line: int, msg: string, err: bool] TOutp = tuple[file, outp: string] TResults = object total, passed: int data: string proc myExec(cmd: string): string = result = osproc.execProcess(cmd) var pegLineError = peg"{[^(]*} '(' {\d+} ', ' \d+ ') Error:' \s* {.*}" pegOtherError = peg"'Error:' \s* {.*}" pegSuccess = peg"'Hint: operation successful'.*" pegOfInterest = pegLineError / pegOtherError / pegSuccess proc callCompiler(filename, options: string): TMsg = var c = parseCmdLine(cmdTemplate % [options, filename]) var a: seq[string] = @[] # slicing is not yet implemented :-( for i in 1 .. c.len-1: add(a, c[i]) var p = startProcess(command=c[0], args=a, options={poStdErrToStdOut, poUseShell}) var outp = p.outputStream var s = "" while running(p) or not outp.atEnd(outp): var x = outp.readLine() if x =~ pegOfInterest: # `s` should contain the last error message s = x result.msg = "" result.file = "" result.err = true result.line = -1 if s =~ pegLineError: result.file = extractFilename(matches[0]) result.line = parseInt(matches[1]) result.msg = matches[2] elif s =~ pegOtherError: result.msg = matches[0] elif s =~ pegSuccess: result.err = false proc setupCvsParser(csvFile: string): TCsvParser = var s = newFileStream(csvFile, fmRead) if s == nil: quit("cannot open the file" & csvFile) result.open(s, csvFile, separator=';', skipInitialSpace=true) proc parseRejectData(dir: string): seq[TMsg] = var p = setupCvsParser(dir / "spec.csv") result = @[] while readRow(p, 3): result.add((p.row[0], parseInt(p.row[1]), p.row[2], true)) close(p) proc parseRunData(dir: string): seq[TOutp] = var p = setupCvsParser(dir / "spec.csv") result = @[] while readRow(p, 2): result.add((p.row[0], p.row[1])) close(p) proc findSpec[T](specs: seq[T], filename: string): int = while result < specs.len: if specs[result].file == filename: return inc(result) quit("cannot find spec for file: " & filename) proc initResults: TResults = result.total = 0 result.passed = 0 result.data = "" proc `$`(x: TResults): string = result = "Tests passed: " & $x.passed & "/" & $x.total & "
\n" proc colorBool(b: bool): string = if b: result = "yes" else: result = "no" const TableHeader4 = "" & "\n" TableHeader3 = "
TestExpectedGivenSuccess
" & "\n" TableFooter = "
TestGivenSuccess
\n" proc addResult(r: var TResults, test, expected, given: string, success: bool) = r.data.addf("$#$#$#$#\n", [ test, expected, given, success.colorBool]) proc addResult(r: var TResults, test, given: string, success: bool) = r.data.addf("$#$#$#\n", [ test, given, success.colorBool]) proc listResults(reject, compile, run: TResults) = var s = "" s.add("

Tests to Reject

\n") s.add($reject) s.add(TableHeader4 & reject.data & TableFooter) s.add("


Tests to Compile

\n") s.add($compile) s.add(TableHeader3 & compile.data & TableFooter) s.add("


Tests to Run

\n") s.add($run) s.add(TableHeader4 & run.data & TableFooter) s.add("") var outp: TFile if open(outp, resultsFile, fmWrite): write(outp, s) close(outp) proc cmpMsgs(r: var TResults, expected, given: TMsg, test: string) = inc(r.total) if strip(expected.msg) notin strip(given.msg): r.addResult(test, expected.msg, given.msg, false) elif expected.file != given.file: r.addResult(test, expected.file, given.file, false) elif expected.line != given.line: r.addResult(test, $expected.line, $given.line, false) else: r.addResult(test, expected.msg, given.msg, true) inc(r.passed) proc reject(r: var TResults, dir, options: string) = ## handle all the tests that the compiler should reject var specs = parseRejectData(dir) for test in os.walkFiles(dir / "t*.nim"): var t = extractFilename(test) inc(r.total) echo t var expected = findSpec(specs, t) var given = callCompiler(test, options) cmpMsgs(r, specs[expected], given, t) proc compile(r: var TResults, pattern, options: string) = for test in os.walkFiles(pattern): var t = extractFilename(test) inc(r.total) echo t var given = callCompiler(test, options) r.addResult(t, given.msg, not given.err) if not given.err: inc(r.passed) proc run(r: var TResults, dir, options: string) = var specs = parseRunData(dir) for test in os.walkFiles(dir / "t*.nim"): var t = extractFilename(test) inc(r.total) echo t var given = callCompiler(test, options) if given.err: r.addResult(t, "", given.msg, not given.err) else: var exeFile = changeFileExt(test, ExeExt) var expected = specs[findSpec(specs, t)] if existsFile(exeFile): var buf = myExec(exeFile) var success = strip(buf) == strip(expected.outp) if success: inc(r.passed) r.addResult(t, expected.outp, buf, success) else: r.addResult(t, expected.outp, "executable not found", false) var options = "" var rejectRes = initResults() var compileRes = initResults() var runRes = initResults() for i in 1.. paramCount(): add(options, " ") add(options, paramStr(i)) reject(rejectRes, "tests/reject", options) compile(compileRes, "tests/accept/compile/t*.nim", options) compile(compileRes, "examples/*.nim", options) compile(compileRes, "examples/gtk/*.nim", options) run(runRes, "tests/accept/run", options) listResults(rejectRes, compileRes, runRes) openDefaultBrowser(resultsFile)