summary refs log tree commit diff stats
path: root/tests/testament/tester.nim
diff options
context:
space:
mode:
Diffstat (limited to 'tests/testament/tester.nim')
-rw-r--r--tests/testament/tester.nim285
1 files changed, 144 insertions, 141 deletions
diff --git a/tests/testament/tester.nim b/tests/testament/tester.nim
index e4bbc3a00..870f9f865 100644
--- a/tests/testament/tester.nim
+++ b/tests/testament/tester.nim
@@ -1,7 +1,7 @@
 #
 #
 #            Nim Tester
-#        (c) Copyright 2015 Andreas Rumpf
+#        (c) Copyright 2017 Andreas Rumpf
 #
 #    See the file "copying.txt", included in this
 #    distribution, for details about the copyright.
@@ -12,7 +12,7 @@
 import
   parseutils, strutils, pegs, os, osproc, streams, parsecfg, json,
   marshal, backend, parseopt, specs, htmlgen, browsers, terminal,
-  algorithm, compiler/nodejs, re, times, sets
+  algorithm, compiler/nodejs, times, sets, md5
 
 const
   resultsFile = "testresults.html"
@@ -24,15 +24,12 @@ Command:
   all                         run all tests
   c|category <category>       run all the tests of a certain category
   r|run <test>                run single test file
-  html [commit]               generate $1 from the database; uses the latest
-                              commit or a specific one (use -1 for the commit
-                              before latest etc)
+  html                        generate $1 from the database
 Arguments:
   arguments are passed to the compiler
 Options:
   --print                   also print results to the console
   --failing                 only show failing/ignored tests
-  --pedantic                return non-zero status code if there are failures
   --targets:"c c++ js objc" run tests for specified targets (default: all)
   --nim:path                use a particular nim executable (default: compiler/nim)
 """ % resultsFile
@@ -47,7 +44,6 @@ type
     name: string
     cat: Category
     options: string
-    target: TTarget
     action: TTestAction
     startTime: float
 
@@ -75,8 +71,14 @@ proc getFileDir(filename: string): string =
   if not result.isAbsolute():
     result = getCurrentDir() / result
 
+proc nimcacheDir(filename, options: string): string =
+  ## Give each test a private nimcache dir so they don't clobber each other's.
+  return "nimcache" / (filename & '_' & options.getMD5)
+
 proc callCompiler(cmdTemplate, filename, options: string,
-                  target: TTarget): TSpec =
+                  target: TTarget, extraOptions=""): TSpec =
+  let nimcache = nimcacheDir(filename, options)
+  let options = options & " --nimCache:" & nimcache.quoteShell & extraOptions
   let c = parseCmdLine(cmdTemplate % ["target", targetToCmd[target],
                        "options", options, "file", filename.quoteShell,
                        "filedir", filename.getFileDir()])
@@ -158,20 +160,21 @@ proc `$`(x: TResults): string =
             "Tests skipped: $2 / $3 <br />\n") %
             [$x.passed, $x.skipped, $x.total]
 
-proc addResult(r: var TResults, test: TTest,
+proc addResult(r: var TResults, test: TTest, target: TTarget,
                expected, given: string, success: TResultEnum) =
-  let name = test.name.extractFilename & test.options
+  let name = test.name.extractFilename & " " & $target & test.options
   let duration = epochTime() - test.startTime
+  let durationStr = duration.formatFloat(ffDecimal, precision = 8)
   backend.writeTestResult(name = name,
                           category = test.cat.string,
-                          target = $test.target,
+                          target = $target,
                           action = $test.action,
                           result = $success,
                           expected = expected,
                           given = given)
   r.data.addf("$#\t$#\t$#\t$#", name, expected, given, $success)
   if success == reSuccess:
-    styledEcho fgGreen, "PASS: ", fgCyan, name
+    styledEcho fgGreen, "PASS: ", fgCyan, alignLeft(name, 60), fgBlue, " (", durationStr, " secs)"
   elif success == reIgnored:
     styledEcho styleDim, fgYellow, "SKIP: ", styleBright, fgCyan, name
   else:
@@ -191,49 +194,54 @@ proc addResult(r: var TResults, test: TTest,
         ("Skipped", "")
       else:
         ("Failed", "Failure: " & $success & "\nExpected:\n" & expected & "\n\n" & "Gotten:\n" & given)
-    var p = startProcess("appveyor", args=["AddTest", test.name.replace("\\", "/") & test.options, "-Framework", "nim-testament", "-FileName", test.cat.string, "-Outcome", outcome, "-ErrorMessage", msg, "-Duration", $(duration*1000).int], options={poStdErrToStdOut, poUsePath, poParentStreams})
+    var p = startProcess("appveyor", args=["AddTest", test.name.replace("\\", "/") & test.options,
+                         "-Framework", "nim-testament", "-FileName",
+                         test.cat.string,
+                         "-Outcome", outcome, "-ErrorMessage", msg,
+                         "-Duration", $(duration*1000).int],
+                         options={poStdErrToStdOut, poUsePath, poParentStreams})
     discard waitForExit(p)
     close(p)
 
-proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest) =
+proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest, target: TTarget) =
   if strip(expected.msg) notin strip(given.msg):
-    r.addResult(test, expected.msg, given.msg, reMsgsDiffer)
+    r.addResult(test, target, expected.msg, given.msg, reMsgsDiffer)
   elif expected.nimout.len > 0 and expected.nimout.normalizeMsg notin given.nimout.normalizeMsg:
-    r.addResult(test, expected.nimout, given.nimout, reMsgsDiffer)
+    r.addResult(test, target, expected.nimout, given.nimout, reMsgsDiffer)
   elif expected.tfile == "" and extractFilename(expected.file) != extractFilename(given.file) and
       "internal error:" notin expected.msg:
-    r.addResult(test, expected.file, given.file, reFilesDiffer)
+    r.addResult(test, target, expected.file, given.file, reFilesDiffer)
   elif expected.line   != given.line   and expected.line   != 0 or
        expected.column != given.column and expected.column != 0:
-    r.addResult(test, $expected.line & ':' & $expected.column,
+    r.addResult(test, target, $expected.line & ':' & $expected.column,
                       $given.line    & ':' & $given.column,
                       reLinesDiffer)
   elif expected.tfile != "" and extractFilename(expected.tfile) != extractFilename(given.tfile) and
       "internal error:" notin expected.msg:
-    r.addResult(test, expected.tfile, given.tfile, reFilesDiffer)
+    r.addResult(test, target, expected.tfile, given.tfile, reFilesDiffer)
   elif expected.tline   != given.tline   and expected.tline   != 0 or
        expected.tcolumn != given.tcolumn and expected.tcolumn != 0:
-    r.addResult(test, $expected.tline & ':' & $expected.tcolumn,
+    r.addResult(test, target, $expected.tline & ':' & $expected.tcolumn,
                       $given.tline    & ':' & $given.tcolumn,
                       reLinesDiffer)
   else:
-    r.addResult(test, expected.msg, given.msg, reSuccess)
+    r.addResult(test, target, expected.msg, given.msg, reSuccess)
     inc(r.passed)
 
-proc generatedFile(path, name: string, target: TTarget): string =
+proc generatedFile(test: TTest, target: TTarget): string =
+  let (_, name, _) = test.name.splitFile
   let ext = targetToExt[target]
-  result = path / "nimcache" /
+  result = nimcacheDir(test.name, test.options) /
     (if target == targetJS: "" else: "compiler_") &
     name.changeFileExt(ext)
 
 proc needsCodegenCheck(spec: TSpec): bool =
   result = spec.maxCodeSize > 0 or spec.ccodeCheck.len > 0
 
-proc codegenCheck(test: TTest, spec: TSpec, expectedMsg: var string,
+proc codegenCheck(test: TTest, target: TTarget, spec: TSpec, expectedMsg: var string,
                   given: var TSpec) =
   try:
-    let (path, name, _) = test.name.splitFile
-    let genFile = generatedFile(path, name, test.target)
+    let genFile = generatedFile(test, target)
     let contents = readFile(genFile).string
     let check = spec.ccodeCheck
     if check.len > 0:
@@ -265,13 +273,13 @@ proc makeDeterministic(s: string): string =
   sort(x, system.cmp)
   result = join(x, "\n")
 
-proc compilerOutputTests(test: TTest, given: var TSpec, expected: TSpec;
-                         r: var TResults) =
+proc compilerOutputTests(test: TTest, target: TTarget, given: var TSpec,
+                         expected: TSpec; r: var TResults) =
   var expectedmsg: string = ""
   var givenmsg: string = ""
   if given.err == reSuccess:
     if expected.needsCodegenCheck:
-      codegenCheck(test, expected, expectedmsg, given)
+      codegenCheck(test, target, expected, expectedmsg, given)
       givenmsg = given.msg
     if expected.nimout.len > 0:
       expectedmsg = expected.nimout
@@ -280,29 +288,9 @@ proc compilerOutputTests(test: TTest, given: var TSpec, expected: TSpec;
   else:
     givenmsg = given.nimout.strip
   if given.err == reSuccess: inc(r.passed)
-  r.addResult(test, expectedmsg, givenmsg, given.err)
-
-proc analyzeAndConsolidateOutput(s: string): string =
-  result = ""
-  let rows = s.splitLines
-  for i in 0 ..< rows.len:
-    if (let pos = find(rows[i], "Traceback (most recent call last)"); pos != -1):
-      result = substr(rows[i], pos) & "\n"
-      for i in i+1 ..< rows.len:
-        result.add rows[i] & "\n"
-        if not (rows[i] =~ re"^[^(]+\(\d+\)\s+"):
-          return
-    elif (let pos = find(rows[i], "SIGSEGV: Illegal storage access."); pos != -1):
-      result = substr(rows[i], pos)
-      return
-
-proc testSpec(r: var TResults, test: TTest) =
-  # major entry point for a single test
-  if test.target notin targets:
-    r.addResult(test, "", "", reIgnored)
-    inc(r.skipped)
-    return
+  r.addResult(test, target, expectedmsg, givenmsg, given.err)
 
+proc testSpec(r: var TResults, test: TTest, target = targetC) =
   let tname = test.name.addFileExt(".nim")
   #echo "TESTING ", tname
   inc(r.total)
@@ -314,83 +302,90 @@ proc testSpec(r: var TResults, test: TTest) =
     expected.action = actionRunNoSpec
 
   if expected.err == reIgnored:
-    r.addResult(test, "", "", reIgnored)
+    r.addResult(test, target, "", "", reIgnored)
     inc(r.skipped)
     return
 
-  case expected.action
-  of actionCompile:
-    var given = callCompiler(expected.cmd, test.name,
-      test.options & " --stdout --hint[Path]:off --hint[Processing]:off",
-      test.target)
-    compilerOutputTests(test, given, expected, r)
-  of actionRun, actionRunNoSpec:
-    # In this branch of code "early return" pattern is clearer than deep
-    # nested conditionals - the empty rows in between to clarify the "danger"
-    var given = callCompiler(expected.cmd, test.name, test.options,
-                             test.target)
-
-    if given.err != reSuccess:
-      r.addResult(test, "", given.msg, given.err)
-      return
-
-    let isJsTarget = test.target == targetJS
-    var exeFile: string
-    if isJsTarget:
-      let (dir, file, _) = splitFile(tname)
-      exeFile = dir / "nimcache" / file & ".js" # *TODO* hardcoded "nimcache"
-    else:
-      exeFile = changeFileExt(tname, ExeExt)
-
-    if not existsFile(exeFile):
-      r.addResult(test, expected.outp, "executable not found", reExeNotFound)
-      return
-
-    let nodejs = if isJsTarget: findNodeJs() else: ""
-    if isJsTarget and nodejs == "":
-      r.addResult(test, expected.outp, "nodejs binary not in PATH",
-                  reExeNotFound)
-      return
-
-    let exeCmd = (if isJsTarget: nodejs & " " else: "") & exeFile
-    var (buf, exitCode) = execCmdEx(exeCmd, options = {poStdErrToStdOut})
-
-    # Treat all failure codes from nodejs as 1. Older versions of nodejs used
-    # to return other codes, but for us it is sufficient to know that it's not 0.
-    if exitCode != 0: exitCode = 1
-
-    let bufB = if expected.sortoutput: makeDeterministic(strip(buf.string))
-               else: strip(buf.string)
-    let expectedOut = strip(expected.outp)
-
-    if exitCode != expected.exitCode:
-      r.addResult(test, "exitcode: " & $expected.exitCode,
-                        "exitcode: " & $exitCode & "\n\nOutput:\n" &
-                        analyzeAndConsolidateOutput(bufB),
-                        reExitCodesDiffer)
-      return
-
-    if bufB != expectedOut and expected.action != actionRunNoSpec:
-      if not (expected.substr and expectedOut in bufB):
-        given.err = reOutputsDiffer
-        r.addResult(test, expected.outp, bufB, reOutputsDiffer)
-        return
-
-    compilerOutputTests(test, given, expected, r)
-    return
-
-  of actionReject:
-    var given = callCompiler(expected.cmd, test.name, test.options,
-                             test.target)
-    cmpMsgs(r, expected, given, test)
-    return
-
-proc testNoSpec(r: var TResults, test: TTest) =
+  if expected.targets == {}:
+    expected.targets.incl(target)
+
+  for target in expected.targets:
+    if target notin targets:
+      r.addResult(test, target, "", "", reIgnored)
+      inc(r.skipped)
+      continue
+
+    case expected.action
+    of actionCompile:
+      var given = callCompiler(expected.cmd, test.name, test.options, target,
+        extraOptions=" --stdout --hint[Path]:off --hint[Processing]:off")
+      compilerOutputTests(test, target, given, expected, r)
+    of actionRun, actionRunNoSpec:
+      # In this branch of code "early return" pattern is clearer than deep
+      # nested conditionals - the empty rows in between to clarify the "danger"
+      var given = callCompiler(expected.cmd, test.name, test.options,
+                               target)
+
+      if given.err != reSuccess:
+        r.addResult(test, target, "", given.msg, given.err)
+        continue
+
+      let isJsTarget = target == targetJS
+      var exeFile: string
+      if isJsTarget:
+        let (_, file, _) = splitFile(tname)
+        exeFile = nimcacheDir(test.name, test.options) / file & ".js"
+      else:
+        exeFile = changeFileExt(tname, ExeExt)
+
+      if not existsFile(exeFile):
+        r.addResult(test, target, expected.outp, "executable not found", reExeNotFound)
+        continue
+
+      let nodejs = if isJsTarget: findNodeJs() else: ""
+      if isJsTarget and nodejs == "":
+        r.addResult(test, target, expected.outp, "nodejs binary not in PATH",
+                    reExeNotFound)
+        continue
+
+      let exeCmd = (if isJsTarget: nodejs & " " else: "") & exeFile
+      var (buf, exitCode) = execCmdEx(exeCmd, options = {poStdErrToStdOut})
+
+      # Treat all failure codes from nodejs as 1. Older versions of nodejs used
+      # to return other codes, but for us it is sufficient to know that it's not 0.
+      if exitCode != 0: exitCode = 1
+
+      let bufB = if expected.sortoutput: makeDeterministic(strip(buf.string))
+                 else: strip(buf.string)
+      let expectedOut = strip(expected.outp)
+
+      if exitCode != expected.exitCode:
+        r.addResult(test, target, "exitcode: " & $expected.exitCode,
+                          "exitcode: " & $exitCode & "\n\nOutput:\n" &
+                          bufB, reExitCodesDiffer)
+        continue
+
+      if bufB != expectedOut and expected.action != actionRunNoSpec:
+        if not (expected.substr and expectedOut in bufB):
+          given.err = reOutputsDiffer
+          r.addResult(test, target, expected.outp, bufB, reOutputsDiffer)
+          continue
+
+      compilerOutputTests(test, target, given, expected, r)
+      continue
+
+    of actionReject:
+      var given = callCompiler(expected.cmd, test.name, test.options,
+                               target)
+      cmpMsgs(r, expected, given, test, target)
+      continue
+
+proc testNoSpec(r: var TResults, test: TTest, target = targetC) =
   # does not extract the spec because the file is not supposed to have any
   #let tname = test.name.addFileExt(".nim")
   inc(r.total)
-  let given = callCompiler(cmdTemplate(), test.name, test.options, test.target)
-  r.addResult(test, "", given.msg, given.err)
+  let given = callCompiler(cmdTemplate(), test.name, test.options, target)
+  r.addResult(test, target, "", given.msg, given.err)
   if given.err == reSuccess: inc(r.passed)
 
 proc testC(r: var TResults, test: TTest) =
@@ -398,9 +393,9 @@ proc testC(r: var TResults, test: TTest) =
   let tname = test.name.addFileExt(".c")
   inc(r.total)
   styledEcho "Processing ", fgCyan, extractFilename(tname)
-  var given = callCCompiler(cmdTemplate(), test.name & ".c", test.options, test.target)
+  var given = callCCompiler(cmdTemplate(), test.name & ".c", test.options, targetC)
   if given.err != reSuccess:
-    r.addResult(test, "", given.msg, given.err)
+    r.addResult(test, targetC, "", given.msg, given.err)
   elif test.action == actionRun:
     let exeFile = changeFileExt(test.name, ExeExt)
     var (_, exitCode) = execCmdEx(exeFile, options = {poStdErrToStdOut, poUsePath})
@@ -408,10 +403,10 @@ proc testC(r: var TResults, test: TTest) =
   if given.err == reSuccess: inc(r.passed)
 
 proc makeTest(test, options: string, cat: Category, action = actionCompile,
-              target = targetC, env: string = ""): TTest =
+              env: string = ""): TTest =
   # start with 'actionCompile', will be overwritten in the spec:
   result = TTest(cat: cat, name: test, options: options,
-                 target: target, action: action, startTime: epochTime())
+                 action: action, startTime: epochTime())
 
 when defined(windows):
   const
@@ -436,7 +431,8 @@ proc main() =
   backend.open()
   var optPrintResults = false
   var optFailing = false
-  var optPedantic = false
+
+  var targetsStr = ""
 
   var p = initOptParser()
   p.next()
@@ -444,8 +440,10 @@ proc main() =
     case p.key.string.normalize
     of "print", "verbose": optPrintResults = true
     of "failing": optFailing = true
-    of "pedantic": optPedantic = true
-    of "targets": targets = parseTargets(p.val.string)
+    of "pedantic": discard "now always enabled"
+    of "targets":
+      targetsStr = p.val.string
+      targets = parseTargets(targetsStr)
     of "nim": compilerPrefix = p.val.string
     else: quit Usage
     p.next()
@@ -456,13 +454,22 @@ proc main() =
   case action
   of "all":
     let testsDir = "tests" & DirSep
+    var myself = quoteShell(findExe("tests" / "testament" / "tester"))
+    if targetsStr.len > 0:
+      myself &= " " & quoteShell("--targets:" & targetsStr)
+
+    myself &= " " & quoteShell("--nim:" & compilerPrefix)
+
+    var cmds: seq[string] = @[]
+    let rest = if p.cmdLineRest.string.len > 0: " " & p.cmdLineRest.string else: ""
     for kind, dir in walkDir(testsDir):
       assert testsDir.startsWith(testsDir)
       let cat = dir[testsDir.len .. ^1]
       if kind == pcDir and cat notin ["testament", "testdata", "nimcache"]:
-        processCategory(r, Category(cat), p.cmdLineRest.string)
-    for a in AdditionalCategories:
-      processCategory(r, Category(a), p.cmdLineRest.string)
+        cmds.add(myself & " cat " & cat & rest)
+    for cat in AdditionalCategories:
+      cmds.add(myself & " cat " & cat & rest)
+    quit osproc.execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams})
   of "c", "cat", "category":
     var cat = Category(p.key)
     p.next
@@ -473,10 +480,7 @@ proc main() =
     var cat = Category(subdir)
     processSingleTest(r, cat, p.cmdLineRest.string, file)
   of "html":
-    var commit = 0
-    discard parseInt(p.cmdLineRest.string, commit)
-    generateHtml(resultsFile, commit, optFailing)
-    generateJson(jsonFile, commit)
+    generateHtml(resultsFile, optFailing)
   else:
     quit Usage
 
@@ -484,11 +488,10 @@ proc main() =
     if action == "html": openDefaultBrowser(resultsFile)
     else: echo r, r.data
   backend.close()
-  if optPedantic:
-    var failed = r.total - r.passed - r.skipped
-    if failed > 0:
-      echo "FAILURE! total: ", r.total, " passed: ", r.passed, " skipped: ", r.skipped
-      quit(QuitFailure)
+  var failed = r.total - r.passed - r.skipped
+  if failed > 0:
+    echo "FAILURE! total: ", r.total, " passed: ", r.passed, " skipped: ", r.skipped
+    quit(QuitFailure)
 
 if paramCount() == 0:
   quit Usage