diff options
-rwxr-xr-x | compiler/ast.nim | 2 | ||||
-rwxr-xr-x | compiler/evals.nim | 11 | ||||
-rwxr-xr-x | compiler/msgs.nim | 6 | ||||
-rwxr-xr-x | compiler/semstmts.nim | 14 | ||||
-rwxr-xr-x | lib/core/macros.nim | 16 | ||||
-rw-r--r-- | lib/pure/unittest.nim | 157 | ||||
-rwxr-xr-x | lib/system.nim | 21 | ||||
-rw-r--r-- | tests/accept/run/tunit.nim | 47 |
8 files changed, 258 insertions, 16 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index b33d99554..e60a52fc6 100755 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -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 diff --git a/compiler/evals.nim b/compiler/evals.nim index b7b3746a5..db15b0370 100755 --- a/compiler/evals.nim +++ b/compiler/evals.nim @@ -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) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index d34c6b410..4f8a21f54 100755 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -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) = diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index c00b68bb5..057c99e94 100755 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -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) diff --git a/lib/core/macros.nim b/lib/core/macros.nim index c5afcdf17..e61575f3e 100755 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -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()) diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim new file mode 100644 index 000000000..a5c97ee9b --- /dev/null +++ b/lib/pure/unittest.nim @@ -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)) + diff --git a/lib/system.nim b/lib/system.nim index 226642771..b6f5243e5 100755 --- a/lib/system.nim +++ b/lib/system.nim @@ -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 diff --git a/tests/accept/run/tunit.nim b/tests/accept/run/tunit.nim new file mode 100644 index 000000000..d0e975119 --- /dev/null +++ b/tests/accept/run/tunit.nim @@ -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() + |