Basic unit testing facilities (suites, fixtures, cases)
Added: PNimrodNode.lineinfo for better error messages from macros Added: seq.splice For easier use from templates and macros, except stament now supports the list of exception types to be supplied in nkBraket node (array literal).
This commit is contained in:
parent
eaeed1f846
commit
22546c44d1
8 changed files with 258 additions and 16 deletions
|
|
@ -369,7 +369,7 @@ type
|
||||||
mCompileOption, mCompileOptionArg,
|
mCompileOption, mCompileOptionArg,
|
||||||
mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, mNKind,
|
mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, mNKind,
|
||||||
mNIntVal, mNFloatVal, mNSymbol, mNIdent, mNGetType, mNStrVal, mNSetIntVal,
|
mNIntVal, mNFloatVal, mNSymbol, mNIdent, mNGetType, mNStrVal, mNSetIntVal,
|
||||||
mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetType, mNSetStrVal,
|
mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetType, mNSetStrVal, mNLineInfo,
|
||||||
mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent, mIdentToStr,
|
mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent, mIdentToStr,
|
||||||
mEqIdent, mEqNimrodNode, mNHint, mNWarning, mNError, mGetTypeInfo
|
mEqIdent, mEqNimrodNode, mNHint, mNWarning, mNError, mGetTypeInfo
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -798,8 +798,9 @@ proc evalMacroCall*(c: PEvalContext, n: PNode, sym: PSym): PNode =
|
||||||
import
|
import
|
||||||
semdata, sem
|
semdata, sem
|
||||||
|
|
||||||
proc evalExpandToAst(c: PEvalContext, n: PNode): PNode =
|
proc evalExpandToAst(c: PEvalContext, original: PNode): PNode =
|
||||||
var
|
var
|
||||||
|
n = original.copyTree
|
||||||
macroCall = n.sons[1]
|
macroCall = n.sons[1]
|
||||||
expandedSym = macroCall.sons[0].sym
|
expandedSym = macroCall.sons[0].sym
|
||||||
|
|
||||||
|
|
@ -1060,6 +1061,10 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
|
||||||
if (a == b) or
|
if (a == b) or
|
||||||
(b.kind in {nkNilLit, nkEmpty}) and (a.kind in {nkNilLit, nkEmpty}):
|
(b.kind in {nkNilLit, nkEmpty}) and (a.kind in {nkNilLit, nkEmpty}):
|
||||||
result.intVal = 1
|
result.intVal = 1
|
||||||
|
of mNLineInfo:
|
||||||
|
result = evalAux(c, n.sons[1], {})
|
||||||
|
if isSpecial(result): return
|
||||||
|
result = newStrNodeT(result.info.toFileLineCol, n)
|
||||||
of mAstToYaml:
|
of mAstToYaml:
|
||||||
var ast = evalAux(c, n.sons[1], {efLValue})
|
var ast = evalAux(c, n.sons[1], {efLValue})
|
||||||
result = newStrNode(nkStrLit, ast.treeToYaml.ropeToStr)
|
result = newStrNode(nkStrLit, ast.treeToYaml.ropeToStr)
|
||||||
|
|
|
||||||
|
|
@ -456,6 +456,12 @@ proc ToLinenumber*(info: TLineInfo): int {.inline.} =
|
||||||
proc toColumn*(info: TLineInfo): int {.inline.} =
|
proc toColumn*(info: TLineInfo): int {.inline.} =
|
||||||
result = info.col
|
result = info.col
|
||||||
|
|
||||||
|
proc toFileLine*(info: TLineInfo): string {.inline.} =
|
||||||
|
result = info.toFilename & ":" & $info.line
|
||||||
|
|
||||||
|
proc toFileLineCol*(info: TLineInfo): string {.inline.} =
|
||||||
|
result = info.toFilename & "(" & $info.line & "," & $info.col & ")"
|
||||||
|
|
||||||
var checkPoints: seq[TLineInfo] = @[]
|
var checkPoints: seq[TLineInfo] = @[]
|
||||||
|
|
||||||
proc addCheckpoint*(info: TLineInfo) =
|
proc addCheckpoint*(info: TLineInfo) =
|
||||||
|
|
|
||||||
|
|
@ -439,6 +439,10 @@ proc semTry(c: PContext, n: PNode): PNode =
|
||||||
checkMinSonsLen(a, 1)
|
checkMinSonsLen(a, 1)
|
||||||
var length = sonsLen(a)
|
var length = sonsLen(a)
|
||||||
if a.kind == nkExceptBranch:
|
if a.kind == nkExceptBranch:
|
||||||
|
if length == 2 and a.sons[0].kind == nkBracket:
|
||||||
|
a.sons.splice(0, 1, a.sons[0].sons)
|
||||||
|
length = a.sonsLen
|
||||||
|
|
||||||
for j in countup(0, length - 2):
|
for j in countup(0, length - 2):
|
||||||
var typ = semTypeNode(c, a.sons[j], nil)
|
var typ = semTypeNode(c, a.sons[j], nil)
|
||||||
if typ.kind == tyRef: typ = typ.sons[0]
|
if typ.kind == tyRef: typ = typ.sons[0]
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,8 @@ proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
## in a string literal node
|
## in a string literal node
|
||||||
return newStrLitNode(repr(n))
|
return newStrLitNode(repr(n))
|
||||||
|
|
||||||
|
proc lineinfo*(n: PNimrodNode): string {.magic: "NLineInfo".}
|
||||||
|
|
||||||
proc toLisp*(n: PNimrodNode): string {.compileTime.} =
|
proc toLisp*(n: PNimrodNode): string {.compileTime.} =
|
||||||
## Convert the AST `n` to a human-readable string
|
## Convert the AST `n` to a human-readable string
|
||||||
##
|
##
|
||||||
|
|
|
||||||
157
lib/pure/unittest.nim
Normal file
157
lib/pure/unittest.nim
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Nimrod's Runtime Library
|
||||||
|
# (c) Copyright 2011 Nimrod Contributors
|
||||||
|
#
|
||||||
|
# See the file "copying.txt", included in this
|
||||||
|
# distribution, for details about the copyright.
|
||||||
|
#
|
||||||
|
|
||||||
|
## 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
|
||||||
|
##
|
||||||
|
## Maintainer: Zahary Karadjov (zah@github)
|
||||||
|
##
|
||||||
|
|
||||||
|
import
|
||||||
|
macros
|
||||||
|
|
||||||
|
type
|
||||||
|
TestStatus* = enum OK, FAILED
|
||||||
|
# ETestFailed* = object of ESynch
|
||||||
|
|
||||||
|
var
|
||||||
|
# XXX: These better be thread-local
|
||||||
|
AbortOnError* = false
|
||||||
|
checkpoints: seq[string] = @[]
|
||||||
|
|
||||||
|
template TestSetupIMPL*: stmt = nil
|
||||||
|
template TestTeardownIMPL*: stmt = nil
|
||||||
|
|
||||||
|
proc shouldRun(testName: string): bool =
|
||||||
|
result = true
|
||||||
|
|
||||||
|
template suite*(name: expr, body: stmt): stmt =
|
||||||
|
block:
|
||||||
|
template setup(setupBody: stmt): stmt =
|
||||||
|
template TestSetupIMPL: stmt = setupBody
|
||||||
|
|
||||||
|
template teardown(teardownBody: stmt): stmt =
|
||||||
|
template TestTeardownIMPL: stmt = teardownBody
|
||||||
|
|
||||||
|
body
|
||||||
|
|
||||||
|
template test*(name: expr, body: stmt): stmt =
|
||||||
|
if bind shouldRun(name):
|
||||||
|
bind checkpoints = @[]
|
||||||
|
var TestStatusIMPL = OK
|
||||||
|
|
||||||
|
try:
|
||||||
|
TestSetupIMPL()
|
||||||
|
body
|
||||||
|
|
||||||
|
finally:
|
||||||
|
TestTeardownIMPL()
|
||||||
|
echo "[" & $TestStatusIMPL & "] " & name
|
||||||
|
|
||||||
|
proc checkpoint*(msg: string) =
|
||||||
|
checkpoints.add(msg)
|
||||||
|
# TODO: add support for something like SCOPED_TRACE from Google Test
|
||||||
|
|
||||||
|
template fail* =
|
||||||
|
for msg in items(bind checkpoints):
|
||||||
|
echo msg
|
||||||
|
|
||||||
|
if AbortOnError: quit(1)
|
||||||
|
|
||||||
|
TestStatusIMPL = FAILED
|
||||||
|
checkpoints = @[]
|
||||||
|
|
||||||
|
macro check*(conditions: stmt): stmt =
|
||||||
|
proc standardRewrite(e: expr): stmt =
|
||||||
|
template rewrite(Exp, lineInfoLit: expr, expLit: string): stmt =
|
||||||
|
if not Exp:
|
||||||
|
checkpoint(lineInfoLit & ": Check failed: " & expLit)
|
||||||
|
fail()
|
||||||
|
|
||||||
|
# XXX: If we don't create a string literal node below, the compiler
|
||||||
|
# will SEGFAULT in a rather strange fashion:
|
||||||
|
#
|
||||||
|
# rewrite(e, e.toStrLit, e.toStrLit) is ok, but
|
||||||
|
#
|
||||||
|
# rewrite(e, e.lineinfo, e.toStrLit) or
|
||||||
|
# rewrite(e, "anything", e.toStrLit) are not
|
||||||
|
#
|
||||||
|
# It may have something to do with the dummyContext hack in
|
||||||
|
# evals.nim/evalTemplate
|
||||||
|
#
|
||||||
|
result = getAst(rewrite(e, newStrLitNode(e.lineinfo), e.toStrLit))
|
||||||
|
|
||||||
|
case conditions.kind
|
||||||
|
of nnkCall, nnkCommand, nnkMacroStmt:
|
||||||
|
case conditions[1].kind
|
||||||
|
of nnkInfix:
|
||||||
|
proc rewriteBinaryOp(op: expr): stmt =
|
||||||
|
template rewrite(op, left, right, lineInfoLit: expr, opLit, leftLit, rightLit: string): stmt =
|
||||||
|
block:
|
||||||
|
var
|
||||||
|
lhs = left
|
||||||
|
rhs = right
|
||||||
|
|
||||||
|
if not `op`(lhs, rhs):
|
||||||
|
checkpoint(lineInfoLit & ": Check failed: " & opLit)
|
||||||
|
checkpoint(" " & leftLit & " was " & $lhs)
|
||||||
|
checkpoint(" " & rightLit & " was " & $rhs)
|
||||||
|
fail()
|
||||||
|
|
||||||
|
result = getAst(rewrite(
|
||||||
|
op[0], op[1], op[2],
|
||||||
|
newStrLitNode(op.lineinfo),
|
||||||
|
op.toStrLit,
|
||||||
|
op[1].toStrLit,
|
||||||
|
op[2].toStrLit))
|
||||||
|
|
||||||
|
result = rewriteBinaryOp(conditions[1])
|
||||||
|
|
||||||
|
of nnkCall, nnkCommand:
|
||||||
|
# TODO: We can print out the call arguments in case of failure
|
||||||
|
result = standardRewrite(conditions[1])
|
||||||
|
|
||||||
|
of nnkStmtList:
|
||||||
|
result = newNimNode(nnkStmtList)
|
||||||
|
for i in countup(0, conditions[1].len - 1):
|
||||||
|
result.add(newCall(!"check", conditions[1][i]))
|
||||||
|
|
||||||
|
else:
|
||||||
|
result = standardRewrite(conditions[1])
|
||||||
|
|
||||||
|
else:
|
||||||
|
error conditions.lineinfo & ": Malformed check statement"
|
||||||
|
|
||||||
|
template require*(conditions: stmt): stmt =
|
||||||
|
block:
|
||||||
|
const AbortOnError = true
|
||||||
|
check conditions
|
||||||
|
|
||||||
|
macro expect*(exp: stmt): stmt =
|
||||||
|
template expectBody(errorTypes, lineInfoLit: expr, body: stmt): stmt =
|
||||||
|
try:
|
||||||
|
body
|
||||||
|
checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.")
|
||||||
|
fail()
|
||||||
|
except errorTypes:
|
||||||
|
nil
|
||||||
|
|
||||||
|
var expectCall = exp[0]
|
||||||
|
var body = exp[1]
|
||||||
|
|
||||||
|
var errorTypes = newNimNode(nnkBracket)
|
||||||
|
for i in countup(1, expectCall.len - 1):
|
||||||
|
errorTypes.add(expectCall[i])
|
||||||
|
|
||||||
|
result = getAst(expectBody(errorTypes, newStrLitNode(exp.lineinfo), body))
|
||||||
|
|
||||||
|
|
@ -857,6 +857,27 @@ proc insert*[T](x: var seq[T], item: T, i = 0) {.noSideEffect.} =
|
||||||
dec(j)
|
dec(j)
|
||||||
x[i] = item
|
x[i] = item
|
||||||
|
|
||||||
|
template spliceImpl(x, start, count, elements: expr): stmt =
|
||||||
|
var
|
||||||
|
shift = elements.len - count
|
||||||
|
newLen = x.len + shift
|
||||||
|
totalShifted = x.len - (start + count)
|
||||||
|
firstShifted = newLen - totalShifted
|
||||||
|
|
||||||
|
if shift > 0:
|
||||||
|
setLen(x, newLen)
|
||||||
|
|
||||||
|
for i in countup(firstShifted, newLen - 1):
|
||||||
|
shallowCopy(x[i], x[i-shift])
|
||||||
|
|
||||||
|
for c in countup(0, elements.len - 1):
|
||||||
|
x[start + c] = elements[c]
|
||||||
|
|
||||||
|
if shift < 0:
|
||||||
|
setLen(x, newLen)
|
||||||
|
|
||||||
|
proc splice*[T](x: var seq[T], start, count: int, elements: openarray[T] = []) =
|
||||||
|
spliceImpl(x, start, count, elements)
|
||||||
|
|
||||||
proc repr*[T](x: T): string {.magic: "Repr", noSideEffect.}
|
proc repr*[T](x: T): string {.magic: "Repr", noSideEffect.}
|
||||||
## takes any Nimrod variable and returns its string representation. It
|
## takes any Nimrod variable and returns its string representation. It
|
||||||
|
|
|
||||||
47
tests/accept/run/tunit.nim
Normal file
47
tests/accept/run/tunit.nim
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import
|
||||||
|
unittest, macros
|
||||||
|
|
||||||
|
var
|
||||||
|
a = 1
|
||||||
|
b = 22
|
||||||
|
c = 1
|
||||||
|
d = 3
|
||||||
|
|
||||||
|
suite "my suite":
|
||||||
|
setup:
|
||||||
|
echo "suite setup"
|
||||||
|
var testVar = "from setup"
|
||||||
|
|
||||||
|
teardown:
|
||||||
|
echo "suite teardown"
|
||||||
|
|
||||||
|
test "first suite test":
|
||||||
|
testVar = "modified"
|
||||||
|
echo "test var: " & testVar
|
||||||
|
check a > b
|
||||||
|
|
||||||
|
test "second suite test":
|
||||||
|
echo "test var: " & testVar
|
||||||
|
|
||||||
|
proc foo: bool =
|
||||||
|
echo "running foo"
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc err =
|
||||||
|
raise newException(EArithmetic, "some exception")
|
||||||
|
|
||||||
|
test "final test":
|
||||||
|
echo "inside suite-less test"
|
||||||
|
|
||||||
|
check:
|
||||||
|
a == c
|
||||||
|
foo()
|
||||||
|
d > 10
|
||||||
|
|
||||||
|
test "arithmetic failure":
|
||||||
|
expect(EArithmetic):
|
||||||
|
err()
|
||||||
|
|
||||||
|
expect(EArithmetic, ESystem):
|
||||||
|
discard foo()
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue