testament.nim 27 KB

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