unittest.nim 23 KB

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