testament.nim 29 KB

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