doAssert, assert now print full path of failing line on error (#8555)
This commit is contained in:
parent
81f920a4ee
commit
3a626179ee
6 changed files with 109 additions and 32 deletions
|
|
@ -8,6 +8,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
include "system/inclrtl"
|
include "system/inclrtl"
|
||||||
|
include "system/helpers"
|
||||||
|
|
||||||
## This module contains the interface to the compiler's abstract syntax
|
## This module contains the interface to the compiler's abstract syntax
|
||||||
## tree (`AST`:idx:). Macros operate on this tree.
|
## tree (`AST`:idx:). Macros operate on this tree.
|
||||||
|
|
@ -422,7 +423,8 @@ type
|
||||||
line*,column*: int
|
line*,column*: int
|
||||||
|
|
||||||
proc `$`*(arg: Lineinfo): string =
|
proc `$`*(arg: Lineinfo): string =
|
||||||
result = arg.filename & "(" & $arg.line & ", " & $arg.column & ")"
|
# BUG: without `result = `, gives compile error
|
||||||
|
result = lineInfoToString(arg.filename, arg.line, arg.column)
|
||||||
|
|
||||||
#proc lineinfo*(n: NimNode): LineInfo {.magic: "NLineInfo", noSideEffect.}
|
#proc lineinfo*(n: NimNode): LineInfo {.magic: "NLineInfo", noSideEffect.}
|
||||||
## returns the position the node appears in the original source file
|
## returns the position the node appears in the original source file
|
||||||
|
|
|
||||||
|
|
@ -3758,6 +3758,16 @@ proc failedAssertImpl*(msg: string) {.raises: [], tags: [].} =
|
||||||
tags: [].}
|
tags: [].}
|
||||||
Hide(raiseAssert)(msg)
|
Hide(raiseAssert)(msg)
|
||||||
|
|
||||||
|
include "system/helpers" # for `lineInfoToString`
|
||||||
|
|
||||||
|
template assertImpl(cond: bool, msg = "", enabled: static[bool]) =
|
||||||
|
const loc = $instantiationInfo(-1, true)
|
||||||
|
bind instantiationInfo
|
||||||
|
mixin failedAssertImpl
|
||||||
|
when enabled:
|
||||||
|
if not cond:
|
||||||
|
failedAssertImpl(loc & " `" & astToStr(cond) & "` " & msg)
|
||||||
|
|
||||||
template assert*(cond: bool, msg = "") =
|
template assert*(cond: bool, msg = "") =
|
||||||
## Raises ``AssertionError`` with `msg` if `cond` is false. Note
|
## Raises ``AssertionError`` with `msg` if `cond` is false. Note
|
||||||
## that ``AssertionError`` is hidden from the effect system, so it doesn't
|
## that ``AssertionError`` is hidden from the effect system, so it doesn't
|
||||||
|
|
@ -3767,23 +3777,11 @@ template assert*(cond: bool, msg = "") =
|
||||||
## The compiler may not generate any code at all for ``assert`` if it is
|
## The compiler may not generate any code at all for ``assert`` if it is
|
||||||
## advised to do so through the ``-d:release`` or ``--assertions:off``
|
## advised to do so through the ``-d:release`` or ``--assertions:off``
|
||||||
## `command line switches <nimc.html#command-line-switches>`_.
|
## `command line switches <nimc.html#command-line-switches>`_.
|
||||||
# NOTE: `true` is correct here; --excessiveStackTrace:on will control whether
|
assertImpl(cond, msg, compileOption("assertions"))
|
||||||
# or not to output full paths.
|
|
||||||
bind instantiationInfo
|
|
||||||
mixin failedAssertImpl
|
|
||||||
{.line: instantiationInfo(fullPaths = true).}:
|
|
||||||
when compileOption("assertions"):
|
|
||||||
if not cond: failedAssertImpl(astToStr(cond) & ' ' & msg)
|
|
||||||
|
|
||||||
template doAssert*(cond: bool, msg = "") =
|
template doAssert*(cond: bool, msg = "") =
|
||||||
## same as `assert` but is always turned on and not affected by the
|
## same as ``assert`` but is always turned on regardless of ``--assertions``
|
||||||
## ``--assertions`` command line switch.
|
assertImpl(cond, msg, true)
|
||||||
# NOTE: `true` is correct here; --excessiveStackTrace:on will control whether
|
|
||||||
# or not to output full paths.
|
|
||||||
bind instantiationInfo
|
|
||||||
mixin failedAssertImpl
|
|
||||||
{.line: instantiationInfo(fullPaths = true).}:
|
|
||||||
if not cond: failedAssertImpl(astToStr(cond) & ' ' & msg)
|
|
||||||
|
|
||||||
iterator items*[T](a: seq[T]): T {.inline.} =
|
iterator items*[T](a: seq[T]): T {.inline.} =
|
||||||
## iterates over each item of `a`.
|
## iterates over each item of `a`.
|
||||||
|
|
|
||||||
11
lib/system/helpers.nim
Normal file
11
lib/system/helpers.nim
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
## helpers used system.nim and other modules, avoids code duplication while
|
||||||
|
## also minimizing symbols exposed in system.nim
|
||||||
|
#
|
||||||
|
# TODO: move other things here that should not be exposed in system.nim
|
||||||
|
|
||||||
|
proc lineInfoToString(file: string, line, column: int): string =
|
||||||
|
file & "(" & $line & ", " & $column & ")"
|
||||||
|
|
||||||
|
proc `$`(info: type(instantiationInfo(0))): string =
|
||||||
|
# The +1 is needed here
|
||||||
|
lineInfoToString(info.fileName, info.line, info.column+1)
|
||||||
12
tests/assert/testhelper.nim
Normal file
12
tests/assert/testhelper.nim
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
from strutils import endsWith, split
|
||||||
|
from ospaths import isAbsolute
|
||||||
|
|
||||||
|
proc checkMsg*(msg, expectedEnd, name: string)=
|
||||||
|
let filePrefix = msg.split(' ', maxSplit = 1)[0]
|
||||||
|
if not filePrefix.isAbsolute:
|
||||||
|
echo name, ":not absolute: `", msg & "`"
|
||||||
|
elif not msg.endsWith expectedEnd:
|
||||||
|
echo name, ":expected suffix:\n`" & expectedEnd & "`\ngot:\n`" & msg & "`"
|
||||||
|
else:
|
||||||
|
echo name, ":ok"
|
||||||
|
|
||||||
|
|
@ -1,20 +1,65 @@
|
||||||
discard """
|
discard """
|
||||||
output: '''
|
output: '''
|
||||||
WARNING: false first assertion from bar
|
test1:ok
|
||||||
ERROR: false second assertion from bar
|
test2:ok
|
||||||
|
test3:ok
|
||||||
|
test4:ok
|
||||||
|
test5:ok
|
||||||
|
test6:ok
|
||||||
|
test7:ok
|
||||||
-1
|
-1
|
||||||
tfailedassert.nim:27 false assertion from foo
|
tfailedassert.nim
|
||||||
|
test7:ok
|
||||||
'''
|
'''
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import testhelper
|
||||||
|
|
||||||
type
|
type
|
||||||
TLineInfo = tuple[filename: string, line: int, column: int]
|
TLineInfo = tuple[filename: string, line: int, column: int]
|
||||||
|
|
||||||
TMyError = object of Exception
|
TMyError = object of Exception
|
||||||
lineinfo: TLineInfo
|
lineinfo: TLineInfo
|
||||||
|
|
||||||
EMyError = ref TMyError
|
EMyError = ref TMyError
|
||||||
|
|
||||||
|
echo("")
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: when entering newlines, adjust `expectedEnd` ouptuts
|
||||||
|
|
||||||
|
try:
|
||||||
|
doAssert(false, "msg1") # doAssert test
|
||||||
|
except AssertionError as e:
|
||||||
|
checkMsg(e.msg, "tfailedassert.nim(30, 11) `false` msg1", "test1")
|
||||||
|
|
||||||
|
try:
|
||||||
|
assert false, "msg2" # assert test
|
||||||
|
except AssertionError as e:
|
||||||
|
checkMsg(e.msg, "tfailedassert.nim(35, 10) `false` msg2", "test2")
|
||||||
|
|
||||||
|
try:
|
||||||
|
assert false # assert test with no msg
|
||||||
|
except AssertionError as e:
|
||||||
|
checkMsg(e.msg, "tfailedassert.nim(40, 10) `false` ", "test3")
|
||||||
|
|
||||||
|
try:
|
||||||
|
let a = 1
|
||||||
|
doAssert(a+a==1) # assert test with Ast expression
|
||||||
|
# BUG: const folding would make "1+1==1" appear as `false` in
|
||||||
|
# assert message
|
||||||
|
except AssertionError as e:
|
||||||
|
checkMsg(e.msg, "`a + a == 1` ", "test4")
|
||||||
|
|
||||||
|
try:
|
||||||
|
let a = 1
|
||||||
|
doAssert a+a==1 # ditto with `doAssert` and no parens
|
||||||
|
except AssertionError as e:
|
||||||
|
checkMsg(e.msg, "`a + a == 1` ", "test5")
|
||||||
|
|
||||||
|
proc fooStatic() =
|
||||||
|
# protect against https://github.com/nim-lang/Nim/issues/8758
|
||||||
|
static: doAssert(true)
|
||||||
|
fooStatic()
|
||||||
|
|
||||||
# module-wide policy to change the failed assert
|
# module-wide policy to change the failed assert
|
||||||
# exception type in order to include a lineinfo
|
# exception type in order to include a lineinfo
|
||||||
onFailedAssert(msg):
|
onFailedAssert(msg):
|
||||||
|
|
@ -26,26 +71,26 @@ onFailedAssert(msg):
|
||||||
proc foo =
|
proc foo =
|
||||||
assert(false, "assertion from foo")
|
assert(false, "assertion from foo")
|
||||||
|
|
||||||
|
|
||||||
proc bar: int =
|
proc bar: int =
|
||||||
# local overrides that are active only
|
# local overrides that are active only in this proc
|
||||||
# in this proc
|
onFailedAssert(msg):
|
||||||
onFailedAssert(msg): echo "WARNING: " & msg
|
checkMsg(msg, "tfailedassert.nim(80, 9) `false` first assertion from bar", "test6")
|
||||||
|
|
||||||
assert(false, "first assertion from bar")
|
assert(false, "first assertion from bar")
|
||||||
|
|
||||||
onFailedAssert(msg):
|
onFailedAssert(msg):
|
||||||
echo "ERROR: " & msg
|
checkMsg(msg, "tfailedassert.nim(86, 9) `false` second assertion from bar", "test7")
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
assert(false, "second assertion from bar")
|
assert(false, "second assertion from bar")
|
||||||
return 10
|
return 10
|
||||||
|
|
||||||
echo("")
|
|
||||||
echo(bar())
|
echo(bar())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
foo()
|
foo()
|
||||||
except:
|
except:
|
||||||
let e = EMyError(getCurrentException())
|
let e = EMyError(getCurrentException())
|
||||||
echo e.lineinfo.filename, ":", e.lineinfo.line, " ", e.msg
|
echo e.lineinfo.filename
|
||||||
|
checkMsg(e.msg, "tfailedassert.nim(72, 9) `false` assertion from foo", "test7")
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,20 @@
|
||||||
discard """
|
discard """
|
||||||
cmd: "nim $target -d:release $options $file"
|
cmd: "nim $target -d:release $options $file"
|
||||||
output: '''
|
output: '''
|
||||||
assertion occured!!!!!! false
|
test1:ok
|
||||||
|
test2:ok
|
||||||
'''
|
'''
|
||||||
"""
|
"""
|
||||||
|
|
||||||
onFailedAssert(msg):
|
import testhelper
|
||||||
echo("assertion occured!!!!!! ", msg)
|
|
||||||
|
|
||||||
doAssert(1 == 2)
|
onFailedAssert(msg):
|
||||||
|
checkMsg(msg, "tfaileddoassert.nim(15, 9) `a == 2` foo", "test1")
|
||||||
|
|
||||||
|
var a = 1
|
||||||
|
doAssert(a == 2, "foo")
|
||||||
|
|
||||||
|
onFailedAssert(msg):
|
||||||
|
checkMsg(msg, "tfaileddoassert.nim(20, 10) `a == 3` ", "test2")
|
||||||
|
|
||||||
|
doAssert a == 3
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue