unittest.nim 22 KB

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