testament.nim 29 KB

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