1
0
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:
Delyan Angelov 2022-08-03 01:14:01 +03:00 committed by GitHub
parent fcde63127f
commit 77495c8d03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 140 additions and 3 deletions

View File

@ -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*

View File

@ -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
} }

View File

@ -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

View File

@ -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')
}
} }
} }

View File

@ -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
} }

View File

@ -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
} }

View 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 | }

View 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
}

View File

@ -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('')
} }

View 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'
}

View File

@ -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
} }

View File

@ -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 {

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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

View File

@ -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

View File

@ -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)
}
}