tester.nim 25 KB

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