From ba6cc5df2ad721bab445466028ffe05e5a6fd087 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Wed, 30 Oct 2019 11:15:33 +0200 Subject: [PATCH] compiler: print asserted source line on failure --- vlib/compiler/compile_errors.v | 17 ++++++--- vlib/compiler/main.v | 11 ++++-- vlib/compiler/parser.v | 37 +++++++++++++++++-- vlib/compiler/preludes/tests_assertions.v | 35 ++++++++++++++++++ .../preludes/tests_with_stats.v} | 0 vlib/compiler/scanner.v | 18 ++++++--- 6 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 vlib/compiler/preludes/tests_assertions.v rename vlib/{benchmark/tests/always_imported.v => compiler/preludes/tests_with_stats.v} (100%) diff --git a/vlib/compiler/compile_errors.v b/vlib/compiler/compile_errors.v index 23ee082367..2b8b1c86f5 100644 --- a/vlib/compiler/compile_errors.v +++ b/vlib/compiler/compile_errors.v @@ -96,8 +96,8 @@ fn (s &Scanner) error_with_col(msg string, col int) { eprintln('${fullpath}:${s.line_nr + 1}:${col}: $final_message') if s.should_print_line_on_error && s.nlines > 0 { - context_start_line := imax(0, (s.line_nr - error_context_before + 1 )) - context_end_line := imin(s.nlines-1, (s.line_nr + error_context_after + 1 )) + context_start_line := imax(0, (s.line_nr - error_context_before )) + context_end_line := imin(s.nlines-1, (s.line_nr + error_context_after + 1 )) for cline := context_start_line; cline < context_end_line; cline++ { line := '${(cline+1):5d}| ' + s.line( cline ) coloredline := if cline == s.line_nr && color_on { term.red(line) } else { line } @@ -132,7 +132,13 @@ fn (s &Scanner) error_with_col(msg string, col int) { [inline] fn imin(a,b int) int { return if a < b { a } else { b } } fn (s &Scanner) get_error_filepath() string { - if s.should_print_relative_paths_on_error { + verror_paths_override := os.getenv('VERROR_PATHS') + use_relative_paths := match verror_paths_override { + 'relative' { true } + 'absolute' { false } + else { s.should_print_relative_paths_on_error } + } + if use_relative_paths { workdir := os.getwd() + os.path_separator if s.file_path.starts_with(workdir) { return s.file_path.replace( workdir, '') @@ -246,11 +252,12 @@ fn (s mut Scanner) get_scanner_pos_of_token(t &Token) ScannerPos { /////////////////// s.get_scanner_pos() /////////////////// which just returns a struct, and that works /////////////////// in gcc and clang, but causes the TCC problem. - + + maxline := imin( s.nlines, tline + 2 * error_context_after) for { prevlinepos = s.pos if s.pos >= s.text.len { break } - if s.line_nr > tline { break } + if s.line_nr > maxline { break } //////////////////////////////////////// if tline == s.line_nr { sptoken = s.get_scanner_pos() diff --git a/vlib/compiler/main.v b/vlib/compiler/main.v index 116deb035e..08e1bfb77d 100644 --- a/vlib/compiler/main.v +++ b/vlib/compiler/main.v @@ -648,11 +648,14 @@ pub fn (v &V) get_user_files() []string { // libs, but we dont know which libs need to be added yet mut user_files := []string - if v.pref.is_test && v.pref.is_stats { - user_files << os.join(v.vroot, 'vlib', 'benchmark', 'tests', - 'always_imported.v') + if v.pref.is_test { + user_files << os.join(v.vroot,'vlib','compiler','preludes','tests_assertions.v') } - + + if v.pref.is_test && v.pref.is_stats { + user_files << os.join(v.vroot,'vlib','compiler','preludes','tests_with_stats.v') + } + // v volt/slack_test.v: compile all .v files to get the environment // I need to implement user packages! TODO is_test_with_imports := dir.ends_with('_test.v') && diff --git a/vlib/compiler/parser.v b/vlib/compiler/parser.v index a85fa0eb80..fd3fa2266d 100644 --- a/vlib/compiler/parser.v +++ b/vlib/compiler/parser.v @@ -3578,20 +3578,49 @@ fn (p mut Parser) assert_statement() { tmp := p.get_tmp() p.gen('bool $tmp = ') p.check_types(p.bool_expression(), 'bool') + nline := p.scanner.line_nr // TODO print "expected: got" for failed tests filename := cescaped_path(p.file_path) - p.genln('; -\n + cfname:=p.cur_fn.name.replace('main__', '') + sourceline := p.scanner.line( nline - 1 ).replace('"', '\'') + if !p.pref.is_test { + // an assert used in a normal v program. no fancy formatting + p.genln(';\n +/// sline: "$sourceline" +if (!$tmp) { + g_test_fails++; + eprintln(tos3("${filename}:${p.scanner.line_nr}: FAILED: ${cfname}()")); + eprintln(tos3("Source: $sourceline")); + v_panic(tos3("An assertion failed.")); + return; +} else { + g_test_oks++; +} +') + return + } + + p.genln(';\n if (!$tmp) { - println(tos2((byte *)"\\x1B[31mFAILED: $p.cur_fn.name() in $filename:$p.scanner.line_nr\\x1B[0m")); g_test_fails++; + main__cb_assertion_failed( + tos3("$filename"), + $p.scanner.line_nr, + tos3("$sourceline"), + tos3("$p.cur_fn.name()") + ); return; // TODO // Maybe print all vars in a test function if it fails? } else { g_test_oks++; - //println(tos2((byte *)"\\x1B[32mPASSED: $p.cur_fn.name()\\x1B[0m")); + main__cb_assertion_ok( + tos3("$filename"), + $p.scanner.line_nr, + tos3("$sourceline"), + tos3("$p.cur_fn.name()") + ); } ') diff --git a/vlib/compiler/preludes/tests_assertions.v b/vlib/compiler/preludes/tests_assertions.v new file mode 100644 index 0000000000..306145f488 --- /dev/null +++ b/vlib/compiler/preludes/tests_assertions.v @@ -0,0 +1,35 @@ +module main + +import os +import term + +//////////////////////////////////////////////////////////////////// +/// This file will get compiled as part of the main program, +/// for a _test.v file. +/// The methods defined here are called back by the test program's +/// assert statements, on each success/fail. The goal is to make +/// customizing the look & feel of the assertions results easier, +/// since it is done in normal V code, instead of in embedded C ... +//////////////////////////////////////////////////////////////////// + +fn cb_assertion_failed(filename string, line int, sourceline string, funcname string){ + color_on := term.can_show_color_on_stderr() + use_relative_paths := match os.getenv('VERROR_PATHS') { + 'absolute' { false } + else { true } + } + final_filename := if use_relative_paths { filename } else { os.realpath( filename ) } + final_funcname := funcname.replace('main__','') + + mut fail_message := 'FAILED assertion' + if color_on { fail_message = term.bold(term.red(fail_message)) } + + eprintln('$final_filename:$line: $fail_message') + eprintln('Function: $final_funcname') + eprintln('Source : $sourceline') +} + +fn cb_assertion_ok(filename string, line int, sourceline string, funcname string){ + //do nothing for now on an OK assertion + //println('OK ${line:5d}|$sourceline ') +} diff --git a/vlib/benchmark/tests/always_imported.v b/vlib/compiler/preludes/tests_with_stats.v similarity index 100% rename from vlib/benchmark/tests/always_imported.v rename to vlib/compiler/preludes/tests_with_stats.v diff --git a/vlib/compiler/scanner.v b/vlib/compiler/scanner.v index 04a7a3feb2..485d4171b1 100644 --- a/vlib/compiler/scanner.v +++ b/vlib/compiler/scanner.v @@ -236,6 +236,12 @@ fn (s mut Scanner) skip_whitespace() { } } +fn (s mut Scanner) end_of_file() ScanRes { + s.pos = s.text.len + s.inc_line_number() + return scan_res(.eof, '') +} + fn (s mut Scanner) scan() ScanRes { //if s.line_comment != '' { //s.fgenln('// LC "$s.line_comment"') @@ -246,7 +252,7 @@ fn (s mut Scanner) scan() ScanRes { } s.started = true if s.pos >= s.text.len { - return scan_res(.eof, '') + return s.end_of_file() } if !s.inside_string { s.skip_whitespace() @@ -263,7 +269,7 @@ fn (s mut Scanner) scan() ScanRes { s.skip_whitespace() // end of file if s.pos >= s.text.len { - return scan_res(.eof, '') + return s.end_of_file() } // handle each char c := s.text[s.pos] @@ -618,7 +624,7 @@ fn (s mut Scanner) scan() ScanRes { } $if windows { if c == `\0` { - return scan_res(.eof, '') + return s.end_of_file() } } mut msg := 'invalid character `${c.str()}`' @@ -626,7 +632,7 @@ fn (s mut Scanner) scan() ScanRes { msg += ', use \' to denote strings' } s.error(msg) - return scan_res(.eof, '') + return s.end_of_file() } fn (s &Scanner) current_column() int { @@ -804,7 +810,9 @@ fn (s mut Scanner) inc_line_number() { s.last_nl_pos = s.pos s.line_nr++ s.line_ends << s.pos - s.nlines++ + if s.line_nr > s.nlines { + s.nlines = s.line_nr + } } fn (s Scanner) line(n int) string {