mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-05 04:27:44 +00:00
200 lines
5.3 KiB
Nim
200 lines
5.3 KiB
Nim
#
|
|
#
|
|
# Nimrod's Runtime Library
|
|
# (c) Copyright 2012 Nimrod Contributors
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
## :Author: Zahary Karadjov
|
|
##
|
|
## This module implements the standard unit testing facilities such as
|
|
## suites, fixtures and test cases as well as facilities for combinatorial
|
|
## and randomzied test case generation (not yet available)
|
|
## and object mocking (not yet available)
|
|
##
|
|
## It is loosely based on C++'s boost.test and Haskell's QuickTest
|
|
|
|
import
|
|
macros
|
|
|
|
when defined(stdout):
|
|
import os
|
|
|
|
when not defined(ECMAScript):
|
|
import terminal
|
|
|
|
type
|
|
TTestStatus* = enum OK, FAILED
|
|
TOutputLevel* = enum PRINT_ALL, PRINT_FAILURES, PRINT_NONE
|
|
|
|
var
|
|
# XXX: These better be thread-local
|
|
AbortOnError*: bool
|
|
OutputLevel*: TOutputLevel
|
|
ColorOutput*: bool
|
|
|
|
checkpoints: seq[string] = @[]
|
|
|
|
template TestSetupIMPL*: stmt {.immediate, dirty.} = nil
|
|
template TestTeardownIMPL*: stmt {.immediate, dirty.} = nil
|
|
|
|
proc shouldRun(testName: string): bool =
|
|
result = true
|
|
|
|
template suite*(name: expr, body: stmt): stmt {.immediate, dirty.} =
|
|
block:
|
|
template setup*(setupBody: stmt): stmt {.immediate, dirty.} =
|
|
template TestSetupIMPL: stmt {.immediate, dirty.} = setupBody
|
|
|
|
template teardown*(teardownBody: stmt): stmt {.immediate, dirty.} =
|
|
template TestTeardownIMPL: stmt {.immediate, dirty.} = teardownBody
|
|
|
|
body
|
|
|
|
proc testDone(name: string, s: TTestStatus) =
|
|
if s == FAILED:
|
|
program_result += 1
|
|
|
|
if OutputLevel != PRINT_NONE and (OutputLevel == PRINT_ALL or s == FAILED):
|
|
template rawPrint() = echo("[", $s, "] ", name, "\n")
|
|
when not defined(ECMAScript):
|
|
if ColorOutput and not defined(ECMAScript):
|
|
var color = (if s == OK: fgGreen else: fgRed)
|
|
styledEcho styleBright, color, "[", $s, "] ", fgWhite, name, "\n"
|
|
else:
|
|
rawPrint()
|
|
else:
|
|
rawPrint()
|
|
|
|
template test*(name: expr, body: stmt): stmt {.immediate, dirty.} =
|
|
bind shouldRun, checkpoints, testDone
|
|
|
|
if shouldRun(name):
|
|
checkpoints = @[]
|
|
var TestStatusIMPL {.inject.} = OK
|
|
|
|
try:
|
|
TestSetupIMPL()
|
|
body
|
|
|
|
except:
|
|
checkpoint("Unhandled exception: " & getCurrentExceptionMsg())
|
|
fail()
|
|
|
|
finally:
|
|
TestTeardownIMPL()
|
|
testDone name, TestStatusIMPL
|
|
|
|
proc checkpoint*(msg: string) =
|
|
checkpoints.add(msg)
|
|
# TODO: add support for something like SCOPED_TRACE from Google Test
|
|
|
|
template fail* =
|
|
bind checkpoints
|
|
for msg in items(checkpoints):
|
|
echo msg
|
|
|
|
when not defined(ECMAScript):
|
|
if AbortOnError: quit(1)
|
|
|
|
TestStatusIMPL = FAILED
|
|
checkpoints = @[]
|
|
|
|
macro check*(conditions: stmt): stmt {.immediate.} =
|
|
let checked = callsite()[1]
|
|
|
|
var
|
|
argsAsgns = newNimNode(nnkStmtList)
|
|
argsPrintOuts = newNimNode(nnkStmtList)
|
|
counter = 0
|
|
|
|
template asgn(a, value: expr): stmt =
|
|
let a = value
|
|
|
|
template print(name, value: expr): stmt =
|
|
when compiles(string($value)):
|
|
checkpoint(name & " was " & $value)
|
|
|
|
proc inspectArgs(exp: PNimrodNode) =
|
|
for i in 1 .. <exp.len:
|
|
if exp[i].kind notin nnkLiterals:
|
|
inc counter
|
|
var arg = newIdentNode(":p" & ($counter))
|
|
var argStr = exp[i].toStrLit
|
|
if exp[i].kind in nnkCallKinds: inspectArgs(exp[i])
|
|
argsAsgns.add getAst(asgn(arg, exp[i]))
|
|
argsPrintOuts.add getAst(print(argStr, arg))
|
|
exp[i] = arg
|
|
|
|
case checked.kind
|
|
of nnkCallKinds:
|
|
template rewrite(call, lineInfoLit: expr, callLit: string,
|
|
argAssgs, argPrintOuts: stmt): stmt =
|
|
block:
|
|
argAssgs
|
|
if not call:
|
|
checkpoint(lineInfoLit & ": Check failed: " & callLit)
|
|
argPrintOuts
|
|
fail()
|
|
|
|
var checkedStr = checked.toStrLit
|
|
inspectArgs(checked)
|
|
result = getAst(rewrite(checked, checked.lineinfo, checkedStr, argsAsgns, argsPrintOuts))
|
|
|
|
of nnkStmtList:
|
|
result = newNimNode(nnkStmtList)
|
|
for i in countup(0, checked.len - 1):
|
|
result.add(newCall(!"check", checked[i]))
|
|
|
|
else:
|
|
template rewrite(Exp, lineInfoLit: expr, expLit: string): stmt =
|
|
if not Exp:
|
|
checkpoint(lineInfoLit & ": Check failed: " & expLit)
|
|
fail()
|
|
|
|
result = getAst(rewrite(checked, checked.lineinfo, checked.toStrLit))
|
|
|
|
template require*(conditions: stmt): stmt {.immediate, dirty.} =
|
|
block:
|
|
const AbortOnError {.inject.} = true
|
|
check conditions
|
|
|
|
macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} =
|
|
let exp = callsite()
|
|
template expectBody(errorTypes, lineInfoLit: expr,
|
|
body: stmt): PNimrodNode {.dirty.} =
|
|
try:
|
|
body
|
|
checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.")
|
|
fail()
|
|
except errorTypes:
|
|
nil
|
|
|
|
var body = exp[exp.len - 1]
|
|
|
|
var errorTypes = newNimNode(nnkBracket)
|
|
for i in countup(1, exp.len - 2):
|
|
errorTypes.add(exp[i])
|
|
|
|
result = getAst(expectBody(errorTypes, exp.lineinfo, body))
|
|
|
|
|
|
when defined(stdout):
|
|
## Reading settings
|
|
var envOutLvl = os.getEnv("NIMTEST_OUTPUT_LVL").string
|
|
|
|
AbortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR")
|
|
ColorOutput = not existsEnv("NIMTEST_NO_COLOR")
|
|
|
|
else:
|
|
var envOutLvl = "" # TODO
|
|
ColorOutput = false
|
|
|
|
if envOutLvl.len > 0:
|
|
for opt in countup(low(TOutputLevel), high(TOutputLevel)):
|
|
if $opt == envOutLvl:
|
|
OutputLevel = opt
|
|
break
|