tester.nim 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. #
  2. #
  3. # Nim Tester
  4. # (c) Copyright 2017 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This program verifies Nim against the testcases.
  10. import
  11. parseutils, strutils, pegs, os, osproc, streams, parsecfg, json,
  12. marshal, backend, parseopt, specs, htmlgen, browsers, terminal,
  13. algorithm, compiler/nodejs, times, sets, md5
  14. var useColors = true
  15. var backendLogging = true
  16. var simulate = false
  17. const
  18. resultsFile = "testresults.html"
  19. #jsonFile = "testresults.json" # not used
  20. Usage = """Usage:
  21. tester [options] command [arguments]
  22. Command:
  23. all run all tests
  24. c|cat|category <category> run all the tests of a certain category
  25. r|run <test> run single test file
  26. html generate $1 from the database
  27. stats generate statistics about test cases
  28. Arguments:
  29. arguments are passed to the compiler
  30. Options:
  31. --print also print results to the console
  32. --simulate see what tests would be run but don't run them (for debugging)
  33. --failing only show failing/ignored tests
  34. --targets:"c c++ js objc" run tests for specified targets (default: all)
  35. --nim:path use a particular nim executable (default: compiler/nim)
  36. --directory:dir Change to directory dir before reading the tests or doing anything else.
  37. --colors:on|off Turn messagescoloring on|off.
  38. --backendLogging:on|off Disable or enable backend logging. By default turned on.
  39. """ % resultsFile
  40. type
  41. Category = distinct string
  42. TResults = object
  43. total, passed, skipped: int
  44. data: string
  45. TTest = object
  46. name: string
  47. cat: Category
  48. options: string
  49. spec: TSpec
  50. startTime: float
  51. # ----------------------------------------------------------------------------
  52. let
  53. pegLineError =
  54. peg"{[^(]*} '(' {\d+} ', ' {\d+} ') ' ('Error') ':' \s* {.*}"
  55. pegLineTemplate =
  56. peg"""
  57. {[^(]*} '(' {\d+} ', ' {\d+} ') '
  58. 'template/generic instantiation' ( ' of `' [^`]+ '`' )? ' from here' .*
  59. """
  60. pegOtherError = peg"'Error:' \s* {.*}"
  61. pegSuccess = peg"'Hint: operation successful'.*"
  62. pegOfInterest = pegLineError / pegOtherError
  63. var gTargets = {low(TTarget)..high(TTarget)}
  64. proc normalizeMsg(s: string): string =
  65. result = newStringOfCap(s.len+1)
  66. for x in splitLines(s):
  67. if result.len > 0: result.add '\L'
  68. result.add x.strip
  69. proc getFileDir(filename: string): string =
  70. result = filename.splitFile().dir
  71. if not result.isAbsolute():
  72. result = getCurrentDir() / result
  73. proc execCmdEx2(command: string, args: openarray[string], options: set[ProcessOption], input: string): tuple[
  74. output: TaintedString,
  75. exitCode: int] {.tags:
  76. [ExecIOEffect, ReadIOEffect, RootEffect], gcsafe.} =
  77. var p = startProcess(command, args=args, options=options)
  78. var outp = outputStream(p)
  79. # There is no way to provide input for the child process
  80. # anymore. Closing it will create EOF on stdin instead of eternal
  81. # blocking.
  82. let instream = inputStream(p)
  83. instream.write(input)
  84. close instream
  85. result = (TaintedString"", -1)
  86. var line = newStringOfCap(120).TaintedString
  87. while true:
  88. if outp.readLine(line):
  89. result[0].string.add(line.string)
  90. result[0].string.add("\n")
  91. else:
  92. result[1] = peekExitCode(p)
  93. if result[1] != -1: break
  94. close(p)
  95. proc nimcacheDir(filename, options: string, target: TTarget): string =
  96. ## Give each test a private nimcache dir so they don't clobber each other's.
  97. let hashInput = options & $target
  98. return "nimcache" / (filename & '_' & hashInput.getMD5)
  99. proc callCompiler(cmdTemplate, filename, options: string,
  100. target: TTarget, extraOptions=""): TSpec =
  101. let nimcache = nimcacheDir(filename, options, target)
  102. let options = options & " " & quoteShell("--nimCache:" & nimcache) & extraOptions
  103. let c = parseCmdLine(cmdTemplate % ["target", targetToCmd[target],
  104. "options", options, "file", filename.quoteShell,
  105. "filedir", filename.getFileDir()])
  106. var p = startProcess(command=c[0], args=c[1 .. ^1],
  107. options={poStdErrToStdOut, poUsePath})
  108. let outp = p.outputStream
  109. var suc = ""
  110. var err = ""
  111. var tmpl = ""
  112. var x = newStringOfCap(120)
  113. result.nimout = ""
  114. while outp.readLine(x.TaintedString) or running(p):
  115. result.nimout.add(x & "\n")
  116. if x =~ pegOfInterest:
  117. # `err` should contain the last error/warning message
  118. err = x
  119. elif x =~ pegLineTemplate and err == "":
  120. # `tmpl` contains the last template expansion before the error
  121. tmpl = x
  122. elif x =~ pegSuccess:
  123. suc = x
  124. close(p)
  125. result.msg = ""
  126. result.file = ""
  127. result.output = ""
  128. result.line = 0
  129. result.column = 0
  130. result.tfile = ""
  131. result.tline = 0
  132. result.tcolumn = 0
  133. if tmpl =~ pegLineTemplate:
  134. result.tfile = extractFilename(matches[0])
  135. result.tline = parseInt(matches[1])
  136. result.tcolumn = parseInt(matches[2])
  137. if err =~ pegLineError:
  138. result.file = extractFilename(matches[0])
  139. result.line = parseInt(matches[1])
  140. result.column = parseInt(matches[2])
  141. result.msg = matches[3]
  142. elif err =~ pegOtherError:
  143. result.msg = matches[0]
  144. elif suc =~ pegSuccess:
  145. result.err = reSuccess
  146. proc callCCompiler(cmdTemplate, filename, options: string,
  147. target: TTarget): TSpec =
  148. let c = parseCmdLine(cmdTemplate % ["target", targetToCmd[target],
  149. "options", options, "file", filename.quoteShell,
  150. "filedir", filename.getFileDir()])
  151. var p = startProcess(command="gcc", args=c[5 .. ^1],
  152. options={poStdErrToStdOut, poUsePath})
  153. let outp = p.outputStream
  154. var x = newStringOfCap(120)
  155. result.nimout = ""
  156. result.msg = ""
  157. result.file = ""
  158. result.output = ""
  159. result.line = -1
  160. while outp.readLine(x.TaintedString) or running(p):
  161. result.nimout.add(x & "\n")
  162. close(p)
  163. if p.peekExitCode == 0:
  164. result.err = reSuccess
  165. proc initResults: TResults =
  166. result.total = 0
  167. result.passed = 0
  168. result.skipped = 0
  169. result.data = ""
  170. import macros
  171. macro ignoreStyleEcho(args: varargs[typed]): untyped =
  172. let typForegroundColor = bindSym"ForegroundColor".getType
  173. let typBackgroundColor = bindSym"BackgroundColor".getType
  174. let typStyle = bindSym"Style".getType
  175. let typTerminalCmd = bindSym"TerminalCmd".getType
  176. result = newCall(bindSym"echo")
  177. for arg in children(args):
  178. if arg.kind == nnkNilLit: continue
  179. let typ = arg.getType
  180. if typ.kind != nnkEnumTy or
  181. typ != typForegroundColor and
  182. typ != typBackgroundColor and
  183. typ != typStyle and
  184. typ != typTerminalCmd:
  185. result.add(arg)
  186. template maybeStyledEcho(args: varargs[untyped]): untyped =
  187. if useColors:
  188. styledEcho(args)
  189. else:
  190. ignoreStyleEcho(args)
  191. proc `$`(x: TResults): string =
  192. result = ("Tests passed: $1 / $3 <br />\n" &
  193. "Tests skipped: $2 / $3 <br />\n") %
  194. [$x.passed, $x.skipped, $x.total]
  195. proc addResult(r: var TResults, test: TTest, target: TTarget,
  196. expected, given: string, success: TResultEnum) =
  197. # test.name is easier to find than test.name.extractFilename
  198. # A bit hacky but simple and works with tests/testament/tshouldfail.nim
  199. var name = test.name.replace(DirSep, '/')
  200. name.add " " & $target & test.options
  201. let duration = epochTime() - test.startTime
  202. let durationStr = duration.formatFloat(ffDecimal, precision = 8).align(11)
  203. if backendLogging:
  204. backend.writeTestResult(name = name,
  205. category = test.cat.string,
  206. target = $target,
  207. action = $test.spec.action,
  208. result = $success,
  209. expected = expected,
  210. given = given)
  211. r.data.addf("$#\t$#\t$#\t$#", name, expected, given, $success)
  212. if success == reSuccess:
  213. maybeStyledEcho fgGreen, "PASS: ", fgCyan, alignLeft(name, 60), fgBlue, " (", durationStr, " secs)"
  214. elif success == reDisabled:
  215. maybeStyledEcho styleDim, fgYellow, "SKIP: ", styleBright, fgCyan, name
  216. elif success == reJoined:
  217. maybeStyledEcho styleDim, fgYellow, "JOINED: ", styleBright, fgCyan, name
  218. else:
  219. maybeStyledEcho styleBright, fgRed, "FAIL: ", fgCyan, name
  220. maybeStyledEcho styleBright, fgCyan, "Test \"", test.name, "\"", " in category \"", test.cat.string, "\""
  221. maybeStyledEcho styleBright, fgRed, "Failure: ", $success
  222. maybeStyledEcho fgYellow, "Expected:"
  223. maybeStyledEcho styleBright, expected, "\n"
  224. maybeStyledEcho fgYellow, "Gotten:"
  225. maybeStyledEcho styleBright, given, "\n"
  226. if backendLogging and existsEnv("APPVEYOR"):
  227. let (outcome, msg) =
  228. if success == reSuccess:
  229. ("Passed", "")
  230. elif success in {reDisabled, reJoined}:
  231. ("Skipped", "")
  232. else:
  233. ("Failed", "Failure: " & $success & "\nExpected:\n" & expected & "\n\n" & "Gotten:\n" & given)
  234. var p = startProcess("appveyor", args=["AddTest", test.name.replace("\\", "/") & test.options,
  235. "-Framework", "nim-testament", "-FileName",
  236. test.cat.string,
  237. "-Outcome", outcome, "-ErrorMessage", msg,
  238. "-Duration", $(duration*1000).int],
  239. options={poStdErrToStdOut, poUsePath, poParentStreams})
  240. discard waitForExit(p)
  241. close(p)
  242. proc cmpMsgs(r: var TResults, expected, given: TSpec, test: TTest, target: TTarget) =
  243. if strip(expected.msg) notin strip(given.msg):
  244. r.addResult(test, target, expected.msg, given.msg, reMsgsDiffer)
  245. elif expected.nimout.len > 0 and expected.nimout.normalizeMsg notin given.nimout.normalizeMsg:
  246. r.addResult(test, target, expected.nimout, given.nimout, reMsgsDiffer)
  247. elif expected.tfile == "" and extractFilename(expected.file) != extractFilename(given.file) and
  248. "internal error:" notin expected.msg:
  249. r.addResult(test, target, expected.file, given.file, reFilesDiffer)
  250. elif expected.line != given.line and expected.line != 0 or
  251. expected.column != given.column and expected.column != 0:
  252. r.addResult(test, target, $expected.line & ':' & $expected.column,
  253. $given.line & ':' & $given.column,
  254. reLinesDiffer)
  255. elif expected.tfile != "" and extractFilename(expected.tfile) != extractFilename(given.tfile) and
  256. "internal error:" notin expected.msg:
  257. r.addResult(test, target, expected.tfile, given.tfile, reFilesDiffer)
  258. elif expected.tline != given.tline and expected.tline != 0 or
  259. expected.tcolumn != given.tcolumn and expected.tcolumn != 0:
  260. r.addResult(test, target, $expected.tline & ':' & $expected.tcolumn,
  261. $given.tline & ':' & $given.tcolumn,
  262. reLinesDiffer)
  263. else:
  264. r.addResult(test, target, expected.msg, given.msg, reSuccess)
  265. inc(r.passed)
  266. proc generatedFile(test: TTest, target: TTarget): string =
  267. let (_, name, _) = test.name.splitFile
  268. let ext = targetToExt[target]
  269. result = nimcacheDir(test.name, test.options, target) /
  270. ((if target == targetJS: "" else: "compiler_") & name.changeFileExt(ext))
  271. proc needsCodegenCheck(spec: TSpec): bool =
  272. result = spec.maxCodeSize > 0 or spec.ccodeCheck.len > 0
  273. proc codegenCheck(test: TTest, target: TTarget, spec: TSpec, expectedMsg: var string,
  274. given: var TSpec) =
  275. try:
  276. let genFile = generatedFile(test, target)
  277. let contents = readFile(genFile).string
  278. let check = spec.ccodeCheck
  279. if check.len > 0:
  280. if check[0] == '\\':
  281. # little hack to get 'match' support:
  282. if not contents.match(check.peg):
  283. given.err = reCodegenFailure
  284. elif contents.find(check.peg) < 0:
  285. given.err = reCodegenFailure
  286. expectedMsg = check
  287. if spec.maxCodeSize > 0 and contents.len > spec.maxCodeSize:
  288. given.err = reCodegenFailure
  289. given.msg = "generated code size: " & $contents.len
  290. expectedMsg = "max allowed size: " & $spec.maxCodeSize
  291. except ValueError:
  292. given.err = reInvalidPeg
  293. echo getCurrentExceptionMsg()
  294. except IOError:
  295. given.err = reCodeNotFound
  296. echo getCurrentExceptionMsg()
  297. proc nimoutCheck(test: TTest; expectedNimout: string; given: var TSpec) =
  298. let giv = given.nimout.strip
  299. var currentPos = 0
  300. # Only check that nimout contains all expected lines in that order.
  301. # There may be more output in nimout. It is ignored here.
  302. for line in expectedNimout.strip.splitLines:
  303. currentPos = giv.find(line.strip, currentPos)
  304. if currentPos < 0:
  305. given.err = reMsgsDiffer
  306. return
  307. proc compilerOutputTests(test: TTest, target: TTarget, given: var TSpec,
  308. expected: TSpec; r: var TResults) =
  309. var expectedmsg: string = ""
  310. var givenmsg: string = ""
  311. if given.err == reSuccess:
  312. if expected.needsCodegenCheck:
  313. codegenCheck(test, target, expected, expectedmsg, given)
  314. givenmsg = given.msg
  315. if expected.nimout.len > 0:
  316. expectedmsg = expected.nimout
  317. givenmsg = given.nimout.strip
  318. nimoutCheck(test, expectedmsg, given)
  319. else:
  320. givenmsg = given.nimout.strip
  321. if given.err == reSuccess: inc(r.passed)
  322. r.addResult(test, target, expectedmsg, givenmsg, given.err)
  323. proc testSpec(r: var TResults, test: TTest, targets: set[TTarget] = {}) =
  324. var expected = test.spec
  325. if expected.parseErrors.len > 0:
  326. # targetC is a lie, but parameter is required
  327. r.addResult(test, targetC, "", expected.parseErrors, reInvalidSpec)
  328. inc(r.total)
  329. return
  330. if expected.err in {reDisabled, reJoined}:
  331. # targetC is a lie, but parameter is required
  332. r.addResult(test, targetC, "", "", expected.err)
  333. inc(r.skipped)
  334. inc(r.total)
  335. return
  336. expected.targets.incl targets
  337. # still no target specified at all
  338. if expected.targets == {}:
  339. if getEnv("NIM_COMPILE_TO_CPP", "false").string == "true":
  340. expected.targets = {targetCpp}
  341. else:
  342. expected.targets = {targetC}
  343. for target in expected.targets:
  344. inc(r.total)
  345. if target notin gTargets:
  346. r.addResult(test, target, "", "", reDisabled)
  347. inc(r.skipped)
  348. continue
  349. if simulate:
  350. var count {.global.} = 0
  351. count.inc
  352. echo "testSpec count: ", count, " expected: ", expected
  353. continue
  354. case expected.action
  355. of actionCompile:
  356. var given = callCompiler(expected.getCmd, test.name, test.options, target,
  357. extraOptions=" --stdout --hint[Path]:off --hint[Processing]:off")
  358. compilerOutputTests(test, target, given, expected, r)
  359. of actionRun:
  360. # In this branch of code "early return" pattern is clearer than deep
  361. # nested conditionals - the empty rows in between to clarify the "danger"
  362. var given = callCompiler(expected.getCmd, test.name, test.options,
  363. target)
  364. if given.err != reSuccess:
  365. r.addResult(test, target, "", given.msg, given.err)
  366. continue
  367. let isJsTarget = target == targetJS
  368. var exeFile: string
  369. if isJsTarget:
  370. let file = test.name.lastPathPart.changeFileExt("js")
  371. exeFile = nimcacheDir(test.name, test.options, target) / file
  372. else:
  373. exeFile = changeFileExt(test.name, ExeExt)
  374. if not existsFile(exeFile):
  375. r.addResult(test, target, expected.output, "executable not found", reExeNotFound)
  376. continue
  377. let nodejs = if isJsTarget: findNodeJs() else: ""
  378. if isJsTarget and nodejs == "":
  379. r.addResult(test, target, expected.output, "nodejs binary not in PATH",
  380. reExeNotFound)
  381. continue
  382. var exeCmd: string
  383. var args: seq[string]
  384. if isJsTarget:
  385. exeCmd = nodejs
  386. args.add exeFile
  387. else:
  388. exeCmd = exeFile
  389. var (buf, exitCode) = execCmdEx2(exeCmd, args, options = {poStdErrToStdOut}, input = expected.input)
  390. # Treat all failure codes from nodejs as 1. Older versions of nodejs used
  391. # to return other codes, but for us it is sufficient to know that it's not 0.
  392. if exitCode != 0: exitCode = 1
  393. let bufB =
  394. if expected.sortoutput:
  395. var x = splitLines(strip(buf.string))
  396. sort(x, system.cmp)
  397. join(x, "\n")
  398. else:
  399. strip(buf.string)
  400. if exitCode != expected.exitCode:
  401. r.addResult(test, target, "exitcode: " & $expected.exitCode,
  402. "exitcode: " & $exitCode & "\n\nOutput:\n" &
  403. bufB, reExitCodesDiffer)
  404. continue
  405. if (expected.outputCheck == ocEqual and expected.output != bufB) or
  406. (expected.outputCheck == ocSubstr and expected.output notin bufB):
  407. given.err = reOutputsDiffer
  408. r.addResult(test, target, expected.output, bufB, reOutputsDiffer)
  409. continue
  410. compilerOutputTests(test, target, given, expected, r)
  411. continue
  412. of actionReject:
  413. var given = callCompiler(expected.getCmd, test.name, test.options,
  414. target)
  415. cmpMsgs(r, expected, given, test, target)
  416. continue
  417. proc testC(r: var TResults, test: TTest, action: TTestAction) =
  418. # runs C code. Doesn't support any specs, just goes by exit code.
  419. let tname = test.name.addFileExt(".c")
  420. inc(r.total)
  421. maybeStyledEcho "Processing ", fgCyan, extractFilename(tname)
  422. var given = callCCompiler(getCmd(TSpec()), test.name & ".c", test.options, targetC)
  423. if given.err != reSuccess:
  424. r.addResult(test, targetC, "", given.msg, given.err)
  425. elif action == actionRun:
  426. let exeFile = changeFileExt(test.name, ExeExt)
  427. var (_, exitCode) = execCmdEx(exeFile, options = {poStdErrToStdOut, poUsePath})
  428. if exitCode != 0: given.err = reExitCodesDiffer
  429. if given.err == reSuccess: inc(r.passed)
  430. proc testExec(r: var TResults, test: TTest) =
  431. # runs executable or script, just goes by exit code
  432. inc(r.total)
  433. let (outp, errC) = execCmdEx(test.options.strip())
  434. var given: TSpec
  435. if errC == 0:
  436. given.err = reSuccess
  437. else:
  438. given.err = reExitCodesDiffer
  439. given.msg = outp.string
  440. if given.err == reSuccess: inc(r.passed)
  441. r.addResult(test, targetC, "", given.msg, given.err)
  442. proc makeTest(test, options: string, cat: Category): TTest =
  443. result.cat = cat
  444. result.name = test
  445. result.options = options
  446. result.spec = parseSpec(addFileExt(test, ".nim"))
  447. result.startTime = epochTime()
  448. # TODO: fix these files
  449. const disabledFilesDefault = @[
  450. "LockFreeHash.nim",
  451. "sharedstrings.nim",
  452. "tableimpl.nim",
  453. # Error: undeclared identifier: 'hasThreadSupport'
  454. "ioselectors_epoll.nim",
  455. "ioselectors_kqueue.nim",
  456. "ioselectors_poll.nim",
  457. # Error: undeclared identifier: 'Timeval'
  458. "ioselectors_select.nim",
  459. ]
  460. when defined(windows):
  461. const
  462. # array of modules disabled from compilation test of stdlib.
  463. disabledFiles = disabledFilesDefault & @["coro.nim"]
  464. else:
  465. const
  466. # array of modules disabled from compilation test of stdlib.
  467. # TODO: why the ["-"]? (previous code should've prob used seq[string] = @[] instead)
  468. disabledFiles = disabledFilesDefault & @["-"]
  469. include categories
  470. const testsDir = "tests" & DirSep
  471. proc main() =
  472. os.putenv "NIMTEST_COLOR", "never"
  473. os.putenv "NIMTEST_OUTPUT_LVL", "PRINT_FAILURES"
  474. backend.open()
  475. var optPrintResults = false
  476. var optFailing = false
  477. var targetsStr = ""
  478. var p = initOptParser()
  479. p.next()
  480. while p.kind == cmdLongoption:
  481. case p.key.string.normalize
  482. of "print", "verbose": optPrintResults = true
  483. of "failing": optFailing = true
  484. of "pedantic": discard "now always enabled"
  485. of "targets":
  486. targetsStr = p.val.string
  487. gTargets = parseTargets(targetsStr)
  488. of "nim":
  489. compilerPrefix = addFileExt(p.val.string, ExeExt)
  490. of "directory":
  491. setCurrentDir(p.val.string)
  492. of "colors":
  493. case p.val.string:
  494. of "on":
  495. useColors = true
  496. of "off":
  497. useColors = false
  498. else:
  499. quit Usage
  500. of "simulate":
  501. simulate = true
  502. of "backendlogging":
  503. case p.val.string:
  504. of "on":
  505. backendLogging = true
  506. of "off":
  507. backendLogging = false
  508. else:
  509. quit Usage
  510. else:
  511. quit Usage
  512. p.next()
  513. if p.kind != cmdArgument:
  514. quit Usage
  515. var action = p.key.string.normalize
  516. p.next()
  517. var r = initResults()
  518. case action
  519. of "all":
  520. #processCategory(r, Category"megatest", p.cmdLineRest.string, testsDir, runJoinableTests = false)
  521. var myself = quoteShell(findExe("testament" / "tester"))
  522. if targetsStr.len > 0:
  523. myself &= " " & quoteShell("--targets:" & targetsStr)
  524. myself &= " " & quoteShell("--nim:" & compilerPrefix)
  525. var cats: seq[string]
  526. let rest = if p.cmdLineRest.string.len > 0: " " & p.cmdLineRest.string else: ""
  527. for kind, dir in walkDir(testsDir):
  528. assert testsDir.startsWith(testsDir)
  529. let cat = dir[testsDir.len .. ^1]
  530. if kind == pcDir and cat notin ["testdata", "nimcache"]:
  531. cats.add cat
  532. cats.add AdditionalCategories
  533. var cmds: seq[string]
  534. for cat in cats:
  535. cmds.add(myself & " pcat " & quoteShell(cat) & rest)
  536. proc progressStatus(idx: int) =
  537. echo "progress[all]: i: " & $idx & " / " & $cats.len & " cat: " & cats[idx]
  538. if simulate:
  539. for i, cati in cats:
  540. progressStatus(i)
  541. processCategory(r, Category(cati), p.cmdLineRest.string, testsDir, runJoinableTests = false)
  542. else:
  543. quit osproc.execProcesses(cmds, {poEchoCmd, poStdErrToStdOut, poUsePath, poParentStreams}, beforeRunEvent = progressStatus)
  544. of "c", "cat", "category":
  545. var cat = Category(p.key)
  546. p.next
  547. processCategory(r, cat, p.cmdLineRest.string, testsDir, runJoinableTests = true)
  548. of "pcat":
  549. # 'pcat' is used for running a category in parallel. Currently the only
  550. # difference is that we don't want to run joinable tests here as they
  551. # are covered by the 'megatest' category.
  552. var cat = Category(p.key)
  553. p.next
  554. processCategory(r, cat, p.cmdLineRest.string, testsDir, runJoinableTests = false)
  555. of "r", "run":
  556. # at least one directory is required in the path, to use as a category name
  557. let pathParts = split(p.key.string, {DirSep, AltSep})
  558. # "stdlib/nre/captures.nim" -> "stdlib" + "nre/captures.nim"
  559. let cat = Category(pathParts[0])
  560. let subPath = joinPath(pathParts[1..^1])
  561. processSingleTest(r, cat, p.cmdLineRest.string, subPath)
  562. of "html":
  563. generateHtml(resultsFile, optFailing)
  564. else:
  565. quit Usage
  566. if optPrintResults:
  567. if action == "html": openDefaultBrowser(resultsFile)
  568. else: echo r, r.data
  569. backend.close()
  570. var failed = r.total - r.passed - r.skipped
  571. if failed != 0:
  572. echo "FAILURE! total: ", r.total, " passed: ", r.passed, " skipped: ",
  573. r.skipped, " failed: ", failed
  574. quit(QuitFailure)
  575. if paramCount() == 0:
  576. quit Usage
  577. main()