testament.nim 29 KB

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