initial commit - from https://github.com/nim-lang/Nim/pull/9724
This commit is contained in:
commit
ae4d471d38
|
@ -0,0 +1,2 @@
|
|||
nimcache/
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2011-2018 Zahary Karadjov. All rights reserved.
|
||||
Copyright (c) 2018-2019 Ștefan Talpalaru <stefantalpalaru@yahoo.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
[ MIT license: http://www.opensource.org/licenses/mit-license.php ]
|
|
@ -0,0 +1,36 @@
|
|||
## description
|
||||
|
||||
**unittest2** is a fork of the [unittest](https://nim-lang.org/docs/unittest.html) module in the
|
||||
[Nim](https://nim-lang.org/) standard library, with a focus on parallel test
|
||||
execution.
|
||||
|
||||
This fork was originally a pull request: [https://github.com/nim-lang/Nim/pull/9724](https://github.com/nim-lang/Nim/pull/9724)
|
||||
|
||||
## testing
|
||||
|
||||
```text
|
||||
nimble test
|
||||
```
|
||||
|
||||
## installation
|
||||
|
||||
```text
|
||||
nimble install
|
||||
```
|
||||
|
||||
## usage
|
||||
|
||||
Replace `import unittest` with `import unittest2` and see the [unittest2.html]() documentation generated by `nim doc unittest2.nim`.
|
||||
|
||||
## license
|
||||
|
||||
MIT
|
||||
|
||||
## credits
|
||||
|
||||
- original author: Zahary Karadjov
|
||||
|
||||
- fork author: Ștefan Talpalaru <stefantalpalaru@yahoo.com>
|
||||
|
||||
- homepage: https://github.com/stefantalpalaru/nim-unittest2
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
@if release:
|
||||
nimcache = "nimcache/release/$projectName"
|
||||
@else:
|
||||
nimcache = "nimcache/debug/$projectName"
|
||||
@end
|
||||
|
||||
--threads:on
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
--path:"$projectDir/.."
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
discard """
|
||||
output: '''[Suite] suite with only teardown
|
||||
|
||||
[Suite] suite with only setup
|
||||
|
||||
[Suite] suite with none
|
||||
|
||||
[Suite] suite with both
|
||||
|
||||
[Suite] bug #4494
|
||||
|
||||
[Suite] bug #5571
|
||||
|
||||
[Suite] bug #5784
|
||||
|
||||
[Suite] test suite
|
||||
|
||||
[Suite] test name filtering
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
import unittest2, sequtils
|
||||
|
||||
proc doThings(spuds: var int): int =
|
||||
spuds = 24
|
||||
return 99
|
||||
test "#964":
|
||||
var spuds = 0
|
||||
check doThings(spuds) == 99
|
||||
check spuds == 24
|
||||
|
||||
|
||||
from strutils import toUpperAscii
|
||||
test "#1384":
|
||||
check(@["hello", "world"].map(toUpperAscii) == @["HELLO", "WORLD"])
|
||||
|
||||
|
||||
import options
|
||||
test "unittest typedescs":
|
||||
check(none(int) == none(int))
|
||||
check(none(int) != some(1))
|
||||
|
||||
|
||||
test "unittest multiple requires":
|
||||
require(true)
|
||||
require(true)
|
||||
|
||||
|
||||
import math, random
|
||||
from strutils import parseInt
|
||||
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
|
||||
test "unittest expect":
|
||||
expect IOError, OSError, ValueError, AssertionError:
|
||||
defectiveRobot()
|
||||
|
||||
var
|
||||
a = 1
|
||||
b = -1
|
||||
c = 1
|
||||
|
||||
#unittests are sequential right now
|
||||
suite "suite with only teardown":
|
||||
teardown:
|
||||
b = 2
|
||||
|
||||
test "unittest with only teardown 1":
|
||||
check a == c
|
||||
|
||||
test "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":
|
||||
check testVar == "from setup"
|
||||
check b > a
|
||||
b = -1
|
||||
|
||||
test "unittest with only setup 2":
|
||||
check b < a
|
||||
|
||||
suite "suite with none":
|
||||
test "unittest with none":
|
||||
check b < a
|
||||
|
||||
suite "suite with both":
|
||||
setup:
|
||||
a = -2
|
||||
|
||||
teardown:
|
||||
c = 2
|
||||
|
||||
test "unittest with both 1":
|
||||
check b > a
|
||||
|
||||
test "unittest with both 2":
|
||||
check c == 2
|
||||
|
||||
suite "bug #4494":
|
||||
test "Uniqueness check":
|
||||
var tags = @[1, 2, 3, 4, 5]
|
||||
check:
|
||||
allIt(0..3, tags[it] != tags[it + 1])
|
||||
|
||||
suite "bug #5571":
|
||||
test "can define gcsafe procs within tests":
|
||||
proc doTest {.gcsafe.} =
|
||||
let line = "a"
|
||||
check: line == "a"
|
||||
doTest()
|
||||
|
||||
suite "bug #5784":
|
||||
test "`or` should short circuit":
|
||||
type Obj = ref object
|
||||
field: int
|
||||
var obj: Obj
|
||||
check obj.isNil or obj.field == 0
|
||||
|
||||
type
|
||||
SomeType = object
|
||||
value: int
|
||||
children: seq[SomeType]
|
||||
|
||||
# bug #5252
|
||||
|
||||
proc `==`(a, b: SomeType): bool =
|
||||
return a.value == b.value
|
||||
|
||||
suite "test suite":
|
||||
test "test":
|
||||
let a = SomeType(value: 10)
|
||||
let b = SomeType(value: 10)
|
||||
|
||||
check(a == b)
|
||||
|
||||
when defined(testing):
|
||||
suite "test name filtering":
|
||||
test "test name":
|
||||
check matchFilter("suite1", "foo", "")
|
||||
check matchFilter("suite1", "foo", "foo")
|
||||
check matchFilter("suite1", "foo", "::")
|
||||
check matchFilter("suite1", "foo", "*")
|
||||
check matchFilter("suite1", "foo", "::foo")
|
||||
check matchFilter("suite1", "::foo", "::foo")
|
||||
|
||||
test "test name - glob":
|
||||
check matchFilter("suite1", "foo", "f*")
|
||||
check matchFilter("suite1", "foo", "*oo")
|
||||
check matchFilter("suite1", "12345", "12*345")
|
||||
check matchFilter("suite1", "q*wefoo", "q*wefoo")
|
||||
check false == matchFilter("suite1", "foo", "::x")
|
||||
check false == matchFilter("suite1", "foo", "::x*")
|
||||
check false == matchFilter("suite1", "foo", "::*x")
|
||||
# overlap
|
||||
check false == matchFilter("suite1", "12345", "123*345")
|
||||
check matchFilter("suite1", "ab*c::d*e::f", "ab*c::d*e::f")
|
||||
|
||||
test "suite name":
|
||||
check matchFilter("suite1", "foo", "suite1::")
|
||||
check false == matchFilter("suite1", "foo", "suite2::")
|
||||
check matchFilter("suite1", "qwe::foo", "qwe::foo")
|
||||
check matchFilter("suite1", "qwe::foo", "suite1::qwe::foo")
|
||||
|
||||
test "suite name - glob":
|
||||
check matchFilter("suite1", "foo", "::*")
|
||||
check matchFilter("suite1", "foo", "*::*")
|
||||
check matchFilter("suite1", "foo", "*::foo")
|
||||
check false == matchFilter("suite1", "foo", "*ite2::")
|
||||
check matchFilter("suite1", "q**we::foo", "q**we::foo")
|
||||
check matchFilter("suite1", "a::b*c::d*e", "a::b*c::d*e")
|
|
@ -0,0 +1,134 @@
|
|||
discard """
|
||||
output: '''[Suite] suite #1
|
||||
|
||||
[Suite] suite #2
|
||||
'''
|
||||
"""
|
||||
|
||||
# Unfortunately, it's not possible to decouple the thread execution order from
|
||||
# the number of available cores, due to how threadpool dynamically (and lazily)
|
||||
# adjusts the number of worker threads, so we can't have a PRINT_ALL output in
|
||||
# the verification section above.
|
||||
|
||||
import unittest2, os
|
||||
|
||||
test "independent test #1":
|
||||
sleep(1000)
|
||||
check 1 == 1
|
||||
# check 1 == 2
|
||||
# require 1 == 2
|
||||
# var g {.global.}: seq[int]
|
||||
# g.add(1)
|
||||
|
||||
test "independent test #2":
|
||||
sleep(800)
|
||||
check 1 == 1
|
||||
|
||||
test "independent test #3":
|
||||
## nested tests
|
||||
# we might as well keep this futile attempt at finding a problem with
|
||||
# uninitialized `flowVars` in child threads
|
||||
test "independent test #4":
|
||||
test "independent test #5":
|
||||
test "independent test #8":
|
||||
test "independent test #9":
|
||||
test "independent test #10":
|
||||
test "independent test #11":
|
||||
test "independent test #12":
|
||||
test "independent test #13":
|
||||
test "independent test #14":
|
||||
test "independent test #15":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
test "independent test #16":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
test "independent test #17":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
test "independent test #18":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
test "independent test #19":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
test "independent test #20":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
test "independent test #21":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
test "independent test #22":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
test "independent test #23":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
test "independent test #24":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
test "independent test #25":
|
||||
test "independent test #26":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
sleep(400)
|
||||
check 1 == 1
|
||||
sleep(600)
|
||||
check 1 == 1
|
||||
|
||||
suite "suite #1":
|
||||
test "suite #1, test #1":
|
||||
sleep(400)
|
||||
check 1 == 1
|
||||
|
||||
test "suite #1, test #2":
|
||||
sleep(300)
|
||||
check 1 == 1
|
||||
|
||||
suite "suite #2":
|
||||
setup:
|
||||
# only here can we set formatters when running the tests in parallel
|
||||
# (this setup will be executed for each test, so it may run multiple times
|
||||
# in the same thread, hence the clearing of the threadvar before adding to it)
|
||||
clearOutputFormatters()
|
||||
addOutputFormatter(newConsoleOutputFormatter(PRINT_FAILURES, colorOutput=false))
|
||||
|
||||
teardown:
|
||||
# we don't want the custom formatter to remain in worker threads after this
|
||||
# suite is done
|
||||
clearOutputFormatters()
|
||||
|
||||
test "suite #2, test #1":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
|
||||
test "suite #2, test #2":
|
||||
sleep(100)
|
||||
check 1 == 1
|
||||
|
||||
test "independent test #6":
|
||||
sleep(200)
|
||||
check 1 == 1
|
||||
|
||||
test "independent test #7":
|
||||
sleep(100)
|
||||
check 1 == 1
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-d:nimtestParallel
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,828 @@
|
|||
# See the file "LICENSE.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## :Authors: Zahary Karadjov, Ștefan Talpalaru
|
||||
##
|
||||
## This module implements boilerplate to make unit testing easy.
|
||||
##
|
||||
## The test status and name is printed after any output or traceback.
|
||||
##
|
||||
## Tests can be nested, however failure of a nested test will not mark the
|
||||
## 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
|
||||
##
|
||||
## .. code::
|
||||
## nim c -r testfile.nim
|
||||
##
|
||||
## exits with 0 or 1.
|
||||
##
|
||||
## Running individual tests
|
||||
## ========================
|
||||
##
|
||||
## Specify the test names as command line arguments.
|
||||
##
|
||||
## .. code::
|
||||
##
|
||||
## 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::
|
||||
##
|
||||
## nim c -r test "my suite name::"
|
||||
##
|
||||
## Selecting tests by pattern
|
||||
## ==========================
|
||||
##
|
||||
## A single ``"*"`` can be used for globbing.
|
||||
##
|
||||
## Delimit the end of a suite name with ``"::"``.
|
||||
##
|
||||
## Tests matching **any** of the arguments are executed.
|
||||
##
|
||||
## .. code::
|
||||
##
|
||||
## 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 #*::' '::#*'
|
||||
##
|
||||
## Running tests in parallel
|
||||
## =========================
|
||||
##
|
||||
## To enable the threadpool-based test parallelisation, "--threads:on" needs to
|
||||
## be passed to the compiler, along with "-d:nimtestParallel" or the
|
||||
## NIMTEST_PARALLEL environment variable:
|
||||
##
|
||||
## .. code::
|
||||
##
|
||||
## nim c -r --threads:on -d:nimtestParallel testfile.nim
|
||||
## # or
|
||||
## NIMTEST_PARALLEL=1 nim c -r --threads:on testfile.nim
|
||||
##
|
||||
## Since output formatters are kept in a threadvar, they need to be initialised
|
||||
## for each thread in the thread pool. This means that customisation can only be
|
||||
## done in a suite's "setup" section, which will run before each test in that
|
||||
## suite - hence the need to clear existing formatters before adding new ones:
|
||||
##
|
||||
## .. code:: nim
|
||||
##
|
||||
## suite "custom formatter":
|
||||
## setup:
|
||||
## clearOutputFormatters()
|
||||
## addOutputFormatter(newConsoleOutputFormatter(PRINT_FAILURES, colorOutput=false))
|
||||
##
|
||||
## # if you need to revert back to the default after the suite
|
||||
## teardown:
|
||||
## clearOutputFormatters()
|
||||
##
|
||||
## There are some implicit barriers where we wait for all the spawned jobs to
|
||||
## complete: before and after each test suite and at the main thread's exit.
|
||||
##
|
||||
## The suite-related barriers are there to avoid mixing test output, but they
|
||||
## also affect which groups of tests can be run in parallel, so keep them in
|
||||
## mind when deciding how many tests to place in different suites (or between
|
||||
## suites).
|
||||
##
|
||||
## Example
|
||||
## -------
|
||||
##
|
||||
## .. code:: nim
|
||||
##
|
||||
## suite "description for this stuff":
|
||||
## echo "suite setup: run once before the tests"
|
||||
##
|
||||
## setup:
|
||||
## echo "run before each test"
|
||||
##
|
||||
## teardown:
|
||||
## echo "run after each test"
|
||||
##
|
||||
## test "essential truths":
|
||||
## # give up and stop if this fails
|
||||
## require(true)
|
||||
##
|
||||
## test "slightly less obvious stuff":
|
||||
## # print a nasty message and move on, skipping
|
||||
## # the remainder of this block
|
||||
## check(1 != 1)
|
||||
## check("asd"[2] == 'd')
|
||||
##
|
||||
## test "out of bounds error is thrown on bad access":
|
||||
## let v = @[1, 2, 3] # you can do initialization here
|
||||
## expect(IndexError):
|
||||
## discard v[4]
|
||||
##
|
||||
## echo "suite teardown: run once after the tests"
|
||||
|
||||
import
|
||||
macros, strutils, streams, times, sets
|
||||
|
||||
when declared(stdout):
|
||||
import os
|
||||
|
||||
when not defined(ECMAScript):
|
||||
import terminal
|
||||
|
||||
when declared(stdout):
|
||||
const paralleliseTests* = existsEnv("NIMTEST_PARALLEL") or defined(nimtestParallel)
|
||||
## Whether parallel test running was enabled (set at compile time).
|
||||
## This constant might be useful in custom output formatters.
|
||||
else:
|
||||
const paralleliseTests* = false
|
||||
|
||||
when paralleliseTests:
|
||||
import threadpool, locks
|
||||
|
||||
# repeatedly calling sync() without waiting for results - on procs that don't
|
||||
# return any - doesn't work properly (probably due to gSomeReady getting its
|
||||
# counter increased back to the pre-call value) so we're stuck with these
|
||||
# dummy flowvars
|
||||
# (`flowVars` will be initialized in each child thread, when using nested tests, by the compiler)
|
||||
var flowVars {.threadvar.}: seq[FlowVarBase]
|
||||
proc repeatableSync() =
|
||||
sync()
|
||||
for flowVar in flowVars:
|
||||
blockUntil(flowVar)
|
||||
flowVars = @[]
|
||||
|
||||
# make sure all the spawned tests are done before exiting
|
||||
# (this will be the last sync, so no need for repeatability)
|
||||
let mainThreadID = getThreadId()
|
||||
proc quitProc() {.noconv.} =
|
||||
# "require" can exit from a worker thread and syncing in there would block
|
||||
if getThreadId() == mainThreadID:
|
||||
sync()
|
||||
addQuitProc(quitProc)
|
||||
|
||||
var outputLock: Lock # used by testEnded() to avoid mixed test outputs
|
||||
initLock(outputLock)
|
||||
|
||||
type
|
||||
TestStatus* = enum ## The status of a test when it is done.
|
||||
OK,
|
||||
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.
|
||||
|
||||
TestResult* = object
|
||||
suiteName*: string
|
||||
## Name of the test suite that contains this test case.
|
||||
## Can be ``nil`` if the test case is not in a suite.
|
||||
testName*: string
|
||||
## Name of the test case
|
||||
status*: TestStatus
|
||||
|
||||
OutputFormatter* = ref object of RootObj
|
||||
|
||||
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.
|
||||
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.
|
||||
isInSuite: bool
|
||||
isInTest: bool
|
||||
|
||||
JUnitOutputFormatter* = ref object of OutputFormatter
|
||||
stream: Stream
|
||||
testErrors: seq[string]
|
||||
testStartTime: float
|
||||
testStackTrace: string
|
||||
|
||||
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.
|
||||
|
||||
checkpoints {.threadvar.}: seq[string]
|
||||
formatters {.threadvar.}: seq[OutputFormatter]
|
||||
testsFilters {.threadvar.}: HashSet[string]
|
||||
disabledParamFiltering: bool
|
||||
|
||||
when declared(stdout):
|
||||
abortOnError = existsEnv("NIMTEST_ABORT_ON_ERROR")
|
||||
|
||||
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.} =
|
||||
## ``stackTrace`` is provided only if the failure occurred due to an exception.
|
||||
## ``checkpoints`` is never ``nil``.
|
||||
discard
|
||||
method testEnded*(formatter: OutputFormatter, testResult: TestResult) {.base, gcsafe.} =
|
||||
discard
|
||||
method suiteEnded*(formatter: OutputFormatter) {.base, gcsafe.} =
|
||||
discard
|
||||
|
||||
proc clearOutputFormatters*() =
|
||||
formatters = @[]
|
||||
|
||||
proc addOutputFormatter*(formatter: OutputFormatter) =
|
||||
formatters.add(formatter)
|
||||
|
||||
proc newConsoleOutputFormatter*(outputLevel: OutputLevel = PRINT_ALL,
|
||||
colorOutput = true): ConsoleOutputFormatter =
|
||||
ConsoleOutputFormatter(
|
||||
outputLevel: outputLevel,
|
||||
colorOutput: colorOutput
|
||||
)
|
||||
|
||||
proc defaultConsoleFormatter*(): ConsoleOutputFormatter =
|
||||
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")
|
||||
if colorEnv == "never":
|
||||
colorOutput = false
|
||||
elif colorEnv == "always":
|
||||
colorOutput = 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()
|
||||
|
||||
method suiteStarted*(formatter: ConsoleOutputFormatter, suiteName: string) =
|
||||
template rawPrint() = echo("\n[Suite] ", suiteName)
|
||||
when not defined(ECMAScript):
|
||||
if formatter.colorOutput:
|
||||
styledEcho styleBright, fgBlue, "\n[Suite] ", resetStyle, suiteName
|
||||
else: rawPrint()
|
||||
else: rawPrint()
|
||||
formatter.isInSuite = true
|
||||
|
||||
method testStarted*(formatter: ConsoleOutputFormatter, testName: string) =
|
||||
formatter.isInTest = true
|
||||
|
||||
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):
|
||||
echo prefix, msg
|
||||
|
||||
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.len > 0: " " else: ""
|
||||
template rawPrint() = echo(prefix, "[", $testResult.status, "] ", testResult.testName)
|
||||
when not defined(ECMAScript):
|
||||
if formatter.colorOutput and not defined(ECMAScript):
|
||||
var color = case testResult.status
|
||||
of OK: fgGreen
|
||||
of FAILED: fgRed
|
||||
of SKIPPED: fgYellow
|
||||
styledEcho styleBright, color, prefix, "[", $testResult.status, "] ", resetStyle, testResult.testName
|
||||
else:
|
||||
rawPrint()
|
||||
else:
|
||||
rawPrint()
|
||||
|
||||
method suiteEnded*(formatter: ConsoleOutputFormatter) =
|
||||
formatter.isInSuite = false
|
||||
|
||||
proc xmlEscape(s: string): string =
|
||||
result = newStringOfCap(s.len)
|
||||
for c in items(s):
|
||||
case c:
|
||||
of '<': result.add("<")
|
||||
of '>': result.add(">")
|
||||
of '&': result.add("&")
|
||||
of '"': result.add(""")
|
||||
of '\'': result.add("'")
|
||||
else:
|
||||
if ord(c) < 32:
|
||||
result.add("&#" & $ord(c) & ';')
|
||||
else:
|
||||
result.add(c)
|
||||
|
||||
proc newJUnitOutputFormatter*(stream: Stream): JUnitOutputFormatter =
|
||||
## Creates a formatter that writes report to the specified stream in
|
||||
## JUnit format.
|
||||
## The ``stream`` is NOT closed automatically when the test are finished,
|
||||
## because the formatter has no way to know when all tests are finished.
|
||||
## You should invoke formatter.close() to finalize the report.
|
||||
result = JUnitOutputFormatter(
|
||||
stream: stream,
|
||||
testErrors: @[],
|
||||
testStackTrace: "",
|
||||
testStartTime: 0.0
|
||||
)
|
||||
stream.writeLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
|
||||
stream.writeLine("<testsuites>")
|
||||
|
||||
proc close*(formatter: JUnitOutputFormatter) =
|
||||
## Completes the report and closes the underlying stream.
|
||||
formatter.stream.writeLine("</testsuites>")
|
||||
formatter.stream.close()
|
||||
|
||||
method suiteStarted*(formatter: JUnitOutputFormatter, suiteName: string) =
|
||||
formatter.stream.writeLine("\t<testsuite name=\"$1\">" % xmlEscape(suiteName))
|
||||
|
||||
method testStarted*(formatter: JUnitOutputFormatter, testName: string) =
|
||||
formatter.testErrors.setLen(0)
|
||||
formatter.testStackTrace.setLen(0)
|
||||
formatter.testStartTime = epochTime()
|
||||
|
||||
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.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:
|
||||
discard
|
||||
of SKIPPED:
|
||||
formatter.stream.writeLine("<skipped />")
|
||||
of FAILED:
|
||||
let failureMsg = if formatter.testStackTrace.len > 0 and
|
||||
formatter.testErrors.len > 0:
|
||||
xmlEscape(formatter.testErrors[^1])
|
||||
elif formatter.testErrors.len > 0:
|
||||
xmlEscape(formatter.testErrors[0])
|
||||
else: "The test failed without outputting an error"
|
||||
|
||||
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
|
||||
|
||||
for errIdx in startIdx..endIdx:
|
||||
if errs.len > 0:
|
||||
errs.add("\n")
|
||||
errs.add(xmlEscape(formatter.testErrors[errIdx]))
|
||||
|
||||
if formatter.testStackTrace.len > 0:
|
||||
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</testcase>")
|
||||
|
||||
method suiteEnded*(formatter: JUnitOutputFormatter) =
|
||||
formatter.stream.writeLine("\t</testsuite>")
|
||||
|
||||
proc glob(matcher, filter: string): bool =
|
||||
## Globbing using a single `*`. Empty `filter` matches everything.
|
||||
if filter.len == 0:
|
||||
return true
|
||||
|
||||
if not filter.contains('*'):
|
||||
return matcher == filter
|
||||
|
||||
let beforeAndAfter = filter.split('*', maxsplit=1)
|
||||
if beforeAndAfter.len == 1:
|
||||
# "foo*"
|
||||
return matcher.startswith(beforeAndAfter[0])
|
||||
|
||||
if matcher.len < filter.len - 1:
|
||||
return false # "12345" should not match "123*345"
|
||||
|
||||
return matcher.startsWith(beforeAndAfter[0]) and matcher.endsWith(beforeAndAfter[1])
|
||||
|
||||
proc matchFilter(suiteName, testName, filter: string): bool =
|
||||
if filter == "":
|
||||
return true
|
||||
if testName == filter:
|
||||
# corner case for tests containing "::" in their name
|
||||
return true
|
||||
let suiteAndTestFilters = filter.split("::", maxsplit=1)
|
||||
|
||||
if suiteAndTestFilters.len == 1:
|
||||
# no suite specified
|
||||
let test_f = suiteAndTestFilters[0]
|
||||
return glob(testName, test_f)
|
||||
|
||||
return glob(suiteName, suiteAndTestFilters[0]) and glob(testName, suiteAndTestFilters[1])
|
||||
|
||||
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):
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
proc ensureInitialized() =
|
||||
if formatters.len == 0:
|
||||
formatters = @[OutputFormatter(defaultConsoleFormatter())]
|
||||
|
||||
if not disabledParamFiltering and not testsFilters.isValid:
|
||||
testsFilters.init()
|
||||
when declared(paramCount):
|
||||
# Read tests to run from the command line.
|
||||
for i in 1 .. paramCount():
|
||||
testsFilters.incl(paramStr(i))
|
||||
|
||||
proc suiteStarted(name: string) =
|
||||
when paralleliseTests:
|
||||
repeatableSync() # wait for any independent tests from the threadpool before starting the suite
|
||||
for formatter in formatters:
|
||||
formatter.suiteStarted(name)
|
||||
|
||||
proc suiteEnded() =
|
||||
when paralleliseTests:
|
||||
repeatableSync() # wait for a suite's tests from the threadpool before moving on to the next suite
|
||||
for formatter in formatters:
|
||||
formatter.suiteEnded()
|
||||
|
||||
proc testStarted(name: string) =
|
||||
for formatter in formatters:
|
||||
formatter.testStarted(name)
|
||||
|
||||
proc testEnded(testResult: TestResult) =
|
||||
for formatter in formatters:
|
||||
when paralleliseTests:
|
||||
withLock outputLock:
|
||||
formatter.testEnded(testResult)
|
||||
else:
|
||||
formatter.testEnded(testResult)
|
||||
|
||||
template suite*(name, body) {.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::
|
||||
##
|
||||
## [Suite] test suite for addition
|
||||
## [OK] 2 + 2 = 4
|
||||
## [OK] (2 + -2) != 4
|
||||
bind formatters, ensureInitialized, suiteStarted, suiteEnded
|
||||
|
||||
block:
|
||||
template setup(setupBody: untyped) {.dirty, used.} =
|
||||
var testSetupIMPLFlag {.used.} = true
|
||||
template testSetupIMPL: untyped {.dirty.} = setupBody
|
||||
|
||||
template teardown(teardownBody: untyped) {.dirty, used.} =
|
||||
var testTeardownIMPLFlag {.used.} = true
|
||||
template testTeardownIMPL: untyped {.dirty.} = teardownBody
|
||||
|
||||
let testSuiteName {.used.} = name
|
||||
|
||||
ensureInitialized()
|
||||
try:
|
||||
suiteStarted(name)
|
||||
body
|
||||
finally:
|
||||
suiteEnded()
|
||||
|
||||
template exceptionTypeName(e: typed): string = $e.name
|
||||
|
||||
template test*(name, body) =
|
||||
## 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, formatters, ensureInitialized, testStarted, testEnded, exceptionTypeName
|
||||
|
||||
# `gensym` can't be in here because it's not a first-class pragma
|
||||
when paralleliseTests:
|
||||
{.pragma: testrunner, gcsafe.}
|
||||
else:
|
||||
{.pragma: testrunner.}
|
||||
|
||||
proc runTest(testSuiteName: string): int {.gensym, testrunner.} =
|
||||
# when running tests in parallel, the only place we can use
|
||||
# addOutputFormatter() is in a suite's setup(), so we need to run it before
|
||||
# ensureInitialized()
|
||||
when declared(testSetupIMPLFlag):
|
||||
testSetupIMPL()
|
||||
|
||||
ensureInitialized()
|
||||
|
||||
if shouldRun(testSuiteName, name):
|
||||
checkpoints = @[]
|
||||
var testStatusIMPL {.inject.} = OK
|
||||
|
||||
testStarted(name)
|
||||
|
||||
try:
|
||||
body
|
||||
except:
|
||||
when not defined(js):
|
||||
let e = getCurrentException()
|
||||
let eTypeDesc = "[" & exceptionTypeName(e) & "]"
|
||||
checkpoint("Unhandled exception: " & getCurrentExceptionMsg() & " " & eTypeDesc)
|
||||
var stackTrace {.inject.} = e.getStackTrace()
|
||||
fail()
|
||||
|
||||
finally:
|
||||
if testStatusIMPL == FAILED:
|
||||
programResult += 1
|
||||
let testResult = TestResult(
|
||||
suiteName: testSuiteName,
|
||||
testName: name,
|
||||
status: testStatusIMPL
|
||||
)
|
||||
testEnded(testResult)
|
||||
checkpoints = @[]
|
||||
# when running tests in parallel, "formatters" manipulation may occur in
|
||||
# teardown(), so it needs to be after testEnded()
|
||||
when declared(testTeardownIMPLFlag):
|
||||
testTeardownIMPL()
|
||||
|
||||
let optionalTestSuiteName = when declared(testSuiteName): testSuiteName else: ""
|
||||
when paralleliseTests:
|
||||
flowVars.add(spawn runTest(optionalTestSuiteName))
|
||||
else:
|
||||
discard runTest(optionalTestSuiteName)
|
||||
|
||||
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 ensureInitialized
|
||||
|
||||
when declared(testStatusIMPL):
|
||||
testStatusIMPL = FAILED
|
||||
else:
|
||||
programResult += 1
|
||||
|
||||
ensureInitialized()
|
||||
|
||||
# var stackTrace: string = nil
|
||||
for formatter in formatters:
|
||||
when declared(stackTrace):
|
||||
formatter.failureOccurred(checkpoints, stackTrace)
|
||||
else:
|
||||
formatter.failureOccurred(checkpoints, "")
|
||||
|
||||
when not defined(ECMAScript):
|
||||
if abortOnError:
|
||||
when declared(testStatusIMPL):
|
||||
# this wasn't incremented yet, because it's normally incremented in the
|
||||
# "test" template, but we're exiting earlier here
|
||||
programResult += 1
|
||||
quit(programResult)
|
||||
|
||||
checkpoints = @[]
|
||||
|
||||
template skip* =
|
||||
## Mark the test as skipped. Should be used directly
|
||||
## in case when it is not possible to perform test
|
||||
## 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()
|
||||
bind checkpoints
|
||||
|
||||
testStatusIMPL = 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
|
||||
let checked = callsite()[1]
|
||||
|
||||
template asgn(a: untyped, value: typed) =
|
||||
var a = value # XXX: we need "var: var" here in order to
|
||||
# preserve the semantics of var params
|
||||
|
||||
template print(name: untyped, value: typed) =
|
||||
when compiles(string($value)):
|
||||
checkpoint(name & " was " & $value)
|
||||
|
||||
proc inspectArgs(exp: NimNode): tuple[assigns, check, printOuts: NimNode] =
|
||||
result.check = copyNimTree(exp)
|
||||
result.assigns = newNimNode(nnkStmtList)
|
||||
result.printOuts = newNimNode(nnkStmtList)
|
||||
|
||||
var counter = 0
|
||||
|
||||
if exp[0].kind == nnkIdent and
|
||||
$exp[0] in ["not", "in", "notin", "==", "<=",
|
||||
">=", "<", ">", "!=", "is", "isnot"]:
|
||||
|
||||
for i in 1 ..< exp.len:
|
||||
if exp[i].kind notin nnkLiterals:
|
||||
inc counter
|
||||
let argStr = exp[i].toStrLit
|
||||
let paramAst = exp[i]
|
||||
if exp[i].kind == nnkIdent:
|
||||
result.printOuts.add getAst(print(argStr, paramAst))
|
||||
if exp[i].kind in nnkCallKinds + { nnkDotExpr, nnkBracketExpr }:
|
||||
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"
|
||||
# IntLit 2
|
||||
result.check[i] = exp[i][1]
|
||||
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))
|
||||
if exp[i].kind != nnkExprEqExpr:
|
||||
result.check[i] = arg
|
||||
else:
|
||||
result.check[i][1] = arg
|
||||
|
||||
case checked.kind
|
||||
of nnkCallKinds:
|
||||
|
||||
let (assigns, check, printOuts) = inspectArgs(checked)
|
||||
let lineinfo = newStrLitNode(checked.lineinfo)
|
||||
let callLit = checked.toStrLit
|
||||
result = quote do:
|
||||
block:
|
||||
`assigns`
|
||||
if not `check`:
|
||||
checkpoint(`lineinfo` & ": Check failed: " & `callLit`)
|
||||
`printOuts`
|
||||
fail()
|
||||
|
||||
of nnkStmtList:
|
||||
result = newNimNode(nnkStmtList)
|
||||
for node in checked:
|
||||
if node.kind != nnkCommentStmt:
|
||||
result.add(newCall(!"check", node))
|
||||
|
||||
else:
|
||||
let lineinfo = newStrLitNode(checked.lineinfo)
|
||||
let callLit = checked.toStrLit
|
||||
|
||||
result = quote do:
|
||||
if not `checked`:
|
||||
checkpoint(`lineinfo` & ": Check failed: " & `callLit`)
|
||||
fail()
|
||||
|
||||
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
|
||||
check conditions
|
||||
abortOnError = savedAbortOnError
|
||||
|
||||
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()
|
||||
template expectBody(errorTypes, lineInfoLit, body): NimNode {.dirty.} =
|
||||
try:
|
||||
body
|
||||
checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.")
|
||||
fail()
|
||||
except errorTypes:
|
||||
discard
|
||||
except:
|
||||
checkpoint(lineInfoLit & ": Expect Failed, unexpected exception was thrown.")
|
||||
fail()
|
||||
|
||||
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))
|
||||
|
||||
proc disableParamFiltering* =
|
||||
## disables filtering tests with the command line params
|
||||
disabledParamFiltering = true
|
|
@ -0,0 +1,14 @@
|
|||
mode = ScriptMode.Verbose
|
||||
|
||||
version = "0.0.1"
|
||||
author = "Ștefan Talpalaru"
|
||||
description = "unittest fork with support for parallel test execution"
|
||||
license = "MIT"
|
||||
requires "nim >= 0.19.4"
|
||||
|
||||
task test, "Run tests":
|
||||
for f in listFiles("tests"):
|
||||
if f.len > 4 and f[^4..^1] == ".nim":
|
||||
exec "nim c -r -f --hints:off --verbosity:0 " & f
|
||||
rmFile(f[0..^5].toExe())
|
||||
|
Loading…
Reference in New Issue