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.
- VLS performance improvements, especially on Windows.
- 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
*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('expr', t.expr(node.expr))
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))
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
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
```v

View File

@ -54,6 +54,8 @@ pub:
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
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
@ -69,6 +71,7 @@ pub fn (ami &VAssertMetaInfo) free() {
ami.rlabel.free()
ami.lvalue.free()
ami.rvalue.free()
ami.message.free()
}
}
@ -81,6 +84,9 @@ fn __print_assert_failure(i &VAssertMetaInfo) {
} else {
eprintln(' right value: $i.rlabel = $i.rvalue')
}
if i.has_msg {
eprintln(' message: $i.message')
}
}
}

View File

@ -1510,9 +1510,11 @@ pub const (
[minify]
pub struct AssertStmt {
pub:
pos token.Pos
pos token.Pos
extra_pos token.Pos
pub mut:
expr Expr
extra Expr
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',
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.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
}
f.expr(expr)
if node.extra !is ast.EmptyExpr {
f.write(', ')
f.expr(node.extra)
}
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)
fn_name := g.fn_decl.name
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()'
g.writeln('\tVAssertMetaInfo $metaname = {0};')
g.writeln('\t${metaname}.fpath = ${ctoslit(mod_path)};')
@ -127,6 +131,15 @@ fn (mut g Gen) gen_assert_metainfo(node ast.AssertStmt) string {
}
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
}

View File

@ -106,8 +106,11 @@ pub fn (mut w Walker) stmt(node_ ast.Stmt) {
}
ast.AssertStmt {
if node.is_used {
w.expr(node.expr)
w.n_asserts++
w.expr(node.expr)
if node.extra !is ast.EmptyExpr {
w.expr(node.extra)
}
}
}
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()
expr := p.expr(0)
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{
expr: expr
extra: extra
extra_pos: extra_pos
pos: pos.extend(p.tok.pos())
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 {
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('')
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)
}
}