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.
|
||||
- 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*
|
||||
|
@ -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
|
||||
}
|
||||
|
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
|
||||
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
|
||||
|
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
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
|
||||
}
|
||||
f.expr(expr)
|
||||
if node.extra !is ast.EmptyExpr {
|
||||
f.write(', ')
|
||||
f.expr(node.extra)
|
||||
}
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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