unittest.nim 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  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. import macros, strutils, streams, times, sets, sequtils
  109. when declared(stdout):
  110. import os
  111. const useTerminal = not defined(js)
  112. when useTerminal:
  113. import terminal
  114. type
  115. TestStatus* = enum ## The status of a test when it is done.
  116. OK,
  117. FAILED,
  118. SKIPPED
  119. OutputLevel* = enum ## The output verbosity of the tests.
  120. PRINT_ALL, ## Print as much as possible.
  121. PRINT_FAILURES, ## Print only the failed tests.
  122. PRINT_NONE ## Print nothing.
  123. TestResult* = object
  124. suiteName*: string
  125. ## Name of the test suite that contains this test case.
  126. ## Can be ``nil`` if the test case is not in a suite.
  127. testName*: string
  128. ## Name of the test case
  129. status*: TestStatus
  130. OutputFormatter* = ref object of RootObj
  131. ConsoleOutputFormatter* = ref object of OutputFormatter
  132. colorOutput: bool
  133. ## Have test results printed in color.
  134. ## Default is `auto` depending on `isatty(stdout)`, or override it with
  135. ## `-d:nimUnittestColor:auto|on|off`.
  136. ##
  137. ## Deprecated: Setting the environment variable `NIMTEST_COLOR` to `always`
  138. ## or `never` changes the default for the non-js target to true or false respectively.
  139. ## Deprecated: the environment variable `NIMTEST_NO_COLOR`, when set, changes the
  140. ## default to true, if `NIMTEST_COLOR` is undefined.
  141. outputLevel: OutputLevel
  142. ## Set the verbosity of test results.
  143. ## Default is `PRINT_ALL`, or override with:
  144. ## `-d:nimUnittestOutputLevel:PRINT_ALL|PRINT_FAILURES|PRINT_NONE`.
  145. ##
  146. ## Deprecated: the `NIMTEST_OUTPUT_LVL` environment variable is set for the non-js target.
  147. isInSuite: bool
  148. isInTest: bool
  149. JUnitOutputFormatter* = ref object of OutputFormatter
  150. stream: Stream
  151. testErrors: seq[string]
  152. testStartTime: float
  153. testStackTrace: string
  154. var
  155. abortOnError* {.threadvar.}: bool ## Set to true in order to quit
  156. ## immediately on fail. Default is false,
  157. ## or override with `-d:nimUnittestAbortOnError:on|off`.
  158. ##
  159. ## Deprecated: can also override depending on whether
  160. ## `NIMTEST_ABORT_ON_ERROR` environment variable is set.
  161. checkpoints {.threadvar.}: seq[string]
  162. formatters {.threadvar.}: seq[OutputFormatter]
  163. testsFilters {.threadvar.}: HashSet[string]
  164. disabledParamFiltering {.threadvar.}: bool
  165. const
  166. outputLevelDefault = PRINT_ALL
  167. nimUnittestOutputLevel {.strdefine.} = $outputLevelDefault
  168. nimUnittestColor {.strdefine.} = "auto" ## auto|on|off
  169. nimUnittestAbortOnError {.booldefine.} = false
  170. template deprecateEnvVarHere() =
  171. # xxx issue a runtime warning to deprecate this envvar.
  172. discard
  173. abortOnError = nimUnittestAbortOnError
  174. when declared(stdout):
  175. if existsEnv("NIMTEST_ABORT_ON_ERROR"):
  176. deprecateEnvVarHere()
  177. abortOnError = true
  178. method suiteStarted*(formatter: OutputFormatter, suiteName: string) {.base, gcsafe.} =
  179. discard
  180. method testStarted*(formatter: OutputFormatter, testName: string) {.base, gcsafe.} =
  181. discard
  182. method failureOccurred*(formatter: OutputFormatter, checkpoints: seq[string],
  183. stackTrace: string) {.base, gcsafe.} =
  184. ## ``stackTrace`` is provided only if the failure occurred due to an exception.
  185. ## ``checkpoints`` is never ``nil``.
  186. discard
  187. method testEnded*(formatter: OutputFormatter, testResult: TestResult) {.base, gcsafe.} =
  188. discard
  189. method suiteEnded*(formatter: OutputFormatter) {.base, gcsafe.} =
  190. discard
  191. proc addOutputFormatter*(formatter: OutputFormatter) =
  192. formatters.add(formatter)
  193. proc delOutputFormatter*(formatter: OutputFormatter) =
  194. keepIf(formatters, proc (x: OutputFormatter): bool =
  195. x != formatter)
  196. proc resetOutputFormatters* {.since: (1, 1).} =
  197. formatters = @[]
  198. proc newConsoleOutputFormatter*(outputLevel: OutputLevel = outputLevelDefault,
  199. colorOutput = true): ConsoleOutputFormatter =
  200. ConsoleOutputFormatter(
  201. outputLevel: outputLevel,
  202. colorOutput: colorOutput
  203. )
  204. proc colorOutput(): bool =
  205. let color = nimUnittestColor
  206. case color
  207. of "auto":
  208. when declared(stdout): result = isatty(stdout)
  209. else: result = false
  210. of "on": result = true
  211. of "off": result = false
  212. else: doAssert false, $color
  213. when declared(stdout):
  214. if existsEnv("NIMTEST_COLOR"):
  215. deprecateEnvVarHere()
  216. let colorEnv = getEnv("NIMTEST_COLOR")
  217. if colorEnv == "never":
  218. result = false
  219. elif colorEnv == "always":
  220. result = true
  221. elif existsEnv("NIMTEST_NO_COLOR"):
  222. deprecateEnvVarHere()
  223. result = false
  224. proc defaultConsoleFormatter*(): ConsoleOutputFormatter =
  225. var colorOutput = colorOutput()
  226. var outputLevel = nimUnittestOutputLevel.parseEnum[:OutputLevel]
  227. when declared(stdout):
  228. const a = "NIMTEST_OUTPUT_LVL"
  229. if existsEnv(a):
  230. # xxx issue a warning to deprecate this envvar.
  231. outputLevel = getEnv(a).parseEnum[:OutputLevel]
  232. result = newConsoleOutputFormatter(outputLevel, colorOutput)
  233. method suiteStarted*(formatter: ConsoleOutputFormatter, suiteName: string) =
  234. template rawPrint() = echo("\n[Suite] ", suiteName)
  235. when useTerminal:
  236. if formatter.colorOutput:
  237. styledEcho styleBright, fgBlue, "\n[Suite] ", resetStyle, suiteName
  238. else: rawPrint()
  239. else: rawPrint()
  240. formatter.isInSuite = true
  241. method testStarted*(formatter: ConsoleOutputFormatter, testName: string) =
  242. formatter.isInTest = true
  243. method failureOccurred*(formatter: ConsoleOutputFormatter,
  244. checkpoints: seq[string], stackTrace: string) =
  245. if stackTrace.len > 0:
  246. echo stackTrace
  247. let prefix = if formatter.isInSuite: " " else: ""
  248. for msg in items(checkpoints):
  249. echo prefix, msg
  250. method testEnded*(formatter: ConsoleOutputFormatter, testResult: TestResult) =
  251. formatter.isInTest = false
  252. if formatter.outputLevel != OutputLevel.PRINT_NONE and
  253. (formatter.outputLevel == OutputLevel.PRINT_ALL or testResult.status == TestStatus.FAILED):
  254. let prefix = if testResult.suiteName.len > 0: " " else: ""
  255. template rawPrint() = echo(prefix, "[", $testResult.status, "] ",
  256. testResult.testName)
  257. when useTerminal:
  258. if formatter.colorOutput:
  259. var color = case testResult.status
  260. of TestStatus.OK: fgGreen
  261. of TestStatus.FAILED: fgRed
  262. of TestStatus.SKIPPED: fgYellow
  263. styledEcho styleBright, color, prefix, "[", $testResult.status, "] ",
  264. resetStyle, testResult.testName
  265. else:
  266. rawPrint()
  267. else:
  268. rawPrint()
  269. method suiteEnded*(formatter: ConsoleOutputFormatter) =
  270. formatter.isInSuite = false
  271. proc xmlEscape(s: string): string =
  272. result = newStringOfCap(s.len)
  273. for c in items(s):
  274. case c:
  275. of '<': result.add("&lt;")
  276. of '>': result.add("&gt;")
  277. of '&': result.add("&amp;")
  278. of '"': result.add("&quot;")
  279. of '\'': result.add("&apos;")
  280. else:
  281. if ord(c) < 32:
  282. result.add("&#" & $ord(c) & ';')
  283. else:
  284. result.add(c)
  285. proc newJUnitOutputFormatter*(stream: Stream): JUnitOutputFormatter =
  286. ## Creates a formatter that writes report to the specified stream in
  287. ## JUnit format.
  288. ## The ``stream`` is NOT closed automatically when the test are finished,
  289. ## because the formatter has no way to know when all tests are finished.
  290. ## You should invoke formatter.close() to finalize the report.
  291. result = JUnitOutputFormatter(
  292. stream: stream,
  293. testErrors: @[],
  294. testStackTrace: "",
  295. testStartTime: 0.0
  296. )
  297. stream.writeLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
  298. stream.writeLine("<testsuites>")
  299. proc close*(formatter: JUnitOutputFormatter) =
  300. ## Completes the report and closes the underlying stream.
  301. formatter.stream.writeLine("</testsuites>")
  302. formatter.stream.close()
  303. method suiteStarted*(formatter: JUnitOutputFormatter, suiteName: string) =
  304. formatter.stream.writeLine("\t<testsuite name=\"$1\">" % xmlEscape(suiteName))
  305. method testStarted*(formatter: JUnitOutputFormatter, testName: string) =
  306. formatter.testErrors.setLen(0)
  307. formatter.testStackTrace.setLen(0)
  308. formatter.testStartTime = epochTime()
  309. method failureOccurred*(formatter: JUnitOutputFormatter,
  310. checkpoints: seq[string], stackTrace: string) =
  311. ## ``stackTrace`` is provided only if the failure occurred due to an exception.
  312. ## ``checkpoints`` is never ``nil``.
  313. formatter.testErrors.add(checkpoints)
  314. if stackTrace.len > 0:
  315. formatter.testStackTrace = stackTrace
  316. method testEnded*(formatter: JUnitOutputFormatter, testResult: TestResult) =
  317. let time = epochTime() - formatter.testStartTime
  318. let timeStr = time.formatFloat(ffDecimal, precision = 8)
  319. formatter.stream.writeLine("\t\t<testcase name=\"$#\" time=\"$#\">" % [
  320. xmlEscape(testResult.testName), timeStr])
  321. case testResult.status
  322. of TestStatus.OK:
  323. discard
  324. of TestStatus.SKIPPED:
  325. formatter.stream.writeLine("<skipped />")
  326. of TestStatus.FAILED:
  327. let failureMsg = if formatter.testStackTrace.len > 0 and
  328. formatter.testErrors.len > 0:
  329. xmlEscape(formatter.testErrors[^1])
  330. elif formatter.testErrors.len > 0:
  331. xmlEscape(formatter.testErrors[0])
  332. else: "The test failed without outputting an error"
  333. var errs = ""
  334. if formatter.testErrors.len > 1:
  335. var startIdx = if formatter.testStackTrace.len > 0: 0 else: 1
  336. var endIdx = if formatter.testStackTrace.len > 0:
  337. formatter.testErrors.len - 2
  338. else: formatter.testErrors.len - 1
  339. for errIdx in startIdx..endIdx:
  340. if errs.len > 0:
  341. errs.add("\n")
  342. errs.add(xmlEscape(formatter.testErrors[errIdx]))
  343. if formatter.testStackTrace.len > 0:
  344. formatter.stream.writeLine("\t\t\t<error message=\"$#\">$#</error>" % [
  345. failureMsg, xmlEscape(formatter.testStackTrace)])
  346. if errs.len > 0:
  347. formatter.stream.writeLine("\t\t\t<system-err>$#</system-err>" % errs)
  348. else:
  349. formatter.stream.writeLine("\t\t\t<failure message=\"$#\">$#</failure>" %
  350. [failureMsg, errs])
  351. formatter.stream.writeLine("\t\t</testcase>")
  352. method suiteEnded*(formatter: JUnitOutputFormatter) =
  353. formatter.stream.writeLine("\t</testsuite>")
  354. proc glob(matcher, filter: string): bool =
  355. ## Globbing using a single `*`. Empty `filter` matches everything.
  356. if filter.len == 0:
  357. return true
  358. if not filter.contains('*'):
  359. return matcher == filter
  360. let beforeAndAfter = filter.split('*', maxsplit = 1)
  361. if beforeAndAfter.len == 1:
  362. # "foo*"
  363. return matcher.startsWith(beforeAndAfter[0])
  364. if matcher.len < filter.len - 1:
  365. return false # "12345" should not match "123*345"
  366. return matcher.startsWith(beforeAndAfter[0]) and matcher.endsWith(
  367. beforeAndAfter[1])
  368. proc matchFilter(suiteName, testName, filter: string): bool =
  369. if filter == "":
  370. return true
  371. if testName == filter:
  372. # corner case for tests containing "::" in their name
  373. return true
  374. let suiteAndTestFilters = filter.split("::", maxsplit = 1)
  375. if suiteAndTestFilters.len == 1:
  376. # no suite specified
  377. let testFilter = suiteAndTestFilters[0]
  378. return glob(testName, testFilter)
  379. return glob(suiteName, suiteAndTestFilters[0]) and
  380. glob(testName, suiteAndTestFilters[1])
  381. when defined(testing): export matchFilter
  382. proc shouldRun(currentSuiteName, testName: string): bool =
  383. ## Check if a test should be run by matching suiteName and testName against
  384. ## test filters.
  385. if testsFilters.len == 0:
  386. return true
  387. for f in testsFilters:
  388. if matchFilter(currentSuiteName, testName, f):
  389. return true
  390. return false
  391. proc ensureInitialized() =
  392. if formatters.len == 0:
  393. formatters = @[OutputFormatter(defaultConsoleFormatter())]
  394. if not disabledParamFiltering:
  395. when declared(paramCount):
  396. # Read tests to run from the command line.
  397. for i in 1 .. paramCount():
  398. testsFilters.incl(paramStr(i))
  399. # These two procs are added as workarounds for
  400. # https://github.com/nim-lang/Nim/issues/5549
  401. proc suiteEnded() =
  402. for formatter in formatters:
  403. formatter.suiteEnded()
  404. proc testEnded(testResult: TestResult) =
  405. for formatter in formatters:
  406. formatter.testEnded(testResult)
  407. template suite*(name, body) {.dirty.} =
  408. ## Declare a test suite identified by `name` with optional ``setup``
  409. ## and/or ``teardown`` section.
  410. ##
  411. ## A test suite is a series of one or more related tests sharing a
  412. ## common fixture (``setup``, ``teardown``). The fixture is executed
  413. ## for EACH test.
  414. ##
  415. ## .. code-block:: nim
  416. ## suite "test suite for addition":
  417. ## setup:
  418. ## let result = 4
  419. ##
  420. ## test "2 + 2 = 4":
  421. ## check(2+2 == result)
  422. ##
  423. ## test "(2 + -2) != 4":
  424. ## check(2 + -2 != result)
  425. ##
  426. ## # No teardown needed
  427. ##
  428. ## The suite will run the individual test cases in the order in which
  429. ## they were listed. With default global settings the above code prints:
  430. ##
  431. ## .. code-block::
  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. template test*(name, body) {.dirty.} =
  456. ## Define a single test case identified by `name`.
  457. ##
  458. ## .. code-block:: nim
  459. ##
  460. ## test "roses are red":
  461. ## let roses = "red"
  462. ## check(roses == "red")
  463. ##
  464. ## The above code outputs:
  465. ##
  466. ## .. code-block::
  467. ##
  468. ## [OK] roses are red
  469. bind shouldRun, checkpoints, formatters, ensureInitialized, testEnded, exceptionTypeName, setProgramResult
  470. ensureInitialized()
  471. if shouldRun(when declared(testSuiteName): testSuiteName else: "", name):
  472. checkpoints = @[]
  473. var testStatusIMPL {.inject.} = TestStatus.OK
  474. for formatter in formatters:
  475. formatter.testStarted(name)
  476. try:
  477. when declared(testSetupIMPLFlag): testSetupIMPL()
  478. when declared(testTeardownIMPLFlag):
  479. defer: testTeardownIMPL()
  480. body
  481. except:
  482. let e = getCurrentException()
  483. let eTypeDesc = "[" & exceptionTypeName(e) & "]"
  484. checkpoint("Unhandled exception: " & getCurrentExceptionMsg() & " " & eTypeDesc)
  485. if e == nil: # foreign
  486. fail()
  487. else:
  488. var stackTrace {.inject.} = e.getStackTrace()
  489. fail()
  490. finally:
  491. if testStatusIMPL == TestStatus.FAILED:
  492. setProgramResult 1
  493. let testResult = TestResult(
  494. suiteName: when declared(testSuiteName): testSuiteName else: "",
  495. testName: name,
  496. status: testStatusIMPL
  497. )
  498. testEnded(testResult)
  499. checkpoints = @[]
  500. proc checkpoint*(msg: string) =
  501. ## Set a checkpoint identified by `msg`. Upon test failure all
  502. ## checkpoints encountered so far are printed out. Example:
  503. ##
  504. ## .. code-block:: nim
  505. ##
  506. ## checkpoint("Checkpoint A")
  507. ## check((42, "the Answer to life and everything") == (1, "a"))
  508. ## checkpoint("Checkpoint B")
  509. ##
  510. ## outputs "Checkpoint A" once it fails.
  511. checkpoints.add(msg)
  512. # TODO: add support for something like SCOPED_TRACE from Google Test
  513. template fail* =
  514. ## Print out the checkpoints encountered so far and quit if ``abortOnError``
  515. ## is true. Otherwise, erase the checkpoints and indicate the test has
  516. ## failed (change exit code and test status). This template is useful
  517. ## for debugging, but is otherwise mostly used internally. Example:
  518. ##
  519. ## .. code-block:: nim
  520. ##
  521. ## checkpoint("Checkpoint A")
  522. ## complicatedProcInThread()
  523. ## fail()
  524. ##
  525. ## outputs "Checkpoint A" before quitting.
  526. bind ensureInitialized, setProgramResult
  527. when declared(testStatusIMPL):
  528. testStatusIMPL = TestStatus.FAILED
  529. else:
  530. setProgramResult 1
  531. ensureInitialized()
  532. # var stackTrace: string = nil
  533. for formatter in formatters:
  534. when declared(stackTrace):
  535. formatter.failureOccurred(checkpoints, stackTrace)
  536. else:
  537. formatter.failureOccurred(checkpoints, "")
  538. if abortOnError: quit(1)
  539. checkpoints = @[]
  540. template skip* =
  541. ## Mark the test as skipped. Should be used directly
  542. ## in case when it is not possible to perform test
  543. ## for reasons depending on outer environment,
  544. ## or certain application logic conditions or configurations.
  545. ## The test code is still executed.
  546. ##
  547. ## .. code-block:: nim
  548. ##
  549. ## if not isGLContextCreated():
  550. ## skip()
  551. bind checkpoints
  552. testStatusIMPL = TestStatus.SKIPPED
  553. checkpoints = @[]
  554. macro check*(conditions: untyped): untyped =
  555. ## Verify if a statement or a list of statements is true.
  556. ## A helpful error message and set checkpoints are printed out on
  557. ## failure (if ``outputLevel`` is not ``PRINT_NONE``).
  558. runnableExamples:
  559. import std/strutils
  560. check("AKB48".toLowerAscii() == "akb48")
  561. let teams = {'A', 'K', 'B', '4', '8'}
  562. check:
  563. "AKB48".toLowerAscii() == "akb48"
  564. 'C' notin teams
  565. let checked = callsite()[1]
  566. template asgn(a: untyped, value: typed) =
  567. var a = value # XXX: we need "var: var" here in order to
  568. # preserve the semantics of var params
  569. template print(name: untyped, value: typed) =
  570. when compiles(string($value)):
  571. checkpoint(name & " was " & $value)
  572. proc inspectArgs(exp: NimNode): tuple[assigns, check, printOuts: NimNode] =
  573. result.check = copyNimTree(exp)
  574. result.assigns = newNimNode(nnkStmtList)
  575. result.printOuts = newNimNode(nnkStmtList)
  576. var counter = 0
  577. if exp[0].kind in {nnkIdent, nnkOpenSymChoice, nnkClosedSymChoice, nnkSym} and
  578. $exp[0] in ["not", "in", "notin", "==", "<=",
  579. ">=", "<", ">", "!=", "is", "isnot"]:
  580. for i in 1 ..< exp.len:
  581. if exp[i].kind notin nnkLiterals:
  582. inc counter
  583. let argStr = exp[i].toStrLit
  584. let paramAst = exp[i]
  585. if exp[i].kind == nnkIdent:
  586. result.printOuts.add getAst(print(argStr, paramAst))
  587. if exp[i].kind in nnkCallKinds + {nnkDotExpr, nnkBracketExpr, nnkPar} and
  588. (exp[i].typeKind notin {ntyTypeDesc} or $exp[0] notin ["is", "isnot"]):
  589. let callVar = newIdentNode(":c" & $counter)
  590. result.assigns.add getAst(asgn(callVar, paramAst))
  591. result.check[i] = callVar
  592. result.printOuts.add getAst(print(argStr, callVar))
  593. if exp[i].kind == nnkExprEqExpr:
  594. # ExprEqExpr
  595. # Ident "v"
  596. # IntLit 2
  597. result.check[i] = exp[i][1]
  598. if exp[i].typeKind notin {ntyTypeDesc}:
  599. let arg = newIdentNode(":p" & $counter)
  600. result.assigns.add getAst(asgn(arg, paramAst))
  601. result.printOuts.add getAst(print(argStr, arg))
  602. if exp[i].kind != nnkExprEqExpr:
  603. result.check[i] = arg
  604. else:
  605. result.check[i][1] = arg
  606. case checked.kind
  607. of nnkCallKinds:
  608. let (assigns, check, printOuts) = inspectArgs(checked)
  609. let lineinfo = newStrLitNode(checked.lineInfo)
  610. let callLit = checked.toStrLit
  611. result = quote do:
  612. block:
  613. `assigns`
  614. if not `check`:
  615. checkpoint(`lineinfo` & ": Check failed: " & `callLit`)
  616. `printOuts`
  617. fail()
  618. of nnkStmtList:
  619. result = newNimNode(nnkStmtList)
  620. for node in checked:
  621. if node.kind != nnkCommentStmt:
  622. result.add(newCall(newIdentNode("check"), node))
  623. else:
  624. let lineinfo = newStrLitNode(checked.lineInfo)
  625. let callLit = checked.toStrLit
  626. result = quote do:
  627. if not `checked`:
  628. checkpoint(`lineinfo` & ": Check failed: " & `callLit`)
  629. fail()
  630. template require*(conditions: untyped) =
  631. ## Same as `check` except any failed test causes the program to quit
  632. ## immediately. Any teardown statements are not executed and the failed
  633. ## test output is not generated.
  634. let savedAbortOnError = abortOnError
  635. block:
  636. abortOnError = true
  637. check conditions
  638. abortOnError = savedAbortOnError
  639. macro expect*(exceptions: varargs[typed], body: untyped): untyped =
  640. ## Test if `body` raises an exception found in the passed `exceptions`.
  641. ## The test passes if the raised exception is part of the acceptable
  642. ## exceptions. Otherwise, it fails.
  643. runnableExamples:
  644. import std/[math, random, strutils]
  645. proc defectiveRobot() =
  646. randomize()
  647. case rand(1..4)
  648. of 1: raise newException(OSError, "CANNOT COMPUTE!")
  649. of 2: discard parseInt("Hello World!")
  650. of 3: raise newException(IOError, "I can't do that Dave.")
  651. else: assert 2 + 2 == 5
  652. expect IOError, OSError, ValueError, AssertionDefect:
  653. defectiveRobot()
  654. template expectBody(errorTypes, lineInfoLit, body): NimNode {.dirty.} =
  655. try:
  656. body
  657. checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.")
  658. fail()
  659. except errorTypes:
  660. discard
  661. except:
  662. checkpoint(lineInfoLit & ": Expect Failed, unexpected exception was thrown.")
  663. fail()
  664. var errorTypes = newNimNode(nnkBracket)
  665. for exp in exceptions:
  666. errorTypes.add(exp)
  667. result = getAst(expectBody(errorTypes, errorTypes.lineInfo, body))
  668. proc disableParamFiltering* =
  669. ## disables filtering tests with the command line params
  670. disabledParamFiltering = true