summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
authorfenekku <fenekku@fenekku.com>2015-07-09 13:22:26 -0400
committerfenekku <fenekku@fenekku.com>2015-07-09 13:22:26 -0400
commit464ec61e9c11deb1a52df431a266ba50bf132304 (patch)
tree1ac53b7c4ca2dec3a2221b83cf0c3d33dbcf95e1 /lib
parent7aa227ba79b6b363ab4b75a2b16ad1d60f51e49a (diff)
downloadNim-464ec61e9c11deb1a52df431a266ba50bf132304.tar.gz
document unittest.nim + code agreement
Diffstat (limited to 'lib')
-rw-r--r--lib/pure/unittest.nim144
1 files changed, 131 insertions, 13 deletions
diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim
index 407db0a51..70b314e63 100644
--- a/lib/pure/unittest.nim
+++ b/lib/pure/unittest.nim
@@ -9,7 +9,7 @@
 
 ## :Author: Zahary Karadjov
 ##
-## This module implements boilerplate to make testing easy.
+## This module implements boilerplate to make unit testing easy.
 ##
 ## Example:
 ##
@@ -41,27 +41,69 @@ when not defined(ECMAScript):
   import terminal
 
 type
-  TestStatus* = enum OK, FAILED
-  OutputLevel* = enum PRINT_ALL, PRINT_FAILURES, PRINT_NONE
+  TestStatus* = enum OK, FAILED ## The status of a test when it is done.
+  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.
 
 {.deprecated: [TTestStatus: TestStatus, TOutputLevel: OutputLevel]}
 
-var
-  abortOnError* {.threadvar.}: bool
-  outputLevel* {.threadvar.}: OutputLevel
-  colorOutput* {.threadvar.}: bool
+var ## Global unittest settings!
+
+  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.
+  outputLevel* {.threadvar.}: 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.
+
+  colorOutput* {.threadvar.}: bool ## Have test results printed in color.
+                                   ## Default is true for the non-js target
+                                   ## unless, the environment variable
+                                   ## ``NIMTEST_NO_COLOR`` is set.
 
   checkpoints {.threadvar.}: seq[string]
 
 checkpoints = @[]
 
-template testSetupIMPL*: stmt {.immediate, dirty.} = discard
+template testSetupIMPL*: stmt {.immediate, dirty.} = discard      #Should this be public or even exist?
 template testTeardownIMPL*: stmt {.immediate, dirty.} = discard
 
 proc shouldRun(testName: string): bool =
   result = true
 
 template suite*(name: expr, body: stmt): stmt {.immediate, dirty.} =
+  ## Declare a test suite identified by `name` with optional ``setup``
+  ## and/or ``teardown`` section.
+  ##
+  ## A test suite is a series of one or more related tests sharing a
+  ## common fixture (``setup``, ``teardown``). The fixture is executed
+  ## for EACH test.
+  ##
+  ## .. code-block:: 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)
+  ##
+  ##    # 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::
+  ##
+  ##  [OK] 2 + 2 = 4
+  ##  [OK] (2 + -2) != 4
   block:
     template setup*(setupBody: stmt): stmt {.immediate, dirty.} =
       template testSetupIMPL: stmt {.immediate, dirty.} = setupBody
@@ -87,6 +129,19 @@ proc testDone(name: string, s: TestStatus) =
       rawPrint()
 
 template test*(name: expr, body: stmt): stmt {.immediate, dirty.} =
+  ## Define a single test case identified by `name`.
+  ##
+  ## .. code-block:: nim
+  ##
+  ##  test "roses are red":
+  ##    let roses = "red"
+  ##    check(roses == "red")
+  ##
+  ## The above code outputs:
+  ##
+  ## .. code-block::
+  ##
+  ##  [OK] roses are red
   bind shouldRun, checkpoints, testDone
 
   if shouldRun(name):
@@ -108,10 +163,32 @@ template test*(name: expr, body: stmt): stmt {.immediate, dirty.} =
       testDone name, testStatusIMPL
 
 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")
+  ##
+  ## outputs "Checkpoint A" once it fails.
   checkpoints.add(msg)
   # TODO: add support for something like SCOPED_TRACE from Google Test
 
 template fail* =
+  ## Print out the checkpoints encountered so far and quit if ``abortOnError``
+  ## is true. Otherwise, erase the checkpoints and indicate the test has
+  ## 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()
+  ##
+  ## outputs "Checkpoint A" before quitting.
   bind checkpoints
   for msg in items(checkpoints):
     echo msg
@@ -127,8 +204,23 @@ template fail* =
   checkpoints = @[]
 
 macro check*(conditions: stmt): stmt {.immediate.} =
+  ## 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".toLower() == "akb48")
+  ##
+  ##  let teams = {'A', 'K', 'B', '4', '8'}
+  ##
+  ##  check:
+  ##    "AKB48".toLower() == "akb48"
+  ##    'C' in teams
   let checked = callsite()[1]
-
   var
     argsAsgns = newNimNode(nnkStmtList)
     argsPrintOuts = newNimNode(nnkStmtList)
@@ -143,7 +235,7 @@ macro check*(conditions: stmt): stmt {.immediate.} =
       checkpoint(name & " was " & $value)
 
   proc inspectArgs(exp: NimNode) =
-    for i in 1 .. <exp.len:
+    for i in countup(1, exp.len - 1):
       if exp[i].kind notin nnkLiterals:
         inc counter
         var arg = newIdentNode(":p" & $counter)
@@ -194,11 +286,34 @@ macro check*(conditions: stmt): stmt {.immediate.} =
     result = getAst(rewrite(checked, checked.lineinfo, checked.toStrLit))
 
 template require*(conditions: stmt): stmt {.immediate, dirty.} =
+  ## Same as `check` except any failed test causes the program to quit
+  ## immediately. Any teardown statements are not executed and the failed
+  ## test output is not generated.
+  let savedAbortOnError = abortOnError
   block:
-    const AbortOnError {.inject.} = true
+    abortOnError = true
     check conditions
+  abortOnError = savedAbortOnError
 
 macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} =
+  ## 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
+  ##  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()
   template expectBody(errorTypes, lineInfoLit: expr,
                       body: stmt): NimNode {.dirty.} =
@@ -208,6 +323,9 @@ macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} =
       fail()
     except errorTypes:
       discard
+    except:
+      checkpoint(lineInfoLit & ": Expect Failed, unexpected exception was thrown.")
+      fail()
 
   var body = exp[exp.len - 1]
 
@@ -219,9 +337,9 @@ macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} =
 
 
 when declared(stdout):
-  ## Reading settings
+  # Reading settings
+  # On a terminal this branch is executed
   var envOutLvl = os.getEnv("NIMTEST_OUTPUT_LVL").string
-
   abortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR")
   colorOutput  = not existsEnv("NIMTEST_NO_COLOR")