unittest.nim 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Nim Contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## :Author: Zahary Karadjov
  10. ##
  11. ## This module implements boilerplate to make unit testing easy.
  12. ##
  13. ## The test status and name is printed after any output or traceback.
  14. ##
  15. ## Tests can be nested, however failure of a nested test will not mark the
  16. ## parent test as failed. Setup and teardown are inherited. Setup can be
  17. ## overridden locally.
  18. ##
  19. ## Compiled test files as well as `nim c -r <testfile.nim>`
  20. ## exit with 0 for success (no failed tests) or 1 for failure.
  21. ##
  22. ## Testament
  23. ## =========
  24. ##
  25. ## Instead of `unittest`, please consider using
  26. ## `the Testament tool <testament.html>`_ which offers process isolation for your tests.
  27. ##
  28. ## Alternatively using `when isMainModule: doAssert conditionHere` is usually a
  29. ## much simpler solution for testing purposes.
  30. ##
  31. ## Running a single test
  32. ## =====================
  33. ##
  34. ## Specify the test name as a command line argument.
  35. ##
  36. ## ```cmd
  37. ## nim c -r test "my test name" "another test"
  38. ## ```
  39. ##
  40. ## Multiple arguments can be used.
  41. ##
  42. ## Running a single test suite
  43. ## ===========================
  44. ##
  45. ## Specify the suite name delimited by `"::"`.
  46. ##
  47. ## ```cmd
  48. ## nim c -r test "my test name::"
  49. ## ```
  50. ##
  51. ## Selecting tests by pattern
  52. ## ==========================
  53. ##
  54. ## A single ``"*"`` can be used for globbing.
  55. ##
  56. ## Delimit the end of a suite name with `"::"`.
  57. ##
  58. ## Tests matching **any** of the arguments are executed.
  59. ##
  60. ## ```cmd
  61. ## nim c -r test fast_suite::mytest1 fast_suite::mytest2
  62. ## nim c -r test "fast_suite::mytest*"
  63. ## nim c -r test "auth*::" "crypto::hashing*"
  64. ## # Run suites starting with 'bug #' and standalone tests starting with '#'
  65. ## nim c -r test 'bug #*::' '::#*'
  66. ## ```
  67. ##
  68. ## Examples
  69. ## ========
  70. ##
  71. ## ```nim
  72. ## suite "description for this stuff":
  73. ## echo "suite setup: run once before the tests"
  74. ##
  75. ## setup:
  76. ## echo "run before each test"
  77. ##
  78. ## teardown:
  79. ## echo "run after each test"
  80. ##
  81. ## test "essential truths":
  82. ## # give up and stop if this fails
  83. ## require(true)
  84. ##
  85. ## test "slightly less obvious stuff":
  86. ## # print a nasty message and move on, skipping
  87. ## # the remainder of this block
  88. ## check(1 != 1)
  89. ## check("asd"[2] == 'd')
  90. ##
  91. ## test "out of bounds error is thrown on bad access":
  92. ## let v = @[1, 2, 3] # you can do initialization here
  93. ## expect(IndexDefect):
  94. ## discard v[4]
  95. ##
  96. ## echo "suite teardown: run once after the tests"
  97. ## ```
  98. ##
  99. ## Limitations/Bugs
  100. ## ================
  101. ## Since `check` will rewrite some expressions for supporting checkpoints
  102. ## (namely assigns expressions to variables), some type conversions are not supported.
  103. ## For example `check 4.0 == 2 + 2` won't work. But `doAssert 4.0 == 2 + 2` works.
  104. ## Make sure both sides of the operator (such as `==`, `>=` and so on) have the same type.
  105. ##
  106. import std/private/since
  107. import std/exitprocs
  108. when defined(nimPreviewSlimSystem):
  109. import std/assertions
  110. import std/[macros, strutils, streams, times, sets, sequtils]
  111. when declared(stdout):
  112. import std/os
  113. const useTerminal = not defined(js)
  114. when useTerminal:
  115. import std/terminal
  116. type
  117. TestStatus* = enum ## The status of a test when it is done.
  118. OK,
  119. FAILED,
  120. SKIPPED
  121. OutputLevel* = enum ## The output verbosity of the tests.
  122. PRINT_ALL, ## Print as much as possible.
  123. PRINT_FAILURES, ## Print only the failed tests.
  124. PRINT_NONE ## Print nothing.
  125. TestResult* = object
  126. suiteName*: string
  127. ## Name of the test suite that contains this test case.
  128. ## Can be ``nil`` if the test case is not in a suite.
  129. testName*: string
  130. ## Name of the test case
  131. status*: TestStatus
  132. OutputFormatter* = ref object of RootObj
  133. ConsoleOutputFormatter* = ref object of OutputFormatter
  134. colorOutput: bool
  135. ## Have test results printed in color.
  136. ## Default is `auto` depending on `isatty(stdout)`, or override it with
  137. ## `-d:nimUnittestColor:auto|on|off`.
  138. ##
  139. ## Deprecated: Setting the environment variable `NIMTEST_COLOR` to `always`
  140. ## or `never` changes the default for the non-js target to true or false respectively.
  141. ## Deprecated: the environment variable `NIMTEST_NO_COLOR`, when set, changes the
  142. ## default to true, if `NIMTEST_COLOR` is undefined.
  143. outputLevel: OutputLevel
  144. ## Set the verbosity of test results.
  145. ## Default is `PRINT_ALL`, or override with:
  146. ## `-d:nimUnittestOutputLevel:PRINT_ALL|PRINT_FAILURES|PRINT_NONE`.
  147. ##
  148. ## Deprecated: the `NIMTEST_OUTPUT_LVL` environment variable is set for the non-js target.
  149. isInSuite: bool
  150. isInTest: bool
  151. JUnitOutputFormatter* = ref object of OutputFormatter
  152. stream: Stream
  153. testErrors: seq[string]
  154. testStartTime: float
  155. testStackTrace: string
  156. var
  157. abortOnError* {.threadvar.}: bool ## Set to true in order to quit
  158. ## immediately on fail. Default is false,
  159. ## or override with `-d:nimUnittestAbortOnError:on|off`.
  160. ##
  161. ## Deprecated: can also override depending on whether
  162. ## `NIMTEST_ABORT_ON_ERROR` environment variable is set.
  163. checkpoints {.threadvar.}: seq[string]
  164. formatters {.threadvar.}: seq[OutputFormatter]
  165. testsFilters {.threadvar.}: HashSet[string]
  166. disabledParamFiltering {.threadvar.}: bool
  167. const
  168. outputLevelDefault = PRINT_ALL
  169. nimUnittestOutputLevel {.strdefine.} = $outputLevelDefault
  170. nimUnittestColor {.strdefine.} = "auto" ## auto|on|off
  171. nimUnittestAbortOnError {.booldefine.} = false
  172. template deprecateEnvVarHere() =
  173. # xxx issue a runtime warning to deprecate this envvar.
  174. discard
  175. abortOnError = nimUnittestAbortOnError
  176. when declared(stdout):
  177. if existsEnv("NIMTEST_ABORT_ON_ERROR"):
  178. deprecateEnvVarHere()
  179. abortOnError = true
  180. method suiteStarted*(formatter: OutputFormatter, suiteName: string) {.base, gcsafe.} =
  181. discard
  182. method testStarted*(formatter: OutputFormatter, testName: string) {.base, gcsafe.} =
  183. discard
  184. method failureOccurred*(formatter: OutputFormatter, checkpoints: seq[string],
  185. stackTrace: string) {.base, gcsafe.} =
  186. ## ``stackTrace`` is provided only if the failure occurred due to an exception.
  187. ## ``checkpoints`` is never ``nil``.
  188. discard
  189. method testEnded*(formatter: OutputFormatter, testResult: TestResult) {.base, gcsafe.} =
  190. discard
  191. method suiteEnded*(formatter: OutputFormatter) {.base, gcsafe.} =
  192. discard
  193. proc addOutputFormatter*(formatter: OutputFormatter) =
  194. formatters.add(formatter)
  195. proc delOutputFormatter*(formatter: OutputFormatter) =
  196. keepIf(formatters, proc (x: OutputFormatter): bool =
  197. x != formatter)
  198. proc resetOutputFormatters* {.since: (1, 1).} =
  199. formatters = @[]
  200. proc newConsoleOutputFormatter*(outputLevel: OutputLevel = outputLevelDefault,
  201. colorOutput = true): ConsoleOutputFormatter =
  202. ConsoleOutputFormatter(
  203. outputLevel: outputLevel,
  204. colorOutput: colorOutput
  205. )
  206. proc colorOutput(): bool =
  207. let color = nimUnittestColor
  208. case color
  209. of "auto":
  210. when declared(stdout): result = isatty(stdout)
  211. else: result = false
  212. of "on": result = true
  213. of "off": result = false
  214. else: raiseAssert $color
  215. when declared(stdout):
  216. if existsEnv("NIMTEST_COLOR"):
  217. deprecateEnvVarHere()
  218. let colorEnv = getEnv("NIMTEST_COLOR")
  219. if colorEnv == "never":
  220. result = false
  221. elif colorEnv == "always":
  222. result = true
  223. elif existsEnv("NIMTEST_NO_COLOR"):
  224. deprecateEnvVarHere()
  225. result = false
  226. proc defaultConsoleFormatter*(): ConsoleOutputFormatter =
  227. var colorOutput = colorOutput()
  228. var outputLevel = nimUnittestOutputLevel.parseEnum[:OutputLevel]
  229. when declared(stdout):
  230. const a = "NIMTEST_OUTPUT_LVL"
  231. if existsEnv(a):
  232. # xxx issue a warning to deprecate this envvar.
  233. outputLevel = getEnv(a).parseEnum[:OutputLevel]
  234. result = newConsoleOutputFormatter(outputLevel, colorOutput)
  235. method suiteStarted*(formatter: ConsoleOutputFormatter, suiteName: string) =
  236. template rawPrint() = echo("\n[Suite] ", suiteName)
  237. when useTerminal:
  238. if formatter.colorOutput:
  239. styledEcho styleBright, fgBlue, "\n[Suite] ", resetStyle, suiteName
  240. else: rawPrint()
  241. else: rawPrint()
  242. formatter.isInSuite = true
  243. method testStarted*(formatter: ConsoleOutputFormatter, testName: string) =
  244. formatter.isInTest = true
  245. method failureOccurred*(formatter: ConsoleOutputFormatter,
  246. checkpoints: seq[string], stackTrace: string) =
  247. if stackTrace.len > 0:
  248. echo stackTrace
  249. let prefix = if formatter.isInSuite: " " else: ""
  250. for msg in items(checkpoints):
  251. echo prefix, msg
  252. method testEnded*(formatter: ConsoleOutputFormatter, testResult: TestResult) =
  253. formatter.isInTest = false
  254. if formatter.outputLevel != OutputLevel.PRINT_NONE and
  255. (formatter.outputLevel == OutputLevel.PRINT_ALL or testResult.status == TestStatus.FAILED):
  256. let prefix = if testResult.suiteName.len > 0: " " else: ""
  257. template rawPrint() = echo(prefix, "[", $testResult.status, "] ",
  258. testResult.testName)
  259. when useTerminal:
  260. if formatter.colorOutput:
  261. var color = case testResult.status
  262. of TestStatus.OK: fgGreen
  263. of TestStatus.FAILED: fgRed
  264. of TestStatus.SKIPPED: fgYellow
  265. styledEcho styleBright, color, prefix, "[", $testResult.status, "] ",
  266. resetStyle, testResult.testName
  267. else:
  268. rawPrint()
  269. else:
  270. rawPrint()
  271. method suiteEnded*(formatter: ConsoleOutputFormatter) =
  272. formatter.isInSuite = false
  273. proc xmlEscape(s: string): string =
  274. result = newStringOfCap(s.len)
  275. for c in items(s):
  276. case c:
  277. of '<': result.add("&lt;")
  278. of '>': result.add("&gt;")
  279. of '&': result.add("&amp;")
  280. of '"': result.add("&quot;")
  281. of '\'': result.add("&apos;")
  282. else:
  283. if ord(c) < 32:
  284. result.add("&#" & $ord(c) & ';')
  285. else:
  286. result.add(c)
  287. proc newJUnitOutputFormatter*(stream: Stream): JUnitOutputFormatter =
  288. ## Creates a formatter that writes report to the specified stream in
  289. ## JUnit format.
  290. ## The ``stream`` is NOT closed automatically when the test are finished,
  291. ## because the formatter has no way to know when all tests are finished.
  292. ## You should invoke formatter.close() to finalize the report.
  293. result = JUnitOutputFormatter(
  294. stream: stream,
  295. testErrors: @[],
  296. testStackTrace: "",
  297. testStartTime: 0.0
  298. )
  299. stream.writeLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
  300. stream.writeLine("<testsuites>")
  301. proc close*(formatter: JUnitOutputFormatter) =
  302. ## Completes the report and closes the underlying stream.
  303. formatter.stream.writeLine("</testsuites>")
  304. formatter.stream.close()
  305. method suiteStarted*(formatter: JUnitOutputFormatter, suiteName: string) =
  306. formatter.stream.writeLine("\t<testsuite name=\"$1\">" % xmlEscape(suiteName))
  307. method testStarted*(formatter: JUnitOutputFormatter, testName: string) =
  308. formatter.testErrors.setLen(0)
  309. formatter.testStackTrace.setLen(0)
  310. formatter.testStartTime = epochTime()
  311. method failureOccurred*(formatter: JUnitOutputFormatter,
  312. checkpoints: seq[string], stackTrace: string) =
  313. ## ``stackTrace`` is provided only if the failure occurred due to an exception.
  314. ## ``checkpoints`` is never ``nil``.
  315. formatter.testErrors.add(checkpoints)
  316. if stackTrace.len > 0:
  317. formatter.testStackTrace = stackTrace
  318. method testEnded*(formatter: JUnitOutputFormatter, testResult: TestResult) =
  319. let time = epochTime() - formatter.testStartTime
  320. let timeStr = time.formatFloat(ffDecimal, precision = 8)
  321. formatter.stream.writeLine("\t\t<testcase name=\"$#\" time=\"$#\">" % [
  322. xmlEscape(testResult.testName), timeStr])
  323. case testResult.status
  324. of TestStatus.OK:
  325. discard
  326. of TestStatus.SKIPPED:
  327. formatter.stream.writeLine("<skipped />")
  328. of TestStatus.FAILED:
  329. let failureMsg = if formatter.testStackTrace.len > 0 and
  330. formatter.testErrors.len > 0:
  331. xmlEscape(formatter.testErrors[^1])
  332. elif formatter.testErrors.len > 0:
  333. xmlEscape(formatter.testErrors[0])
  334. else: "The test failed without outputting an error"
  335. var errs = ""
  336. if formatter.testErrors.len > 1:
  337. var startIdx = if formatter.testStackTrace.len > 0: 0 else: 1
  338. var endIdx = if formatter.testStackTrace.len > 0:
  339. formatter.testErrors.len - 2
  340. else: formatter.testErrors.len - 1
  341. for errIdx in startIdx..endIdx:
  342. if errs.len > 0:
  343. errs.add("\n")
  344. errs.add(xmlEscape(formatter.testErrors[errIdx]))
  345. if formatter.testStackTrace.len > 0:
  346. formatter.stream.writeLine("\t\t\t<error message=\"$#\">$#</error>" % [
  347. failureMsg, xmlEscape(formatter.testStackTrace)])
  348. if errs.len > 0:
  349. formatter.stream.writeLine("\t\t\t<system-err>$#</system-err>" % errs)
  350. else:
  351. formatter.stream.writeLine("\t\t\t<failure message=\"$#\">$#</failure>" %
  352. [failureMsg, errs])
  353. formatter.stream.writeLine("\t\t</testcase>")
  354. method suiteEnded*(formatter: JUnitOutputFormatter) =
  355. formatter.stream.writeLine("\t</testsuite>")
  356. proc glob(matcher, filter: string): bool =
  357. ## Globbing using a single `*`. Empty `filter` matches everything.
  358. if filter.len == 0:
  359. return true
  360. if not filter.contains('*'):
  361. return matcher == filter
  362. let beforeAndAfter = filter.split('*', maxsplit = 1)
  363. if beforeAndAfter.len == 1:
  364. # "foo*"
  365. return matcher.startsWith(beforeAndAfter[0])
  366. if matcher.len < filter.len - 1:
  367. return false # "12345" should not match "123*345"
  368. return matcher.startsWith(beforeAndAfter[0]) and matcher.endsWith(
  369. beforeAndAfter[1])
  370. proc matchFilter(suiteName, testName, filter: string): bool =
  371. if filter == "":
  372. return true
  373. if testName == filter:
  374. # corner case for tests containing "::" in their name
  375. return true
  376. let suiteAndTestFilters = filter.split("::", maxsplit = 1)
  377. if suiteAndTestFilters.len == 1:
  378. # no suite specified
  379. let testFilter = suiteAndTestFilters[0]
  380. return glob(testName, testFilter)
  381. return glob(suiteName, suiteAndTestFilters[0]) and
  382. glob(testName, suiteAndTestFilters[1])
  383. proc shouldRun(currentSuiteName, testName: string): bool =
  384. ## Check if a test should be run by matching suiteName and testName against
  385. ## test filters.
  386. if testsFilters.len == 0:
  387. return true
  388. for f in testsFilters:
  389. if matchFilter(currentSuiteName, testName, f):
  390. return true
  391. return false
  392. proc ensureInitialized() =
  393. if formatters.len == 0:
  394. formatters = @[OutputFormatter(defaultConsoleFormatter())]
  395. if not disabledParamFiltering:
  396. when declared(paramCount):
  397. # Read tests to run from the command line.
  398. for i in 1 .. paramCount():
  399. testsFilters.incl(paramStr(i))
  400. # These two procs are added as workarounds for
  401. # https://github.com/nim-lang/Nim/issues/5549
  402. proc suiteEnded() =
  403. for formatter in formatters:
  404. formatter.suiteEnded()
  405. proc testEnded(testResult: TestResult) =
  406. for formatter in formatters:
  407. formatter.testEnded(testResult)
  408. template suite*(name, body) {.dirty.} =
  409. ## Declare a test suite identified by `name` with optional ``setup``
  410. ## and/or ``teardown`` section.
  411. ##
  412. ## A test suite is a series of one or more related tests sharing a
  413. ## common fixture (``setup``, ``teardown``). The fixture is executed
  414. ## for EACH test.
  415. ##
  416. ## ```nim
  417. ## suite "test suite for addition":
  418. ## setup:
  419. ## let result = 4
  420. ##
  421. ## test "2 + 2 = 4":
  422. ## check(2+2 == result)
  423. ##
  424. ## test "(2 + -2) != 4":
  425. ## check(2 + -2 != result)
  426. ##
  427. ## # No teardown needed
  428. ## ```
  429. ##
  430. ## The suite will run the individual test cases in the order in which
  431. ## they were listed. With default global settings the above code prints:
  432. ##
  433. ## [Suite] test suite for addition
  434. ## [OK] 2 + 2 = 4
  435. ## [OK] (2 + -2) != 4
  436. bind formatters, ensureInitialized, suiteEnded
  437. block:
  438. template setup(setupBody: untyped) {.dirty, used.} =
  439. var testSetupIMPLFlag {.used.} = true
  440. template testSetupIMPL: untyped {.dirty.} = setupBody
  441. template teardown(teardownBody: untyped) {.dirty, used.} =
  442. var testTeardownIMPLFlag {.used.} = true
  443. template testTeardownIMPL: untyped {.dirty.} = teardownBody
  444. let testSuiteName {.used.} = name
  445. ensureInitialized()
  446. try:
  447. for formatter in formatters:
  448. formatter.suiteStarted(name)
  449. body
  450. finally:
  451. suiteEnded()
  452. proc exceptionTypeName(e: ref Exception): string {.inline.} =
  453. if e == nil: "<foreign exception>"
  454. else: $e.name
  455. when not declared(setProgramResult):
  456. {.warning: "setProgramResult not available on platform, unittest will not" &
  457. " give failing exit code on test failure".}
  458. template setProgramResult(a: int) =
  459. discard
  460. template test*(name, body) {.dirty.} =
  461. ## Define a single test case identified by `name`.
  462. ##
  463. ## ```nim
  464. ## test "roses are red":
  465. ## let roses = "red"
  466. ## check(roses == "red")
  467. ## ```
  468. ##
  469. ## The above code outputs:
  470. ##
  471. ## [OK] roses are red
  472. bind shouldRun, checkpoints, formatters, ensureInitialized, testEnded, exceptionTypeName, setProgramResult
  473. ensureInitialized()
  474. if shouldRun(when declared(testSuiteName): testSuiteName else: "", name):
  475. checkpoints = @[]
  476. var testStatusIMPL {.inject.} = TestStatus.OK
  477. for formatter in formatters:
  478. formatter.testStarted(name)
  479. {.push warning[BareExcept]:off.}
  480. try:
  481. when declared(testSetupIMPLFlag): testSetupIMPL()
  482. when declared(testTeardownIMPLFlag):
  483. defer: testTeardownIMPL()
  484. {.push warning[BareExcept]:on.}
  485. body
  486. {.pop.}
  487. except:
  488. let e = getCurrentException()
  489. let eTypeDesc = "[" & exceptionTypeName(e) & "]"
  490. checkpoint("Unhandled exception: " & getCurrentExceptionMsg() & " " & eTypeDesc)
  491. if e == nil: # foreign
  492. fail()
  493. else:
  494. var stackTrace {.inject.} = e.getStackTrace()
  495. fail()
  496. finally:
  497. if testStatusIMPL == TestStatus.FAILED:
  498. setProgramResult 1
  499. let testResult = TestResult(
  500. suiteName: when declared(testSuiteName): testSuiteName else: "",
  501. testName: name,
  502. status: testStatusIMPL
  503. )
  504. testEnded(testResult)
  505. checkpoints = @[]
  506. {.pop.}
  507. proc checkpoint*(msg: string) =
  508. ## Set a checkpoint identified by `msg`. Upon test failure all
  509. ## checkpoints encountered so far are printed out. Example:
  510. ##
  511. ## ```nim
  512. ## checkpoint("Checkpoint A")
  513. ## check((42, "the Answer to life and everything") == (1, "a"))
  514. ## checkpoint("Checkpoint B")
  515. ## ```
  516. ##
  517. ## outputs "Checkpoint A" once it fails.
  518. checkpoints.add(msg)
  519. # TODO: add support for something like SCOPED_TRACE from Google Test
  520. template fail* =
  521. ## Print out the checkpoints encountered so far and quit if ``abortOnError``
  522. ## is true. Otherwise, erase the checkpoints and indicate the test has
  523. ## failed (change exit code and test status). This template is useful
  524. ## for debugging, but is otherwise mostly used internally. Example:
  525. ##
  526. ## ```nim
  527. ## checkpoint("Checkpoint A")
  528. ## complicatedProcInThread()
  529. ## fail()
  530. ## ```
  531. ##
  532. ## outputs "Checkpoint A" before quitting.
  533. bind ensureInitialized, setProgramResult
  534. when declared(testStatusIMPL):
  535. testStatusIMPL = TestStatus.FAILED
  536. else:
  537. setProgramResult 1
  538. ensureInitialized()
  539. # var stackTrace: string = nil
  540. for formatter in formatters:
  541. when declared(stackTrace):
  542. formatter.failureOccurred(checkpoints, stackTrace)
  543. else:
  544. formatter.failureOccurred(checkpoints, "")
  545. if abortOnError: quit(1)
  546. checkpoints = @[]
  547. template skip* =
  548. ## Mark the test as skipped. Should be used directly
  549. ## in case when it is not possible to perform test
  550. ## for reasons depending on outer environment,
  551. ## or certain application logic conditions or configurations.
  552. ## The test code is still executed.
  553. ## ```nim
  554. ## if not isGLContextCreated():
  555. ## skip()
  556. ## ```
  557. bind checkpoints
  558. testStatusIMPL = TestStatus.SKIPPED
  559. checkpoints = @[]
  560. macro check*(conditions: untyped): untyped =
  561. ## Verify if a statement or a list of statements is true.
  562. ## A helpful error message and set checkpoints are printed out on
  563. ## failure (if ``outputLevel`` is not ``PRINT_NONE``).
  564. runnableExamples:
  565. import std/strutils
  566. check("AKB48".toLowerAscii() == "akb48")
  567. let teams = {'A', 'K', 'B', '4', '8'}
  568. check:
  569. "AKB48".toLowerAscii() == "akb48"
  570. 'C' notin teams
  571. let checked = callsite()[1]
  572. template asgn(a: untyped, value: typed) =
  573. var a = value # XXX: we need "var: var" here in order to
  574. # preserve the semantics of var params
  575. template print(name: untyped, value: typed) =
  576. when compiles(string($value)):
  577. checkpoint(name & " was " & $value)
  578. proc inspectArgs(exp: NimNode): tuple[assigns, check, printOuts: NimNode] =
  579. result.check = copyNimTree(exp)
  580. result.assigns = newNimNode(nnkStmtList)
  581. result.printOuts = newNimNode(nnkStmtList)
  582. var counter = 0
  583. if exp[0].kind in {nnkIdent, nnkOpenSymChoice, nnkClosedSymChoice, nnkSym} and
  584. $exp[0] in ["not", "in", "notin", "==", "<=",
  585. ">=", "<", ">", "!=", "is", "isnot"]:
  586. for i in 1 ..< exp.len:
  587. if exp[i].kind notin nnkLiterals:
  588. inc counter
  589. let argStr = exp[i].toStrLit
  590. let paramAst = exp[i]
  591. if exp[i].kind == nnkIdent:
  592. result.printOuts.add getAst(print(argStr, paramAst))
  593. if exp[i].kind in nnkCallKinds + {nnkDotExpr, nnkBracketExpr, nnkPar} and
  594. (exp[i].typeKind notin {ntyTypeDesc} or $exp[0] notin ["is", "isnot"]):
  595. let callVar = newIdentNode(":c" & $counter)
  596. result.assigns.add getAst(asgn(callVar, paramAst))
  597. result.check[i] = callVar
  598. result.printOuts.add getAst(print(argStr, callVar))
  599. if exp[i].kind == nnkExprEqExpr:
  600. # ExprEqExpr
  601. # Ident "v"
  602. # IntLit 2
  603. result.check[i] = exp[i][1]
  604. if exp[i].typeKind notin {ntyTypeDesc}:
  605. let arg = newIdentNode(":p" & $counter)
  606. result.assigns.add getAst(asgn(arg, paramAst))
  607. result.printOuts.add getAst(print(argStr, arg))
  608. if exp[i].kind != nnkExprEqExpr:
  609. result.check[i] = arg
  610. else:
  611. result.check[i][1] = arg
  612. case checked.kind
  613. of nnkCallKinds:
  614. let (assigns, check, printOuts) = inspectArgs(checked)
  615. let lineinfo = newStrLitNode(checked.lineInfo)
  616. let callLit = checked.toStrLit
  617. result = quote do:
  618. block:
  619. `assigns`
  620. if `check`:
  621. discard
  622. else:
  623. checkpoint(`lineinfo` & ": Check failed: " & `callLit`)
  624. `printOuts`
  625. fail()
  626. of nnkStmtList:
  627. result = newNimNode(nnkStmtList)
  628. for node in checked:
  629. if node.kind != nnkCommentStmt:
  630. result.add(newCall(newIdentNode("check"), node))
  631. else:
  632. let lineinfo = newStrLitNode(checked.lineInfo)
  633. let callLit = checked.toStrLit
  634. result = quote do:
  635. if `checked`:
  636. discard
  637. else:
  638. checkpoint(`lineinfo` & ": Check failed: " & `callLit`)
  639. fail()
  640. template require*(conditions: untyped) =
  641. ## Same as `check` except any failed test causes the program to quit
  642. ## immediately. Any teardown statements are not executed and the failed
  643. ## test output is not generated.
  644. let savedAbortOnError = abortOnError
  645. block:
  646. abortOnError = true
  647. check conditions
  648. abortOnError = savedAbortOnError
  649. macro expect*(exceptions: varargs[typed], body: untyped): untyped =
  650. ## Test if `body` raises an exception found in the passed `exceptions`.
  651. ## The test passes if the raised exception is part of the acceptable
  652. ## exceptions. Otherwise, it fails.
  653. runnableExamples:
  654. import std/[math, random, strutils]
  655. proc defectiveRobot() =
  656. randomize()
  657. case rand(1..4)
  658. of 1: raise newException(OSError, "CANNOT COMPUTE!")
  659. of 2: discard parseInt("Hello World!")
  660. of 3: raise newException(IOError, "I can't do that Dave.")
  661. else: assert 2 + 2 == 5
  662. expect IOError, OSError, ValueError, AssertionDefect:
  663. defectiveRobot()
  664. template expectBody(errorTypes, lineInfoLit, body): NimNode {.dirty.} =
  665. {.push warning[BareExcept]:off.}
  666. try:
  667. {.push warning[BareExcept]:on.}
  668. body
  669. {.pop.}
  670. checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.")
  671. fail()
  672. except errorTypes:
  673. discard
  674. except:
  675. let err = getCurrentException()
  676. checkpoint(lineInfoLit & ": Expect Failed, " & $err.name & " was thrown.")
  677. fail()
  678. {.pop.}
  679. var errorTypes = newNimNode(nnkBracket)
  680. for exp in exceptions:
  681. errorTypes.add(exp)
  682. result = getAst(expectBody(errorTypes, errorTypes.lineInfo, body))
  683. proc disableParamFiltering* =
  684. ## disables filtering tests with the command line params
  685. disabledParamFiltering = true