# # # Nim Testament # (c) Copyright 2017 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This program verifies Nim against the testcases. import std/[strutils, pegs, os, osproc, streams, json, parseopt, browsers, terminal, exitprocs, algorithm, times, intsets, macros] import backend, specs, azure, htmlgen from std/sugar import dup import compiler/nodejs import lib/stdtest/testutils from lib/stdtest/specialpaths import splitTestFile from std/private/gitutils import diffStrings import ../dist/checksums/src/checksums/md5 proc trimUnitSep(x: var string) = let L = x.len if L > 0 and x[^1] == '\31': setLen x, L-1 var useColors = true var backendLogging = true var simulate = false var optVerbose = false var useMegatest = true var valgrindEnabled = true proc verboseCmd(cmd: string) = if optVerbose: echo "executing: ", cmd const failString* = "FAIL: " # ensures all failures can be searched with 1 keyword in CI logs testsDir = "tests" & DirSep resultsFile = "testresults.html" Usage = """Usage: testament [options] command [arguments] Command: p|pat|pattern run all the tests matching the given pattern all run all tests in category folders c|cat|category run all the tests of a certain category r|run run single test file html generate $1 from the database Arguments: arguments are passed to the compiler Options: --print print results to the console --verbose print commands (compiling and running tests) --simulate see what tests would be run but don't run them (for debugging) --failing only show failing/ignored tests --targets:"c cpp js objc" run tests for specified targets (default: c) --nim:path use a particular nim executable (default: $$PATH/nim) --directory:dir Change to directory dir before reading the tests or doing anything else. --colors:on|off Turn messages coloring on|off. --backendLogging:on|off Disable or enable backend logging. By default turned on. --megatest:on|off Enable or disable megatest. Default is on. --valgrind:on|off Enable or disable valgrind support. Default is on. --skipFrom:file Read tests to skip from `file` - one test per line, # comments ignored On Azure Pipelines, testament will also publish test results via Azure Pipelines' Test Management API provided that System.AccessToken is made available via the environment variable SYSTEM_ACCESSTOKEN. Experimental: using environment variable `NIM_TESTAMENT_REMOTE_NETWORKING=1` enables tests with remote networking (as in CI). """ % resultsFile proc isNimRepoTests(): bool = # this logic could either be specific to cwd, or to some file derived from # the input file, eg testament r /pathto/tests/foo/tmain.nim; we choose # the former since it's simpler and also works with `testament all`. let file = "testament"/"testament.nim.cfg" result = file.fileExists type Category = distinct string TResults = object total, passed, failedButAllowed, skipped: int ## xxx rename passed to passedOrAllowedFailure data: string TTest = object name: string cat: Category options: string testArgs: seq[string] spec: TSpec startTime: float debugInfo: string # ---------------------------------------------------------------------------- let pegLineError = peg"{[^(]*} '(' {\d+} ', ' {\d+} ') ' ('Error') ':' \s* {.*}" pegOtherError = peg"'Error:' \s* {.*}" pegOfInterest = pegLineError / pegOtherError var gTargets = {low(TTarget)..high(TTarget)} var targetsSet = false proc isSuccess(input: string): bool = # not clear how to do the equivalent of pkg/regex's: re"FOO(.*?)BAR" in pegs # note: this doesn't handle colors, eg: `\e[1m\e[0m\e[32mHint:`; while we # could handle colors, there would be other issues such as handling other flags # that may appear in user config (eg: `--filenames`). # Passing `XDG_CONFIG_HOME= testament args...` can be used to ignore user config # stored in XDG_CONFIG_HOME, refs https://wiki.archlinux.org/index.php/XDG_Base_Directory input.startsWith("Hint: ") and input.endsWith("[SuccessX]") proc getFileDir(filename: string): string = result = filename.splitFile().dir if not result.isAbsolute(): result = getCurrentDir() / result proc execCmdEx2(command: string, args: openArray[string]; workingDir, input: string = ""): tuple[ cmdLine: string, output: string, exitCode: int] {.tags: [ExecIOEffect, ReadIOEffect, RootEffect], gcsafe.} = result.cmdLine.add quoteShell(command) for arg in args: result.cmdLine.add ' ' result.cmdLine.add quoteShell(arg) verboseCmd(result.cmdLine) var p = startProcess(command, workingDir = workingDir, args = args, options = {poStdErrToStdOut, poUsePath}) var outp = outputStream(p) # There is no way to provide input for the child process # anymore. Closing it will create EOF on stdin instead of eternal # blocking. let instream = inputStream(p) instream.write(input) close instream result.exitCode = -1 var line = newStringOfCap(120) while true: if outp.readLine(line): result.output.add line result.output.add '\n' else: result.exitCode = peekExitCode(p) if result.exitCode != -1: break close(p) proc nimcacheDir(filename, options: string, target: TTarget): string = ## Give each test a private nimcache dir so they don't clobber each other's. let hashInput = options & $target result = "nimcache" / (filename & '_' & hashInput.getMD5) proc prepareTestCmd(cmdTemplate, filename, options, nimcache: string, target: TTarget, extraOptions = ""): string = var options = target.defaultOptions & ' ' & options if nimcache.len > 0: options.add(" --nimCache:$#" % nimcache.quoteShell) options.add ' ' & extraOptions # we avoid using `parseCmdLine` which is buggy, refs bug #14343 result = cmdTemplate % ["target", targetToCmd[target], "options", options, "file", filename.quoteShell, "filedir", filename.getFileDir(), "nim", compilerPrefix] proc callNimCompiler(cmdTemplate, filename, options, nimcache: string, target: TTarget, extraOptions = ""): TSpec = result.cmd = prepareTestCmd(cmdTemplate, filename, options, nimcache, target, extraOptions) verboseCmd(result.cmd) var p = startProcess(command = result.cmd, options = {poStdErrToStdOut, poUsePath, poEvalCommand}) let outp = p.outputStream var foundSuccessMsg = false var foundErrorMsg = false var err = "" var x = newStringOfCap(120) result.nimout = "" while true: if outp.readLine(x): trimUnitSep x result.nimout.add(x & '\n') if x =~ pegOfInterest: # `err` should contain the last error message err = x foundErrorMsg = true elif x.isSuccess: foundSuccessMsg = true elif not running(p): break result.msg = "" result.file = "" result.output = "" result.line = 0 result.column = 0 result.err = reNimcCrash result.exitCode = p.peekExitCode close p case result.exitCode of 0: if foundErrorMsg: result.debugInfo.add " compiler exit code was 0 but some Error's were found." else: result.err = reSuccess of 1: if not foundErrorMsg: result.debugInfo.add " compiler exit code was 1 but no Error's were found." if foundSuccessMsg: result.debugInfo.add " compiler exit code was 1 but no `isSuccess` was true." else: result.debugInfo.add " expected compiler exit code 0 or 1, got $1." % $result.exitCode if err =~ pegLineError: result.file = extractFilename(matches[0]) result.line = parseInt(matches[1]) result.column = parseInt(matches[2]) result.msg = matches[3] elif err =~ pegOtherError: result.msg = matches[0] trimUnitSep result.msg proc initResults: TResults = result.total = 0 result.passed = 0 result.failedButAllowed = 0 result.skipped = 0 result.data = "" macro ignoreStyleEcho(args: varargs[typed]): untyped = let typForegroundColor = bindSym"ForegroundColor".getType let typBackgroundColor = bindSym"BackgroundColor".getType let typStyle = bindSym"Style".getType let typTerminalCmd = bindSym"TerminalCmd".getType result = newCall(bindSym"echo") for arg in children(args): if arg.kind == nnkNilLit: continue let typ = arg.getType if typ.kind != nnkEnumTy or typ != typForegroundColor and typ != typBackgroundColor and typ != typStyle and typ != typTerminalCmd: result.add(arg) template maybeStyledEcho(args: varargs[untyped]): untyped = if useColors: styledEcho(args) else: ignoreStyleEcho(args) proc `$`(x: TResults): string = result = """ Tests passed or allowed to fail: $2 / $1
Tests failed and allowed to fail: $3 / $1
Tests skipped: $4 / $1
""" % [$x.total, $x.passed, $x.failedButAllowed, $x.skipped] proc testName(test: TTest, target: TTarget, extraOptions: string, allowFailure: bool): string = var name = test.name.replace(DirSep, '/') name.add ' ' & $target if allowFailure: name.add " (allowed to fail) " if test.options.len > 0: name.add ' ' & test.options if extraOptions.len > 0: name.add ' ' & extraOptions name.strip() proc addResult(r: var TResults, test: TTest, target: TTarget, extraOptions, expected, given: string, successOrig: TResultEnum, allowFailure = false, givenSpec: ptr TSpec = nil) = # instead of `ptr TSpec` we could also use `Option[TSpec]`; passing `givenSpec` makes it easier to get what we need # instead of having to pass individual fields, or abusing existing ones like expected vs given. # test.name is easier to find than test.name.extractFilename # A bit hacky but simple and works with tests/testament/tshould_not_work.nim let name = testName(test, target, extraOptions, allowFailure) let duration = epochTime() - test.startTime let success = if test.spec.timeout > 0.0 and duration > test.spec.timeout: reTimeout else: successOrig let durationStr = duration.formatFloat(ffDecimal, precision = 2).align(5) if backendLogging: backend.writeTestResult(name = name, category = test.cat.string, target = $target, action = $test.spec.action, result = $success, expected = expected, given = given) r.data.addf("$#\t$#\t$#\t$#", name, expected, given, $success) template dispNonSkipped(color, outcome) = maybeStyledEcho color, outcome, fgCyan, test.debugInfo, alignLeft(name, 60), fgBlue, " (", durationStr, " sec)" template disp(msg) = maybeStyledEcho styleDim, fgYellow, msg & ' ', styleBright, fgCyan, name if success == reSuccess: dispNonSkipped(fgGreen, "PASS: ") elif success == reDisabled: if test.spec.inCurrentBatch: disp("SKIP:") else: disp("NOTINBATCH:") elif success == reJoined: disp("JOINED:") else: dispNonSkipped(fgRed, failString) maybeStyledEcho styleBright, fgCyan, "Test \"", test.name, "\"", " in category \"", test.cat.string, "\"" maybeStyledEcho styleBright, fgRed, "Failure: ", $success if givenSpec != nil and givenSpec.debugInfo.len > 0: echo "debugInfo: " & givenSpec.debugInfo if success in {reBuildFailed, reNimcCrash, reInstallFailed}: # expected is empty, no reason to print it. echo given else: maybeStyledEcho fgYellow, "Expected:" maybeStyledEcho styleBright, expected, "\n" maybeStyledEcho fgYellow, "Gotten:" maybeStyledEcho styleBright, given, "\n" echo diffStrings(expected, given).output if backendLogging and (isAppVeyor or isAzure): let (outcome, msg) = case success of reSuccess: ("Passed", "") of reDisabled, reJoined: ("Skipped", "") of reBuildFailed, reNimcCrash, reInstallFailed: ("Failed", "Failure: " & $success & '\n' & given) else: ("Failed", "Failure: " & $success & "\nExpected:\n" & expected & "\n\n" & "Gotten:\n" & given) if isAzure: azure.addTestResult(name, test.cat.string, int(duration * 1000), msg, success) else: 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 toString(inlineError: InlineError, filename: string): string = result.add "$file($line, $col) $kind: $msg" % [ "file", filename, "line", $inlineError.line, "col", $inlineError.col, "kind", $inlineError.kind, "msg", $inlineError.msg ] proc inlineErrorsMsgs(expected: TSpec): string = for inlineError in expected.inlineErrors.items: result.addLine inlineError.toString(expected.filename) proc checkForInlineErrors(expected, given: TSpec): bool = for inlineError in expected.inlineErrors: if inlineError.toString(expected.filename) notin given.nimout: return false true proc nimoutCheck(expected, given: TSpec): bool = result = true if expected.nimoutFull: if expected.nimout != given.nimout: result = false elif expected.nimout.len > 0 and not greedyOrderedSubsetLines(expected.nimout, given.nimout): result = false proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest, target: TTarget, extraOptions: string) = if not checkForInlineErrors(expected, given) or (not expected.nimoutFull and not nimoutCheck(expected, given)): r.addResult(test, target, extraOptions, expected.nimout & inlineErrorsMsgs(expected), given.nimout, reMsgsDiffer) elif strip(expected.msg) notin strip(given.msg): r.addResult(test, target, extraOptions, expected.msg, given.msg, reMsgsDiffer) elif not nimoutCheck(expected, given): r.addResult(test, target, extraOptions, expected.nimout, given.nimout, reMsgsDiffer) elif extractFilename(expected.file) != extractFilename(given.file) and "internal error:" notin expected.msg: r.addResult(test, target, extraOptions, 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, target, extraOptions, $expected.line & ':' & $expected.column, $given.line & ':' & $given.column, reLinesDiffer) else: r.addResult(test, target, extraOptions, expected.msg, given.msg, reSuccess) inc(r.passed) proc generatedFile(test: TTest, target: TTarget): string = if target == targetJS: result = test.name.changeFileExt("js") else: let (_, name, _) = test.name.splitFile let ext = targetToExt[target] result = nimcacheDir(test.name, test.options, target) / "@m" & name.changeFileExt(ext) proc needsCodegenCheck(spec: TSpec): bool = result = spec.maxCodeSize > 0 or spec.ccodeCheck.len > 0 proc codegenCheck(test: TTest, target: TTarget, spec: TSpec, expectedMsg: var string, given: var TSpec) = try: let genFile = generatedFile(test, target) let contents = readFile(genFile) for check in spec.ccodeCheck: if check.len > 0 and check[0] == '\\': # little hack to get 'match' support: if not contents.match(check.peg): given.err = reCodegenFailure elif contents.find(check.peg) < 0: given.err = reCodegenFailure expectedMsg = check if spec.maxCodeSize > 0 and contents.len > spec.maxCodeSize: given.err = reCodegenFailure given.msg = "generated code size: " & $contents.len expectedMsg = "max allowed size: " & $spec.maxCodeSize except ValueError: given.err = reInvalidPeg echo getCurrentExceptionMsg() except IOError: given.err = reCodeNotFound echo getCurrentExceptionMsg() proc compilerOutputTests(test: TTest, target: TTarget, extraOptions: string, given: var TSpec, expected: TSpec; r: var TResults) = var expectedmsg: string = "" var givenmsg: string = "" if given.err == reSuccess: if expected.needsCodegenCheck: codegenCheck(test, target, expected, expectedmsg, given) givenmsg = given.msg if not nimoutCheck(expected, given) or not checkForInlineErrors(expected, given): given.err = reMsgsDiffer expectedmsg = expected.nimout & inlineErrorsMsgs(expected) givenmsg = given.nimout.strip else: givenmsg = "$ " & given.cmd & '\n' & given.nimout if given.err == reSuccess: inc(r.passed) r.addResult(test, target, extraOptions, expectedmsg, givenmsg, given.err) proc getTestSpecTarget(): TTarget = if getEnv("NIM_COMPILE_TO_CPP", "false") == "true": result = targetCpp else: result = targetC var count = 0 proc equalModuloLastNewline(a, b: string): bool = # allow lazy output spec that omits last newline, but really those should be fixed instead result = a == b or b.endsWith("\n") and a == b[0 ..< ^1] proc testSpecHelper(r: var TResults, test: var TTest, expected: TSpec, target: TTarget, extraOptions: string, nimcache: string) = test.startTime = epochTime() if testName(test, target, extraOptions, false) in skips: test.spec.err = reDisabled if test.spec.err in {reDisabled, reJoined}: r.addResult(test, target, extraOptions, "", "", test.spec.err) inc(r.skipped) return var given = callNimCompiler(expected.getCmd, test.name, test.options, nimcache, target, extraOptions) case expected.action of actionCompile: compilerOutputTests(test, target, extraOptions, given, expected, r) of actionRun: if given.err != reSuccess: r.addResult(test, target, extraOptions, "", "$ " & given.cmd & '\n' & given.nimout, given.err, givenSpec = given.addr) else: let isJsTarget = target == targetJS var exeFile = changeFileExt(test.name, if isJsTarget: "js" else: ExeExt) if not fileExists(exeFile): r.addResult(test, target, extraOptions, expected.output, "executable not found: " & exeFile, reExeNotFound) else: let nodejs = if isJsTarget: findNodeJs() else: "" if isJsTarget and nodejs == "": r.addResult(test, target, extraOptions, expected.output, "nodejs binary not in PATH", reExeNotFound) else: var exeCmd: string var args = test.testArgs if isJsTarget: exeCmd = nodejs # see D20210217T215950 args = @["--unhandled-rejections=strict", exeFile] & args else: exeCmd = exeFile.dup(normalizeExe) if valgrindEnabled and expected.useValgrind != disabled: var valgrindOptions = @["--error-exitcode=1"] if expected.useValgrind != leaking: valgrindOptions.add "--leak-check=yes" args = valgrindOptions & exeCmd & args exeCmd = "valgrind" var (_, buf, exitCode) = execCmdEx2(exeCmd, args, input = expected.input) # 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: var buf2 = buf buf2.stripLineEnd var x = splitLines(buf2) sort(x, system.cmp) join(x, "\n") & '\n' else: buf if exitCode != expected.exitCode: given.err = reExitcodesDiffer r.addResult(test, target, extraOptions, "exitcode: " & $expected.exitCode, "exitcode: " & $exitCode & "\n\nOutput:\n" & bufB, reExitcodesDiffer) elif (expected.outputCheck == ocEqual and not expected.output.equalModuloLastNewline(bufB)) or (expected.outputCheck == ocSubstr and expected.output notin bufB): given.err = reOutputsDiffer r.addResult(test, target, extraOptions, expected.output, bufB, reOutputsDiffer) compilerOutputTests(test, target, extraOptions, given, expected, r) of actionReject: # Make sure its the compiler rejecting and not the system (e.g. segfault) cmpMsgs(r, expected, given, test, target, extraOptions) if given.exitCode != QuitFailure: r.addResult(test, target, extraOptions, "exitcode: " & $QuitFailure, "exitcode: " & $given.exitCode & "\n\nOutput:\n" & given.nimout, reExitcodesDiffer) proc changeTarget(extraOptions: string; defaultTarget: TTarget): TTarget = result = defaultTarget var p = parseopt.initOptParser(extraOptions) while true: parseopt.next(p) case p.kind of cmdEnd: break of cmdLongOption, cmdShortOption: if p.key == "b" or p.key == "backend": result = parseEnum[TTarget](p.val.normalize) # chooses the last one else: discard proc targetHelper(r: var TResults, test: TTest, expected: TSpec, extraOptions: string) = for target in expected.targets: inc(r.total) if target notin gTargets: r.addResult(test, target, extraOptions, "", "", reDisabled) inc(r.skipped) elif simulate: inc count echo "testSpec count: ", count, " expected: ", expected else: let nimcache = nimcacheDir(test.name, test.options, target) var testClone = test let target = changeTarget(extraOptions, target) testSpecHelper(r, testClone, expected, target, extraOptions, nimcache) proc testSpec(r: var TResults, test: TTest, targets: set[TTarget] = {}) = var expected = test.spec if expected.parseErrors.len > 0: # targetC is a lie, but a parameter is required r.addResult(test, targetC, "", "", expected.parseErrors, reInvalidSpec) inc(r.total) return expected.targets.incl targets # still no target specified at all if expected.targets == {}: expected.targets = {getTestSpecTarget()} if test.spec.matrix.len > 0: for m in test.spec.matrix: targetHelper(r, test, expected, m) else: targetHelper(r, test, expected, "") proc testSpecWithNimcache(r: var TResults, test: TTest; nimcache: string) {.used.} = for target in test.spec.targets: inc(r.total) var testClone = test testSpecHelper(r, testClone, test.spec, target, "", nimcache) proc makeTest(test, options: string, cat: Category): TTest = result.cat = cat result.name = test result.options = options result.spec = parseSpec(addFileExt(test, ".nim")) result.startTime = epochTime() proc makeRawTest(test, options: string, cat: Category): TTest {.used.} = result.cat = cat result.name = test result.options = options result.spec = initSpec(addFileExt(test, ".nim")) result.spec.action = actionCompile result.spec.targets = {getTestSpecTarget()} result.startTime = epochTime() # TODO: fix these files const disabledFilesDefault = @[ "tableimpl.nim", "setimpl.nim", "hashcommon.nim", # Requires compiling with '--threads:on` "sharedlist.nim", "sharedtables.nim", # Error: undeclared identifier: 'hasThreadSupport' "ioselectors_epoll.nim", "ioselectors_kqueue.nim", "ioselectors_poll.nim", # Error: undeclared identifier: 'Timeval' "ioselectors_select.nim", ] when defined(windows): const # array of modules disabled from compilation test of stdlib. disabledFiles = disabledFilesDefault & @["coro.nim"] else: const # array of modules disabled from compilation test of stdlib. disabledFiles = disabledFilesDefault include categories proc loadSkipFrom(name: string): seq[string] = if name.len == 0: return # One skip per line, comments start with # # used by `nlvm` (at least) for line in lines(name): let sline = line.strip() if sline.len > 0 and not sline.startsWith('#'): result.add sline proc main() = azure.init() backend.open() var optPrintResults = false var optFailing = false var targetsStr = "" var isMainProcess = true var skipFrom = "" var p = initOptParser() p.next() while p.kind in {cmdLongOption, cmdShortOption}: case p.key.normalize of "print": optPrintResults = true of "verbose": optVerbose = true of "failing": optFailing = true of "pedantic": discard # deadcode refs https://github.com/nim-lang/Nim/issues/16731 of "targets": targetsStr = p.val gTargets = parseTargets(targetsStr) targetsSet = true of "nim": compilerPrefix = addFileExt(p.val.absolutePath, ExeExt) of "directory": setCurrentDir(p.val) of "colors": case p.val: of "on": useColors = true of "off": useColors = false else: quit Usage of "batch": testamentData0.batchArg = p.val if p.val != "_" and p.val.len > 0 and p.val[0] in {'0'..'9'}: let s = p.val.split("_") doAssert s.len == 2, $(p.val, s) testamentData0.testamentBatch = s[0].parseInt testamentData0.testamentNumBatch = s[1].parseInt doAssert testamentData0.testamentNumBatch > 0 doAssert testamentData0.testamentBatch >= 0 and testamentData0.testamentBatch < testamentData0.testamentNumBatch of "simulate": simulate = true of "megatest": case p.val: of "on": useMegatest = true of "off": useMegatest = false else: quit Usage of "valgrind": case p.val: of "on": valgrindEnabled = true of "off": valgrindEnabled = false else: quit Usage of "backendlogging": case p.val: of "on": backendLogging = true of "off": backendLogging = false else: quit Usage of "skipfrom": skipFrom = p.val else: quit Usage p.next() if p.kind != cmdArgument: quit Usage var action = p.key.normalize p.next() var r = initResults() case action of "all": #processCategory(r, Category"megatest", p.cmdLineRest, testsDir, runJoinableTests = false) var myself = quoteShell(getAppFilename()) if targetsStr.len > 0: myself &= " " & quoteShell("--targets:" & targetsStr) myself &= " " & quoteShell("--nim:" & compilerPrefix) if testamentData0.batchArg.len > 0: myself &= " --batch:" & testamentData0.batchArg if skipFrom.len > 0: myself &= " " & quoteShell("--skipFrom:" & skipFrom) var cats: seq[string] let rest = if p.cmdLineRest.len > 0: " " & p.cmdLineRest else: "" for kind, dir in walkDir(testsDir): assert testsDir.startsWith(testsDir) let cat = dir[testsDir.len .. ^1] if kind == pcDir and cat notin ["testdata", "nimcache"]: cats.add cat if isNimRepoTests(): cats.add AdditionalCategories if useMegatest: cats.add MegaTestCat var cmds: seq[string] for cat in cats: let runtype = if useMegatest: " pcat " else: " cat " cmds.add(myself & runtype & quoteShell(cat) & rest) proc progressStatus(idx: int) = echo "progress[all]: $1/$2 starting: cat: $3" % [$idx, $cats.len, cats[idx]] if simulate: skips = loadSkipFrom(skipFrom) for i, cati in cats: progressStatus(i) processCategory(r, Category(cati), p.cmdLineRest, testsDir, runJoinableTests = false) else: addExitProc azure.finalize quit osproc.execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams}, beforeRunEvent = progressStatus) of "c", "cat", "category": skips = loadSkipFrom(skipFrom) var cat = Category(p.key) processCategory(r, cat, p.cmdLineRest, testsDir, runJoinableTests = true) of "pcat": skips = loadSkipFrom(skipFrom) # 'pcat' is used for running a category in parallel. Currently the only # difference is that we don't want to run joinable tests here as they # are covered by the 'megatest' category. isMainProcess = false var cat = Category(p.key) p.next processCategory(r, cat, p.cmdLineRest, testsDir, runJoinableTests = false) of "p", "pat", "pattern": skips = loadSkipFrom(skipFrom) let pattern = p.key p.next processPattern(r, pattern, p.cmdLineRest, simulate) of "r", "run": let (cat, path) = splitTestFile(p.key) processSingleTest(r, cat.Category, p.cmdLineRest, path, gTargets, targetsSet) of "html": generateHtml(resultsFile, optFailing) else: quit Usage if optPrintResults: if action == "html": openDefaultBrowser(resultsFile) else: echo r, r.data azure.finalize() backend.close() var failed = r.total - r.passed - r.skipped if failed != 0: echo "FAILURE! total: ", r.total, " passed: ", r.passed, " skipped: ", r.skipped, " failed: ", failed quit(QuitFailure) if isMainProcess: echo "Used ", compilerPrefix, " to run the tests. Use --nim to override." if paramCount() == 0: quit Usage main()