document unittest.nim + code agreement

This commit is contained in:
fenekku 2015-07-09 13:22:26 -04:00
commit 464ec61e9c
2 changed files with 145 additions and 13 deletions

View file

@ -9,7 +9,7 @@
## :Author: Zahary Karadjov ## :Author: Zahary Karadjov
## ##
## This module implements boilerplate to make testing easy. ## This module implements boilerplate to make unit testing easy.
## ##
## Example: ## Example:
## ##
@ -41,27 +41,69 @@ when not defined(ECMAScript):
import terminal import terminal
type type
TestStatus* = enum OK, FAILED TestStatus* = enum OK, FAILED ## The status of a test when it is done.
OutputLevel* = enum PRINT_ALL, PRINT_FAILURES, PRINT_NONE 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]} {.deprecated: [TTestStatus: TestStatus, TOutputLevel: OutputLevel]}
var var ## Global unittest settings!
abortOnError* {.threadvar.}: bool
outputLevel* {.threadvar.}: OutputLevel abortOnError* {.threadvar.}: bool ## Set to true in order to quit
colorOutput* {.threadvar.}: bool ## 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 {.threadvar.}: seq[string]
checkpoints = @[] 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 template testTeardownIMPL*: stmt {.immediate, dirty.} = discard
proc shouldRun(testName: string): bool = proc shouldRun(testName: string): bool =
result = true result = true
template suite*(name: expr, body: stmt): stmt {.immediate, dirty.} = 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: block:
template setup*(setupBody: stmt): stmt {.immediate, dirty.} = template setup*(setupBody: stmt): stmt {.immediate, dirty.} =
template testSetupIMPL: stmt {.immediate, dirty.} = setupBody template testSetupIMPL: stmt {.immediate, dirty.} = setupBody
@ -87,6 +129,19 @@ proc testDone(name: string, s: TestStatus) =
rawPrint() rawPrint()
template test*(name: expr, body: stmt): stmt {.immediate, dirty.} = 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 bind shouldRun, checkpoints, testDone
if shouldRun(name): if shouldRun(name):
@ -108,10 +163,32 @@ template test*(name: expr, body: stmt): stmt {.immediate, dirty.} =
testDone name, testStatusIMPL testDone name, testStatusIMPL
proc checkpoint*(msg: string) = 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) checkpoints.add(msg)
# TODO: add support for something like SCOPED_TRACE from Google Test # TODO: add support for something like SCOPED_TRACE from Google Test
template fail* = 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 bind checkpoints
for msg in items(checkpoints): for msg in items(checkpoints):
echo msg echo msg
@ -127,8 +204,23 @@ template fail* =
checkpoints = @[] checkpoints = @[]
macro check*(conditions: stmt): stmt {.immediate.} = 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] let checked = callsite()[1]
var var
argsAsgns = newNimNode(nnkStmtList) argsAsgns = newNimNode(nnkStmtList)
argsPrintOuts = newNimNode(nnkStmtList) argsPrintOuts = newNimNode(nnkStmtList)
@ -143,7 +235,7 @@ macro check*(conditions: stmt): stmt {.immediate.} =
checkpoint(name & " was " & $value) checkpoint(name & " was " & $value)
proc inspectArgs(exp: NimNode) = proc inspectArgs(exp: NimNode) =
for i in 1 .. <exp.len: for i in countup(1, exp.len - 1):
if exp[i].kind notin nnkLiterals: if exp[i].kind notin nnkLiterals:
inc counter inc counter
var arg = newIdentNode(":p" & $counter) var arg = newIdentNode(":p" & $counter)
@ -194,11 +286,34 @@ macro check*(conditions: stmt): stmt {.immediate.} =
result = getAst(rewrite(checked, checked.lineinfo, checked.toStrLit)) result = getAst(rewrite(checked, checked.lineinfo, checked.toStrLit))
template require*(conditions: stmt): stmt {.immediate, dirty.} = 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: block:
const AbortOnError {.inject.} = true abortOnError = true
check conditions check conditions
abortOnError = savedAbortOnError
macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} = 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() let exp = callsite()
template expectBody(errorTypes, lineInfoLit: expr, template expectBody(errorTypes, lineInfoLit: expr,
body: stmt): NimNode {.dirty.} = body: stmt): NimNode {.dirty.} =
@ -208,6 +323,9 @@ macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} =
fail() fail()
except errorTypes: except errorTypes:
discard discard
except:
checkpoint(lineInfoLit & ": Expect Failed, unexpected exception was thrown.")
fail()
var body = exp[exp.len - 1] var body = exp[exp.len - 1]
@ -219,9 +337,9 @@ macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} =
when declared(stdout): when declared(stdout):
## Reading settings # Reading settings
# On a terminal this branch is executed
var envOutLvl = os.getEnv("NIMTEST_OUTPUT_LVL").string var envOutLvl = os.getEnv("NIMTEST_OUTPUT_LVL").string
abortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR") abortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR")
colorOutput = not existsEnv("NIMTEST_NO_COLOR") colorOutput = not existsEnv("NIMTEST_NO_COLOR")

View file

@ -19,3 +19,17 @@ import options
test "unittest typedescs": test "unittest typedescs":
check(none(int) == none(int)) check(none(int) == none(int))
check(none(int) != some(1)) check(none(int) != some(1))
import math
from strutils import parseInt
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
test "unittest expect":
expect IOError, OSError, ValueError, AssertionError:
defectiveRobot()