Improved unittest check macro, fixes #5784 (#6446)

This commit is contained in:
GULPF 2017-09-30 15:39:00 +02:00 • committed by Andreas Rumpf
commit 5cf789ac3f
2 changed files with 64 additions and 48 deletions

View file

@ -509,10 +509,6 @@ macro check*(conditions: untyped): untyped =
## "AKB48".toLowerAscii() == "akb48" ## "AKB48".toLowerAscii() == "akb48"
## 'C' in teams ## 'C' in teams
let checked = callsite()[1] let checked = callsite()[1]
var
argsAsgns = newNimNode(nnkStmtList)
argsPrintOuts = newNimNode(nnkStmtList)
counter = 0
template asgn(a: untyped, value: typed) = template asgn(a: untyped, value: typed) =
var a = value # XXX: we need "var: var" here in order to var a = value # XXX: we need "var: var" here in order to
@ -522,66 +518,71 @@ macro check*(conditions: untyped): untyped =
when compiles(string($value)): when compiles(string($value)):
checkpoint(name & " was " & $value) checkpoint(name & " was " & $value)
proc inspectArgs(exp: NimNode): NimNode = proc inspectArgs(exp: NimNode): tuple[assigns, check, printOuts: NimNode] =
result = copyNimTree(exp) result.check = copyNimTree(exp)
result.assigns = newNimNode(nnkStmtList)
result.printOuts = newNimNode(nnkStmtList)
var counter = 0
if exp[0].kind == nnkIdent and if exp[0].kind == nnkIdent and
$exp[0] in ["and", "or", "not", "in", "notin", "==", "<=", $exp[0] in ["not", "in", "notin", "==", "<=",
">=", "<", ">", "!=", "is", "isnot"]: ">=", "<", ">", "!=", "is", "isnot"]:
for i in countup(1, exp.len - 1):
for i in 1 ..< exp.len:
if exp[i].kind notin nnkLiterals: if exp[i].kind notin nnkLiterals:
inc counter inc counter
var arg = newIdentNode(":p" & $counter) let argStr = exp[i].toStrLit
var argStr = exp[i].toStrLit let paramAst = exp[i]
var paramAst = exp[i]
if exp[i].kind == nnkIdent: if exp[i].kind == nnkIdent:
argsPrintOuts.add getAst(print(argStr, paramAst)) result.printOuts.add getAst(print(argStr, paramAst))
if exp[i].kind in nnkCallKinds: if exp[i].kind in nnkCallKinds + { nnkDotExpr, nnkBracketExpr }:
var callVar = newIdentNode(":c" & $counter) let callVar = newIdentNode(":c" & $counter)
argsAsgns.add getAst(asgn(callVar, paramAst)) result.assigns.add getAst(asgn(callVar, paramAst))
result[i] = callVar result.check[i] = callVar
argsPrintOuts.add getAst(print(argStr, callVar)) result.printOuts.add getAst(print(argStr, callVar))
if exp[i].kind == nnkExprEqExpr: if exp[i].kind == nnkExprEqExpr:
# ExprEqExpr # ExprEqExpr
# Ident !"v" # Ident !"v"
# IntLit 2 # IntLit 2
result[i] = exp[i][1] result.check[i] = exp[i][1]
if exp[i].typekind notin {ntyTypeDesc}: if exp[i].typekind notin {ntyTypeDesc}:
argsAsgns.add getAst(asgn(arg, paramAst)) let arg = newIdentNode(":p" & $counter)
argsPrintOuts.add getAst(print(argStr, arg)) result.assigns.add getAst(asgn(arg, paramAst))
result.printOuts.add getAst(print(argStr, arg))
if exp[i].kind != nnkExprEqExpr: if exp[i].kind != nnkExprEqExpr:
result[i] = arg result.check[i] = arg
else: else:
result[i][1] = arg result.check[i][1] = arg
case checked.kind case checked.kind
of nnkCallKinds: of nnkCallKinds:
template rewrite(call, lineInfoLit, callLit,
argAssgs, argPrintOuts) =
block:
argAssgs #all callables (and assignments) are run here
if not call:
checkpoint(lineInfoLit & ": Check failed: " & callLit)
argPrintOuts
fail()
var checkedStr = checked.toStrLit let (assigns, check, printOuts) = inspectArgs(checked)
let parameterizedCheck = inspectArgs(checked) let lineinfo = newStrLitNode(checked.lineinfo)
result = getAst(rewrite(parameterizedCheck, checked.lineinfo, checkedStr, let callLit = checked.toStrLit
argsAsgns, argsPrintOuts)) result = quote do:
block:
`assigns`
if not `check`:
checkpoint(`lineinfo` & ": Check failed: " & `callLit`)
`printOuts`
fail()
of nnkStmtList: of nnkStmtList:
result = newNimNode(nnkStmtList) result = newNimNode(nnkStmtList)
for i in countup(0, checked.len - 1): for node in checked:
if checked[i].kind != nnkCommentStmt: if node.kind != nnkCommentStmt:
result.add(newCall(!"check", checked[i])) result.add(newCall(!"check", node))
else: else:
template rewrite(exp, lineInfoLit, expLit) = let lineinfo = newStrLitNode(checked.lineinfo)
if not exp: let callLit = checked.toStrLit
checkpoint(lineInfoLit & ": Check failed: " & expLit)
fail()
result = getAst(rewrite(checked, checked.lineinfo, checked.toStrLit)) result = quote do:
if not `checked`:
checkpoint(`lineinfo` & ": Check failed: " & `callLit`)
fail()
template require*(conditions: untyped) = template require*(conditions: untyped) =
## Same as `check` except any failed test causes the program to quit ## Same as `check` except any failed test causes the program to quit

View file

@ -1,12 +1,23 @@
discard """ discard """
nimout: "compile start\ncompile end" 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
'''
""" """
import unittest, sequtils import unittest, sequtils
static:
echo "compile start"
proc doThings(spuds: var int): int = proc doThings(spuds: var int): int =
spuds = 24 spuds = 24
return 99 return 99
@ -103,5 +114,9 @@ suite "bug #5571":
check: line == "a" check: line == "a"
doTest() doTest()
static: suite "bug #5784":
echo "compile end" test "`or` should short circuit":
type Obj = ref object
field: int
var obj: Obj
check obj.isNil or obj.field == 0