compile-time test support (#34)
This change brings 3 new items to `unittest2`: * `-d:unittest2Static` compile-time flag that enables `test` to run both at compile time and runtime * `staticTest` that only run at compilet ime no matter the flag * `runtimeTest` that only run at run time no matter the flag
This commit is contained in:
parent
91973dfa38
commit
333e74fa2d
|
@ -156,16 +156,10 @@ jobs:
|
|||
- name: Run tests
|
||||
run: |
|
||||
nim --version
|
||||
env TEST_LANG="c" NIMFLAGS="${NIMFLAGS} --gc:refc" nim test
|
||||
env TEST_LANG="cpp" NIMFLAGS="${NIMFLAGS} --gc:refc" nim test
|
||||
if [[ "${{ matrix.branch }}" == "devel" ]]; then
|
||||
echo -e "\nTesting with '--gc:orc':\n"
|
||||
if env TEST_LANG="c" NIMFLAGS="${NIMFLAGS} --gc:orc" nim test; then
|
||||
echo "Nim devel with --gc:orc works again! Please remove this check in ci.yml"
|
||||
false
|
||||
fi
|
||||
if env TEST_LANG="cpp" NIMFLAGS="${NIMFLAGS} --gc:orc" nim test; then
|
||||
echo "Nim devel with --gc:orc works again! Please remove this check in ci.yml"
|
||||
false
|
||||
fi
|
||||
env TEST_LANG="c" NIMFLAGS="${NIMFLAGS}" nim test
|
||||
env TEST_LANG="cpp" NIMFLAGS="${NIMFLAGS}" nim test
|
||||
if [[ "${{ matrix.branch }}" != "version-1-6" ]]; then
|
||||
echo -e "\nTesting with '--mm:refc':\n"
|
||||
TEST_LANG="c" NIMFLAGS="${NIMFLAGS} --mm:refc" nim test
|
||||
TEST_LANG="cpp" NIMFLAGS="${NIMFLAGS} --mm:refc" nim test
|
||||
fi
|
||||
|
|
|
@ -41,12 +41,10 @@ test "unittest typedescs":
|
|||
check(none(int) == none(int))
|
||||
check(none(int) != some(1))
|
||||
|
||||
|
||||
test "unittest multiple requires":
|
||||
require(true)
|
||||
require(true)
|
||||
|
||||
|
||||
import random
|
||||
proc defectiveRobot() =
|
||||
randomize()
|
||||
|
@ -55,7 +53,7 @@ proc defectiveRobot() =
|
|||
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":
|
||||
runtimeTest "unittest expect":
|
||||
expect IOError, OSError, ValueError, AssertionDefect:
|
||||
defectiveRobot()
|
||||
expect CatchableError:
|
||||
|
@ -77,26 +75,26 @@ suite "suite with only teardown":
|
|||
teardown:
|
||||
b = 2
|
||||
|
||||
test "unittest with only teardown 1":
|
||||
runtimeTest "unittest with only teardown 1":
|
||||
check a == c
|
||||
|
||||
test "unittest with only teardown 2":
|
||||
runtimeTest "unittest with only teardown 2":
|
||||
check b > a
|
||||
|
||||
suite "suite with only setup":
|
||||
setup:
|
||||
var testVar {.used.} = "from setup"
|
||||
|
||||
test "unittest with only setup 1":
|
||||
runtimeTest "unittest with only setup 1":
|
||||
check testVar == "from setup"
|
||||
check b > a
|
||||
b = -1
|
||||
|
||||
test "unittest with only setup 2":
|
||||
runtimeTest "unittest with only setup 2":
|
||||
check b < a
|
||||
|
||||
suite "suite with none":
|
||||
test "unittest with none":
|
||||
runtimeTest "unittest with none":
|
||||
check b < a
|
||||
|
||||
suite "suite with both":
|
||||
|
@ -106,10 +104,10 @@ suite "suite with both":
|
|||
teardown:
|
||||
c = 2
|
||||
|
||||
test "unittest with both 1":
|
||||
runtimeTest "unittest with both 1":
|
||||
check b > a
|
||||
|
||||
test "unittest with both 2":
|
||||
runtimeTest "unittest with both 2":
|
||||
check c == 2
|
||||
|
||||
suite "bug #4494":
|
||||
|
@ -187,4 +185,3 @@ when defined(testing):
|
|||
|
||||
# Also supposed to work outside tests:
|
||||
check 1 == 1
|
||||
|
||||
|
|
256
unittest2.nim
256
unittest2.nim
|
@ -1,6 +1,5 @@
|
|||
# unittest2
|
||||
#
|
||||
#
|
||||
# Nim's Runtime Library
|
||||
# (c) Copyright 2015 Nim Contributors
|
||||
# (c) Copyright 2019-2021 Ștefan Talpalaru
|
||||
# (c) Copyright 2021-Onwards Status Research and Development
|
||||
|
@ -156,6 +155,11 @@ const
|
|||
unittest2PreviewIsolate {.booldefine.} = false
|
||||
## Preview isolation mode where each test is run in a separate process - may
|
||||
## be removed in the future
|
||||
unittest2Static* {.booldefine.} = false
|
||||
## Run tests at compile time as well - only a subset of functionality is
|
||||
## enabled at compile-time meaning that tests must be written
|
||||
## conservatively. `suite` features (`setup` etc) in particular are not
|
||||
## supported.
|
||||
|
||||
when useTerminal:
|
||||
import std/terminal
|
||||
|
@ -406,6 +410,23 @@ const
|
|||
maxStatusLen = 7
|
||||
maxDurationLen = 6
|
||||
|
||||
func formatStatus(status: string): string =
|
||||
"[" & alignLeft(status, maxStatusLen) & "]"
|
||||
|
||||
func formatStatus(status: TestStatus): string =
|
||||
formatStatus($status)
|
||||
|
||||
proc formatDuration(dur: Duration, aligned = true): string =
|
||||
let
|
||||
seconds = dur.inMilliseconds.float / 1000.0
|
||||
precision = max(3 - ($seconds.int).len, 1)
|
||||
str = formatFloat(seconds, ffDecimal, precision)
|
||||
|
||||
if aligned:
|
||||
"(" & align(str, maxDurationLen) & "s)"
|
||||
else:
|
||||
"(" & str & "s)"
|
||||
|
||||
when collect:
|
||||
proc formatFraction(cur, total: int): string =
|
||||
let
|
||||
|
@ -448,7 +469,7 @@ method suiteStarted*(formatter: ConsoleOutputFormatter, suiteName: string) =
|
|||
counter =
|
||||
when collect: formatFraction(formatter.curSuite, formatter.tests.len) & " "
|
||||
else:
|
||||
if formatter.outputLevel == VERBOSE: "[Suite ] " else: ""
|
||||
if formatter.outputLevel == VERBOSE: formatStatus("Suite") & " " else: ""
|
||||
maxNameLen = when collect: max(toSeq(formatter.tests.keys()).mapIt(it.len)) else: 0
|
||||
eol = if formatter.outputLevel == VERBOSE: "\n" else: " "
|
||||
formatter.write do:
|
||||
|
@ -479,7 +500,7 @@ method testStarted*(formatter: ConsoleOutputFormatter, testName: string) =
|
|||
try: formatFraction(formatter.curTest, formatter.tests[formatter.curSuiteName]) & " "
|
||||
except CatchableError: ""
|
||||
else:
|
||||
"[Test ]"
|
||||
formatStatus("Test")
|
||||
|
||||
formatter.write do:
|
||||
stdout.styledWrite " ", fgBlue, alignLeft(counter, maxStatusLen + maxDurationLen + 7)
|
||||
|
@ -510,20 +531,6 @@ proc marker(status: TestStatus): string =
|
|||
of TestStatus.FAILED: "F"
|
||||
of TestStatus.SKIPPED: "s"
|
||||
|
||||
proc formatDuration(dur: Duration, aligned = true): string =
|
||||
let
|
||||
seconds = dur.inMilliseconds.float / 1000.0
|
||||
precision = max(3 - ($seconds.int).len, 1)
|
||||
str = formatFloat(seconds, ffDecimal, precision)
|
||||
|
||||
if aligned:
|
||||
"(" & align(str, maxDurationLen) & "s)"
|
||||
else:
|
||||
"(" & str & "s)"
|
||||
|
||||
proc formatStatus(status: TestStatus): string =
|
||||
"[" & alignLeft($status, maxStatusLen) & "]"
|
||||
|
||||
proc getAppFilename2(): string =
|
||||
# TODO https://github.com/nim-lang/Nim/pull/22544
|
||||
try:
|
||||
|
@ -850,14 +857,17 @@ when defined(testing): export matchFilter
|
|||
proc shouldRun(currentSuiteName, testName: string): bool =
|
||||
## Check if a test should be run by matching suiteName and testName against
|
||||
## test filters.
|
||||
if testsFilters.len == 0:
|
||||
return true
|
||||
|
||||
for f in testsFilters:
|
||||
if matchFilter(currentSuiteName, testName, f):
|
||||
when nimvm:
|
||||
true
|
||||
else:
|
||||
if testsFilters.len == 0:
|
||||
return true
|
||||
|
||||
return false
|
||||
for f in testsFilters:
|
||||
if matchFilter(currentSuiteName, testName, f):
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
proc parseParameters*(args: openArray[string]) =
|
||||
var
|
||||
|
@ -950,16 +960,18 @@ template suite*(nameParam: string, body: untyped) {.dirty.} =
|
|||
var testSuiteTeardownIMPLFlag {.used.} = true
|
||||
template testSuiteTeardownIMPL: untyped {.dirty.} = suiteTeardownBody
|
||||
|
||||
let suiteName {.inject.} = nameParam
|
||||
when nimvm:
|
||||
discard
|
||||
else:
|
||||
let suiteName {.inject.} = nameParam
|
||||
when not collect:
|
||||
# TODO deal with suite nesting
|
||||
if currentSuite.len > 0:
|
||||
suiteEnded()
|
||||
currentSuite.reset()
|
||||
currentSuite = suiteName
|
||||
|
||||
when not collect:
|
||||
# TODO deal with suite nesting
|
||||
if currentSuite.len > 0:
|
||||
suiteEnded()
|
||||
currentSuite.reset()
|
||||
currentSuite = suiteName
|
||||
|
||||
suiteStarted(suiteName)
|
||||
suiteStarted(suiteName)
|
||||
|
||||
# TODO what about exceptions in the suite itself?
|
||||
body
|
||||
|
@ -967,9 +979,12 @@ template suite*(nameParam: string, body: untyped) {.dirty.} =
|
|||
when declared(testSuiteTeardownIMPLFlag):
|
||||
testSuiteTeardownIMPL()
|
||||
|
||||
when not collect:
|
||||
suiteEnded()
|
||||
currentSuite.reset()
|
||||
when nimvm:
|
||||
discard
|
||||
else:
|
||||
when not collect:
|
||||
suiteEnded()
|
||||
currentSuite.reset()
|
||||
|
||||
template checkpoint*(msg: string) =
|
||||
## Set a checkpoint identified by `msg`. Upon test failure all
|
||||
|
@ -982,8 +997,16 @@ template checkpoint*(msg: string) =
|
|||
## checkpoint("Checkpoint B")
|
||||
##
|
||||
## outputs "Checkpoint A" once it fails.
|
||||
checkpoints.add(msg)
|
||||
# TODO: add support for something like SCOPED_TRACE from Google Test
|
||||
when nimvm:
|
||||
when compiles(testName):
|
||||
echo testName
|
||||
|
||||
echo msg
|
||||
else:
|
||||
bind checkpoints
|
||||
|
||||
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``
|
||||
|
@ -998,24 +1021,28 @@ template fail* =
|
|||
## fail()
|
||||
##
|
||||
## outputs "Checkpoint A" before quitting.
|
||||
when declared(testStatusIMPL):
|
||||
testStatusIMPL = TestStatus.FAILED
|
||||
when nimvm:
|
||||
echo "Tests failed"
|
||||
quit 1
|
||||
else:
|
||||
when declared(testStatusIMPL):
|
||||
testStatusIMPL = TestStatus.FAILED
|
||||
|
||||
programResult = 1
|
||||
programResult = 1
|
||||
|
||||
for formatter in formatters:
|
||||
let formatter = formatter # avoid lent iterator
|
||||
when declared(stackTrace):
|
||||
when stackTrace is string:
|
||||
formatter.failureOccurred(checkpoints, stackTrace)
|
||||
for formatter in formatters:
|
||||
let formatter = formatter # avoid lent iterator
|
||||
when declared(stackTrace):
|
||||
when stackTrace is string:
|
||||
formatter.failureOccurred(checkpoints, stackTrace)
|
||||
else:
|
||||
formatter.failureOccurred(checkpoints, "")
|
||||
else:
|
||||
formatter.failureOccurred(checkpoints, "")
|
||||
else:
|
||||
formatter.failureOccurred(checkpoints, "")
|
||||
|
||||
if abortOnError: quit(1)
|
||||
if abortOnError: quit(1)
|
||||
|
||||
checkpoints.reset()
|
||||
checkpoints.reset()
|
||||
|
||||
template skip* =
|
||||
## Mark the test as skipped. Should be used directly
|
||||
|
@ -1028,10 +1055,13 @@ template skip* =
|
|||
##
|
||||
## if not isGLContextCreated():
|
||||
## skip()
|
||||
bind checkpoints
|
||||
when nimvm:
|
||||
discard
|
||||
else:
|
||||
bind checkpoints
|
||||
|
||||
testStatusIMPL = TestStatus.SKIPPED
|
||||
checkpoints = @[]
|
||||
testStatusIMPL = TestStatus.SKIPPED
|
||||
checkpoints = @[]
|
||||
|
||||
proc runDirect(test: Test) =
|
||||
when not collect:
|
||||
|
@ -1063,23 +1093,12 @@ proc runDirect(test: Test) =
|
|||
duration: duration
|
||||
))
|
||||
|
||||
template test*(nameParam: string, body: untyped) =
|
||||
## 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
|
||||
template runtimeTest*(nameParam: string, body: untyped) =
|
||||
## Similar to `test` but runs only at run time, no matter the `unittest2Static`
|
||||
## setting
|
||||
bind collect, runDirect, shouldRun, checkpoints
|
||||
|
||||
proc runTest(suiteName, testName: string): TestStatus {.gensym.} =
|
||||
proc runTest(suiteName, testName: string): TestStatus {.raises: [], gensym.} =
|
||||
var testStatusIMPL {.inject.} = TestStatus.OK
|
||||
let suiteName {.inject, used.} = suiteName
|
||||
let testName {.inject, used.} = testName
|
||||
|
@ -1127,6 +1146,37 @@ template test*(nameParam: string, body: untyped) =
|
|||
else:
|
||||
runDirect(instance)
|
||||
|
||||
|
||||
template staticTest*(nameParam: string, body: untyped) =
|
||||
## Similar to `test` but runs only at compiletime, no matter the
|
||||
## `unittest2Static` setting
|
||||
static:
|
||||
block:
|
||||
echo "[Test ] ", nameParam
|
||||
body
|
||||
echo "[", TestStatus.OK, " ] ", nameParam
|
||||
|
||||
template test*(nameParam: string, body: untyped) =
|
||||
## 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
|
||||
when nimvm:
|
||||
when unittest2Static:
|
||||
staticTest nameParam:
|
||||
body
|
||||
runtimeTest nameParam:
|
||||
body
|
||||
|
||||
{.pop.} # raises: []
|
||||
|
||||
macro check*(conditions: untyped): untyped =
|
||||
|
@ -1194,18 +1244,12 @@ macro check*(conditions: untyped): untyped =
|
|||
else:
|
||||
result.check[i][1] = arg
|
||||
|
||||
let
|
||||
checkpointSym = bindSym("checkpoint")
|
||||
checkSym = bindSym("check")
|
||||
failSym = bindSym("fail")
|
||||
proc buildCheck(lineinfo, callLit, assigns, check, printOuts: NimNode): NimNode =
|
||||
let
|
||||
checkpointSym = bindSym("checkpoint")
|
||||
failSym = bindSym("fail")
|
||||
|
||||
case checked.kind
|
||||
of nnkCallKinds:
|
||||
let (assigns, check, printOuts) = inspectArgs(checked)
|
||||
let lineinfo = newStrLitNode(checked.lineInfo)
|
||||
let callLit = checked.toStrLit
|
||||
let checkpointSym = bindSym("checkpoint")
|
||||
result = nnkBlockStmt.newTree(
|
||||
nnkBlockStmt.newTree(
|
||||
newEmptyNode(),
|
||||
nnkStmtList.newTree(
|
||||
assigns,
|
||||
|
@ -1233,6 +1277,17 @@ macro check*(conditions: untyped): untyped =
|
|||
)
|
||||
)
|
||||
|
||||
let
|
||||
checkSym = bindSym("check")
|
||||
|
||||
case checked.kind
|
||||
of nnkCallKinds:
|
||||
let
|
||||
(assigns, check, printOuts) = inspectArgs(checked)
|
||||
lineinfo = newStrLitNode(checked.lineInfo)
|
||||
callLit = checked.toStrLit
|
||||
result = buildCheck(lineinfo, callLit, assigns, check, printOuts)
|
||||
|
||||
of nnkStmtList:
|
||||
result = newNimNode(nnkStmtList)
|
||||
for node in checked:
|
||||
|
@ -1240,44 +1295,25 @@ macro check*(conditions: untyped): untyped =
|
|||
result.add(newCall(checkSym, node))
|
||||
|
||||
else:
|
||||
let lineinfo = newStrLitNode(checked.lineInfo)
|
||||
let callLit = checked.toStrLit
|
||||
let
|
||||
lineinfo = newStrLitNode(checked.lineInfo)
|
||||
callLit = checked.toStrLit
|
||||
|
||||
result = nnkBlockStmt.newTree(
|
||||
newEmptyNode(),
|
||||
nnkStmtList.newTree(
|
||||
nnkIfStmt.newTree(
|
||||
nnkElifBranch.newTree(
|
||||
nnkCall.newTree(ident("not"), checked),
|
||||
nnkStmtList.newTree(
|
||||
nnkCall.newTree(
|
||||
checkpointSym,
|
||||
nnkInfix.newTree(
|
||||
ident("&"),
|
||||
nnkInfix.newTree(
|
||||
ident("&"),
|
||||
lineinfo,
|
||||
newLit(": Check failed: ")
|
||||
),
|
||||
callLit
|
||||
)
|
||||
),
|
||||
nnkCall.newTree(failSym)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
result = buildCheck(
|
||||
lineinfo, callLit, newEmptyNode(), checked, newEmptyNode())
|
||||
|
||||
template require*(conditions: untyped) =
|
||||
## 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:
|
||||
abortOnError = true
|
||||
when nimvm:
|
||||
check conditions
|
||||
abortOnError = savedAbortOnError
|
||||
else:
|
||||
let savedAbortOnError = abortOnError
|
||||
block:
|
||||
abortOnError = true
|
||||
check conditions
|
||||
abortOnError = savedAbortOnError
|
||||
|
||||
macro expect*(exceptions: varargs[typed], body: untyped): untyped =
|
||||
## Test if `body` raises an exception found in the passed `exceptions`.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
mode = ScriptMode.Verbose
|
||||
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
author = "Status Research & Development GmbH"
|
||||
description = "unittest fork with support for parallel test execution"
|
||||
license = "MIT"
|
||||
|
|
Loading…
Reference in New Issue