mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
all: support assert condition, extra_message
, evaluating and showing extra_message on assert failure (#15322)
This commit is contained in:
parent
fcde63127f
commit
77495c8d03
@ -11,6 +11,7 @@
|
|||||||
is made via `vgret` and is compared to the expected result.
|
is made via `vgret` and is compared to the expected result.
|
||||||
- VLS performance improvements, especially on Windows.
|
- VLS performance improvements, especially on Windows.
|
||||||
- Add `v ls` tool for installing, for updating, and for launching VLS (V Language Server)
|
- Add `v ls` tool for installing, for updating, and for launching VLS (V Language Server)
|
||||||
|
- Support `assert condition, extra_message`, where the `extra_message` will be evaluated and shown if the assertion fails.
|
||||||
|
|
||||||
## V 0.3
|
## V 0.3
|
||||||
*30 Jun 2022*
|
*30 Jun 2022*
|
||||||
|
@ -949,6 +949,10 @@ fn (t Tree) assert_stmt(node ast.AssertStmt) &Node {
|
|||||||
obj.add_terse('ast_type', t.string_node('AssertStmt'))
|
obj.add_terse('ast_type', t.string_node('AssertStmt'))
|
||||||
obj.add_terse('expr', t.expr(node.expr))
|
obj.add_terse('expr', t.expr(node.expr))
|
||||||
obj.add_terse('is_used', t.bool_node(node.is_used))
|
obj.add_terse('is_used', t.bool_node(node.is_used))
|
||||||
|
if node.extra !is ast.EmptyExpr {
|
||||||
|
obj.add_terse('extra', t.expr(node.extra))
|
||||||
|
obj.add('extra_pos', t.pos(node.extra_pos))
|
||||||
|
}
|
||||||
obj.add('pos', t.pos(node.pos))
|
obj.add('pos', t.pos(node.pos))
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
14
doc/docs.md
14
doc/docs.md
@ -3995,6 +3995,20 @@ assert fails it is reported to *stderr*, and the values on each side of a compar
|
|||||||
(such as `<`, `==`) will be printed when possible. This is useful to easily find an
|
(such as `<`, `==`) will be printed when possible. This is useful to easily find an
|
||||||
unexpected value. Assert statements can be used in any function.
|
unexpected value. Assert statements can be used in any function.
|
||||||
|
|
||||||
|
### Asserts with an extra message
|
||||||
|
|
||||||
|
This form of the `assert` statement, will print the extra message when it fails. Note, that
|
||||||
|
you can use any string expression there - string literals, functions returning a string,
|
||||||
|
strings that interpolate variables, etc.
|
||||||
|
|
||||||
|
```v
|
||||||
|
fn test_assertion_with_extra_message_failure() {
|
||||||
|
for i in 0 .. 100 {
|
||||||
|
assert i * 2 - 45 < 75 + 10, 'assertion failed for i: $i'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Test files
|
### Test files
|
||||||
|
|
||||||
```v
|
```v
|
||||||
|
@ -54,6 +54,8 @@ pub:
|
|||||||
rlabel string // the right side of the infix expressions as source
|
rlabel string // the right side of the infix expressions as source
|
||||||
lvalue string // the stringified *actual value* of the left side of a failed assertion
|
lvalue string // the stringified *actual value* of the left side of a failed assertion
|
||||||
rvalue string // the stringified *actual value* of the right side of a failed assertion
|
rvalue string // the stringified *actual value* of the right side of a failed assertion
|
||||||
|
message string // the value of the `message` from `assert cond, message`
|
||||||
|
has_msg bool // false for assertions like `assert cond`, true for `assert cond, 'oh no'`
|
||||||
}
|
}
|
||||||
|
|
||||||
// free frees the memory occupied by the assertion meta data. It is called automatically by
|
// free frees the memory occupied by the assertion meta data. It is called automatically by
|
||||||
@ -69,6 +71,7 @@ pub fn (ami &VAssertMetaInfo) free() {
|
|||||||
ami.rlabel.free()
|
ami.rlabel.free()
|
||||||
ami.lvalue.free()
|
ami.lvalue.free()
|
||||||
ami.rvalue.free()
|
ami.rvalue.free()
|
||||||
|
ami.message.free()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +84,9 @@ fn __print_assert_failure(i &VAssertMetaInfo) {
|
|||||||
} else {
|
} else {
|
||||||
eprintln(' right value: $i.rlabel = $i.rvalue')
|
eprintln(' right value: $i.rlabel = $i.rvalue')
|
||||||
}
|
}
|
||||||
|
if i.has_msg {
|
||||||
|
eprintln(' message: $i.message')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1510,9 +1510,11 @@ pub const (
|
|||||||
[minify]
|
[minify]
|
||||||
pub struct AssertStmt {
|
pub struct AssertStmt {
|
||||||
pub:
|
pub:
|
||||||
pos token.Pos
|
pos token.Pos
|
||||||
|
extra_pos token.Pos
|
||||||
pub mut:
|
pub mut:
|
||||||
expr Expr
|
expr Expr
|
||||||
|
extra Expr
|
||||||
is_used bool // asserts are used in _test.v files, as well as in non -prod builds of all files
|
is_used bool // asserts are used in _test.v files, as well as in non -prod builds of all files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1612,6 +1612,14 @@ fn (mut c Checker) assert_stmt(node ast.AssertStmt) {
|
|||||||
c.error('assert can be used only with `bool` expressions, but found `$atype_name` instead',
|
c.error('assert can be used only with `bool` expressions, but found `$atype_name` instead',
|
||||||
node.pos)
|
node.pos)
|
||||||
}
|
}
|
||||||
|
if node.extra !is ast.EmptyExpr {
|
||||||
|
extra_type := c.expr(node.extra)
|
||||||
|
if extra_type != ast.string_type {
|
||||||
|
extra_type_name := c.table.sym(extra_type).name
|
||||||
|
c.error('assert allows only a single string as its second argument, but found `$extra_type_name` instead',
|
||||||
|
node.extra_pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
c.fail_if_unreadable(node.expr, ast.bool_type_idx, 'assertion')
|
c.fail_if_unreadable(node.expr, ast.bool_type_idx, 'assertion')
|
||||||
c.expected_type = cur_exp_typ
|
c.expected_type = cur_exp_typ
|
||||||
}
|
}
|
||||||
|
20
vlib/v/checker/tests/assert_extra_message.out
Normal file
20
vlib/v/checker/tests/assert_extra_message.out
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
vlib/v/checker/tests/assert_extra_message.vv:12:24: error: assert allows only a single string as its second argument, but found `void` instead
|
||||||
|
10 | assert 2 == 6 / 3, 'math works'
|
||||||
|
11 | assert 10 == i - 120, 'i: $i'
|
||||||
|
12 | assert 10 == i - 120, abc(i)
|
||||||
|
| ~~~~~~
|
||||||
|
13 | assert 10 == i - 120, multy(i)
|
||||||
|
14 | assert 10 == i - 120, 456
|
||||||
|
vlib/v/checker/tests/assert_extra_message.vv:13:24: error: assert allows only a single string as its second argument, but found `(int, string)` instead
|
||||||
|
11 | assert 10 == i - 120, 'i: $i'
|
||||||
|
12 | assert 10 == i - 120, abc(i)
|
||||||
|
13 | assert 10 == i - 120, multy(i)
|
||||||
|
| ~~~~~~~~
|
||||||
|
14 | assert 10 == i - 120, 456
|
||||||
|
15 | }
|
||||||
|
vlib/v/checker/tests/assert_extra_message.vv:14:24: error: assert allows only a single string as its second argument, but found `int literal` instead
|
||||||
|
12 | assert 10 == i - 120, abc(i)
|
||||||
|
13 | assert 10 == i - 120, multy(i)
|
||||||
|
14 | assert 10 == i - 120, 456
|
||||||
|
| ~~~
|
||||||
|
15 | }
|
15
vlib/v/checker/tests/assert_extra_message.vv
Normal file
15
vlib/v/checker/tests/assert_extra_message.vv
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
fn abc(i int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn multy(i int) (int, string) {
|
||||||
|
return i, 'aa'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
i := 123
|
||||||
|
assert 2 == 6 / 3, 'math works'
|
||||||
|
assert 10 == i - 120, 'i: $i'
|
||||||
|
assert 10 == i - 120, abc(i)
|
||||||
|
assert 10 == i - 120, multy(i)
|
||||||
|
assert 10 == i - 120, 456
|
||||||
|
}
|
@ -750,6 +750,10 @@ pub fn (mut f Fmt) assert_stmt(node ast.AssertStmt) {
|
|||||||
expr = (expr as ast.ParExpr).expr
|
expr = (expr as ast.ParExpr).expr
|
||||||
}
|
}
|
||||||
f.expr(expr)
|
f.expr(expr)
|
||||||
|
if node.extra !is ast.EmptyExpr {
|
||||||
|
f.write(', ')
|
||||||
|
f.expr(node.extra)
|
||||||
|
}
|
||||||
f.writeln('')
|
f.writeln('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
6
vlib/v/fmt/tests/assert_extra_message_keep.vv
Normal file
6
vlib/v/fmt/tests/assert_extra_message_keep.vv
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
fn main() {
|
||||||
|
i := 123
|
||||||
|
assert 4 == 2 * 2
|
||||||
|
assert 2 == 6 / 3, 'math works'
|
||||||
|
assert 10 == i - 120, 'i: $i'
|
||||||
|
}
|
@ -99,7 +99,11 @@ fn (mut g Gen) gen_assert_metainfo(node ast.AssertStmt) string {
|
|||||||
mod_path := cestring(g.file.path)
|
mod_path := cestring(g.file.path)
|
||||||
fn_name := g.fn_decl.name
|
fn_name := g.fn_decl.name
|
||||||
line_nr := node.pos.line_nr
|
line_nr := node.pos.line_nr
|
||||||
src := cestring(node.expr.str())
|
mut src := node.expr.str()
|
||||||
|
if node.extra !is ast.EmptyExpr {
|
||||||
|
src += ', ' + node.extra.str()
|
||||||
|
}
|
||||||
|
src = cestring(src)
|
||||||
metaname := 'v_assert_meta_info_$g.new_tmp_var()'
|
metaname := 'v_assert_meta_info_$g.new_tmp_var()'
|
||||||
g.writeln('\tVAssertMetaInfo $metaname = {0};')
|
g.writeln('\tVAssertMetaInfo $metaname = {0};')
|
||||||
g.writeln('\t${metaname}.fpath = ${ctoslit(mod_path)};')
|
g.writeln('\t${metaname}.fpath = ${ctoslit(mod_path)};')
|
||||||
@ -127,6 +131,15 @@ fn (mut g Gen) gen_assert_metainfo(node ast.AssertStmt) string {
|
|||||||
}
|
}
|
||||||
else {}
|
else {}
|
||||||
}
|
}
|
||||||
|
if node.extra is ast.EmptyExpr {
|
||||||
|
g.writeln('\t${metaname}.has_msg = false;')
|
||||||
|
g.writeln('\t${metaname}.message = _SLIT0;')
|
||||||
|
} else {
|
||||||
|
g.writeln('\t${metaname}.has_msg = true;')
|
||||||
|
g.write('\t${metaname}.message = ')
|
||||||
|
g.gen_assert_single_expr(node.extra, ast.string_type)
|
||||||
|
g.writeln(';')
|
||||||
|
}
|
||||||
return metaname
|
return metaname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,8 +106,11 @@ pub fn (mut w Walker) stmt(node_ ast.Stmt) {
|
|||||||
}
|
}
|
||||||
ast.AssertStmt {
|
ast.AssertStmt {
|
||||||
if node.is_used {
|
if node.is_used {
|
||||||
w.expr(node.expr)
|
|
||||||
w.n_asserts++
|
w.n_asserts++
|
||||||
|
w.expr(node.expr)
|
||||||
|
if node.extra !is ast.EmptyExpr {
|
||||||
|
w.expr(node.extra)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ast.AssignStmt {
|
ast.AssignStmt {
|
||||||
|
@ -877,8 +877,19 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt {
|
|||||||
mut pos := p.tok.pos()
|
mut pos := p.tok.pos()
|
||||||
expr := p.expr(0)
|
expr := p.expr(0)
|
||||||
pos.update_last_line(p.prev_tok.line_nr)
|
pos.update_last_line(p.prev_tok.line_nr)
|
||||||
|
mut extra := ast.empty_expr
|
||||||
|
mut extra_pos := p.tok.pos()
|
||||||
|
if p.tok.kind == .comma {
|
||||||
|
p.next()
|
||||||
|
extra_pos = p.tok.pos()
|
||||||
|
extra = p.expr(0)
|
||||||
|
// dump(extra)
|
||||||
|
extra_pos = extra_pos.extend(p.tok.pos())
|
||||||
|
}
|
||||||
return ast.AssertStmt{
|
return ast.AssertStmt{
|
||||||
expr: expr
|
expr: expr
|
||||||
|
extra: extra
|
||||||
|
extra_pos: extra_pos
|
||||||
pos: pos.extend(p.tok.pos())
|
pos: pos.extend(p.tok.pos())
|
||||||
is_used: p.inside_test_file || !p.pref.is_prod
|
is_used: p.inside_test_file || !p.pref.is_prod
|
||||||
}
|
}
|
||||||
|
@ -154,6 +154,15 @@ fn (mut runner NormalTestRunner) assert_fail(i &VAssertMetaInfo) {
|
|||||||
} else {
|
} else {
|
||||||
eprintln(' $final_src')
|
eprintln(' $final_src')
|
||||||
}
|
}
|
||||||
|
if i.has_msg {
|
||||||
|
mut mtitle := ' Message:'
|
||||||
|
mut mvalue := '$i.message'
|
||||||
|
if runner.use_color {
|
||||||
|
mvalue = term.yellow(mvalue)
|
||||||
|
mtitle = term.gray(mtitle)
|
||||||
|
}
|
||||||
|
eprintln('$mtitle $mvalue')
|
||||||
|
}
|
||||||
eprintln('')
|
eprintln('')
|
||||||
runner.all_assertsions << i
|
runner.all_assertsions << i
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
vlib/v/tests/skip_unused/assert_with_extra_message_works_test.vv:7: fn test_interpolation_with_assert_that_has_extra_message
|
||||||
|
> assert 'abc$i' != 'abc77', my_failure_message(i)
|
||||||
|
Left value: abc77
|
||||||
|
Right value: abc77
|
||||||
|
Message: the assert failed :-|, i: 77
|
||||||
|
|
@ -0,0 +1,6 @@
|
|||||||
|
vlib/v/tests/skip_unused/assert_with_extra_message_works_test.vv:7: fn test_interpolation_with_assert_that_has_extra_message
|
||||||
|
> assert 'abc$i' != 'abc77', my_failure_message(i)
|
||||||
|
Left value: abc77
|
||||||
|
Right value: abc77
|
||||||
|
Message: the assert failed :-|, i: 77
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
fn my_failure_message(i int) string {
|
||||||
|
return 'the assert failed :-|, i: $i'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_interpolation_with_assert_that_has_extra_message() {
|
||||||
|
for i in 0 .. 100 {
|
||||||
|
assert 'abc$i' != 'abc77', my_failure_message(i)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user