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:
Zahary Karadjov
2011-10-05 16:03:24 +03:00
parent eaeed1f846
commit 22546c44d1
8 changed files with 258 additions and 16 deletions

View File

@@ -369,7 +369,7 @@ type
mCompileOption, mCompileOptionArg,
mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, mNKind,
mNIntVal, mNFloatVal, mNSymbol, mNIdent, mNGetType, mNStrVal, mNSetIntVal,
mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetType, mNSetStrVal,
mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetType, mNSetStrVal, mNLineInfo,
mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent, mIdentToStr,
mEqIdent, mEqNimrodNode, mNHint, mNWarning, mNError, mGetTypeInfo

View File

@@ -798,8 +798,9 @@ proc evalMacroCall*(c: PEvalContext, n: PNode, sym: PSym): PNode =
import
semdata, sem
proc evalExpandToAst(c: PEvalContext, n: PNode): PNode =
var
proc evalExpandToAst(c: PEvalContext, original: PNode): PNode =
var
n = original.copyTree
macroCall = n.sons[1]
expandedSym = macroCall.sons[0].sym
@@ -854,7 +855,7 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
of mParseExprToAst: result = evalParseExpr(c, n)
of mParseStmtToAst: result = evalParseStmt(c, n)
of mExpandMacroToAst: result = evalExpandToAst(c, n)
of mNLen:
of mNLen:
result = evalAux(c, n.sons[1], {efLValue})
if isSpecial(result): return
var a = result
@@ -1060,6 +1061,10 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
if (a == b) or
(b.kind in {nkNilLit, nkEmpty}) and (a.kind in {nkNilLit, nkEmpty}):
result.intVal = 1
of mNLineInfo:
result = evalAux(c, n.sons[1], {})
if isSpecial(result): return
result = newStrNodeT(result.info.toFileLineCol, n)
of mAstToYaml:
var ast = evalAux(c, n.sons[1], {efLValue})
result = newStrNode(nkStrLit, ast.treeToYaml.ropeToStr)

View File

@@ -456,6 +456,12 @@ proc ToLinenumber*(info: TLineInfo): int {.inline.} =
proc toColumn*(info: TLineInfo): int {.inline.} =
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] = @[]
proc addCheckpoint*(info: TLineInfo) =

View File

@@ -428,7 +428,7 @@ proc semRaise(c: PContext, n: PNode): PNode =
var typ = n.sons[0].typ
if typ.kind != tyRef or typ.sons[0].kind != tyObject:
localError(n.info, errExprCannotBeRaised)
proc semTry(c: PContext, n: PNode): PNode =
result = n
checkMinSonsLen(n, 2)
@@ -438,15 +438,19 @@ proc semTry(c: PContext, n: PNode): PNode =
var a = n.sons[i]
checkMinSonsLen(a, 1)
var length = sonsLen(a)
if a.kind == nkExceptBranch:
for j in countup(0, length - 2):
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):
var typ = semTypeNode(c, a.sons[j], nil)
if typ.kind == tyRef: typ = typ.sons[0]
if typ.kind != tyObject:
if typ.kind != tyObject:
GlobalError(a.sons[j].info, errExprCannotBeRaised)
a.sons[j] = newNodeI(nkType, a.sons[j].info)
a.sons[j].typ = typ
if ContainsOrIncl(check, typ.id):
if ContainsOrIncl(check, typ.id):
localError(a.sons[j].info, errExceptionAlreadyHandled)
elif a.kind != nkFinally:
illFormedAst(n)

View File

@@ -36,8 +36,8 @@ type
nnkMacroDef, nnkTemplateDef, nnkIteratorDef, nnkOfBranch,
nnkElifBranch, nnkExceptBranch, nnkElse, nnkMacroStmt,
nnkAsmStmt, nnkPragma, nnkIfStmt, nnkWhenStmt,
nnkForStmt, nnkWhileStmt, nnkCaseStmt,
nnkVarSection, nnkLetSection, nnkConstSection,
nnkForStmt, nnkWhileStmt, nnkCaseStmt,
nnkVarSection, nnkLetSection, nnkConstSection,
nnkConstDef, nnkTypeSection, nnkTypeDef,
nnkYieldStmt, nnkTryStmt, nnkFinally, nnkRaiseStmt,
nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt,
@@ -45,8 +45,8 @@ type
nnkIncludeStmt, nnkCommentStmt, nnkStmtListExpr, nnkBlockExpr,
nnkStmtListType, nnkBlockType, nnkTypeOfExpr, nnkObjectTy,
nnkTupleTy, nnkRecList, nnkRecCase, nnkRecWhen,
nnkRefTy, nnkPtrTy, nnkVarTy,
nnkConstTy, nnkMutableTy,
nnkRefTy, nnkPtrTy, nnkVarTy,
nnkConstTy, nnkMutableTy,
nnkDistinctTy,
nnkProcTy, nnkEnumTy, nnkEnumFieldDef, nnkReturnToken
TNimNodeKinds* = set[TNimrodNodeKind]
@@ -187,6 +187,8 @@ proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} =
## in a string literal node
return newStrLitNode(repr(n))
proc lineinfo*(n: PNimrodNode): string {.magic: "NLineInfo".}
proc toLisp*(n: PNimrodNode): string {.compileTime.} =
## Convert the AST `n` to a human-readable string
##
@@ -230,9 +232,9 @@ proc parseStmt*(s: string): stmt {.magic: "ParseStmtToAst".}
proc getAst*(macroOrTemplate: expr): expr {.magic: "ExpandMacroToAst".}
## Obtains the AST nodes returned from a macro or template invocation.
## Example:
##
## .. code-block:: nimrod
## Example:
##
## .. code-block:: nimrod
##
## macro FooMacro() =
## var ast = getAst(BarTemplate())

157
lib/pure/unittest.nim Normal file
View 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))

View File

@@ -857,6 +857,27 @@ proc insert*[T](x: var seq[T], item: T, i = 0) {.noSideEffect.} =
dec(j)
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.}
## takes any Nimrod variable and returns its string representation. It

View 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()