tester.nim 25 KB

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