testament.nim 31 KB

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