Added more robust type printing and test cases.

This commit is contained in:
Joey Payne 2015-07-11 22:07:16 -06:00
commit e3d27fa550
2 changed files with 227 additions and 145 deletions

View file

@ -19,6 +19,8 @@ import typetraits
when not defined(ECMAScript): when not defined(ECMAScript):
import terminal import terminal
# ----------------- Helper Procs and Macros -----------------------------------
proc `$`*[T](ar: openarray[T]): string= proc `$`*[T](ar: openarray[T]): string=
## Converts an array into a string ## Converts an array into a string
result = "[" result = "["
@ -27,14 +29,38 @@ proc `$`*[T](ar: openarray[T]): string=
for i in 1..ar.len()-1: for i in 1..ar.len()-1:
result &= ", " & $ar[i] result &= ", " & $ar[i]
result &= "]" result &= "]"
return result
proc `$`*[T](some:typedesc[T]): string = name(T) proc `$`*[T](some:typedesc[T]): string = name(T)
proc `$`*(s: ref object): string= proc `$`*(s: ref object): string =
result = ($type(s)).replace(":ObjectType", "") & $s[] result = ($type(s)).replace(":ObjectType", "") & $s[]
proc `==`*[T](ar: openarray[T], ar2: openarray[T]): bool= proc objToStr*[T: object](obj: T): string =
result = $type(obj) & system.`$`(obj)
proc objToStr*[T: tuple](obj: T): string =
result = "tuple " & $type(obj) & system.`$`(obj)
macro toString*(obj: typed): untyped =
## this macro is to work around not being
## able to override system.`$`
##
## Basically, I want to use my proc to print
## objects and tuples, but the regular $ for
## everything else
let kind = obj.getType().typeKind
case kind:
of ntyTuple, ntyObject:
template toStrAst(obj): string=
einheit.objToStr(obj)
result = getAst(toStrAst(obj))
else:
template toStrAst(obj): string=
$(obj)
result = getAst(toStrAst(obj))
proc `==`*[T](ar: openarray[T], ar2: openarray[T]): bool =
## helper proc to compare arrays
if len(ar) != len(ar2): if len(ar) != len(ar2):
return false return false
for i in countup(0, ar.len()): for i in countup(0, ar.len()):
@ -42,6 +68,7 @@ proc `==`*[T](ar: openarray[T], ar2: openarray[T]): bool=
return false return false
return true return true
# ----------------------- Test Suite Types ------------------------------------
type type
TestSuite = ref object of RootObj TestSuite = ref object of RootObj
## The base TestSuite ## The base TestSuite
@ -68,7 +95,7 @@ method setup*(suite: TestSuite)=
discard discard
method tearDown*(suite: TestSuite)= method tearDown*(suite: TestSuite)=
## Base method for setup code ## Base method for tearDown code
discard discard
method runTests*(suite: TestSuite)= method runTests*(suite: TestSuite)=
@ -99,7 +126,7 @@ template returnException(name, testName, snip, vals, pos, posRel)=
exc.checkFuncName = name exc.checkFuncName = name
raise exc raise exc
# ------------------------ Templates for checkion ---------------------------- # ------------------------ Templates for checking ----------------------------
template checkRaises*(self: TestSuite, error: Exception, template checkRaises*(self: TestSuite, error: Exception,
code: untyped): untyped {.immediate.}= code: untyped): untyped {.immediate.}=
@ -133,44 +160,57 @@ template checkRaises*(self: TestSuite, error: Exception,
returnException("checkRaises", testName, snip, vals, pos, posRel) returnException("checkRaises", testName, snip, vals, pos, posRel)
template recursive(node, action): expr {.dirty.} = template recursive(node, action): expr {.dirty.} =
## recursively iterate over AST nodes and perform an
## action on them
proc helper(child: NimNode): NimNode {.gensym.} = proc helper(child: NimNode): NimNode {.gensym.} =
action action
result = child.copy() result = child.copy()
for c in child.children: for c in child.children:
if child.kind == nnkCall and c.kind == nnkDotExpr: if child.kind == nnkCall and c.kind == nnkDotExpr:
# ignore dot expressions that are also calls
continue continue
result.add helper(c) result.add helper(c)
discard helper(node) discard helper(node)
template strRep(n: NimNode): untyped= template strRep(n: NimNode): untyped=
$(n) toString(n)
template tableEntry(n: NimNode): untyped= template tableEntry(n: NimNode): untyped=
newNimNode(nnkExprColonExpr).add(n.toStrLit(), getAst(strRep(n))) newNimNode(nnkExprColonExpr).add(n.toStrLit(), getAst(strRep(n)))
macro getSyms(code:untyped): untyped= macro getSyms(code:untyped): untyped=
## This macro gets all symbols and values of an expression
## into a table
##
## Table[string, string] -> symbolName, value
##
var var
tableCall = newNimNode(nnkCall).add(ident("toTable")) tableCall = newNimNode(nnkCall).add(ident("toTable"))
tableConstr = newNimNode(nnkTableConstr) tableConstr = newNimNode(nnkTableConstr)
recursive(code): recursive(code):
let ch1 = child let ch1 = child
if ch1.kind == nnkInfix: case ch1.kind:
if child[1].kind == nnkIdent: of nnkInfix:
tableConstr.add(tableEntry(child[1])) if child[1].kind == nnkIdent:
if child[2].kind == nnkIdent: tableConstr.add(tableEntry(child[1]))
tableConstr.add(tableEntry(child[2])) if child[2].kind == nnkIdent:
elif ch1.kind == nnkExprColonExpr: tableConstr.add(tableEntry(child[2]))
if child[0].kind == nnkIdent: of nnkExprColonExpr:
tableConstr.add(tableEntry(child[0])) if child[0].kind == nnkIdent:
if child[1].kind == nnkIdent: tableConstr.add(tableEntry(child[0]))
tableConstr.add(tableEntry(child[1])) if child[1].kind == nnkIdent:
elif ch1.kind == nnkCall or ch1.kind == nnkCommand: tableConstr.add(tableEntry(child[1]))
tableConstr.add(tableEntry(ch1)) of nnkCall, nnkCommand:
for i in 1..<ch1.len(): tableConstr.add(tableEntry(ch1))
tableConstr.add(tableEntry(ch1[i])) if ch1.len() > 0 and ch1[0].kind == nnkDotExpr:
elif ch1.kind == nnkDotExpr: tableConstr.add(tableEntry(ch1[0][0]))
tableConstr.add(tableEntry(ch1)) for i in 1..<ch1.len():
tableConstr.add(tableEntry(ch1[i]))
of nnkDotExpr:
tableConstr.add(tableEntry(ch1))
else:
discard
if tableConstr.len() != 0: if tableConstr.len() != 0:
tableCall.add(tableConstr) tableCall.add(tableConstr)
result = tableCall result = tableCall
@ -179,7 +219,6 @@ macro getSyms(code:untyped): untyped=
initTable[string, string]() initTable[string, string]()
result = getAst(emptyTable()) result = getAst(emptyTable())
template check*(self: TestSuite, code: untyped){.immediate.}= template check*(self: TestSuite, code: untyped){.immediate.}=
## Assertions for tests ## Assertions for tests
if not code: if not code:
@ -444,6 +483,66 @@ macro testSuite*(head: untyped, body: untyped): untyped =
baseTearMethodName)) baseTearMethodName))
body.add(teardownBaseAst[0]) body.add(teardownBaseAst[0])
template setTestName(self, procName)=
self.currentTestName = procName
template tryBlock(self, testCall)=
self.numTests += 1
try:
testCall
when defined(quiet):
when defined(noColors):
stdout.write(".")
else:
setForegroundColor(fgGreen)
writeStyled(".", {styleBright})
setForegroundColor(fgWhite)
else:
when not defined(noColors):
styledEcho(styleBright, fgGreen, "[OK]",
fgWhite, " ", self.currentTestName)
else:
echo "[OK] $1".format(self.currentTestName)
self.testsPassed += 1
except TestAssertError:
let e = (ref TestAssertError)(getCurrentException())
when defined(quiet):
when defined(noColors):
stdout.write("F")
else:
setForegroundColor(fgRed)
writeStyled("F", {styleBright})
setForegroundColor(fgWhite)
else:
when not defined(noColors):
styledEcho(styleBright,
fgRed, "[Failed]",
fgWhite, " ", self.currentTestName)
else:
echo "[Failed] $1".format(self.currentTestName)
let
name = e.checkFuncName
snip = e.codeSnip
line = e.lineNumber
filename = e.fileName
vals = e.valTable
when not defined(noColors):
styledEcho(styleDim, fgWhite, " Condition: $2($1)\n".format(snip, name), " Where:")
for k, v in vals.pairs:
styledEcho(styleDim, fgCyan, " ", k,
fgWhite, " -> ",
fgGreen, v)
styledEcho(styleDim, fgWhite, " Location: $1; line $2\n".format(filename, line))
else:
echo " Condition: $2($1)".format(snip, name)
echo " Where:"
for k, v in vals.pairs:
echo " ", k, " -> ", v
echo " Location: $1; line $2\n".format(filename, line)
# Iterate over the statements, adding `self: T` # Iterate over the statements, adding `self: T`
# to the parameters of functions # to the parameters of functions
@ -455,7 +554,7 @@ macro testSuite*(head: untyped, body: untyped): untyped =
n.params.insert(1, newIdentDefs(ident(objReference), typeName)) n.params.insert(1, newIdentDefs(ident(objReference), typeName))
# Copy the proc or method for inheritance # Copy the proc or method for inheritance
# ie: procName_ClassName() # ie: procNameClassName()
let n2 = copyNimTree(node) let n2 = copyNimTree(node)
n2.params.insert(1, newIdentDefs(ident(objReference), typeName)) n2.params.insert(1, newIdentDefs(ident(objReference), typeName))
@ -475,68 +574,6 @@ macro testSuite*(head: untyped, body: untyped): untyped =
let procCall = newDotExpr(ident(objReference), let procCall = newDotExpr(ident(objReference),
ident(procName & typeName)) ident(procName & typeName))
template setTestName(self, procName)=
self.currentTestName = procName
template tryBlock(self, testCall)=
self.numTests += 1
try:
testCall
when defined(quiet):
when defined(noColors):
stdout.write(".")
else:
setForegroundColor(fgGreen)
writeStyled(".", {styleBright})
setForegroundColor(fgWhite)
else:
when not defined(noColors):
styledEcho(styleBright, fgGreen, "[OK]",
fgWhite, " ", self.currentTestName)
else:
echo "[OK] $1".format(self.currentTestName)
self.testsPassed += 1
except TestAssertError:
let e = (ref TestAssertError)(getCurrentException())
when defined(quiet):
when defined(noColors):
stdout.write("F")
else:
setForegroundColor(fgRed)
writeStyled("F", {styleBright})
setForegroundColor(fgWhite)
else:
when not defined(noColors):
styledEcho(styleBright,
fgRed, "[Failed]",
fgWhite, " ", self.currentTestName)
else:
echo "[Failed] $1".format(self.currentTestName)
let
name = e.checkFuncName
snip = e.codeSnip
line = e.lineNumber
filename = e.fileName
vals = e.valTable
when not defined(noColors):
styledEcho(styleDim, fgWhite, " Condition: $2($1)\n".format(snip, name), " Where:")
for k, v in vals.pairs:
styledEcho(styleDim, fgCyan, " ", k,
fgWhite, " -> ",
fgGreen, v)
styledEcho(styleDim, fgWhite, " Location: $1; line $2\n".format(filename, line))
else:
echo " Condition: $2($1)".format(snip, name)
echo " Where:"
for k, v in vals.pairs:
echo " ", k, " -> ", v
echo " Location: $1; line $2\n".format(filename, line)
runTests[0][6].add(getAst(setTestName(ident(objReference), procName))) runTests[0][6].add(getAst(setTestName(ident(objReference), procName)))
runTests[0][6].add(getAst(tryBlock(ident(objReference), procCall))) runTests[0][6].add(getAst(tryBlock(ident(objReference), procCall)))
@ -632,65 +669,50 @@ macro testSuite*(head: untyped, body: untyped): untyped =
result.add(getAst(addTestSuite(typeName))) result.add(getAst(addTestSuite(typeName)))
proc runTests*()= proc printRunning(suite: TestSuite)=
## The method that runs the tests. Invoke
## after setting up all of the tests and
## usually inside a "when isMainModule" block
var var
totalTests = 0 numTicks = 80 - 12 - len(suite.name)
totalTestsPassed = 0 ticks = ""
when defined(quiet): for i in 0..<numTicks:
echo "" ticks &= "-"
for suite in testSuites: when not defined(quiet):
suite.setup() when not defined(noColors):
styledEcho(styleBright,
fgYellow, "\n[Running]",
fgWhite, " $1 ".format(suite.name),
fgYellow, " ", ticks, "\n")
else:
echo "\n[Running] $1 $2\n".format(suite.name, ticks)
var
numTicks = 80 - 12 - len(suite.name)
ticks = ""
for i in 0..<numTicks: proc printPassedTests(suite: TestSuite)=
ticks &= "-" # Output red if tests didn't pass, green otherwise
var color = fgGreen
when not defined(quiet): if suite.testsPassed != suite.numTests:
when not defined(noColors): color = fgRed
styledEcho(styleBright,
fgYellow, "\n[Running]",
fgWhite, " $1 ".format(suite.name),
fgYellow, " ", ticks, "\n")
else:
echo "\n[Running] $1 $2\n".format(suite.name, ticks)
suite.runTests() var passedStr = "[" & $suite.testsPassed & "/" & $suite.numTests & "]"
suite.tearDown()
totalTests += suite.numTests
totalTestsPassed += suite.testsPassed
# Output red if tests didn't pass, green otherwise
var color = fgGreen
if suite.testsPassed != suite.numTests:
color = fgRed
var passedStr = "[" & $suite.testsPassed & "/" & $suite.numTests & "]"
var
ticks = " " ticks = " "
numTicks = 80 - len(passedStr) - 20 - len(suite.name) numTicks = 80 - len(passedStr) - 20 - len(suite.name)
for i in 0..<numTicks: for i in 0..<numTicks:
ticks &= "-" ticks &= "-"
when not defined(quiet): when not defined(quiet):
when not defined(noColors): when not defined(noColors):
styledEcho(styleBright, color, styledEcho(styleBright, color,
"\n", passedStr, "\n", passedStr,
fgWhite, " tests passed for ", suite.name, ".", fgWhite, " tests passed for ", suite.name, ".",
fgYellow, ticks, "\n") fgYellow, ticks, "\n")
else: else:
echo "\n$1 tests passed for $2.$3\n".format(passedStr, suite.name, ticks) echo "\n$1 tests passed for $2.$3\n".format(passedStr, suite.name, ticks)
proc printSummary(totalTestsPassed: int, totalTests: int)=
var summaryColor = fgGreen var summaryColor = fgGreen
if totalTestsPassed != totalTests: if totalTestsPassed != totalTests:
@ -716,4 +738,29 @@ proc runTests*()=
echo "\n[Summary]" echo "\n[Summary]"
echo "\n $1 tests passed.".format(passedStr) echo "\n $1 tests passed.".format(passedStr)
proc runTests*()=
## The method that runs the tests. Invoke
## after setting up all of the tests and
## usually inside a "when isMainModule" block
var
totalTests = 0
totalTestsPassed = 0
when defined(quiet):
echo ""
for suite in testSuites:
suite.setup()
suite.printRunning()
suite.runTests()
suite.tearDown()
suite.printPassedTests()
totalTests += suite.numTests
totalTestsPassed += suite.testsPassed
printSummary(totalTestsPassed, totalTests)

View file

@ -49,7 +49,7 @@ testSuite UnitTestsNew:
self.check("Stuff" == "Stuff") self.check("Stuff" == "Stuff")
proc returnTrue(): bool= proc returnTrue(): bool=
return false result = false
method testMore()= method testMore()=
var more = 23 var more = 23
@ -108,29 +108,64 @@ testSuite MoreInheritance of TestInherit:
proc doStuff(arg: int, arg2: string): string= proc doStuff(arg: int, arg2: string): string=
result = $arg & arg2 result = $arg & arg2
method testComplex()= method testRefObject()=
type type
TestObj = ref object TestObj = ref object
t: int t: int
Person = tuple[name: string, age: int]
var
d = TestObj(t: 3)
k = TestObj(t: 30)
proc `==`(d: TestObj, d2: TestObj): bool= proc `==`(d: TestObj, d2: TestObj): bool=
return d.t == d2.t result = d.t == d2.t
self.check(d == k)
method testObject()=
type
TestObj = object
t: int
var
d = TestObj(t: 3)
k = TestObj(t: 30)
proc `==`(d: TestObj, d2: TestObj): bool=
result = d.t == d2.t
self.check(d != k)
self.check(d == k)
method testComplexObject()=
type
Obj1 = object
e: string
Obj2 = object
d: Obj1
var x = Obj2(d:Obj1(e: "Hey"))
var p = 4
proc isObj(obj: Obj2, q: int): bool =
result = false
self.check(x.isObj(p))
method testTuple()=
type
Person = tuple[name: string, age: int]
var
t: Person = (name: "Peter", age: 30)
r: Person = (name: "P", age: 3)
self.check(t != r)
self.check(t == r)
method testComplex()=
var var
a = 5 a = 5
s = "stuff" s = "stuff"
y = 45 y = 45
t: Person = (name: "Peter", age: 30)
r: Person = (name: "P", age: 3)
d = TestObj(t: 3)
k = TestObj(t: 30)
self.check(t != r)
self.check(d == k)
self.check(self.doStuff(a, s) == "5stuff" and self.doStuff(a, self.doStuff(a, self.doStuff(y, s))) == "something?") self.check(self.doStuff(a, s) == "5stuff" and self.doStuff(a, self.doStuff(a, self.doStuff(y, s))) == "something?")