summary refs log tree commit diff stats
path: root/lib/pure/unittest.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/unittest.nim')
-rw-r--r--lib/pure/unittest.nim476
1 files changed, 269 insertions, 207 deletions
diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim
index d804ba7c8..cfb762258 100644
--- a/lib/pure/unittest.nim
+++ b/lib/pure/unittest.nim
@@ -17,51 +17,59 @@
 ## parent test as failed. Setup and teardown are inherited. Setup can be
 ## overridden locally.
 ##
-## Compiled test files return the number of failed test as exit code, while
-## ``nim c -r <testfile.nim>`` exits with 0 or 1
+## Compiled test files as well as `nim c -r <testfile.nim>`
+## exit with 0 for success (no failed tests) or 1 for failure.
+##
+## Testament
+## =========
+##
+## Instead of `unittest`, please consider using
+## `the Testament tool <testament.html>`_ which offers process isolation for your tests.
+##
+## Alternatively using `when isMainModule: doAssert conditionHere` is usually a
+## much simpler solution for testing purposes.
 ##
 ## Running a single test
 ## =====================
 ##
 ## Specify the test name as a command line argument.
 ##
-## .. code::
-##
+##   ```cmd
 ##   nim c -r test "my test name" "another test"
+##   ```
 ##
 ## Multiple arguments can be used.
 ##
 ## Running a single test suite
 ## ===========================
 ##
-## Specify the suite name delimited by ``"::"``.
-##
-## .. code::
+## Specify the suite name delimited by `"::"`.
 ##
+##   ```cmd
 ##   nim c -r test "my test name::"
+##   ```
 ##
 ## Selecting tests by pattern
 ## ==========================
 ##
 ## A single ``"*"`` can be used for globbing.
 ##
-## Delimit the end of a suite name with ``"::"``.
+## Delimit the end of a suite name with `"::"`.
 ##
 ## Tests matching **any** of the arguments are executed.
 ##
-## .. code::
-##
+##   ```cmd
 ##   nim c -r test fast_suite::mytest1 fast_suite::mytest2
 ##   nim c -r test "fast_suite::mytest*"
 ##   nim c -r test "auth*::" "crypto::hashing*"
 ##   # Run suites starting with 'bug #' and standalone tests starting with '#'
 ##   nim c -r test 'bug #*::' '::#*'
+##   ```
 ##
-## Example
-## -------
-##
-## .. code:: nim
+## Examples
+## ========
 ##
+##   ```nim
 ##   suite "description for this stuff":
 ##     echo "suite setup: run once before the tests"
 ##
@@ -83,19 +91,35 @@
 ##
 ##     test "out of bounds error is thrown on bad access":
 ##       let v = @[1, 2, 3]  # you can do initialization here
-##       expect(IndexError):
+##       expect(IndexDefect):
 ##         discard v[4]
 ##
 ##     echo "suite teardown: run once after the tests"
+##   ```
+##
+## Limitations/Bugs
+## ================
+## Since `check` will rewrite some expressions for supporting checkpoints
+## (namely assigns expressions to variables), some type conversions are not supported.
+## For example `check 4.0 == 2 + 2` won't work. But `doAssert 4.0 == 2 + 2` works.
+## Make sure both sides of the operator (such as `==`, `>=` and so on) have the same type.
+##
+
+import std/private/since
+import std/exitprocs
 
-import
-  macros, strutils, streams, times, sets
+when defined(nimPreviewSlimSystem):
+  import std/assertions
+
+import std/[macros, strutils, streams, times, sets, sequtils]
 
 when declared(stdout):
-  import os
+  import std/os
+
+const useTerminal = not defined(js)
 
-when not defined(ECMAScript):
-  import terminal
+when useTerminal:
+  import std/terminal
 
 type
   TestStatus* = enum ## The status of a test when it is done.
@@ -103,10 +127,10 @@ type
     FAILED,
     SKIPPED
 
-  OutputLevel* = enum  ## The output verbosity of the tests.
-    PRINT_ALL,         ## Print as much as possible.
-    PRINT_FAILURES,    ## Print only the failed tests.
-    PRINT_NONE         ## Print nothing.
+  OutputLevel* = enum ## The output verbosity of the tests.
+    PRINT_ALL,        ## Print as much as possible.
+    PRINT_FAILURES,   ## Print only the failed tests.
+    PRINT_NONE        ## Print nothing.
 
   TestResult* = object
     suiteName*: string
@@ -121,21 +145,19 @@ type
   ConsoleOutputFormatter* = ref object of OutputFormatter
     colorOutput: bool
       ## Have test results printed in color.
-      ## Default is true for the non-js target,
-      ## for which ``stdout`` is a tty.
-      ## Setting the environment variable
-      ## ``NIMTEST_COLOR`` to ``always`` or
-      ## ``never`` changes the default for the
-      ## non-js target to true or false respectively.
-      ## The deprecated environment variable
-      ## ``NIMTEST_NO_COLOR``, when set,
-      ## changes the defualt to true, if
-      ## ``NIMTEST_COLOR`` is undefined.
+      ## Default is `auto` depending on `isatty(stdout)`, or override it with
+      ## `-d:nimUnittestColor:auto|on|off`.
+      ##
+      ## Deprecated: Setting the environment variable `NIMTEST_COLOR` to `always`
+      ## or `never` changes the default for the non-js target to true or false respectively.
+      ## Deprecated: the environment variable `NIMTEST_NO_COLOR`, when set, changes the
+      ## default to true, if `NIMTEST_COLOR` is undefined.
     outputLevel: OutputLevel
       ## Set the verbosity of test results.
-      ## Default is ``PRINT_ALL``, unless
-      ## the ``NIMTEST_OUTPUT_LVL`` environment
-      ## variable is set for the non-js target.
+      ## Default is `PRINT_ALL`, or override with:
+      ## `-d:nimUnittestOutputLevel:PRINT_ALL|PRINT_FAILURES|PRINT_NONE`.
+      ##
+      ## Deprecated: the `NIMTEST_OUTPUT_LVL` environment variable is set for the non-js target.
     isInSuite: bool
     isInTest: bool
 
@@ -145,28 +167,41 @@ type
     testStartTime: float
     testStackTrace: string
 
-{.deprecated: [TTestStatus: TestStatus, TOutputLevel: OutputLevel]}
-
 var
   abortOnError* {.threadvar.}: bool ## Set to true in order to quit
                                     ## immediately on fail. Default is false,
-                                    ## unless the ``NIMTEST_ABORT_ON_ERROR``
-                                    ## environment variable is set for
-                                    ## the non-js target.
+                                    ## or override with `-d:nimUnittestAbortOnError:on|off`.
+                                    ##
+                                    ## Deprecated: can also override depending on whether
+                                    ## `NIMTEST_ABORT_ON_ERROR` environment variable is set.
 
   checkpoints {.threadvar.}: seq[string]
   formatters {.threadvar.}: seq[OutputFormatter]
   testsFilters {.threadvar.}: HashSet[string]
   disabledParamFiltering {.threadvar.}: bool
 
+const
+  outputLevelDefault = PRINT_ALL
+  nimUnittestOutputLevel {.strdefine.} = $outputLevelDefault
+  nimUnittestColor {.strdefine.} = "auto" ## auto|on|off
+  nimUnittestAbortOnError {.booldefine.} = false
+
+template deprecateEnvVarHere() =
+  # xxx issue a runtime warning to deprecate this envvar.
+  discard
+
+abortOnError = nimUnittestAbortOnError
 when declared(stdout):
-  abortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR")
+  if existsEnv("NIMTEST_ABORT_ON_ERROR"):
+    deprecateEnvVarHere()
+    abortOnError = true
 
 method suiteStarted*(formatter: OutputFormatter, suiteName: string) {.base, gcsafe.} =
   discard
 method testStarted*(formatter: OutputFormatter, testName: string) {.base, gcsafe.} =
   discard
-method failureOccurred*(formatter: OutputFormatter, checkpoints: seq[string], stackTrace: string) {.base, gcsafe.} =
+method failureOccurred*(formatter: OutputFormatter, checkpoints: seq[string],
+    stackTrace: string) {.base, gcsafe.} =
   ## ``stackTrace`` is provided only if the failure occurred due to an exception.
   ## ``checkpoints`` is never ``nil``.
   discard
@@ -176,45 +211,57 @@ method suiteEnded*(formatter: OutputFormatter) {.base, gcsafe.} =
   discard
 
 proc addOutputFormatter*(formatter: OutputFormatter) =
-  if formatters == nil:
-    formatters = @[formatter]
-  else:
-    formatters.add(formatter)
+  formatters.add(formatter)
+
+proc delOutputFormatter*(formatter: OutputFormatter) =
+  keepIf(formatters, proc (x: OutputFormatter): bool =
+    x != formatter)
 
-proc newConsoleOutputFormatter*(outputLevel: OutputLevel = PRINT_ALL,
+proc resetOutputFormatters* {.since: (1, 1).} =
+  formatters = @[]
+
+proc newConsoleOutputFormatter*(outputLevel: OutputLevel = outputLevelDefault,
                                 colorOutput = true): ConsoleOutputFormatter =
   ConsoleOutputFormatter(
     outputLevel: outputLevel,
     colorOutput: colorOutput
   )
 
-proc defaultConsoleFormatter*(): ConsoleOutputFormatter =
+proc colorOutput(): bool =
+  let color = nimUnittestColor
+  case color
+  of "auto":
+    when declared(stdout): result = isatty(stdout)
+    else: result = false
+  of "on": result = true
+  of "off": result = false
+  else: raiseAssert $color
+
   when declared(stdout):
-    # Reading settings
-    # On a terminal this branch is executed
-    var envOutLvl = os.getEnv("NIMTEST_OUTPUT_LVL").string
-    var colorOutput  = isatty(stdout)
     if existsEnv("NIMTEST_COLOR"):
-      let colorEnv = getenv("NIMTEST_COLOR")
+      deprecateEnvVarHere()
+      let colorEnv = getEnv("NIMTEST_COLOR")
       if colorEnv == "never":
-        colorOutput = false
+        result = false
       elif colorEnv == "always":
-        colorOutput = true
+        result = true
     elif existsEnv("NIMTEST_NO_COLOR"):
-      colorOutput = false
-    var outputLevel = PRINT_ALL
-    if envOutLvl.len > 0:
-      for opt in countup(low(OutputLevel), high(OutputLevel)):
-        if $opt == envOutLvl:
-          outputLevel = opt
-          break
-    result = newConsoleOutputFormatter(outputLevel, colorOutput)
-  else:
-    result = newConsoleOutputFormatter()
+      deprecateEnvVarHere()
+      result = false
+
+proc defaultConsoleFormatter*(): ConsoleOutputFormatter =
+  var colorOutput = colorOutput()
+  var outputLevel = nimUnittestOutputLevel.parseEnum[:OutputLevel]
+  when declared(stdout):
+    const a = "NIMTEST_OUTPUT_LVL"
+    if existsEnv(a):
+      # xxx issue a warning to deprecate this envvar.
+      outputLevel = getEnv(a).parseEnum[:OutputLevel]
+  result = newConsoleOutputFormatter(outputLevel, colorOutput)
 
 method suiteStarted*(formatter: ConsoleOutputFormatter, suiteName: string) =
   template rawPrint() = echo("\n[Suite] ", suiteName)
-  when not defined(ECMAScript):
+  when useTerminal:
     if formatter.colorOutput:
       styledEcho styleBright, fgBlue, "\n[Suite] ", resetStyle, suiteName
     else: rawPrint()
@@ -224,8 +271,9 @@ method suiteStarted*(formatter: ConsoleOutputFormatter, suiteName: string) =
 method testStarted*(formatter: ConsoleOutputFormatter, testName: string) =
   formatter.isInTest = true
 
-method failureOccurred*(formatter: ConsoleOutputFormatter, checkpoints: seq[string], stackTrace: string) =
-  if stackTrace != nil:
+method failureOccurred*(formatter: ConsoleOutputFormatter,
+                        checkpoints: seq[string], stackTrace: string) =
+  if stackTrace.len > 0:
     echo stackTrace
   let prefix = if formatter.isInSuite: "    " else: ""
   for msg in items(checkpoints):
@@ -234,18 +282,19 @@ method failureOccurred*(formatter: ConsoleOutputFormatter, checkpoints: seq[stri
 method testEnded*(formatter: ConsoleOutputFormatter, testResult: TestResult) =
   formatter.isInTest = false
 
-  if formatter.outputLevel != PRINT_NONE and
-     (formatter.outputLevel == PRINT_ALL or testResult.status == FAILED):
-    let prefix = if testResult.suiteName != nil: "  " else: ""
-    template rawPrint() = echo(prefix, "[", $testResult.status, "] ", testResult.testName)
-    when not defined(ECMAScript):
-      if formatter.colorOutput and not defined(ECMAScript):
+  if formatter.outputLevel != OutputLevel.PRINT_NONE and
+      (formatter.outputLevel == OutputLevel.PRINT_ALL or testResult.status == TestStatus.FAILED):
+    let prefix = if testResult.suiteName.len > 0: "  " else: ""
+    template rawPrint() = echo(prefix, "[", $testResult.status, "] ",
+        testResult.testName)
+    when useTerminal:
+      if formatter.colorOutput:
         var color = case testResult.status
-                    of OK: fgGreen
-                    of FAILED: fgRed
-                    of SKIPPED: fgYellow
-                    else: fgWhite
-        styledEcho styleBright, color, prefix, "[", $testResult.status, "] ", resetStyle, testResult.testName
+          of TestStatus.OK: fgGreen
+          of TestStatus.FAILED: fgRed
+          of TestStatus.SKIPPED: fgYellow
+        styledEcho styleBright, color, prefix, "[", $testResult.status, "] ",
+            resetStyle, testResult.testName
       else:
         rawPrint()
     else:
@@ -297,23 +346,25 @@ method testStarted*(formatter: JUnitOutputFormatter, testName: string) =
   formatter.testStackTrace.setLen(0)
   formatter.testStartTime = epochTime()
 
-method failureOccurred*(formatter: JUnitOutputFormatter, checkpoints: seq[string], stackTrace: string) =
+method failureOccurred*(formatter: JUnitOutputFormatter,
+                        checkpoints: seq[string], stackTrace: string) =
   ## ``stackTrace`` is provided only if the failure occurred due to an exception.
   ## ``checkpoints`` is never ``nil``.
   formatter.testErrors.add(checkpoints)
-  if stackTrace != nil:
+  if stackTrace.len > 0:
     formatter.testStackTrace = stackTrace
 
 method testEnded*(formatter: JUnitOutputFormatter, testResult: TestResult) =
   let time = epochTime() - formatter.testStartTime
   let timeStr = time.formatFloat(ffDecimal, precision = 8)
-  formatter.stream.writeLine("\t\t<testcase name=\"$#\" time=\"$#\">" % [xmlEscape(testResult.testName), timeStr])
-  case testResult.status:
-  of OK:
+  formatter.stream.writeLine("\t\t<testcase name=\"$#\" time=\"$#\">" % [
+      xmlEscape(testResult.testName), timeStr])
+  case testResult.status
+  of TestStatus.OK:
     discard
-  of SKIPPED:
+  of TestStatus.SKIPPED:
     formatter.stream.writeLine("<skipped />")
-  of FAILED:
+  of TestStatus.FAILED:
     let failureMsg = if formatter.testStackTrace.len > 0 and
                         formatter.testErrors.len > 0:
                        xmlEscape(formatter.testErrors[^1])
@@ -324,8 +375,9 @@ method testEnded*(formatter: JUnitOutputFormatter, testResult: TestResult) =
     var errs = ""
     if formatter.testErrors.len > 1:
       var startIdx = if formatter.testStackTrace.len > 0: 0 else: 1
-      var endIdx = if formatter.testStackTrace.len > 0: formatter.testErrors.len - 2
-                   else: formatter.testErrors.len - 1
+      var endIdx = if formatter.testStackTrace.len > 0:
+          formatter.testErrors.len - 2
+        else: formatter.testErrors.len - 1
 
       for errIdx in startIdx..endIdx:
         if errs.len > 0:
@@ -333,11 +385,13 @@ method testEnded*(formatter: JUnitOutputFormatter, testResult: TestResult) =
         errs.add(xmlEscape(formatter.testErrors[errIdx]))
 
     if formatter.testStackTrace.len > 0:
-      formatter.stream.writeLine("\t\t\t<error message=\"$#\">$#</error>" % [failureMsg, xmlEscape(formatter.testStackTrace)])
+      formatter.stream.writeLine("\t\t\t<error message=\"$#\">$#</error>" % [
+          failureMsg, xmlEscape(formatter.testStackTrace)])
       if errs.len > 0:
         formatter.stream.writeLine("\t\t\t<system-err>$#</system-err>" % errs)
     else:
-      formatter.stream.writeLine("\t\t\t<failure message=\"$#\">$#</failure>" % [failureMsg, errs])
+      formatter.stream.writeLine("\t\t\t<failure message=\"$#\">$#</failure>" %
+          [failureMsg, errs])
 
   formatter.stream.writeLine("\t\t</testcase>")
 
@@ -352,15 +406,16 @@ proc glob(matcher, filter: string): bool =
   if not filter.contains('*'):
     return matcher == filter
 
-  let beforeAndAfter = filter.split('*', maxsplit=1)
+  let beforeAndAfter = filter.split('*', maxsplit = 1)
   if beforeAndAfter.len == 1:
     # "foo*"
-    return matcher.startswith(beforeAndAfter[0])
+    return matcher.startsWith(beforeAndAfter[0])
 
   if matcher.len < filter.len - 1:
-    return false  # "12345" should not match "123*345"
+    return false # "12345" should not match "123*345"
 
-  return matcher.startsWith(beforeAndAfter[0]) and matcher.endsWith(beforeAndAfter[1])
+  return matcher.startsWith(beforeAndAfter[0]) and matcher.endsWith(
+      beforeAndAfter[1])
 
 proc matchFilter(suiteName, testName, filter: string): bool =
   if filter == "":
@@ -368,16 +423,15 @@ proc matchFilter(suiteName, testName, filter: string): bool =
   if testName == filter:
     # corner case for tests containing "::" in their name
     return true
-  let suiteAndTestFilters = filter.split("::", maxsplit=1)
+  let suiteAndTestFilters = filter.split("::", maxsplit = 1)
 
   if suiteAndTestFilters.len == 1:
     # no suite specified
-    let test_f = suiteAndTestFilters[0]
-    return glob(testName, test_f)
+    let testFilter = suiteAndTestFilters[0]
+    return glob(testName, testFilter)
 
-  return glob(suiteName, suiteAndTestFilters[0]) and glob(testName, suiteAndTestFilters[1])
-
-when defined(testing): export matchFilter
+  return glob(suiteName, suiteAndTestFilters[0]) and
+         glob(testName, suiteAndTestFilters[1])
 
 proc shouldRun(currentSuiteName, testName: string): bool =
   ## Check if a test should be run by matching suiteName and testName against
@@ -392,11 +446,10 @@ proc shouldRun(currentSuiteName, testName: string): bool =
   return false
 
 proc ensureInitialized() =
-  if formatters == nil:
+  if formatters.len == 0:
     formatters = @[OutputFormatter(defaultConsoleFormatter())]
 
-  if not disabledParamFiltering and not testsFilters.isValid:
-    testsFilters.init()
+  if not disabledParamFiltering:
     when declared(paramCount):
       # Read tests to run from the command line.
       for i in 1 .. paramCount():
@@ -420,27 +473,26 @@ template suite*(name, body) {.dirty.} =
   ## common fixture (``setup``, ``teardown``). The fixture is executed
   ## for EACH test.
   ##
-  ## .. code-block:: nim
-  ##  suite "test suite for addition":
-  ##    setup:
-  ##      let result = 4
+  ##   ```nim
+  ##   suite "test suite for addition":
+  ##     setup:
+  ##       let result = 4
   ##
-  ##    test "2 + 2 = 4":
-  ##      check(2+2 == result)
+  ##     test "2 + 2 = 4":
+  ##       check(2+2 == result)
   ##
-  ##    test "(2 + -2) != 4":
-  ##      check(2 + -2 != result)
+  ##     test "(2 + -2) != 4":
+  ##       check(2 + -2 != result)
   ##
-  ##    # No teardown needed
+  ##     # No teardown needed
+  ##   ```
   ##
   ## The suite will run the individual test cases in the order in which
   ## they were listed. With default global settings the above code prints:
   ##
-  ## .. code-block::
-  ##
-  ##  [Suite] test suite for addition
-  ##    [OK] 2 + 2 = 4
-  ##    [OK] (2 + -2) != 4
+  ##     [Suite] test suite for addition
+  ##       [OK] 2 + 2 = 4
+  ##       [OK] (2 + -2) != 4
   bind formatters, ensureInitialized, suiteEnded
 
   block:
@@ -462,71 +514,81 @@ template suite*(name, body) {.dirty.} =
     finally:
       suiteEnded()
 
-template exceptionTypeName(e: typed): string = $e.name
+proc exceptionTypeName(e: ref Exception): string {.inline.} =
+  if e == nil: "<foreign exception>"
+  else: $e.name
+
+when not declared(setProgramResult):
+  {.warning: "setProgramResult not available on platform, unittest will not" &
+    " give failing exit code on test failure".}
+  template setProgramResult(a: int) =
+    discard
 
 template test*(name, body) {.dirty.} =
   ## Define a single test case identified by `name`.
   ##
-  ## .. code-block:: nim
-  ##
-  ##  test "roses are red":
-  ##    let roses = "red"
-  ##    check(roses == "red")
+  ##   ```nim
+  ##   test "roses are red":
+  ##     let roses = "red"
+  ##     check(roses == "red")
+  ##   ```
   ##
   ## The above code outputs:
   ##
-  ## .. code-block::
-  ##
-  ##  [OK] roses are red
-  bind shouldRun, checkpoints, formatters, ensureInitialized, testEnded, exceptionTypeName
+  ##     [OK] roses are red
+  bind shouldRun, checkpoints, formatters, ensureInitialized, testEnded, exceptionTypeName, setProgramResult
 
   ensureInitialized()
 
   if shouldRun(when declared(testSuiteName): testSuiteName else: "", name):
     checkpoints = @[]
-    var testStatusIMPL {.inject.} = OK
+    var testStatusIMPL {.inject.} = TestStatus.OK
 
     for formatter in formatters:
       formatter.testStarted(name)
 
+    {.push warning[BareExcept]:off.}
     try:
       when declared(testSetupIMPLFlag): testSetupIMPL()
       when declared(testTeardownIMPLFlag):
         defer: testTeardownIMPL()
+      {.push warning[BareExcept]:on.}
       body
+      {.pop.}
 
     except:
-      when not defined(js):
-        let e = getCurrentException()
-        let eTypeDesc = "[" & exceptionTypeName(e) & "]"
-        checkpoint("Unhandled exception: " & getCurrentExceptionMsg() & " " & eTypeDesc)
+      let e = getCurrentException()
+      let eTypeDesc = "[" & exceptionTypeName(e) & "]"
+      checkpoint("Unhandled exception: " & getCurrentExceptionMsg() & " " & eTypeDesc)
+      if e == nil: # foreign
+        fail()
+      else:
         var stackTrace {.inject.} = e.getStackTrace()
-      fail()
+        fail()
 
     finally:
-      if testStatusIMPL == FAILED:
-        programResult += 1
+      if testStatusIMPL == TestStatus.FAILED:
+        setProgramResult 1
       let testResult = TestResult(
-        suiteName: when declared(testSuiteName): testSuiteName else: nil,
+        suiteName: when declared(testSuiteName): testSuiteName else: "",
         testName: name,
         status: testStatusIMPL
       )
       testEnded(testResult)
       checkpoints = @[]
+    {.pop.}
 
 proc checkpoint*(msg: string) =
   ## Set a checkpoint identified by `msg`. Upon test failure all
   ## checkpoints encountered so far are printed out. Example:
   ##
-  ## .. code-block:: nim
-  ##
-  ##  checkpoint("Checkpoint A")
-  ##  check((42, "the Answer to life and everything") == (1, "a"))
-  ##  checkpoint("Checkpoint B")
+  ##   ```nim
+  ##   checkpoint("Checkpoint A")
+  ##   check((42, "the Answer to life and everything") == (1, "a"))
+  ##   checkpoint("Checkpoint B")
+  ##   ```
   ##
   ## outputs "Checkpoint A" once it fails.
-  if checkpoints == nil:
-    checkpoints = @[]
   checkpoints.add(msg)
   # TODO: add support for something like SCOPED_TRACE from Google Test
 
@@ -536,19 +598,18 @@ template fail* =
   ## failed (change exit code and test status). This template is useful
   ## for debugging, but is otherwise mostly used internally. Example:
   ##
-  ## .. code-block:: nim
-  ##
-  ##  checkpoint("Checkpoint A")
-  ##  complicatedProcInThread()
-  ##  fail()
+  ##   ```nim
+  ##   checkpoint("Checkpoint A")
+  ##   complicatedProcInThread()
+  ##   fail()
+  ##   ```
   ##
   ## outputs "Checkpoint A" before quitting.
-  bind ensureInitialized
-
+  bind ensureInitialized, setProgramResult
   when declared(testStatusIMPL):
-    testStatusIMPL = FAILED
+    testStatusIMPL = TestStatus.FAILED
   else:
-    programResult += 1
+    setProgramResult 1
 
   ensureInitialized()
 
@@ -557,10 +618,9 @@ template fail* =
     when declared(stackTrace):
       formatter.failureOccurred(checkpoints, stackTrace)
     else:
-      formatter.failureOccurred(checkpoints, nil)
+      formatter.failureOccurred(checkpoints, "")
 
-  when not defined(ECMAScript):
-    if abortOnError: quit(programResult)
+  if abortOnError: quit(1)
 
   checkpoints = @[]
 
@@ -570,33 +630,30 @@ template skip* =
   ## for reasons depending on outer environment,
   ## or certain application logic conditions or configurations.
   ## The test code is still executed.
-  ##
-  ## .. code-block:: nim
-  ##
-  ##  if not isGLConextCreated():
-  ##    skip()
+  ##   ```nim
+  ##   if not isGLContextCreated():
+  ##     skip()
+  ##   ```
   bind checkpoints
 
-  testStatusIMPL = SKIPPED
+  testStatusIMPL = TestStatus.SKIPPED
   checkpoints = @[]
 
 macro check*(conditions: untyped): untyped =
   ## Verify if a statement or a list of statements is true.
   ## A helpful error message and set checkpoints are printed out on
   ## failure (if ``outputLevel`` is not ``PRINT_NONE``).
-  ## Example:
-  ##
-  ## .. code-block:: nim
-  ##
-  ##  import strutils
-  ##
-  ##  check("AKB48".toLowerAscii() == "akb48")
-  ##
-  ##  let teams = {'A', 'K', 'B', '4', '8'}
-  ##
-  ##  check:
-  ##    "AKB48".toLowerAscii() == "akb48"
-  ##    'C' in teams
+  runnableExamples:
+    import std/strutils
+
+    check("AKB48".toLowerAscii() == "akb48")
+
+    let teams = {'A', 'K', 'B', '4', '8'}
+
+    check:
+      "AKB48".toLowerAscii() == "akb48"
+      'C' notin teams
+
   let checked = callsite()[1]
 
   template asgn(a: untyped, value: typed) =
@@ -614,7 +671,7 @@ macro check*(conditions: untyped): untyped =
 
     var counter = 0
 
-    if exp[0].kind == nnkIdent and
+    if exp[0].kind in {nnkIdent, nnkOpenSymChoice, nnkClosedSymChoice, nnkSym} and
         $exp[0] in ["not", "in", "notin", "==", "<=",
                     ">=", "<", ">", "!=", "is", "isnot"]:
 
@@ -625,17 +682,18 @@ macro check*(conditions: untyped): untyped =
           let paramAst = exp[i]
           if exp[i].kind == nnkIdent:
             result.printOuts.add getAst(print(argStr, paramAst))
-          if exp[i].kind in nnkCallKinds + { nnkDotExpr, nnkBracketExpr }:
+          if exp[i].kind in nnkCallKinds + {nnkDotExpr, nnkBracketExpr, nnkPar} and
+                  (exp[i].typeKind notin {ntyTypeDesc} or $exp[0] notin ["is", "isnot"]):
             let callVar = newIdentNode(":c" & $counter)
             result.assigns.add getAst(asgn(callVar, paramAst))
             result.check[i] = callVar
             result.printOuts.add getAst(print(argStr, callVar))
           if exp[i].kind == nnkExprEqExpr:
             # ExprEqExpr
-            #   Ident !"v"
+            #   Ident "v"
             #   IntLit 2
             result.check[i] = exp[i][1]
-          if exp[i].typekind notin {ntyTypeDesc}:
+          if exp[i].typeKind notin {ntyTypeDesc}:
             let arg = newIdentNode(":p" & $counter)
             result.assigns.add getAst(asgn(arg, paramAst))
             result.printOuts.add getAst(print(argStr, arg))
@@ -648,12 +706,14 @@ macro check*(conditions: untyped): untyped =
   of nnkCallKinds:
 
     let (assigns, check, printOuts) = inspectArgs(checked)
-    let lineinfo = newStrLitNode(checked.lineinfo)
+    let lineinfo = newStrLitNode(checked.lineInfo)
     let callLit = checked.toStrLit
     result = quote do:
       block:
         `assigns`
-        if not `check`:
+        if `check`:
+          discard
+        else:
           checkpoint(`lineinfo` & ": Check failed: " & `callLit`)
           `printOuts`
           fail()
@@ -662,14 +722,16 @@ macro check*(conditions: untyped): untyped =
     result = newNimNode(nnkStmtList)
     for node in checked:
       if node.kind != nnkCommentStmt:
-        result.add(newCall(!"check", node))
+        result.add(newCall(newIdentNode("check"), node))
 
   else:
-    let lineinfo = newStrLitNode(checked.lineinfo)
+    let lineinfo = newStrLitNode(checked.lineInfo)
     let callLit = checked.toStrLit
 
     result = quote do:
-      if not `checked`:
+      if `checked`:
+        discard
+      else:
         checkpoint(`lineinfo` & ": Check failed: " & `callLit`)
         fail()
 
@@ -687,40 +749,40 @@ macro expect*(exceptions: varargs[typed], body: untyped): untyped =
   ## Test if `body` raises an exception found in the passed `exceptions`.
   ## The test passes if the raised exception is part of the acceptable
   ## exceptions. Otherwise, it fails.
-  ## Example:
-  ##
-  ## .. code-block:: nim
-  ##
-  ##  import math, random
-  ##  proc defectiveRobot() =
-  ##    randomize()
-  ##    case random(1..4)
-  ##    of 1: raise newException(OSError, "CANNOT COMPUTE!")
-  ##    of 2: discard parseInt("Hello World!")
-  ##    of 3: raise newException(IOError, "I can't do that Dave.")
-  ##    else: assert 2 + 2 == 5
-  ##
-  ##  expect IOError, OSError, ValueError, AssertionError:
-  ##    defectiveRobot()
-  let exp = callsite()
+  runnableExamples:
+    import std/[math, random, strutils]
+    proc defectiveRobot() =
+      randomize()
+      case rand(1..4)
+      of 1: raise newException(OSError, "CANNOT COMPUTE!")
+      of 2: discard parseInt("Hello World!")
+      of 3: raise newException(IOError, "I can't do that Dave.")
+      else: assert 2 + 2 == 5
+
+    expect IOError, OSError, ValueError, AssertionDefect:
+      defectiveRobot()
+
   template expectBody(errorTypes, lineInfoLit, body): NimNode {.dirty.} =
+    {.push warning[BareExcept]:off.}
     try:
+      {.push warning[BareExcept]:on.}
       body
+      {.pop.}
       checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.")
       fail()
     except errorTypes:
       discard
     except:
-      checkpoint(lineInfoLit & ": Expect Failed, unexpected exception was thrown.")
+      let err = getCurrentException()
+      checkpoint(lineInfoLit & ": Expect Failed, " & $err.name & " was thrown.")
       fail()
-
-  var body = exp[exp.len - 1]
+    {.pop.}
 
   var errorTypes = newNimNode(nnkBracket)
-  for i in countup(1, exp.len - 2):
-    errorTypes.add(exp[i])
+  for exp in exceptions:
+    errorTypes.add(exp)
 
-  result = getAst(expectBody(errorTypes, exp.lineinfo, body))
+  result = getAst(expectBody(errorTypes, errorTypes.lineInfo, body))
 
 proc disableParamFiltering* =
   ## disables filtering tests with the command line params