unittest.nim 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  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. ## .. code::
  37. ##
  38. ## nim c -r test "my test name" "another test"
  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. ## .. code::
  48. ##
  49. ## nim c -r test "my test name::"
  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. ## .. code::
  61. ##
  62. ## nim c -r test fast_suite::mytest1 fast_suite::mytest2
  63. ## nim c -r test "fast_suite::mytest*"
  64. ## nim c -r test "auth*::" "crypto::hashing*"
  65. ## # Run suites starting with 'bug #' and standalone tests starting with '#'
  66. ## nim c -r test 'bug #*::' '::#*'
  67. ##
  68. ## Examples
  69. ## ========
  70. ##
  71. ## .. code:: nim
  72. ##
  73. ## suite "description for this stuff":
  74. ## echo "suite setup: run once before the tests"
  75. ##
  76. ## setup:
  77. ## echo "run before each test"
  78. ##
  79. ## teardown:
  80. ## echo "run after each test"
  81. ##
  82. ## test "essential truths":
  83. ## # give up and stop if this fails
  84. ## require(true)
  85. ##
  86. ## test "slightly less obvious stuff":
  87. ## # print a nasty message and move on, skipping
  88. ## # the remainder of this block
  89. ## check(1 != 1)
  90. ## check("asd"[2] == 'd')
  91. ##
  92. ## test "out of bounds error is thrown on bad access":
  93. ## let v = @[1, 2, 3] # you can do initialization here
  94. ## expect(IndexDefect):
  95. ## discard v[4]
  96. ##
  97. ## echo "suite teardown: run once after the tests"
  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 macros, strutils, streams, times, sets, sequtils
  111. when declared(stdout):
  112. import os
  113. const useTerminal = not defined(js)
  114. when useTerminal:
  115. import 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: doAssert false, $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. ## .. code-block:: 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. ## The suite will run the individual test cases in the order in which
  430. ## they were listed. With default global settings the above code prints:
  431. ##
  432. ## .. code-block::
  433. ##
  434. ## [Suite] test suite for addition
  435. ## [OK] 2 + 2 = 4
  436. ## [OK] (2 + -2) != 4
  437. bind formatters, ensureInitialized, suiteEnded
  438. block:
  439. template setup(setupBody: untyped) {.dirty, used.} =
  440. var testSetupIMPLFlag {.used.} = true
  441. template testSetupIMPL: untyped {.dirty.} = setupBody
  442. template teardown(teardownBody: untyped) {.dirty, used.} =
  443. var testTeardownIMPLFlag {.used.} = true
  444. template testTeardownIMPL: untyped {.dirty.} = teardownBody
  445. let testSuiteName {.used.} = name
  446. ensureInitialized()
  447. try:
  448. for formatter in formatters:
  449. formatter.suiteStarted(name)
  450. body
  451. finally:
  452. suiteEnded()
  453. proc exceptionTypeName(e: ref Exception): string {.inline.} =
  454. if e == nil: "<foreign exception>"
  455. else: $e.name
  456. template test*(name, body) {.dirty.} =
  457. ## Define a single test case identified by `name`.
  458. ##
  459. ## .. code-block:: nim
  460. ##
  461. ## test "roses are red":
  462. ## let roses = "red"
  463. ## check(roses == "red")
  464. ##
  465. ## The above code outputs:
  466. ##
  467. ## .. code-block::
  468. ##
  469. ## [OK] roses are red
  470. bind shouldRun, checkpoints, formatters, ensureInitialized, testEnded, exceptionTypeName, setProgramResult
  471. ensureInitialized()
  472. if shouldRun(when declared(testSuiteName): testSuiteName else: "", name):
  473. checkpoints = @[]
  474. var testStatusIMPL {.inject.} = TestStatus.OK
  475. for formatter in formatters:
  476. formatter.testStarted(name)
  477. try:
  478. when declared(testSetupIMPLFlag): testSetupIMPL()
  479. when declared(testTeardownIMPLFlag):
  480. defer: testTeardownIMPL()
  481. body
  482. except:
  483. let e = getCurrentException()
  484. let eTypeDesc = "[" & exceptionTypeName(e) & "]"
  485. checkpoint("Unhandled exception: " & getCurrentExceptionMsg() & " " & eTypeDesc)
  486. if e == nil: # foreign
  487. fail()
  488. else:
  489. var stackTrace {.inject.} = e.getStackTrace()
  490. fail()
  491. finally:
  492. if testStatusIMPL == TestStatus.FAILED:
  493. setProgramResult 1
  494. let testResult = TestResult(
  495. suiteName: when declared(testSuiteName): testSuiteName else: "",
  496. testName: name,
  497. status: testStatusIMPL
  498. )
  499. testEnded(testResult)
  500. checkpoints = @[]
  501. proc checkpoint*(msg: string) =
  502. ## Set a checkpoint identified by `msg`. Upon test failure all
  503. ## checkpoints encountered so far are printed out. Example:
  504. ##
  505. ## .. code-block:: nim
  506. ##
  507. ## checkpoint("Checkpoint A")
  508. ## check((42, "the Answer to life and everything") == (1, "a"))
  509. ## checkpoint("Checkpoint B")
  510. ##
  511. ## outputs "Checkpoint A" once it fails.
  512. checkpoints.add(msg)
  513. # TODO: add support for something like SCOPED_TRACE from Google Test
  514. template fail* =
  515. ## Print out the checkpoints encountered so far and quit if ``abortOnError``
  516. ## is true. Otherwise, erase the checkpoints and indicate the test has
  517. ## failed (change exit code and test status). This template is useful
  518. ## for debugging, but is otherwise mostly used internally. Example:
  519. ##
  520. ## .. code-block:: nim
  521. ##
  522. ## checkpoint("Checkpoint A")
  523. ## complicatedProcInThread()
  524. ## fail()
  525. ##
  526. ## outputs "Checkpoint A" before quitting.
  527. bind ensureInitialized, setProgramResult
  528. when declared(testStatusIMPL):
  529. testStatusIMPL = TestStatus.FAILED
  530. else:
  531. setProgramResult 1
  532. ensureInitialized()
  533. # var stackTrace: string = nil
  534. for formatter in formatters:
  535. when declared(stackTrace):
  536. formatter.failureOccurred(checkpoints, stackTrace)
  537. else:
  538. formatter.failureOccurred(checkpoints, "")
  539. if abortOnError: quit(1)
  540. checkpoints = @[]
  541. template skip* =
  542. ## Mark the test as skipped. Should be used directly
  543. ## in case when it is not possible to perform test
  544. ## for reasons depending on outer environment,
  545. ## or certain application logic conditions or configurations.
  546. ## The test code is still executed.
  547. ##
  548. ## .. code-block:: nim
  549. ##
  550. ## if not isGLContextCreated():
  551. ## skip()
  552. bind checkpoints
  553. testStatusIMPL = TestStatus.SKIPPED
  554. checkpoints = @[]
  555. macro check*(conditions: untyped): untyped =
  556. ## Verify if a statement or a list of statements is true.
  557. ## A helpful error message and set checkpoints are printed out on
  558. ## failure (if ``outputLevel`` is not ``PRINT_NONE``).
  559. runnableExamples:
  560. import std/strutils
  561. check("AKB48".toLowerAscii() == "akb48")
  562. let teams = {'A', 'K', 'B', '4', '8'}
  563. check:
  564. "AKB48".toLowerAscii() == "akb48"
  565. 'C' notin teams
  566. let checked = callsite()[1]
  567. template asgn(a: untyped, value: typed) =
  568. var a = value # XXX: we need "var: var" here in order to
  569. # preserve the semantics of var params
  570. template print(name: untyped, value: typed) =
  571. when compiles(string($value)):
  572. checkpoint(name & " was " & $value)
  573. proc inspectArgs(exp: NimNode): tuple[assigns, check, printOuts: NimNode] =
  574. result.check = copyNimTree(exp)
  575. result.assigns = newNimNode(nnkStmtList)
  576. result.printOuts = newNimNode(nnkStmtList)
  577. var counter = 0
  578. if exp[0].kind in {nnkIdent, nnkOpenSymChoice, nnkClosedSymChoice, nnkSym} and
  579. $exp[0] in ["not", "in", "notin", "==", "<=",
  580. ">=", "<", ">", "!=", "is", "isnot"]:
  581. for i in 1 ..< exp.len:
  582. if exp[i].kind notin nnkLiterals:
  583. inc counter
  584. let argStr = exp[i].toStrLit
  585. let paramAst = exp[i]
  586. if exp[i].kind == nnkIdent:
  587. result.printOuts.add getAst(print(argStr, paramAst))
  588. if exp[i].kind in nnkCallKinds + {nnkDotExpr, nnkBracketExpr, nnkPar} and
  589. (exp[i].typeKind notin {ntyTypeDesc} or $exp[0] notin ["is", "isnot"]):
  590. let callVar = newIdentNode(":c" & $counter)
  591. result.assigns.add getAst(asgn(callVar, paramAst))
  592. result.check[i] = callVar
  593. result.printOuts.add getAst(print(argStr, callVar))
  594. if exp[i].kind == nnkExprEqExpr:
  595. # ExprEqExpr
  596. # Ident "v"
  597. # IntLit 2
  598. result.check[i] = exp[i][1]
  599. if exp[i].typeKind notin {ntyTypeDesc}:
  600. let arg = newIdentNode(":p" & $counter)
  601. result.assigns.add getAst(asgn(arg, paramAst))
  602. result.printOuts.add getAst(print(argStr, arg))
  603. if exp[i].kind != nnkExprEqExpr:
  604. result.check[i] = arg
  605. else:
  606. result.check[i][1] = arg
  607. case checked.kind
  608. of nnkCallKinds:
  609. let (assigns, check, printOuts) = inspectArgs(checked)
  610. let lineinfo = newStrLitNode(checked.lineInfo)
  611. let callLit = checked.toStrLit
  612. result = quote do:
  613. block:
  614. `assigns`
  615. if `check`:
  616. discard
  617. else:
  618. checkpoint(`lineinfo` & ": Check failed: " & `callLit`)
  619. `printOuts`
  620. fail()
  621. of nnkStmtList:
  622. result = newNimNode(nnkStmtList)
  623. for node in checked:
  624. if node.kind != nnkCommentStmt:
  625. result.add(newCall(newIdentNode("check"), node))
  626. else:
  627. let lineinfo = newStrLitNode(checked.lineInfo)
  628. let callLit = checked.toStrLit
  629. result = quote do:
  630. if `checked`:
  631. discard
  632. else:
  633. checkpoint(`lineinfo` & ": Check failed: " & `callLit`)
  634. fail()
  635. template require*(conditions: untyped) =
  636. ## Same as `check` except any failed test causes the program to quit
  637. ## immediately. Any teardown statements are not executed and the failed
  638. ## test output is not generated.
  639. let savedAbortOnError = abortOnError
  640. block:
  641. abortOnError = true
  642. check conditions
  643. abortOnError = savedAbortOnError
  644. macro expect*(exceptions: varargs[typed], body: untyped): untyped =
  645. ## Test if `body` raises an exception found in the passed `exceptions`.
  646. ## The test passes if the raised exception is part of the acceptable
  647. ## exceptions. Otherwise, it fails.
  648. runnableExamples:
  649. import std/[math, random, strutils]
  650. proc defectiveRobot() =
  651. randomize()
  652. case rand(1..4)
  653. of 1: raise newException(OSError, "CANNOT COMPUTE!")
  654. of 2: discard parseInt("Hello World!")
  655. of 3: raise newException(IOError, "I can't do that Dave.")
  656. else: assert 2 + 2 == 5
  657. expect IOError, OSError, ValueError, AssertionDefect:
  658. defectiveRobot()
  659. template expectBody(errorTypes, lineInfoLit, body): NimNode {.dirty.} =
  660. try:
  661. body
  662. checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.")
  663. fail()
  664. except errorTypes:
  665. discard
  666. except:
  667. checkpoint(lineInfoLit & ": Expect Failed, unexpected exception was thrown.")
  668. fail()
  669. var errorTypes = newNimNode(nnkBracket)
  670. for exp in exceptions:
  671. errorTypes.add(exp)
  672. result = getAst(expectBody(errorTypes, errorTypes.lineInfo, body))
  673. proc disableParamFiltering* =
  674. ## disables filtering tests with the command line params
  675. disabledParamFiltering = true