From 5cad7494dfd92b8c764d14d0ff550e4be533b53e Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Sun, 31 Mar 2024 17:06:33 -0300 Subject: [PATCH 01/38] fix --- vlib/v/gen/c/assert.v | 2 +- vlib/v/gen/c/cgen.v | 87 ++++++++++++++++++++++++++++++++++++------- vlib/v/gen/c/fn.v | 2 +- vlib/v/gen/c/match.v | 12 +++--- vlib/v/pref/pref.c.v | 4 ++ 5 files changed, 86 insertions(+), 21 deletions(-) diff --git a/vlib/v/gen/c/assert.v b/vlib/v/gen/c/assert.v index 2da73b854128d4..f0801b6894bea0 100644 --- a/vlib/v/gen/c/assert.v +++ b/vlib/v/gen/c/assert.v @@ -106,7 +106,7 @@ fn (mut g Gen) assert_subexpression_to_ctemp(expr ast.Expr, expr_type ast.Type) } fn (mut g Gen) gen_assert_postfailure_mode(node ast.AssertStmt) { - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) if g.pref.assert_failure_mode == .continues || g.fn_decl.attrs.any(it.name == 'assert_continues') { return diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 7760c061adfed6..0eaa97fd03a964 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -119,6 +119,9 @@ mut: labeled_loops map[string]&ast.Stmt inner_loop &ast.Stmt = unsafe { nil } shareds map[int]string // types with hidden mutex for which decl has been emitted + coverage_files []&ast.File + coverage_total_lines int + coverage_idx int inside_ternary int // ?: comma separated statements on a single line inside_map_postfix bool // inside map++/-- postfix expr inside_map_infix bool // inside map< 0 || g.out_results_forward.len > 0 { tail := g.type_definitions.cut_to(g.options_pos_forward) @@ -2051,7 +2058,7 @@ fn (mut g Gen) expr_with_tmp_var(expr ast.Expr, expr_typ ast.Type, ret_typ ast.T } @[inline] -fn (mut g Gen) write_v_source_line_info(pos token.Pos) { +fn (mut g Gen) write_v_source_line_info_pos(pos token.Pos) { if g.inside_ternary == 0 && g.pref.is_vlines && g.is_vlines_enabled { nline := pos.line_nr + 1 lineinfo := '\n#line ${nline} "${g.vlines_path}"' @@ -2062,6 +2069,46 @@ fn (mut g Gen) write_v_source_line_info(pos token.Pos) { } } +@[inline] +fn (mut g Gen) write_v_source_line_info(node ast.Node) { + g.write_v_source_line_info_pos(node.pos()) + if g.inside_ternary == 0 && g.pref.is_coverage + && node !in [ast.MatchBranch, ast.IfBranch, ast.InfixExpr] { + if g.file !in g.coverage_files { + g.coverage_idx = if g.coverage_files.len == 0 { + 0 + } else { + g.coverage_total_lines + } + g.coverage_total_lines += g.file.nr_lines + g.coverage_files << g.file + } + if g.fn_decl != unsafe { nil } { + g.writeln('_v_cov[${g.coverage_idx}+${node.pos().line_nr}] = 1;') + } + } +} + +@[inline] +fn (mut g Gen) write_v_source_line_info_stmt(stmt ast.Stmt) { + g.write_v_source_line_info_pos(stmt.pos) + if g.inside_ternary == 0 && g.pref.is_coverage && !g.inside_for_c_stmt + && stmt !in [ast.ExprStmt, ast.FnDecl, ast.ForCStmt, ast.ForInStmt, ast.ForStmt] { + if g.file !in g.coverage_files { + g.coverage_idx = if g.coverage_files.len == 0 { + 0 + } else { + g.coverage_total_lines + } + g.coverage_total_lines += g.file.nr_lines + g.coverage_files << g.file + } + if g.fn_decl != unsafe { nil } { + g.writeln('_v_cov[${g.coverage_idx}+${stmt.pos.line_nr}] = \'1\';') + } + } +} + fn (mut g Gen) stmt(node ast.Stmt) { $if trace_cgen_stmt ? { ntype := typeof(node).replace('v.ast.', '') @@ -2077,19 +2124,19 @@ fn (mut g Gen) stmt(node ast.Stmt) { } match node { ast.AsmStmt { - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) g.asm_stmt(node) } ast.AssertStmt { - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) g.assert_stmt(node) } ast.AssignStmt { - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) g.assign_stmt(node) } ast.Block { - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) if node.is_unsafe { g.writeln('{ // Unsafe block') } else { @@ -2099,11 +2146,11 @@ fn (mut g Gen) stmt(node ast.Stmt) { g.writeln('}') } ast.BranchStmt { - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) g.branch_stmt(node) } ast.ConstDecl { - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) g.const_decl(node) } ast.ComptimeFor { @@ -2123,7 +2170,7 @@ fn (mut g Gen) stmt(node ast.Stmt) { g.enum_decl(node) } ast.ExprStmt { - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) // af := g.autofree && node.expr is ast.CallExpr && !g.is_builtin_mod // if af { // g.autofree_call_pregen(node.expr as ast.CallExpr) @@ -2163,7 +2210,7 @@ fn (mut g Gen) stmt(node ast.Stmt) { g.labeled_loops[node.label] = &node } } - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) g.for_c_stmt(node) g.branch_parent_pos = prev_branch_parent_pos g.labeled_loops.delete(node.label) @@ -2179,7 +2226,7 @@ fn (mut g Gen) stmt(node ast.Stmt) { g.labeled_loops[node.label] = &node } } - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) g.for_in_stmt(node) g.branch_parent_pos = prev_branch_parent_pos g.labeled_loops.delete(node.label) @@ -2195,7 +2242,7 @@ fn (mut g Gen) stmt(node ast.Stmt) { g.labeled_loops[node.label] = &node } } - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) g.for_stmt(node) g.branch_parent_pos = prev_branch_parent_pos g.labeled_loops.delete(node.label) @@ -2208,7 +2255,7 @@ fn (mut g Gen) stmt(node ast.Stmt) { g.writeln('${c_name(node.name)}: {}') } ast.GotoStmt { - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) g.writeln('goto ${c_name(node.name)};') } ast.HashStmt { @@ -5246,7 +5293,7 @@ fn (mut g Gen) branch_stmt(node ast.BranchStmt) { fn (mut g Gen) return_stmt(node ast.Return) { g.set_current_pos_as_last_stmt_pos() - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) g.inside_return = true defer { @@ -6383,6 +6430,20 @@ fn (mut g Gen) write_init_function() { if g.pref.use_coroutines { g.writeln('\tdelete_photon_work_pool();') } + if g.pref.is_coverage { + g.writeln('\tprintf("V coverage\\n");') + g.writeln('\tprintf("${'-':50r}\\n");') + mut last_offset := 0 + for file in g.coverage_files { + g.writeln('{') + g.writeln('\tint counter = 0;') + g.writeln('\tfor (int i = 0, offset = ${last_offset}; i < ${file.nr_lines}; ++i)') + g.writeln('\t\tif (_v_cov[offset+i]) counter++;') + g.writeln('\tprintf("> ${file.path} | ${file.nr_lines} | %d\\n", counter);') + g.writeln('}') + last_offset += file.nr_lines + } + } g.writeln('}') if g.pref.printfn_list.len > 0 && '_vcleanup' in g.pref.printfn_list { println(g.out.after(fn_vcleanup_start_pos)) diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index f07bdc0de4618b..1cf7931b510162 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -212,7 +212,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { g.definitions.writeln(';') } - g.write_v_source_line_info(node.pos) + g.write_v_source_line_info_stmt(node) fn_attrs := g.write_fn_attrs(node.attrs) // Live is_livefn := node.attrs.contains('live') diff --git a/vlib/v/gen/c/match.v b/vlib/v/gen/c/match.v index 98883b6c666120..6ffce6eae41995 100644 --- a/vlib/v/gen/c/match.v +++ b/vlib/v/gen/c/match.v @@ -183,7 +183,7 @@ fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var str g.write(' : ') } else { g.writeln('') - g.write_v_source_line_info(branch.pos) + g.write_v_source_line_info(branch) g.writeln('else {') } } else { @@ -191,7 +191,7 @@ fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var str if use_ternary { g.write(' : ') } else { - g.write_v_source_line_info(branch.pos) + g.write_v_source_line_info(branch) g.write('else ') } } @@ -201,7 +201,7 @@ fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var str if j == 0 && sumtype_index == 0 { g.empty_line = true } - g.write_v_source_line_info(branch.pos) + g.write_v_source_line_info(branch) g.write('if (') } g.write(cond_var) @@ -428,7 +428,7 @@ fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var str g.write(' : ') } else { g.writeln('') - g.write_v_source_line_info(branch.pos) + g.write_v_source_line_info(branch) g.writeln('else {') } } @@ -438,7 +438,7 @@ fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var str g.write(' : ') } else { g.writeln('') - g.write_v_source_line_info(branch.pos) + g.write_v_source_line_info(branch) g.write('else ') } } @@ -448,7 +448,7 @@ fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var str if j == 0 { g.writeln('') } - g.write_v_source_line_info(branch.pos) + g.write_v_source_line_info(branch) g.write('if (') } for i, expr in branch.exprs { diff --git a/vlib/v/pref/pref.c.v b/vlib/v/pref/pref.c.v index b720edabf56c46..9527b7051b60e3 100644 --- a/vlib/v/pref/pref.c.v +++ b/vlib/v/pref/pref.c.v @@ -134,6 +134,7 @@ pub mut: is_cstrict bool // turn on more C warnings; slightly slower is_callstack bool // turn on callstack registers on each call when v.debug is imported is_trace bool // turn on possibility to trace fn call where v.debug is imported + is_coverage bool // turn on code coverage stats eval_argument string // `println(2+2)` on `v -e "println(2+2)"`. Note that this source code, will be evaluated in vsh mode, so 'v -e 'println(ls(".")!)' is valid. test_runner string // can be 'simple' (fastest, but much less detailed), 'tap', 'normal' profile_file string // the profile results will be stored inside profile_file @@ -1084,6 +1085,9 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin if 'trace' in res.compile_defines_all { res.is_trace = true } + if 'coverage' in res.compile_defines_all { + res.is_coverage = true + } // keep only the unique res.build_options: mut m := map[string]string{} for x in res.build_options { From c25591411e6796fe58e57555208621ae244b5f8b Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Mon, 1 Apr 2024 09:49:28 -0300 Subject: [PATCH 02/38] fix --- vlib/v/gen/c/cgen.v | 84 +++++++++++++++++++++++++++----------------- vlib/v/pref/pref.c.v | 10 ++++-- 2 files changed, 58 insertions(+), 36 deletions(-) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 0eaa97fd03a964..a6769e7641d686 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -42,6 +42,15 @@ fn string_array_to_map(a []string) map[string]bool { return res } +// V coverage info +@[heap] +struct CoverageInfo { +pub mut: + idx int // index + points []u64 // code point line nr + file &ast.File = unsafe { nil } +} + pub struct Gen { pref &pref.Preferences = unsafe { nil } field_data_type ast.Type // cache her to avoid map lookups @@ -119,9 +128,7 @@ mut: labeled_loops map[string]&ast.Stmt inner_loop &ast.Stmt = unsafe { nil } shareds map[int]string // types with hidden mutex for which decl has been emitted - coverage_files []&ast.File - coverage_total_lines int - coverage_idx int + coverage_files map[u64]&CoverageInfo inside_ternary int // ?: comma separated statements on a single line inside_map_postfix bool // inside map++/-- postfix expr inside_map_infix bool // inside map< ${file.path} | ${file.nr_lines} | %d\\n", counter);') + g.writeln('\tfor (int i = 0, offset = ${last_offset}; i < ${nr_points}; ++i)') + g.writeln('\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) counter++;') + g.writeln('\tt_counter += counter;') + g.writeln('\tprintf("> ${cov.file.path} | ${nr_points} | %d | %.2f% \\n", counter, ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0);') g.writeln('}') - last_offset += file.nr_lines + last_offset += nr_points } + g.writeln('\tprintf("${'-':50r}\\n");') + g.writeln('\tprintf("Total coverage: ${g.coverage_files.len} files, %.2f%% coverage\\n", ((double)t_counter/${t_points})*100);') } g.writeln('}') if g.pref.printfn_list.len > 0 && '_vcleanup' in g.pref.printfn_list { diff --git a/vlib/v/pref/pref.c.v b/vlib/v/pref/pref.c.v index 9527b7051b60e3..28edc5c02a8f60 100644 --- a/vlib/v/pref/pref.c.v +++ b/vlib/v/pref/pref.c.v @@ -138,6 +138,7 @@ pub mut: eval_argument string // `println(2+2)` on `v -e "println(2+2)"`. Note that this source code, will be evaluated in vsh mode, so 'v -e 'println(ls(".")!)' is valid. test_runner string // can be 'simple' (fastest, but much less detailed), 'tap', 'normal' profile_file string // the profile results will be stored inside profile_file + coverage_file string // the coverage results will be stored inside coverage_file profile_no_inline bool // when true, [inline] functions would not be profiled profile_fns []string // when set, profiling will be off by default, but inside these functions (and what they call) it will be on. translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc @@ -617,6 +618,12 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin res.build_options << '${arg} ${res.profile_file}' i++ } + '-cov', '-coverage' { + // res.coverage_file = cmdline.option(current_args, arg, '-') + res.is_coverage = true + // res.build_options << '${arg} ${res.profile_file}' + // i++ + } '-profile-fns' { profile_fns := cmdline.option(current_args, arg, '').split(',') if profile_fns.len > 0 { @@ -1085,9 +1092,6 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin if 'trace' in res.compile_defines_all { res.is_trace = true } - if 'coverage' in res.compile_defines_all { - res.is_coverage = true - } // keep only the unique res.build_options: mut m := map[string]string{} for x in res.build_options { From f9ed550c4aacdc9d8f89a756ca8221c36a7fd87b Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Mon, 1 Apr 2024 10:35:56 -0300 Subject: [PATCH 03/38] improvement --- vlib/v/gen/c/cgen.v | 48 +++++++---------------------- vlib/v/gen/c/coverage.v | 67 +++++++++++++++++++++++++++++++++++++++++ vlib/v/pref/pref.c.v | 6 ++-- 3 files changed, 81 insertions(+), 40 deletions(-) create mode 100644 vlib/v/gen/c/coverage.v diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index a6769e7641d686..c3a5cdc7366e69 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -78,6 +78,7 @@ mut: auto_str_funcs strings.Builder // function bodies of all auto generated _str funcs dump_funcs strings.Builder // function bodies of all auto generated _str funcs pcs_declarations strings.Builder // -prof profile counter declarations for each function + cov_declarations strings.Builder // -cov coverage embedded_data strings.Builder // data to embed in the executable/binary shared_types strings.Builder // shared/lock types shared_functions strings.Builder // shared constructors @@ -309,6 +310,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) (str auto_str_funcs: strings.new_builder(100) dump_funcs: strings.new_builder(100) pcs_declarations: strings.new_builder(100) + cov_declarations: strings.new_builder(100) embedded_data: strings.new_builder(1000) out_options_forward: strings.new_builder(100) out_options: strings.new_builder(100) @@ -389,6 +391,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) (str global_g.dump_funcs.write(g.auto_str_funcs) or { panic(err) } global_g.comptime_definitions.write(g.comptime_definitions) or { panic(err) } global_g.pcs_declarations.write(g.pcs_declarations) or { panic(err) } + global_g.cov_declarations.write(g.cov_declarations) or { panic(err) } global_g.hotcode_definitions.write(g.hotcode_definitions) or { panic(err) } global_g.embedded_data.write(g.embedded_data) or { panic(err) } global_g.shared_types.write(g.shared_types) or { panic(err) } @@ -562,6 +565,10 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) (str b.writeln('\n// V profile counters:') b.write_string(g.pcs_declarations.str()) } + if g.pref.is_coverage { + b.writeln('\n// V coverage:') + b.write_string(g.cov_declarations.str()) + } b.writeln('\n// V includes:') b.write_string(g.includes.str()) b.writeln('\n// Enum definitions:') @@ -676,6 +683,7 @@ fn cgen_process_one_file_cb(mut p pool.PoolProcessor, idx int, wid int) &Gen { auto_str_funcs: strings.new_builder(100) comptime_definitions: strings.new_builder(100) pcs_declarations: strings.new_builder(100) + cov_declarations: strings.new_builder(100) hotcode_definitions: strings.new_builder(100) embedded_data: strings.new_builder(1000) out_options_forward: strings.new_builder(100) @@ -746,6 +754,7 @@ pub fn (mut g Gen) free_builders() { g.dump_funcs.free() g.comptime_definitions.free() g.pcs_declarations.free() + g.cov_declarations.free() g.hotcode_definitions.free() g.embedded_data.free() g.shared_types.free() @@ -2084,24 +2093,6 @@ fn (mut g Gen) write_v_source_line_info_pos(pos token.Pos) { } } -@[inline] -fn (mut g Gen) write_coverage_point(pos token.Pos) { - if g.unique_file_path_hash !in g.coverage_files { - g.coverage_files[g.unique_file_path_hash] = &CoverageInfo{ - points: [] - file: g.file - } - } - if g.fn_decl != unsafe { nil } { - curr_line := u64(pos.line_nr) - mut curr_cov := unsafe { g.coverage_files[g.unique_file_path_hash] } - if curr_line !in curr_cov.points { - curr_cov.points << curr_line - } - g.writeln('_v_cov[_v_cov_file_offset_${g.unique_file_path_hash}+${curr_cov.points.len - 1}] = 1;') - } -} - @[inline] fn (mut g Gen) write_v_source_line_info(node ast.Node) { g.write_v_source_line_info_pos(node.pos()) @@ -6442,25 +6433,8 @@ fn (mut g Gen) write_init_function() { g.writeln('\tdelete_photon_work_pool();') } if g.pref.is_coverage { - g.writeln('\tprintf("V coverage\\n");') - g.writeln('\tprintf("${'-':50r}\\n");') - g.writeln('int t_counter = 0;') - mut last_offset := 0 - mut t_points := 0 - for k, cov in g.coverage_files { - nr_points := cov.points.len - t_points += nr_points - g.writeln('{') - g.writeln('\tint counter = 0;') - g.writeln('\tfor (int i = 0, offset = ${last_offset}; i < ${nr_points}; ++i)') - g.writeln('\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) counter++;') - g.writeln('\tt_counter += counter;') - g.writeln('\tprintf("> ${cov.file.path} | ${nr_points} | %d | %.2f% \\n", counter, ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0);') - g.writeln('}') - last_offset += nr_points - } - g.writeln('\tprintf("${'-':50r}\\n");') - g.writeln('\tprintf("Total coverage: ${g.coverage_files.len} files, %.2f%% coverage\\n", ((double)t_counter/${t_points})*100);') + g.write_coverage_stats() + g.writeln('\tvprint_coverage_stats();') } g.writeln('}') if g.pref.printfn_list.len > 0 && '_vcleanup' in g.pref.printfn_list { diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v new file mode 100644 index 00000000000000..6a7a7a54416c24 --- /dev/null +++ b/vlib/v/gen/c/coverage.v @@ -0,0 +1,67 @@ +// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module c + +import v.token + +@[inline] +fn (mut g Gen) write_coverage_point(pos token.Pos) { + if g.unique_file_path_hash !in g.coverage_files { + g.coverage_files[g.unique_file_path_hash] = &CoverageInfo{ + points: [] + file: g.file + } + } + if g.fn_decl != unsafe { nil } { + curr_line := u64(pos.line_nr) + mut curr_cov := unsafe { g.coverage_files[g.unique_file_path_hash] } + if curr_line !in curr_cov.points { + curr_cov.points << curr_line + } + g.writeln('_v_cov[_v_cov_file_offset_${g.unique_file_path_hash}+${curr_cov.points.len - 1}] = 1;') + } +} + +fn (mut g Gen) write_coverage_stats() { + is_stdout := g.pref.coverage_file == '-' + + g.cov_declarations.writeln('void vprint_coverage_stats() {') + if is_stdout { + g.cov_declarations.writeln('\tprintf("V coverage\\n");') + g.cov_declarations.writeln('\tprintf("${'-':50r}\\n");') + } else { + g.cov_declarations.writeln('FILE *fp = stdout;') + g.cov_declarations.writeln('fp = fopen ("${g.pref.coverage_file}", "w+");') + g.cov_declarations.writeln('\tfprintf(fp, "V coverage\\n");') + g.cov_declarations.writeln('\tfprintf(fp, "${'-':50r}\\n");') + } + g.cov_declarations.writeln('int t_counter = 0;') + mut last_offset := 0 + mut t_points := 0 + for k, cov in g.coverage_files { + nr_points := cov.points.len + t_points += nr_points + g.cov_declarations.writeln('{') + g.cov_declarations.writeln('\tint counter = 0;') + g.cov_declarations.writeln('\tfor (int i = 0, offset = ${last_offset}; i < ${nr_points}; ++i)') + g.cov_declarations.writeln('\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) counter++;') + g.cov_declarations.writeln('\tt_counter += counter;') + if is_stdout { + g.cov_declarations.writeln('\tprintf("> ${cov.file.path} | ${nr_points} | %d | %.2f% \\n", counter, ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0);') + } else { + g.cov_declarations.writeln('\tfprintf(fp, "> ${cov.file.path} | ${nr_points} | %d | %.2f% \\n", counter, ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0);') + } + g.cov_declarations.writeln('}') + last_offset += nr_points + } + if is_stdout { + g.cov_declarations.writeln('\tprintf("${'-':50r}\\n");') + g.cov_declarations.writeln('\tprintf("Total coverage: ${g.coverage_files.len} files, %.2f%% coverage\\n", ((double)t_counter/${t_points})*100);') + } else { + g.cov_declarations.writeln('\tfprintf(fp, "${'-':50r}\\n");') + g.cov_declarations.writeln('\tfprintf(fp, "Total coverage: ${g.coverage_files.len} files, %.2f%% coverage\\n", ((double)t_counter/${t_points})*100);') + g.cov_declarations.writeln('\tfclose(fp);') + } + g.cov_declarations.writeln('}') +} diff --git a/vlib/v/pref/pref.c.v b/vlib/v/pref/pref.c.v index 28edc5c02a8f60..d5a46c94598282 100644 --- a/vlib/v/pref/pref.c.v +++ b/vlib/v/pref/pref.c.v @@ -619,10 +619,10 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin i++ } '-cov', '-coverage' { - // res.coverage_file = cmdline.option(current_args, arg, '-') + res.coverage_file = cmdline.option(current_args, arg, '-') res.is_coverage = true - // res.build_options << '${arg} ${res.profile_file}' - // i++ + res.build_options << '${arg} ${res.coverage_file}' + i++ } '-profile-fns' { profile_fns := cmdline.option(current_args, arg, '').split(',') From a1c524c93d6d0fe0bf7a9ada117151e65c49a6e9 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Mon, 1 Apr 2024 11:33:54 -0300 Subject: [PATCH 04/38] fix --- vlib/v/gen/c/coverage.v | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 6a7a7a54416c24..e25ed7b0648fca 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. +// Copyright (c) 2024 Felipe Pena. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module c @@ -31,28 +31,28 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('\tprintf("V coverage\\n");') g.cov_declarations.writeln('\tprintf("${'-':50r}\\n");') } else { - g.cov_declarations.writeln('FILE *fp = stdout;') - g.cov_declarations.writeln('fp = fopen ("${g.pref.coverage_file}", "w+");') + g.cov_declarations.writeln('\tFILE *fp = stdout;') + g.cov_declarations.writeln('\tfp = fopen ("${g.pref.coverage_file}", "w+");') g.cov_declarations.writeln('\tfprintf(fp, "V coverage\\n");') g.cov_declarations.writeln('\tfprintf(fp, "${'-':50r}\\n");') } - g.cov_declarations.writeln('int t_counter = 0;') + g.cov_declarations.writeln('\tint t_counter = 0;') mut last_offset := 0 mut t_points := 0 for k, cov in g.coverage_files { nr_points := cov.points.len t_points += nr_points - g.cov_declarations.writeln('{') - g.cov_declarations.writeln('\tint counter = 0;') - g.cov_declarations.writeln('\tfor (int i = 0, offset = ${last_offset}; i < ${nr_points}; ++i)') - g.cov_declarations.writeln('\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) counter++;') - g.cov_declarations.writeln('\tt_counter += counter;') + g.cov_declarations.writeln('\t{') + g.cov_declarations.writeln('\t\tint counter = 0;') + g.cov_declarations.writeln('\t\tfor (int i = 0, offset = ${last_offset}; i < ${nr_points}; ++i)') + g.cov_declarations.writeln('\t\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) counter++;') + g.cov_declarations.writeln('\t\tt_counter += counter;') if is_stdout { - g.cov_declarations.writeln('\tprintf("> ${cov.file.path} | ${nr_points} | %d | %.2f% \\n", counter, ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0);') + g.cov_declarations.writeln('\t\tprintf("> ${cov.file.path} | ${nr_points} | %d | %.2f%% \\n", counter, ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0);') } else { - g.cov_declarations.writeln('\tfprintf(fp, "> ${cov.file.path} | ${nr_points} | %d | %.2f% \\n", counter, ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0);') + g.cov_declarations.writeln('\t\tfprintf(fp, "> ${cov.file.path} | ${nr_points} | %d | %.2f%% \\n", counter, ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0);') } - g.cov_declarations.writeln('}') + g.cov_declarations.writeln('\t}') last_offset += nr_points } if is_stdout { From 68bd90d9fe0ed395138bd192b0d36d5e2923c3ae Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Mon, 1 Apr 2024 11:41:47 -0300 Subject: [PATCH 05/38] improvement --- vlib/v/gen/c/coverage.v | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index e25ed7b0648fca..765ead7afa0d43 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -48,9 +48,9 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('\t\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) counter++;') g.cov_declarations.writeln('\t\tt_counter += counter;') if is_stdout { - g.cov_declarations.writeln('\t\tprintf("> ${cov.file.path} | ${nr_points} | %d | %.2f%% \\n", counter, ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0);') + g.cov_declarations.writeln('\t\tprintf("[%7.2f%%] %4d / ${nr_points:4d} | ${cov.file.path}\\n", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, counter);') } else { - g.cov_declarations.writeln('\t\tfprintf(fp, "> ${cov.file.path} | ${nr_points} | %d | %.2f%% \\n", counter, ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0);') + g.cov_declarations.writeln('\t\tfprintf(fp, "[%7.2f%%] %4d / ${nr_points:4d} | ${cov.file.path}\\n", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, counter);') } g.cov_declarations.writeln('\t}') last_offset += nr_points From f05811a45d6ab018667ccaddbf67570669b1e955 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Mon, 13 May 2024 20:38:23 -0300 Subject: [PATCH 06/38] improve --- vlib/v/gen/c/cgen.v | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index c3a5cdc7366e69..a17e42e357904b 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2106,8 +2106,15 @@ fn (mut g Gen) write_v_source_line_info(node ast.Node) { fn (mut g Gen) write_v_source_line_info_stmt(stmt ast.Stmt) { g.write_v_source_line_info_pos(stmt.pos) if g.inside_ternary == 0 && g.pref.is_coverage && !g.inside_for_c_stmt - && stmt !in [ast.ExprStmt, ast.FnDecl, ast.ForCStmt, ast.ForInStmt, ast.ForStmt] { - g.write_coverage_point(stmt.pos) + && stmt !in [ast.FnDecl, ast.ForCStmt, ast.ForInStmt, ast.ForStmt] { + if stmt is ast.ExprStmt { + if !g.inside_assign + && stmt.expr !in [ast.StringInterLiteral, ast.StringLiteral, ast.Ident] { + g.write_coverage_point(stmt.pos) + } + } else { + g.write_coverage_point(stmt.pos) + } } } From 5a8ecb80509a8bcdd4387914d125573deed49da4 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Mon, 13 May 2024 20:45:33 -0300 Subject: [PATCH 07/38] fix merge --- vlib/v/pref/pref.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 0f29e6372df7eb..53cc08b6bafd8b 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -604,7 +604,7 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin i++ } '-cov', '-coverage' { - res.coverage_file = cmdline.option(current_args, arg, '-') + res.coverage_file = cmdline.option(args[i..], arg, '-') res.is_coverage = true res.build_options << '${arg} ${res.coverage_file}' i++ From 0adcd12c9f248a379814fe7b62b67127942fda9d Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Thu, 23 May 2024 12:14:51 -0300 Subject: [PATCH 08/38] wip --- vlib/v/gen/c/cgen.v | 8 ++++---- vlib/v/gen/c/coverage.v | 38 +++++++++++++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 27a434fcd894d8..eb9f7ed6d9282b 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -565,10 +565,6 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) (str b.writeln('\n// V profile counters:') b.write_string(g.pcs_declarations.str()) } - if g.pref.is_coverage { - b.writeln('\n// V coverage:') - b.write_string(g.cov_declarations.str()) - } b.writeln('\n// V includes:') b.write_string(g.includes.str()) b.writeln('\n// Enum definitions:') @@ -645,6 +641,10 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) (str b.writeln(fn_def) } } + if g.pref.is_coverage { + b.writeln('\n// V coverage:') + b.write_string(g.cov_declarations.str()) + } b.writeln('\n// end of V out') mut header := b.last_n(b.len) header = '#ifndef V_HEADER_FILE\n#define V_HEADER_FILE' + header diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 765ead7afa0d43..3a12ee9cc3b246 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -5,6 +5,21 @@ module c import v.token +@[inline] +fn current_time() u64 { + unsafe { + $if windows { + tm := u64(0) + C.QueryPerformanceCounter(&tm) + return tm + } $else { + ts := C.timespec{} + C.clock_gettime(C.CLOCK_MONOTONIC, &ts) + return u64(ts.tv_sec) * 1000000000 + u64(ts.tv_nsec) + } + } +} + @[inline] fn (mut g Gen) write_coverage_point(pos token.Pos) { if g.unique_file_path_hash !in g.coverage_files { @@ -24,15 +39,26 @@ fn (mut g Gen) write_coverage_point(pos token.Pos) { } fn (mut g Gen) write_coverage_stats() { - is_stdout := g.pref.coverage_file == '-' + is_stdout := false // g.pref.coverage_file == '-' + g.cov_declarations.writeln('char* vcoverage_fnv1a(const u8* data) {') + g.cov_declarations.writeln('\tu32 h = 2166136261UL;') + g.cov_declarations.writeln('\tsize_t size = strlen(data);') + g.cov_declarations.writeln('\tfor (size_t i = 0; i < size; i++) {') + g.cov_declarations.writeln('\th ^= data[i];') + g.cov_declarations.writeln('\th *= 16777619;') + g.cov_declarations.writeln('\t}') + g.cov_declarations.writeln('\treturn u32_str(h).str;') + g.cov_declarations.writeln('}') + g.cov_declarations.writeln('') g.cov_declarations.writeln('void vprint_coverage_stats() {') if is_stdout { g.cov_declarations.writeln('\tprintf("V coverage\\n");') g.cov_declarations.writeln('\tprintf("${'-':50r}\\n");') } else { - g.cov_declarations.writeln('\tFILE *fp = stdout;') - g.cov_declarations.writeln('\tfp = fopen ("${g.pref.coverage_file}", "w+");') + g.cov_declarations.writeln('time_t rawtime;') + g.cov_declarations.writeln('time(&rawtime);') + g.cov_declarations.writeln('\tFILE *fp = fopen(vcoverage_fnv1a(asctime(localtime(&rawtime))), "w+");') g.cov_declarations.writeln('\tfprintf(fp, "V coverage\\n");') g.cov_declarations.writeln('\tfprintf(fp, "${'-':50r}\\n");') } @@ -47,11 +73,13 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('\t\tfor (int i = 0, offset = ${last_offset}; i < ${nr_points}; ++i)') g.cov_declarations.writeln('\t\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) counter++;') g.cov_declarations.writeln('\t\tt_counter += counter;') + g.cov_declarations.writeln('\t\tif (counter) {') if is_stdout { - g.cov_declarations.writeln('\t\tprintf("[%7.2f%%] %4d / ${nr_points:4d} | ${cov.file.path}\\n", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, counter);') + g.cov_declarations.writeln('\t\t\tprintf("[%7.2f%%] %4d / ${nr_points:4d} | ${cov.file.path}\\n", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, counter);') } else { - g.cov_declarations.writeln('\t\tfprintf(fp, "[%7.2f%%] %4d / ${nr_points:4d} | ${cov.file.path}\\n", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, counter);') + g.cov_declarations.writeln('\t\t\tfprintf(fp, "[%7.2f%%] %4d / ${nr_points:4d} | ${cov.file.path}\\n", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, counter);') } + g.cov_declarations.writeln('\t\t}') g.cov_declarations.writeln('\t}') last_offset += nr_points } From b5d9fa699fa77a92fd32804799f501e054ccf618 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Fri, 24 May 2024 10:06:18 -0300 Subject: [PATCH 09/38] wip --- cmd/tools/vcover/vcover.v | 20 ++++++++++++++++++++ cmd/v/v.v | 1 + vlib/v/gen/c/cgen.v | 4 ++-- vlib/v/gen/c/coverage.v | 27 ++++++++++++++++++++++++--- 4 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 cmd/tools/vcover/vcover.v diff --git a/cmd/tools/vcover/vcover.v b/cmd/tools/vcover/vcover.v new file mode 100644 index 00000000000000..e06b9c575db2b4 --- /dev/null +++ b/cmd/tools/vcover/vcover.v @@ -0,0 +1,20 @@ +// Copyright (c) 2024 Felipe Pena. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module main + +import os +import os.cmdline + +const tmp_dir = os.join_path(os.vtmp_dir(), 'cover') + +fn main() { + args := cmdline.options_after(os.args, ['cover']) + covdir := os.real_path(cmdline.option(args, '-covdir', '')) + reportfile := os.real_path(cmdline.option(args, '-report', '')) + mergedir := os.real_path(cmdline.option(args, '-merge', '')) + + dump(covdir) + dump(reportfile) + dump(mergedir) +} diff --git a/cmd/v/v.v b/cmd/v/v.v index ea8fb7e4926d54..4946696d2dac6d 100644 --- a/cmd/v/v.v +++ b/cmd/v/v.v @@ -23,6 +23,7 @@ const external_tools = [ 'check-md', 'complete', 'compress', + 'cover', 'doc', 'doctor', 'fmt', diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index eb9f7ed6d9282b..ff86fdd0a36cd1 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2095,7 +2095,7 @@ fn (mut g Gen) write_v_source_line_info_pos(pos token.Pos) { fn (mut g Gen) write_v_source_line_info(node ast.Node) { g.write_v_source_line_info_pos(node.pos()) if g.inside_ternary == 0 && g.pref.is_coverage - && node !in [ast.MatchBranch, ast.IfBranch, ast.InfixExpr] { + && node !in [ast.MatchBranch, ast.IfBranch, ast.InfixExpr, ast.UnsafeExpr] { g.write_coverage_point(node.pos()) } } @@ -2107,7 +2107,7 @@ fn (mut g Gen) write_v_source_line_info_stmt(stmt ast.Stmt) { && stmt !in [ast.FnDecl, ast.ForCStmt, ast.ForInStmt, ast.ForStmt] { if stmt is ast.ExprStmt { if !g.inside_assign - && stmt.expr !in [ast.StringInterLiteral, ast.StringLiteral, ast.Ident] { + && stmt.expr !in [ast.StringInterLiteral, ast.StringLiteral, ast.Ident, ast.UnsafeExpr] { g.write_coverage_point(stmt.pos) } } else { diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 3a12ee9cc3b246..3b63a6e4d08af0 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -3,6 +3,7 @@ // that can be found in the LICENSE file. module c +import hash.fnv1a import v.token @[inline] @@ -40,6 +41,7 @@ fn (mut g Gen) write_coverage_point(pos token.Pos) { fn (mut g Gen) write_coverage_stats() { is_stdout := false // g.pref.coverage_file == '-' + is_json := true g.cov_declarations.writeln('char* vcoverage_fnv1a(const u8* data) {') g.cov_declarations.writeln('\tu32 h = 2166136261UL;') @@ -51,20 +53,32 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('\treturn u32_str(h).str;') g.cov_declarations.writeln('}') g.cov_declarations.writeln('') + + build_hash := fnv1a.sum64_string(current_time().str()) + g.cov_declarations.writeln('void vprint_coverage_stats() {') if is_stdout { g.cov_declarations.writeln('\tprintf("V coverage\\n");') g.cov_declarations.writeln('\tprintf("${'-':50r}\\n");') } else { g.cov_declarations.writeln('time_t rawtime;') + g.cov_declarations.writeln('char cov_filename[200];') g.cov_declarations.writeln('time(&rawtime);') - g.cov_declarations.writeln('\tFILE *fp = fopen(vcoverage_fnv1a(asctime(localtime(&rawtime))), "w+");') - g.cov_declarations.writeln('\tfprintf(fp, "V coverage\\n");') - g.cov_declarations.writeln('\tfprintf(fp, "${'-':50r}\\n");') + g.cov_declarations.writeln('snprintf(cov_filename, 200, "vcover.%s.%s", "${build_hash}", vcoverage_fnv1a(asctime(localtime(&rawtime))));') + + g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "w+");') + + if is_json { + g.cov_declarations.writeln('\tfprintf(fp, "[");') + } else { + g.cov_declarations.writeln('\tfprintf(fp, "V coverage\\n");') + g.cov_declarations.writeln('\tfprintf(fp, "${'-':50r}\\n");') + } } g.cov_declarations.writeln('\tint t_counter = 0;') mut last_offset := 0 mut t_points := 0 + mut is_first := true for k, cov in g.coverage_files { nr_points := cov.points.len t_points += nr_points @@ -76,6 +90,11 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('\t\tif (counter) {') if is_stdout { g.cov_declarations.writeln('\t\t\tprintf("[%7.2f%%] %4d / ${nr_points:4d} | ${cov.file.path}\\n", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, counter);') + } else if is_json { + g.cov_declarations.writeln("\t\t\tfprintf(fp, \"%s{\\\"file\\\":\\\"%s\\\",\\\"cov\\\":%.2f,\\\"points\\\":%d}\", ${int(is_first)} ? \"\" : \",\", \"${cov.file.path}\", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, ${nr_points});") + if is_first { + is_first = !is_first + } } else { g.cov_declarations.writeln('\t\t\tfprintf(fp, "[%7.2f%%] %4d / ${nr_points:4d} | ${cov.file.path}\\n", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, counter);') } @@ -86,6 +105,8 @@ fn (mut g Gen) write_coverage_stats() { if is_stdout { g.cov_declarations.writeln('\tprintf("${'-':50r}\\n");') g.cov_declarations.writeln('\tprintf("Total coverage: ${g.coverage_files.len} files, %.2f%% coverage\\n", ((double)t_counter/${t_points})*100);') + } else if is_json { + g.cov_declarations.writeln('\tfprintf(fp, "]");') } else { g.cov_declarations.writeln('\tfprintf(fp, "${'-':50r}\\n");') g.cov_declarations.writeln('\tfprintf(fp, "Total coverage: ${g.coverage_files.len} files, %.2f%% coverage\\n", ((double)t_counter/${t_points})*100);') From cf6bb6f6fb8e339d5092507c54ca6922f96a9a6a Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Fri, 24 May 2024 10:15:37 -0300 Subject: [PATCH 10/38] fix --- vlib/v/gen/c/cgen.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index ff86fdd0a36cd1..1acc589f49cd74 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2095,7 +2095,7 @@ fn (mut g Gen) write_v_source_line_info_pos(pos token.Pos) { fn (mut g Gen) write_v_source_line_info(node ast.Node) { g.write_v_source_line_info_pos(node.pos()) if g.inside_ternary == 0 && g.pref.is_coverage - && node !in [ast.MatchBranch, ast.IfBranch, ast.InfixExpr, ast.UnsafeExpr] { + && node !in [ast.MatchBranch, ast.IfBranch, ast.InfixExpr] { g.write_coverage_point(node.pos()) } } From c223eb93bc63151d81a29e49e11bbb5a3077ff93 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Fri, 24 May 2024 10:24:25 -0300 Subject: [PATCH 11/38] wip --- vlib/v/gen/c/coverage.v | 70 +++++++---------------------------------- 1 file changed, 12 insertions(+), 58 deletions(-) diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 3b63a6e4d08af0..0519a4b65d844c 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -3,23 +3,8 @@ // that can be found in the LICENSE file. module c -import hash.fnv1a import v.token - -@[inline] -fn current_time() u64 { - unsafe { - $if windows { - tm := u64(0) - C.QueryPerformanceCounter(&tm) - return tm - } $else { - ts := C.timespec{} - C.clock_gettime(C.CLOCK_MONOTONIC, &ts) - return u64(ts.tv_sec) * 1000000000 + u64(ts.tv_nsec) - } - } -} +import rand { ulid } @[inline] fn (mut g Gen) write_coverage_point(pos token.Pos) { @@ -40,9 +25,6 @@ fn (mut g Gen) write_coverage_point(pos token.Pos) { } fn (mut g Gen) write_coverage_stats() { - is_stdout := false // g.pref.coverage_file == '-' - is_json := true - g.cov_declarations.writeln('char* vcoverage_fnv1a(const u8* data) {') g.cov_declarations.writeln('\tu32 h = 2166136261UL;') g.cov_declarations.writeln('\tsize_t size = strlen(data);') @@ -54,63 +36,35 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('}') g.cov_declarations.writeln('') - build_hash := fnv1a.sum64_string(current_time().str()) + build_hash := ulid() g.cov_declarations.writeln('void vprint_coverage_stats() {') - if is_stdout { - g.cov_declarations.writeln('\tprintf("V coverage\\n");') - g.cov_declarations.writeln('\tprintf("${'-':50r}\\n");') - } else { - g.cov_declarations.writeln('time_t rawtime;') - g.cov_declarations.writeln('char cov_filename[200];') - g.cov_declarations.writeln('time(&rawtime);') - g.cov_declarations.writeln('snprintf(cov_filename, 200, "vcover.%s.%s", "${build_hash}", vcoverage_fnv1a(asctime(localtime(&rawtime))));') - - g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "w+");') - if is_json { - g.cov_declarations.writeln('\tfprintf(fp, "[");') - } else { - g.cov_declarations.writeln('\tfprintf(fp, "V coverage\\n");') - g.cov_declarations.writeln('\tfprintf(fp, "${'-':50r}\\n");') - } - } + g.cov_declarations.writeln('time_t rawtime;') + g.cov_declarations.writeln('char cov_filename[120];') + g.cov_declarations.writeln('time(&rawtime);') + g.cov_declarations.writeln('snprintf(cov_filename, 120, "vcover.%s.%s", "${build_hash}", vcoverage_fnv1a(asctime(localtime(&rawtime))));') + g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "w+");') + g.cov_declarations.writeln('\tfprintf(fp, "[");') g.cov_declarations.writeln('\tint t_counter = 0;') mut last_offset := 0 - mut t_points := 0 mut is_first := true for k, cov in g.coverage_files { nr_points := cov.points.len - t_points += nr_points g.cov_declarations.writeln('\t{') g.cov_declarations.writeln('\t\tint counter = 0;') g.cov_declarations.writeln('\t\tfor (int i = 0, offset = ${last_offset}; i < ${nr_points}; ++i)') g.cov_declarations.writeln('\t\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) counter++;') g.cov_declarations.writeln('\t\tt_counter += counter;') g.cov_declarations.writeln('\t\tif (counter) {') - if is_stdout { - g.cov_declarations.writeln('\t\t\tprintf("[%7.2f%%] %4d / ${nr_points:4d} | ${cov.file.path}\\n", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, counter);') - } else if is_json { - g.cov_declarations.writeln("\t\t\tfprintf(fp, \"%s{\\\"file\\\":\\\"%s\\\",\\\"cov\\\":%.2f,\\\"points\\\":%d}\", ${int(is_first)} ? \"\" : \",\", \"${cov.file.path}\", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, ${nr_points});") - if is_first { - is_first = !is_first - } - } else { - g.cov_declarations.writeln('\t\t\tfprintf(fp, "[%7.2f%%] %4d / ${nr_points:4d} | ${cov.file.path}\\n", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, counter);') + g.cov_declarations.writeln("\t\t\tfprintf(fp, \"%s{\\\"file\\\":\\\"%s\\\",\\\"cov\\\":%.2f,\\\"points\\\":%d}\", ${int(is_first)} ? \"\" : \",\", \"${cov.file.path}\", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, ${nr_points});") + if is_first { + is_first = !is_first } g.cov_declarations.writeln('\t\t}') g.cov_declarations.writeln('\t}') last_offset += nr_points } - if is_stdout { - g.cov_declarations.writeln('\tprintf("${'-':50r}\\n");') - g.cov_declarations.writeln('\tprintf("Total coverage: ${g.coverage_files.len} files, %.2f%% coverage\\n", ((double)t_counter/${t_points})*100);') - } else if is_json { - g.cov_declarations.writeln('\tfprintf(fp, "]");') - } else { - g.cov_declarations.writeln('\tfprintf(fp, "${'-':50r}\\n");') - g.cov_declarations.writeln('\tfprintf(fp, "Total coverage: ${g.coverage_files.len} files, %.2f%% coverage\\n", ((double)t_counter/${t_points})*100);') - g.cov_declarations.writeln('\tfclose(fp);') - } + g.cov_declarations.writeln('\tfprintf(fp, "]");') g.cov_declarations.writeln('}') } From 791d7ba4f7b14caaa14d7b81dbcf01eb1ba90340 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Fri, 24 May 2024 14:57:20 -0300 Subject: [PATCH 12/38] wip --- cmd/tools/vcover/vcover.v | 31 +++++++++++++++++++++++++------ vlib/v/gen/c/coverage.v | 2 +- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/cmd/tools/vcover/vcover.v b/cmd/tools/vcover/vcover.v index e06b9c575db2b4..a2ed4fcdaef2f0 100644 --- a/cmd/tools/vcover/vcover.v +++ b/cmd/tools/vcover/vcover.v @@ -3,18 +3,37 @@ // that can be found in the LICENSE file. module main +import json import os import os.cmdline +import arrays const tmp_dir = os.join_path(os.vtmp_dir(), 'cover') +struct VCoverData { + file string // file name + hits u64 // file coverage hits + points u64 // file counter +} + +fn report_file(covfile string) ! { + json_content := os.read_file(covfile)! + data := json.decode([]VCoverData, json_content)! + hits := arrays.sum(data.map(it.hits))! + points := arrays.sum(data.map(it.points))! + for lineinfo in data { + println('${(f64(lineinfo.hits) / lineinfo.points) * 100:7.2f} | ${lineinfo.hits:4u} | ${lineinfo.points:4u} | ${lineinfo.file}') + } + println('Total coverage: ${data.len} files, ${(f64(hits) / points) * 100:.2f}% coverage') +} + fn main() { args := cmdline.options_after(os.args, ['cover']) - covdir := os.real_path(cmdline.option(args, '-covdir', '')) - reportfile := os.real_path(cmdline.option(args, '-report', '')) - mergedir := os.real_path(cmdline.option(args, '-merge', '')) + covfile := os.real_path(cmdline.option(args, '-file', '')) + // covdir := os.real_path(cmdline.option(args, '-dir', '')) + // reportfile := os.real_path(cmdline.option(args, '-report', '')) - dump(covdir) - dump(reportfile) - dump(mergedir) + if covfile != '' { + report_file(covfile)! + } } diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 0519a4b65d844c..3610877394b337 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -57,7 +57,7 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('\t\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) counter++;') g.cov_declarations.writeln('\t\tt_counter += counter;') g.cov_declarations.writeln('\t\tif (counter) {') - g.cov_declarations.writeln("\t\t\tfprintf(fp, \"%s{\\\"file\\\":\\\"%s\\\",\\\"cov\\\":%.2f,\\\"points\\\":%d}\", ${int(is_first)} ? \"\" : \",\", \"${cov.file.path}\", ${nr_points} > 0 ? ((double)counter/${nr_points})*100 : 0, ${nr_points});") + g.cov_declarations.writeln("\t\t\tfprintf(fp, \"%s{\\\"file\\\":\\\"%s\\\",\\\"hits\\\":%d,\\\"points\\\":%d}\", ${int(is_first)} ? \"\" : \",\", \"${cov.file.path}\", counter, ${nr_points});") if is_first { is_first = !is_first } From a56142642363b1c66ec32946c79705f83bc58af9 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Fri, 24 May 2024 15:38:13 -0300 Subject: [PATCH 13/38] wip --- vlib/v/gen/c/coverage.v | 11 ++++++----- vlib/v/pref/pref.v | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 3610877394b337..47968dc647806d 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -4,6 +4,7 @@ module c import v.token +import os import rand { ulid } @[inline] @@ -39,11 +40,11 @@ fn (mut g Gen) write_coverage_stats() { build_hash := ulid() g.cov_declarations.writeln('void vprint_coverage_stats() {') - - g.cov_declarations.writeln('time_t rawtime;') - g.cov_declarations.writeln('char cov_filename[120];') - g.cov_declarations.writeln('time(&rawtime);') - g.cov_declarations.writeln('snprintf(cov_filename, 120, "vcover.%s.%s", "${build_hash}", vcoverage_fnv1a(asctime(localtime(&rawtime))));') + g.cov_declarations.writeln('\ttime_t rawtime;') + g.cov_declarations.writeln('\tchar cov_filename[120];') + g.cov_declarations.writeln('\ttime(&rawtime);') + g.cov_declarations.writeln('\tchar *cov_dir = getenv("VCOVDIR") != NULL ? getenv("VCOVDIR") : "${os.real_path(g.pref.coverage_dir)}";') + g.cov_declarations.writeln('\tsnprintf(cov_filename, 120, "%s/vcover.%s.%s", cov_dir, "${build_hash}", vcoverage_fnv1a(asctime(localtime(&rawtime))));') g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "w+");') g.cov_declarations.writeln('\tfprintf(fp, "[");') g.cov_declarations.writeln('\tint t_counter = 0;') diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index c8603f3ca7f517..3d775efb739c54 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -120,7 +120,7 @@ pub mut: eval_argument string // `println(2+2)` on `v -e "println(2+2)"`. Note that this source code, will be evaluated in vsh mode, so 'v -e 'println(ls(".")!)' is valid. test_runner string // can be 'simple' (fastest, but much less detailed), 'tap', 'normal' profile_file string // the profile results will be stored inside profile_file - coverage_file string // the coverage results will be stored inside coverage_file + coverage_dir string // the coverage files will be stored inside coverage_dir profile_no_inline bool // when true, [inline] functions would not be profiled profile_fns []string // when set, profiling will be off by default, but inside these functions (and what they call) it will be on. translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc @@ -604,9 +604,9 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin i++ } '-cov', '-coverage' { - res.coverage_file = cmdline.option(args[i..], arg, '-') + res.coverage_dir = cmdline.option(args[i..], arg, '-') res.is_coverage = true - res.build_options << '${arg} ${res.coverage_file}' + res.build_options << '${arg} ${res.coverage_dir}' i++ } '-profile-fns' { From 01ed4d75e0e50d6d2e96ad75964869bc11b55755 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Sat, 25 May 2024 10:40:28 -0300 Subject: [PATCH 14/38] improve --- cmd/tools/vcover/vcover.v | 52 +++++++++++++++++++++++++++++++++------ vlib/v/gen/c/cgen.v | 2 +- vlib/v/gen/c/coverage.v | 10 +++----- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/cmd/tools/vcover/vcover.v b/cmd/tools/vcover/vcover.v index a2ed4fcdaef2f0..bb795fe80c3a86 100644 --- a/cmd/tools/vcover/vcover.v +++ b/cmd/tools/vcover/vcover.v @@ -10,15 +10,24 @@ import arrays const tmp_dir = os.join_path(os.vtmp_dir(), 'cover') +@[heap] struct VCoverData { - file string // file name - hits u64 // file coverage hits - points u64 // file counter + file string // file name +mut: + hits u64 // file coverage hits + points u64 // file counter } -fn report_file(covfile string) ! { - json_content := os.read_file(covfile)! - data := json.decode([]VCoverData, json_content)! +pub fn (covdata []&VCoverData) find(filename string) ?&VCoverData { + for data in covdata { + if data.file == filename { + return unsafe { data } + } + } + return none +} + +fn display_result(data []VCoverData) ! { hits := arrays.sum(data.map(it.hits))! points := arrays.sum(data.map(it.points))! for lineinfo in data { @@ -27,13 +36,40 @@ fn report_file(covfile string) ! { println('Total coverage: ${data.len} files, ${(f64(hits) / points) * 100:.2f}% coverage') } +fn report_file(covfile string) ! { + json_content := os.read_file(covfile)! + data := json.decode([]VCoverData, json_content)! + display_result(data)! +} + +fn summarize_coverage(covfile string, mut covdata []&VCoverData) ! { + json_content := os.read_file(covfile)! + data := json.decode([]VCoverData, json_content)! + for lineinfo in data { + if mut fileinfo := covdata.find(lineinfo.file) { + if fileinfo.hits < lineinfo.hits { + fileinfo.hits = lineinfo.hits + } + } else { + covdata << &VCoverData{ + ...lineinfo + } + } + } +} + fn main() { args := cmdline.options_after(os.args, ['cover']) covfile := os.real_path(cmdline.option(args, '-file', '')) - // covdir := os.real_path(cmdline.option(args, '-dir', '')) - // reportfile := os.real_path(cmdline.option(args, '-report', '')) + covdir := os.real_path(cmdline.option(args, '-dir', '')) if covfile != '' { report_file(covfile)! + } else if covdir != '' { + mut sum_data := []&VCoverData{} + for coverfile in os.glob(covdir + '/vcover.*')! { + summarize_coverage(coverfile, mut sum_data)! + } + display_result(sum_data.map(*it))! } } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index d73dd295657934..f506a68889e419 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2107,7 +2107,7 @@ fn (mut g Gen) write_v_source_line_info_stmt(stmt ast.Stmt) { && stmt !in [ast.FnDecl, ast.ForCStmt, ast.ForInStmt, ast.ForStmt] { if stmt is ast.ExprStmt { if !g.inside_assign - && stmt.expr !in [ast.StringInterLiteral, ast.StringLiteral, ast.Ident, ast.UnsafeExpr] { + && stmt.expr !in [ast.IndexExpr, ast.StringInterLiteral, ast.StringLiteral, ast.Ident, ast.UnsafeExpr] { g.write_coverage_point(stmt.pos) } } else { diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 47968dc647806d..35304fd7332e50 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -48,20 +48,18 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "w+");') g.cov_declarations.writeln('\tfprintf(fp, "[");') g.cov_declarations.writeln('\tint t_counter = 0;') + g.cov_declarations.writeln('\tint is_first = 1;') mut last_offset := 0 - mut is_first := true for k, cov in g.coverage_files { nr_points := cov.points.len g.cov_declarations.writeln('\t{') g.cov_declarations.writeln('\t\tint counter = 0;') - g.cov_declarations.writeln('\t\tfor (int i = 0, offset = ${last_offset}; i < ${nr_points}; ++i)') + g.cov_declarations.writeln('\t\tfor (int i = 0, is_first = 1, offset = ${last_offset}; i < ${nr_points}; ++i)') g.cov_declarations.writeln('\t\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) counter++;') g.cov_declarations.writeln('\t\tt_counter += counter;') g.cov_declarations.writeln('\t\tif (counter) {') - g.cov_declarations.writeln("\t\t\tfprintf(fp, \"%s{\\\"file\\\":\\\"%s\\\",\\\"hits\\\":%d,\\\"points\\\":%d}\", ${int(is_first)} ? \"\" : \",\", \"${cov.file.path}\", counter, ${nr_points});") - if is_first { - is_first = !is_first - } + g.cov_declarations.writeln("\t\t\tfprintf(fp, \"%s{\\\"file\\\":\\\"%s\\\",\\\"hits\\\":%d,\\\"points\\\":%d}\", is_first ? \"\" : \",\", \"${cov.file.path}\", counter, ${nr_points});") + g.cov_declarations.writeln('\t\t\tis_first = 0;') g.cov_declarations.writeln('\t\t}') g.cov_declarations.writeln('\t}') last_offset += nr_points From 1096fb5438b79c1e2ce7b395d88b269259846c0f Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Sat, 25 May 2024 11:01:21 -0300 Subject: [PATCH 15/38] fix --- vlib/v/gen/c/cgen.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index f506a68889e419..c3f83383942ccf 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2107,7 +2107,7 @@ fn (mut g Gen) write_v_source_line_info_stmt(stmt ast.Stmt) { && stmt !in [ast.FnDecl, ast.ForCStmt, ast.ForInStmt, ast.ForStmt] { if stmt is ast.ExprStmt { if !g.inside_assign - && stmt.expr !in [ast.IndexExpr, ast.StringInterLiteral, ast.StringLiteral, ast.Ident, ast.UnsafeExpr] { + && stmt.expr !in [ast.CastExpr, ast.IndexExpr, ast.StringInterLiteral, ast.StringLiteral, ast.Ident, ast.UnsafeExpr] { g.write_coverage_point(stmt.pos) } } else { From c80b978519e4034d30e8c64465078777334602fe Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Sat, 25 May 2024 11:32:17 -0300 Subject: [PATCH 16/38] added -filter --- cmd/tools/vcover/vcover.v | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/cmd/tools/vcover/vcover.v b/cmd/tools/vcover/vcover.v index bb795fe80c3a86..7107ebbbc79715 100644 --- a/cmd/tools/vcover/vcover.v +++ b/cmd/tools/vcover/vcover.v @@ -27,19 +27,34 @@ pub fn (covdata []&VCoverData) find(filename string) ?&VCoverData { return none } -fn display_result(data []VCoverData) ! { - hits := arrays.sum(data.map(it.hits))! - points := arrays.sum(data.map(it.points))! - for lineinfo in data { +pub fn (covdata []VCoverData) filter_data(filters []string) []VCoverData { + mut arr := []VCoverData{} + for data in covdata { + if filters.any(data.file.contains(it)) { + arr << data + } + } + return arr +} + +fn display_result(filter string, data []VCoverData) ! { + mut covdata := data.clone() + if filter != '' { + filters := filter.split(',') + covdata = covdata.filter_data(filters) + } + hits := arrays.sum(covdata.map(it.hits))! + points := arrays.sum(covdata.map(it.points))! + for lineinfo in covdata { println('${(f64(lineinfo.hits) / lineinfo.points) * 100:7.2f} | ${lineinfo.hits:4u} | ${lineinfo.points:4u} | ${lineinfo.file}') } - println('Total coverage: ${data.len} files, ${(f64(hits) / points) * 100:.2f}% coverage') + println('Total coverage: ${covdata.len} files, ${(f64(hits) / points) * 100:.2f}% coverage') } -fn report_file(covfile string) ! { +fn report_file(filter string, covfile string) ! { json_content := os.read_file(covfile)! data := json.decode([]VCoverData, json_content)! - display_result(data)! + display_result(filter, data)! } fn summarize_coverage(covfile string, mut covdata []&VCoverData) ! { @@ -62,14 +77,15 @@ fn main() { args := cmdline.options_after(os.args, ['cover']) covfile := os.real_path(cmdline.option(args, '-file', '')) covdir := os.real_path(cmdline.option(args, '-dir', '')) + filter := cmdline.option(args, '-filter', '') if covfile != '' { - report_file(covfile)! + report_file(filter, covfile)! } else if covdir != '' { mut sum_data := []&VCoverData{} for coverfile in os.glob(covdir + '/vcover.*')! { summarize_coverage(coverfile, mut sum_data)! } - display_result(sum_data.map(*it))! + display_result(filter, sum_data.map(*it))! } } From 73c387835b367b88d6b037160f8488cae4e3ae59 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Sat, 25 May 2024 11:52:58 -0300 Subject: [PATCH 17/38] ignore test funcs --- cmd/tools/vcover/vcover.v | 7 +++++-- vlib/v/gen/c/coverage.v | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cmd/tools/vcover/vcover.v b/cmd/tools/vcover/vcover.v index 7107ebbbc79715..06761a32c2523a 100644 --- a/cmd/tools/vcover/vcover.v +++ b/cmd/tools/vcover/vcover.v @@ -42,6 +42,9 @@ fn display_result(filter string, data []VCoverData) ! { if filter != '' { filters := filter.split(',') covdata = covdata.filter_data(filters) + if covdata.len == 0 { + return error('No result found with such filter') + } } hits := arrays.sum(covdata.map(it.hits))! points := arrays.sum(covdata.map(it.points))! @@ -80,12 +83,12 @@ fn main() { filter := cmdline.option(args, '-filter', '') if covfile != '' { - report_file(filter, covfile)! + report_file(filter, covfile) or { println('Error: ${err}') } } else if covdir != '' { mut sum_data := []&VCoverData{} for coverfile in os.glob(covdir + '/vcover.*')! { summarize_coverage(coverfile, mut sum_data)! } - display_result(filter, sum_data.map(*it))! + display_result(filter, sum_data.map(*it)) or { println('Error: ${err}') } } } diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 35304fd7332e50..655490c32b49ba 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -15,7 +15,7 @@ fn (mut g Gen) write_coverage_point(pos token.Pos) { file: g.file } } - if g.fn_decl != unsafe { nil } { + if g.fn_decl != unsafe { nil } && !g.fn_decl.is_test { curr_line := u64(pos.line_nr) mut curr_cov := unsafe { g.coverage_files[g.unique_file_path_hash] } if curr_line !in curr_cov.points { From 0d6665fbcb41327899eb6319e6f647db91f3d27a Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Sat, 25 May 2024 12:06:35 -0300 Subject: [PATCH 18/38] improve filtering --- cmd/tools/vcover/vcover.v | 11 ++++++++--- vlib/v/gen/c/coverage.v | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cmd/tools/vcover/vcover.v b/cmd/tools/vcover/vcover.v index 06761a32c2523a..de4e5086dc1ada 100644 --- a/cmd/tools/vcover/vcover.v +++ b/cmd/tools/vcover/vcover.v @@ -27,10 +27,13 @@ pub fn (covdata []&VCoverData) find(filename string) ?&VCoverData { return none } -pub fn (covdata []VCoverData) filter_data(filters []string) []VCoverData { +pub fn (covdata []VCoverData) filter_files(filters []string, remove_test bool) []VCoverData { mut arr := []VCoverData{} for data in covdata { - if filters.any(data.file.contains(it)) { + if remove_test && data.file.ends_with('_test.v') { + continue + } + if filters.len == 0 || filters.any(data.file.contains(it)) { arr << data } } @@ -41,10 +44,12 @@ fn display_result(filter string, data []VCoverData) ! { mut covdata := data.clone() if filter != '' { filters := filter.split(',') - covdata = covdata.filter_data(filters) + covdata = covdata.filter_files(filters, true) if covdata.len == 0 { return error('No result found with such filter') } + } else { + covdata = covdata.filter_files([], true) } hits := arrays.sum(covdata.map(it.hits))! points := arrays.sum(covdata.map(it.points))! diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 655490c32b49ba..35304fd7332e50 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -15,7 +15,7 @@ fn (mut g Gen) write_coverage_point(pos token.Pos) { file: g.file } } - if g.fn_decl != unsafe { nil } && !g.fn_decl.is_test { + if g.fn_decl != unsafe { nil } { curr_line := u64(pos.line_nr) mut curr_cov := unsafe { g.coverage_files[g.unique_file_path_hash] } if curr_line !in curr_cov.points { From d16866fcf7d8fb2c0be42acb007c3fc52657c7a4 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 25 May 2024 19:53:52 +0300 Subject: [PATCH 19/38] change format to csv, add more details, add cmd/tools/vcover/testdata/example1 for more isolated testing, independent of the contents of any vlib module in particular --- cmd/tools/vcover/testdata/example1/abc.v | 313 ++++++++++++++++++ .../testdata/example1/internal_abc01_test.v | 5 + .../example1/internal_abc10_abc30_test.v | 25 ++ .../example1/internal_abc20_abc25_test.v | 10 + cmd/tools/vcover/testdata/example1/v.mod | 0 vlib/v/gen/c/cgen.v | 2 +- vlib/v/gen/c/coverage.v | 44 +-- 7 files changed, 368 insertions(+), 31 deletions(-) create mode 100644 cmd/tools/vcover/testdata/example1/abc.v create mode 100644 cmd/tools/vcover/testdata/example1/internal_abc01_test.v create mode 100644 cmd/tools/vcover/testdata/example1/internal_abc10_abc30_test.v create mode 100644 cmd/tools/vcover/testdata/example1/internal_abc20_abc25_test.v create mode 100644 cmd/tools/vcover/testdata/example1/v.mod diff --git a/cmd/tools/vcover/testdata/example1/abc.v b/cmd/tools/vcover/testdata/example1/abc.v new file mode 100644 index 00000000000000..c062e09e7c2e80 --- /dev/null +++ b/cmd/tools/vcover/testdata/example1/abc.v @@ -0,0 +1,313 @@ +module example1 + +fn abc01() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc02() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc03() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc04() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc05() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc06() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc07() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc08() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc09() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc10() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc11() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc12() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc13() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc14() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc15() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc16() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc17() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc18() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc19() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc20() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc21() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc22() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc23() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc24() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc25() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc26() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc27() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc28() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc29() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc30() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc31() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc32() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc33() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc34() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc35() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc36() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc37() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc38() { + if true { + println('true') + } else { + println(false) + } +} + +fn abc39() { + if true { + println('true') + } else { + println(false) + } +} diff --git a/cmd/tools/vcover/testdata/example1/internal_abc01_test.v b/cmd/tools/vcover/testdata/example1/internal_abc01_test.v new file mode 100644 index 00000000000000..3d4f09c4d729e5 --- /dev/null +++ b/cmd/tools/vcover/testdata/example1/internal_abc01_test.v @@ -0,0 +1,5 @@ +module example1 + +fn test_abc() { + abc01() +} diff --git a/cmd/tools/vcover/testdata/example1/internal_abc10_abc30_test.v b/cmd/tools/vcover/testdata/example1/internal_abc10_abc30_test.v new file mode 100644 index 00000000000000..b0252f8daa9ab5 --- /dev/null +++ b/cmd/tools/vcover/testdata/example1/internal_abc10_abc30_test.v @@ -0,0 +1,25 @@ +module example1 + +fn test_abc() { + abc10() + abc11() + abc12() + abc13() + abc14() + abc15() + abc16() + abc17() + abc18() + abc19() + abc20() + abc21() + abc22() + abc23() + abc24() + abc25() + abc26() + abc27() + abc28() + abc29() + abc30() +} diff --git a/cmd/tools/vcover/testdata/example1/internal_abc20_abc25_test.v b/cmd/tools/vcover/testdata/example1/internal_abc20_abc25_test.v new file mode 100644 index 00000000000000..c3ca1c646f5e0a --- /dev/null +++ b/cmd/tools/vcover/testdata/example1/internal_abc20_abc25_test.v @@ -0,0 +1,10 @@ +module example1 + +fn test_abc() { + abc20() + abc21() + abc22() + abc23() + abc24() + abc25() +} diff --git a/cmd/tools/vcover/testdata/example1/v.mod b/cmd/tools/vcover/testdata/example1/v.mod new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index c3f83383942ccf..bff270b997e964 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -527,7 +527,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) (str g.cheaders.writeln('#define _v_cov_file_offset_${k} ${total_code_points}') total_code_points += cov.points.len } - g.cheaders.writeln('char _v_cov[${total_code_points}] = {0};') + g.cheaders.writeln('long int _v_cov[${total_code_points}] = {0};') } // insert for options forward diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 35304fd7332e50..878211b9a10896 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -5,7 +5,7 @@ module c import v.token import os -import rand { ulid } +import rand @[inline] fn (mut g Gen) write_coverage_point(pos token.Pos) { @@ -21,49 +21,33 @@ fn (mut g Gen) write_coverage_point(pos token.Pos) { if curr_line !in curr_cov.points { curr_cov.points << curr_line } - g.writeln('_v_cov[_v_cov_file_offset_${g.unique_file_path_hash}+${curr_cov.points.len - 1}] = 1;') + g.writeln('_v_cov[_v_cov_file_offset_${g.unique_file_path_hash}+${curr_cov.points.len - 1}]++;') } } fn (mut g Gen) write_coverage_stats() { - g.cov_declarations.writeln('char* vcoverage_fnv1a(const u8* data) {') - g.cov_declarations.writeln('\tu32 h = 2166136261UL;') - g.cov_declarations.writeln('\tsize_t size = strlen(data);') - g.cov_declarations.writeln('\tfor (size_t i = 0; i < size; i++) {') - g.cov_declarations.writeln('\th ^= data[i];') - g.cov_declarations.writeln('\th *= 16777619;') - g.cov_declarations.writeln('\t}') - g.cov_declarations.writeln('\treturn u32_str(h).str;') - g.cov_declarations.writeln('}') + ulid_hash := rand.ulid() // rand.ulid provides a hash+timestamp, so that a collision is extremely unlikely g.cov_declarations.writeln('') - - build_hash := ulid() - g.cov_declarations.writeln('void vprint_coverage_stats() {') - g.cov_declarations.writeln('\ttime_t rawtime;') - g.cov_declarations.writeln('\tchar cov_filename[120];') - g.cov_declarations.writeln('\ttime(&rawtime);') + // g.cov_declarations.writeln('\tstruct timespec ts;') + // g.cov_declarations.writeln('\tclock_gettime(CLOCK_MONOTONIC, &ts);') + g.cov_declarations.writeln('\tchar cov_filename[512];') g.cov_declarations.writeln('\tchar *cov_dir = getenv("VCOVDIR") != NULL ? getenv("VCOVDIR") : "${os.real_path(g.pref.coverage_dir)}";') - g.cov_declarations.writeln('\tsnprintf(cov_filename, 120, "%s/vcover.%s.%s", cov_dir, "${build_hash}", vcoverage_fnv1a(asctime(localtime(&rawtime))));') - g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "w+");') - g.cov_declarations.writeln('\tfprintf(fp, "[");') - g.cov_declarations.writeln('\tint t_counter = 0;') - g.cov_declarations.writeln('\tint is_first = 1;') + g.cov_declarations.writeln('\tsnprintf(cov_filename, sizeof(cov_filename), "%s/vcover_${ulid_hash}.csv", cov_dir);') + g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "wb+");') + g.cov_declarations.writeln('\tfprintf(fp, "file,points,counter,hits\\n");') mut last_offset := 0 for k, cov in g.coverage_files { nr_points := cov.points.len g.cov_declarations.writeln('\t{') - g.cov_declarations.writeln('\t\tint counter = 0;') - g.cov_declarations.writeln('\t\tfor (int i = 0, is_first = 1, offset = ${last_offset}; i < ${nr_points}; ++i)') - g.cov_declarations.writeln('\t\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) counter++;') - g.cov_declarations.writeln('\t\tt_counter += counter;') - g.cov_declarations.writeln('\t\tif (counter) {') - g.cov_declarations.writeln("\t\t\tfprintf(fp, \"%s{\\\"file\\\":\\\"%s\\\",\\\"hits\\\":%d,\\\"points\\\":%d}\", is_first ? \"\" : \",\", \"${cov.file.path}\", counter, ${nr_points});") - g.cov_declarations.writeln('\t\t\tis_first = 0;') + g.cov_declarations.writeln('\t\tfor (int i = 0, offset = ${last_offset}; i < ${nr_points}; ++i) {') + g.cov_declarations.writeln('\t\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) {') + g.cov_declarations.writeln("\t\t\t\tfprintf(fp, \"\\\"%s\\\",%d,%d,%ld\\n\", \"${cov.file.path}\", ${nr_points}, i, _v_cov[_v_cov_file_offset_${k}+i]);") + g.cov_declarations.writeln('\t\t\t}') g.cov_declarations.writeln('\t\t}') g.cov_declarations.writeln('\t}') last_offset += nr_points } - g.cov_declarations.writeln('\tfprintf(fp, "]");') + g.cov_declarations.writeln('\tfclose(fp);') g.cov_declarations.writeln('}') } From 2f4dbb1103d9c0e6e3aa8606b66829228d81dbed Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 25 May 2024 22:01:58 +0300 Subject: [PATCH 20/38] make test runs of cmd/tools/vcover/testdata/example1/ silent for now --- cmd/tools/vcover/testdata/example1/abc.v | 234 +++++++++++------------ 1 file changed, 117 insertions(+), 117 deletions(-) diff --git a/cmd/tools/vcover/testdata/example1/abc.v b/cmd/tools/vcover/testdata/example1/abc.v index c062e09e7c2e80..cd2e92f3a36824 100644 --- a/cmd/tools/vcover/testdata/example1/abc.v +++ b/cmd/tools/vcover/testdata/example1/abc.v @@ -1,313 +1,313 @@ module example1 -fn abc01() { +fn abc01() int { if true { - println('true') + return 1 } else { - println(false) + return 2 } } -fn abc02() { +fn abc02() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc03() { +fn abc03() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc04() { +fn abc04() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc05() { +fn abc05() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc06() { +fn abc06() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc07() { +fn abc07() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc08() { +fn abc08() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc09() { +fn abc09() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc10() { +fn abc10() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc11() { +fn abc11() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc12() { +fn abc12() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc13() { +fn abc13() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc14() { +fn abc14() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc15() { +fn abc15() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc16() { +fn abc16() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc17() { +fn abc17() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc18() { +fn abc18() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc19() { +fn abc19() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc20() { +fn abc20() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc21() { +fn abc21() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc22() { +fn abc22() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc23() { +fn abc23() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc24() { +fn abc24() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc25() { +fn abc25() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc26() { +fn abc26() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc27() { +fn abc27() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc28() { +fn abc28() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc29() { +fn abc29() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc30() { +fn abc30() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc31() { +fn abc31() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc32() { +fn abc32() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc33() { +fn abc33() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc34() { +fn abc34() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc35() { +fn abc35() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc36() { +fn abc36() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc37() { +fn abc37() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc38() { +fn abc38() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } -fn abc39() { +fn abc39() int { if true { - println('true') + return 1 } else { - println(false) + return 0 } } From 622ed4a9de0640a403b0cc0a953175943f6afc30 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 25 May 2024 22:46:48 +0300 Subject: [PATCH 21/38] cleanup output format for both the csv counters, and the json meta data for the source files, containing the points, etc --- vlib/v/gen/c/cgen.v | 9 ------ vlib/v/gen/c/coverage.v | 66 +++++++++++++++++++++++++++++++++-------- vlib/v/pref/pref.v | 10 +++++-- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index bff270b997e964..a1ae5829f528f4 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -42,15 +42,6 @@ fn string_array_to_map(a []string) map[string]bool { return res } -// V coverage info -@[heap] -struct CoverageInfo { -pub mut: - idx int // index - points []u64 // code point line nr - file &ast.File = unsafe { nil } -} - pub struct Gen { pref &pref.Preferences = unsafe { nil } field_data_type ast.Type // cache her to avoid map lookups diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 878211b9a10896..708c6dc677fdf3 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -3,16 +3,33 @@ // that can be found in the LICENSE file. module c -import v.token import os import rand +import v.ast +import v.token +import v.util.version +import hash + +// V coverage info +@[heap] +struct CoverageInfo { +mut: + idx int // index + points []u64 // code point line nr + file &ast.File = unsafe { nil } + fhash string // hash(fpath, build_options), prevents collisions for runs with different options, like `-os windows` or `-gc none`, which may affect the points, due to `$if ... {` etc + build_options string +} -@[inline] fn (mut g Gen) write_coverage_point(pos token.Pos) { if g.unique_file_path_hash !in g.coverage_files { + build_options := g.pref.build_options.join(' ') + fhash := hash.sum64_string('${build_options}:${g.unique_file_path_hash}', 32).hex_full() g.coverage_files[g.unique_file_path_hash] = &CoverageInfo{ points: [] file: g.file + fhash: fhash + build_options: build_options } } if g.fn_decl != unsafe { nil } { @@ -26,27 +43,50 @@ fn (mut g Gen) write_coverage_point(pos token.Pos) { } fn (mut g Gen) write_coverage_stats() { - ulid_hash := rand.ulid() // rand.ulid provides a hash+timestamp, so that a collision is extremely unlikely + coverage_meta_folder := os.join_path(g.pref.coverage_dir, 'meta') + if !os.exists(coverage_meta_folder) { + os.mkdir_all(coverage_meta_folder) or {} + } + counter_ulid := rand.ulid() // rand.ulid provides a hash+timestamp, so that a collision is extremely unlikely g.cov_declarations.writeln('') g.cov_declarations.writeln('void vprint_coverage_stats() {') - // g.cov_declarations.writeln('\tstruct timespec ts;') - // g.cov_declarations.writeln('\tclock_gettime(CLOCK_MONOTONIC, &ts);') - g.cov_declarations.writeln('\tchar cov_filename[512];') - g.cov_declarations.writeln('\tchar *cov_dir = getenv("VCOVDIR") != NULL ? getenv("VCOVDIR") : "${os.real_path(g.pref.coverage_dir)}";') - g.cov_declarations.writeln('\tsnprintf(cov_filename, sizeof(cov_filename), "%s/vcover_${ulid_hash}.csv", cov_dir);') + g.cov_declarations.writeln('\tchar cov_filename[2048];') + g.cov_declarations.writeln('\tchar *cov_dir = "${os.real_path(g.pref.coverage_dir)}";') + for _, mut cov in g.coverage_files { + metadata_coverage_fpath := os.join_path(coverage_meta_folder, '${cov.fhash}.txt') + if os.exists(metadata_coverage_fpath) { + continue + } + mut fmeta := os.create(metadata_coverage_fpath) or { continue } + fmeta.writeln('{') or { continue } + fmeta.writeln(' "file": "${cov.file.path}",') or { continue } + fmeta.writeln(' "v_version": "${version.full_v_version(true)}",') or { continue } + fmeta.writeln(' "build_options": "${cov.build_options}",') or { continue } + fmeta.writeln(' "npoints": ${cov.points.len},') or { continue } + fmeta.write_string(' "points": [ ') or { continue } + for idx, p in cov.points { + fmeta.write_string('${p + 1}') or { continue } + if idx < cov.points.len - 1 { + fmeta.write_string(',') or { continue } + } + } + fmeta.writeln(' ]') or { continue } + fmeta.writeln('}') or { continue } + fmeta.close() + } + g.cov_declarations.writeln('\tsnprintf(cov_filename, sizeof(cov_filename), "%s/vcounters_${counter_ulid}.csv", cov_dir);') g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "wb+");') - g.cov_declarations.writeln('\tfprintf(fp, "file,points,counter,hits\\n");') - mut last_offset := 0 + g.cov_declarations.writeln('\tfprintf(fp, "# path: ${g.pref.path}\\n");') + g.cov_declarations.writeln('\tfprintf(fp, "meta,point,hits\\n");') for k, cov in g.coverage_files { nr_points := cov.points.len g.cov_declarations.writeln('\t{') - g.cov_declarations.writeln('\t\tfor (int i = 0, offset = ${last_offset}; i < ${nr_points}; ++i) {') + g.cov_declarations.writeln('\t\tfor (int i = 0; i < ${nr_points}; ++i) {') g.cov_declarations.writeln('\t\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) {') - g.cov_declarations.writeln("\t\t\t\tfprintf(fp, \"\\\"%s\\\",%d,%d,%ld\\n\", \"${cov.file.path}\", ${nr_points}, i, _v_cov[_v_cov_file_offset_${k}+i]);") + g.cov_declarations.writeln("\t\t\t\tfprintf(fp, \"%s,%d,%ld\\n\", \"${cov.fhash}\", i, _v_cov[_v_cov_file_offset_${k}+i]);") g.cov_declarations.writeln('\t\t\t}') g.cov_declarations.writeln('\t\t}') g.cov_declarations.writeln('\t}') - last_offset += nr_points } g.cov_declarations.writeln('\tfclose(fp);') g.cov_declarations.writeln('}') diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 3d775efb739c54..8e70d93e8eefe6 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -310,6 +310,10 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin if os.getenv('VNORUN') != '' { res.skip_running = true } + coverage_dir_from_env := os.getenv('VCOVDIR') + if coverage_dir_from_env != '' { + res.coverage_dir = coverage_dir_from_env + } /* $if macos || linux { res.use_cache = true res.skip_unused = true @@ -605,8 +609,6 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin } '-cov', '-coverage' { res.coverage_dir = cmdline.option(args[i..], arg, '-') - res.is_coverage = true - res.build_options << '${arg} ${res.coverage_dir}' i++ } '-profile-fns' { @@ -1073,6 +1075,10 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin if 'trace' in res.compile_defines_all { res.is_trace = true } + if res.coverage_dir != '' { + res.is_coverage = true + res.build_options << '-coverage ${res.coverage_dir}' + } // keep only the unique res.build_options: mut m := map[string]string{} for x in res.build_options { From def6f9b6e4290a3786e56075face32c91d79f32d Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 25 May 2024 22:59:36 +0300 Subject: [PATCH 22/38] add the build options to the .csv file too, as a comment (for easier diagnostic); add condition_test.v, to test coverage by passing `-d condition1` to `-d condition7` --- .../vcover/testdata/example1/condition.v | 30 +++++++++++++++++++ .../vcover/testdata/example1/condition_test.v | 5 ++++ vlib/v/gen/c/coverage.v | 2 ++ 3 files changed, 37 insertions(+) create mode 100644 cmd/tools/vcover/testdata/example1/condition.v create mode 100644 cmd/tools/vcover/testdata/example1/condition_test.v diff --git a/cmd/tools/vcover/testdata/example1/condition.v b/cmd/tools/vcover/testdata/example1/condition.v new file mode 100644 index 00000000000000..86ff777086e177 --- /dev/null +++ b/cmd/tools/vcover/testdata/example1/condition.v @@ -0,0 +1,30 @@ +module example1 + +fn condition() int { + mut res := 0 + $if condition1 ? { + res += 1 + } + $if condition2 ? { + res += 2 + } + $if condition3 ? { + res += 4 + } + $if condition4 ? { + res += 8 + } + $if condition5 ? { + res += 16 + } + $if condition6 ? { + res += 32 + } + $if condition7 ? { + res += 64 + } + $if condition8 ? { + return 128 + } + return res +} diff --git a/cmd/tools/vcover/testdata/example1/condition_test.v b/cmd/tools/vcover/testdata/example1/condition_test.v new file mode 100644 index 00000000000000..a929b0fc390071 --- /dev/null +++ b/cmd/tools/vcover/testdata/example1/condition_test.v @@ -0,0 +1,5 @@ +module example1 + +fn test_condition() { + println(condition()) +} diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 708c6dc677fdf3..203d3bd129d97b 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -43,6 +43,7 @@ fn (mut g Gen) write_coverage_point(pos token.Pos) { } fn (mut g Gen) write_coverage_stats() { + build_options := g.pref.build_options.join(' ') coverage_meta_folder := os.join_path(g.pref.coverage_dir, 'meta') if !os.exists(coverage_meta_folder) { os.mkdir_all(coverage_meta_folder) or {} @@ -77,6 +78,7 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('\tsnprintf(cov_filename, sizeof(cov_filename), "%s/vcounters_${counter_ulid}.csv", cov_dir);') g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "wb+");') g.cov_declarations.writeln('\tfprintf(fp, "# path: ${g.pref.path}\\n");') + g.cov_declarations.writeln('\tfprintf(fp, "# build_options: ${build_options}\\n");') g.cov_declarations.writeln('\tfprintf(fp, "meta,point,hits\\n");') for k, cov in g.coverage_files { nr_points := cov.points.len From 44b0c08c9bd7c328e0897800974b8918322b9995 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sat, 25 May 2024 23:26:52 +0300 Subject: [PATCH 23/38] move condition_test.v to a separate folder example2/ --- cmd/tools/vcover/testdata/example1/condition_test.v | 5 ----- cmd/tools/vcover/testdata/{example1 => example2}/condition.v | 4 ++-- cmd/tools/vcover/testdata/example2/condition_test.v | 5 +++++ cmd/tools/vcover/testdata/example2/v.mod | 0 4 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 cmd/tools/vcover/testdata/example1/condition_test.v rename cmd/tools/vcover/testdata/{example1 => example2}/condition.v (88%) create mode 100644 cmd/tools/vcover/testdata/example2/condition_test.v create mode 100644 cmd/tools/vcover/testdata/example2/v.mod diff --git a/cmd/tools/vcover/testdata/example1/condition_test.v b/cmd/tools/vcover/testdata/example1/condition_test.v deleted file mode 100644 index a929b0fc390071..00000000000000 --- a/cmd/tools/vcover/testdata/example1/condition_test.v +++ /dev/null @@ -1,5 +0,0 @@ -module example1 - -fn test_condition() { - println(condition()) -} diff --git a/cmd/tools/vcover/testdata/example1/condition.v b/cmd/tools/vcover/testdata/example2/condition.v similarity index 88% rename from cmd/tools/vcover/testdata/example1/condition.v rename to cmd/tools/vcover/testdata/example2/condition.v index 86ff777086e177..900fd3ec932f5d 100644 --- a/cmd/tools/vcover/testdata/example1/condition.v +++ b/cmd/tools/vcover/testdata/example2/condition.v @@ -1,6 +1,6 @@ -module example1 +module example2 -fn condition() int { +pub fn condition() int { mut res := 0 $if condition1 ? { res += 1 diff --git a/cmd/tools/vcover/testdata/example2/condition_test.v b/cmd/tools/vcover/testdata/example2/condition_test.v new file mode 100644 index 00000000000000..07f20ac13464d1 --- /dev/null +++ b/cmd/tools/vcover/testdata/example2/condition_test.v @@ -0,0 +1,5 @@ +import example2 + +fn test_condition() { + println(example2.condition()) +} diff --git a/cmd/tools/vcover/testdata/example2/v.mod b/cmd/tools/vcover/testdata/example2/v.mod new file mode 100644 index 00000000000000..e69de29bb2d1d6 From 0b03e8719fef4db340172334aaf8838e728de387 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 01:16:52 +0300 Subject: [PATCH 24/38] fix `# build_options` in cgen for `v -cc clang -cstrict -gc none -cov /tmp/cover/ a.v`, restore runtime timestamp for the counter files --- vlib/v/gen/c/coverage.v | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 203d3bd129d97b..530830ea3f87cf 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -60,7 +60,7 @@ fn (mut g Gen) write_coverage_stats() { } mut fmeta := os.create(metadata_coverage_fpath) or { continue } fmeta.writeln('{') or { continue } - fmeta.writeln(' "file": "${cov.file.path}",') or { continue } + fmeta.writeln(' "file": "${cov.file.path}", "fhash": "${cov.fhash}",') or { continue } fmeta.writeln(' "v_version": "${version.full_v_version(true)}",') or { continue } fmeta.writeln(' "build_options": "${cov.build_options}",') or { continue } fmeta.writeln(' "npoints": ${cov.points.len},') or { continue } @@ -75,10 +75,12 @@ fn (mut g Gen) write_coverage_stats() { fmeta.writeln('}') or { continue } fmeta.close() } - g.cov_declarations.writeln('\tsnprintf(cov_filename, sizeof(cov_filename), "%s/vcounters_${counter_ulid}.csv", cov_dir);') + g.cov_declarations.writeln('\tstruct timespec ts;') + g.cov_declarations.writeln('\tclock_gettime(CLOCK_MONOTONIC, &ts);') + g.cov_declarations.writeln('\tsnprintf(cov_filename, sizeof(cov_filename), "%s/vcounters_${counter_ulid}.%ld.%ld.csv", cov_dir, ts.tv_sec, ts.tv_nsec);') g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "wb+");') g.cov_declarations.writeln('\tfprintf(fp, "# path: ${g.pref.path}\\n");') - g.cov_declarations.writeln('\tfprintf(fp, "# build_options: ${build_options}\\n");') + g.cov_declarations.writeln('\tfprintf(fp, "# build_options: ${cescape_nonascii(cestring(build_options))}\\n");') g.cov_declarations.writeln('\tfprintf(fp, "meta,point,hits\\n");') for k, cov in g.coverage_files { nr_points := cov.points.len From 729ecc9a5bb9e663ce9b0241bde3aca330ed540e Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 01:29:21 +0300 Subject: [PATCH 25/38] escape the paths and build options involved in coverage reports, when they are put in generated C or generated JSON files --- vlib/v/gen/c/coverage.v | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 530830ea3f87cf..26031bbd1876a7 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -54,15 +54,17 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('\tchar cov_filename[2048];') g.cov_declarations.writeln('\tchar *cov_dir = "${os.real_path(g.pref.coverage_dir)}";') for _, mut cov in g.coverage_files { - metadata_coverage_fpath := os.join_path(coverage_meta_folder, '${cov.fhash}.txt') + metadata_coverage_fpath := os.join_path(coverage_meta_folder, '${cov.fhash}.json') if os.exists(metadata_coverage_fpath) { continue } mut fmeta := os.create(metadata_coverage_fpath) or { continue } fmeta.writeln('{') or { continue } - fmeta.writeln(' "file": "${cov.file.path}", "fhash": "${cov.fhash}",') or { continue } - fmeta.writeln(' "v_version": "${version.full_v_version(true)}",') or { continue } - fmeta.writeln(' "build_options": "${cov.build_options}",') or { continue } + fmeta.writeln(' "file": "${jesc(os.real_path(cov.file.path))}", "fhash": "${jesc(cov.fhash)}",') or { + continue + } + fmeta.writeln(' "v_version": "${jesc(version.full_v_version(true))}",') or { continue } + fmeta.writeln(' "build_options": "${jesc(cov.build_options)}",') or { continue } fmeta.writeln(' "npoints": ${cov.points.len},') or { continue } fmeta.write_string(' "points": [ ') or { continue } for idx, p in cov.points { @@ -79,8 +81,8 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('\tclock_gettime(CLOCK_MONOTONIC, &ts);') g.cov_declarations.writeln('\tsnprintf(cov_filename, sizeof(cov_filename), "%s/vcounters_${counter_ulid}.%ld.%ld.csv", cov_dir, ts.tv_sec, ts.tv_nsec);') g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "wb+");') - g.cov_declarations.writeln('\tfprintf(fp, "# path: ${g.pref.path}\\n");') - g.cov_declarations.writeln('\tfprintf(fp, "# build_options: ${cescape_nonascii(cestring(build_options))}\\n");') + g.cov_declarations.writeln('\tfprintf(fp, "# path: ${cesc(os.real_path(g.pref.path))}\\n");') + g.cov_declarations.writeln('\tfprintf(fp, "# build_options: ${cesc(build_options)}\\n");') g.cov_declarations.writeln('\tfprintf(fp, "meta,point,hits\\n");') for k, cov in g.coverage_files { nr_points := cov.points.len @@ -95,3 +97,11 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('\tfclose(fp);') g.cov_declarations.writeln('}') } + +fn cesc(s string) string { + return cescape_nonascii(cestring(s)) +} + +fn jesc(s string) string { + return escape_quotes(s) +} From f08eafb205aadaa4a50dd6cddc79b8230791193f Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 01:43:19 +0300 Subject: [PATCH 26/38] update copyright --- vlib/v/gen/c/coverage.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 26031bbd1876a7..0ea5fe60af34c0 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Felipe Pena. All rights reserved. +// Copyright (c) 2024 Felipe Pena and Delyan Angelov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module c From 7f9c0d6f863b118d5d4b72c85e1d97dab7d2a089 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 01:58:22 +0300 Subject: [PATCH 27/38] draft of `v cover` working with the updated .csv and .json coverage data --- cmd/tools/vcover/data.v | 34 +++ cmd/tools/vcover/main.v | 203 ++++++++++++++++++ cmd/tools/vcover/{vcover.v => reporting.v} | 12 +- .../testdata/example2/runtime_condition.v | 36 ++++ .../example2/runtime_condition_test.v | 5 + 5 files changed, 280 insertions(+), 10 deletions(-) create mode 100644 cmd/tools/vcover/data.v create mode 100644 cmd/tools/vcover/main.v rename cmd/tools/vcover/{vcover.v => reporting.v} (86%) create mode 100644 cmd/tools/vcover/testdata/example2/runtime_condition.v create mode 100644 cmd/tools/vcover/testdata/example2/runtime_condition_test.v diff --git a/cmd/tools/vcover/data.v b/cmd/tools/vcover/data.v new file mode 100644 index 00000000000000..468cb754e81b78 --- /dev/null +++ b/cmd/tools/vcover/data.v @@ -0,0 +1,34 @@ +// Copyright (c) 2024 Felipe Pena and Delyan Angelov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module main + +// vcounter_*.csv files contain counter lines in a CSV format. They can get quite large, +// for big programs, since they contain all non zero coverage counters. +// Their names are timestamp (rand.ulid + clock_gettime) based, to minimise the chance that parallel runs +// will overwrite each other, but without the overhead of additional synchronisation/locks. +struct CounterLine { +mut: + file string // retrieved based on the loaded meta + line int // retrieved based on the loaded meta + // + meta string // A filename in the sibling meta/ folder, should exist, to match the value from this field. The filename is a hash of both the path and the used build options, to facilitate merging coverage data from different builds/programs + point int // The index of a source point. Note that it is not a line number, but an index in the meta data file, keyed by the field `meta` above. + hits u64 // How many times the coverage point was executed. Only counters that are != 0 are recorded. +} + +// Source metadata files in meta/*.txt, contain JSON encoded fields (mappings from v source files to point line numbers). +// Their names are a result of a hashing function, applied over both the source file name, and the build options. +// This has several benefits: +// a) it makes sure, that the resulting path is normalised +// b) the meta data is deduplicated between runs that use the same source files +// c) coverage data from different runs can be merged by simply reusing the same -coverage folder, +// or by copy/pasting all files from 1 run, to the folder of another. +struct MetaData { + file string // V source file path + fhash string // fhash is the name of the meta file + v_version string // the V version, used to generate the coverage meta data file + build_options string // the build options for the program + npoints int // the number of stored coverage points + points []int // the line numbers corresponding to each point +} diff --git a/cmd/tools/vcover/main.v b/cmd/tools/vcover/main.v new file mode 100644 index 00000000000000..6bec1f3e51b142 --- /dev/null +++ b/cmd/tools/vcover/main.v @@ -0,0 +1,203 @@ +// Copyright (c) 2024 Felipe Pena and Delyan Angelov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module main + +import os +import log +import flag +import json +import arrays +import encoding.csv + +// program options, storage etc +struct Context { +mut: + show_help bool + show_hotspots bool + show_percentages bool + show_test_files bool + use_absolute_paths bool + be_verbose bool + filter string + working_folder string + // + targets []string + meta map[string]MetaData // aggregated meta data, read from all .json files + all_lines_per_file map[string][]int // aggregated by load_meta + // + counters map[string]u64 // incremented by process_target, based on each .csv file + lines_per_file map[string]map[int]int // incremented by process_target, based on each .csv file + processed_points u64 +} + +const metadata_extension = '.json' +const vcounter_glob_pattern = 'vcounters_*.csv' + +fn (mut ctx Context) load_meta(folder string) { + for mfile in os.walk_ext(folder, metadata_extension) { + content := os.read_file(mfile) or { '' } + meta := os.file_name(mfile.replace(metadata_extension, '')) + data := json.decode(MetaData, content) or { + log.error('${@METHOD} failed to load ${mfile}') + continue + } + ctx.meta[meta] = data + mut lines_per_file := ctx.all_lines_per_file[data.file] + lines_per_file << data.points + ctx.all_lines_per_file[data.file] = arrays.distinct(lines_per_file) + } +} + +fn (mut ctx Context) post_process_all_metas() { + ctx.verbose('${@METHOD}') + for _, m in ctx.meta { + lines_per_file := ctx.all_lines_per_file[m.file] + for line in lines_per_file { + ctx.counters['${m.file}:${line}:'] = 0 + } + } +} + +fn (mut ctx Context) post_process_all_targets() { + ctx.verbose('${@METHOD}') + ctx.verbose('ctx.processed_points: ${ctx.processed_points}') +} + +fn (ctx &Context) verbose(msg string) { + if ctx.be_verbose { + log.info(msg) + } +} + +fn (mut ctx Context) process_target(tfile string) ! { + ctx.verbose('${@METHOD} ${tfile}') + mut reader := csv.new_reader_from_file(tfile)! + header := reader.read()! + if header != ['meta', 'point', 'hits'] { + return error('invalid header in .csv file') + } + for { + row := reader.read() or { break } + mut cline := CounterLine{ + meta: row[0] + point: row[1].int() + hits: row[2].u64() + } + m := ctx.meta[cline.meta] or { + ctx.verbose('> skipping invalid meta: ${cline.meta} in file: ${cline.file}, csvfile: ${tfile}') + continue + } + cline.file = m.file + cline.line = m.points[cline.point] or { + ctx.verbose('> skipping invalid point: ${cline.point} in file: ${cline.file}, meta: ${cline.meta}, csvfile: ${tfile}') + continue + } + ctx.counters['${cline.file}:${cline.line}:'] += cline.hits + mut lines := ctx.lines_per_file[cline.file].move() + lines[cline.line]++ + ctx.lines_per_file[cline.file] = lines.move() + // dump( ctx.lines_per_file[cline.meta][cline.point] ) + ctx.processed_points++ + } +} + +fn (mut ctx Context) show_report() ! { + filters := ctx.filter.split(',').filter(it != '') + if ctx.show_hotspots { + for location, hits in ctx.counters { + if filters.len > 0 { + if !filters.any(location.contains(it)) { + continue + } + } + println('${hits:-8} ${location}') + } + } + if ctx.show_percentages { + for file, lines in ctx.lines_per_file { + if !ctx.show_test_files { + if file.ends_with('_test.v') || file.ends_with('_test.c.v') { + continue + } + } + if filters.len > 0 { + if !filters.any(file.contains(it)) { + continue + } + } + total_lines := ctx.all_lines_per_file[file].len + executed_points := lines.len + coverage_percent := 100.0 * f64(executed_points) / f64(total_lines) + mut final_path := normalize_path(file) + if !ctx.use_absolute_paths { + final_path = file.all_after_first('${ctx.working_folder}/') + } + println('${final_path:-80s} | ${executed_points:6} | ${total_lines:6} | ${coverage_percent:6.2f}%') + } + } +} + +fn normalize_path(path string) string { + return path.replace(os.path_separator, '/') +} + +fn main() { + mut ctx := Context{} + ctx.working_folder = normalize_path(os.real_path(os.getwd())) + mut fp := flag.new_flag_parser(os.args#[1..]) + fp.application('v cover') + fp.version('0.0.2') + fp.description('Analyze & make reports, based on cover files, produced by running programs and tests, compiled with `-coverage folder/`') + fp.arguments_description('[folder1/ file2 ...]') + fp.skip_executable() + ctx.show_help = fp.bool('help', `h`, false, 'Show this help text.') + ctx.be_verbose = fp.bool('verbose', `v`, false, 'Be more verbose while processing the coverages.') + ctx.show_hotspots = fp.bool('hotspots', `H`, false, 'Show most frequently executed covered lines.') + ctx.show_percentages = fp.bool('percentages', `P`, true, 'Show coverage percentage per file.') + ctx.show_test_files = fp.bool('show_test_files', `S`, false, 'Show `_test.v` files as well (normally filtered).') + ctx.use_absolute_paths = fp.bool('absolute', `A`, false, 'Use absolute paths for all files, no matter the current folder. By default, files inside the current folder, are shown with a relative path.') + ctx.filter = fp.string('filter', `f`, '', 'Filter only the matching source path patterns.') + if ctx.show_help { + println(fp.usage()) + exit(0) + } + targets := fp.finalize() or { + log.error(fp.usage()) + exit(1) + } + ctx.verbose('Targets: ${targets}') + for t in targets { + if !os.exists(t) { + log.error('Skipping ${t}, since it does not exist') + continue + } + if os.is_dir(t) { + found_counter_files := os.glob('${t}/${vcounter_glob_pattern}')! + if found_counter_files.len == 0 { + log.error('Skipping ${t}, since there are 0 ${vcounter_glob_pattern} files in it') + continue + } + for counterfile in found_counter_files { + ctx.targets << counterfile + ctx.load_meta(t) + } + } else { + ctx.targets << t + ctx.load_meta(os.dir(t)) + } + } + ctx.post_process_all_metas() + ctx.verbose('Final ctx.targets.len: ${ctx.targets.len}') + ctx.verbose('Final ctx.meta.len: ${ctx.meta.len}') + ctx.verbose('Final ctx.filter: ${ctx.filter}') + if ctx.targets.len == 0 { + log.error('0 cover targets') + exit(1) + } + for t in ctx.targets { + ctx.process_target(t)! + } + ctx.post_process_all_targets() + ctx.show_report()! +} diff --git a/cmd/tools/vcover/vcover.v b/cmd/tools/vcover/reporting.v similarity index 86% rename from cmd/tools/vcover/vcover.v rename to cmd/tools/vcover/reporting.v index de4e5086dc1ada..75aaf758e2ee44 100644 --- a/cmd/tools/vcover/vcover.v +++ b/cmd/tools/vcover/reporting.v @@ -1,15 +1,12 @@ -// Copyright (c) 2024 Felipe Pena. All rights reserved. +// Copyright (c) 2024 Felipe Pena and Delyan Angelov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module main import json import os -import os.cmdline import arrays -const tmp_dir = os.join_path(os.vtmp_dir(), 'cover') - @[heap] struct VCoverData { file string // file name @@ -81,12 +78,7 @@ fn summarize_coverage(covfile string, mut covdata []&VCoverData) ! { } } -fn main() { - args := cmdline.options_after(os.args, ['cover']) - covfile := os.real_path(cmdline.option(args, '-file', '')) - covdir := os.real_path(cmdline.option(args, '-dir', '')) - filter := cmdline.option(args, '-filter', '') - +fn process_file_or_folder(filter string, covfile string, covdir string) ! { if covfile != '' { report_file(filter, covfile) or { println('Error: ${err}') } } else if covdir != '' { diff --git a/cmd/tools/vcover/testdata/example2/runtime_condition.v b/cmd/tools/vcover/testdata/example2/runtime_condition.v new file mode 100644 index 00000000000000..f877089b249f3b --- /dev/null +++ b/cmd/tools/vcover/testdata/example2/runtime_condition.v @@ -0,0 +1,36 @@ +module example2 + +import os + +pub fn runtime_condition() int { + mut res := 0 + branch := os.getenv('CONDITION') + if branch == '' { + return res + } + if branch.contains('1') { + res += 1 + } + if branch.contains('2') { + res += 2 + } + if branch.contains('3') { + res += 4 + } + if branch.contains('4') { + res += 8 + } + if branch.contains('5') { + res += 16 + } + if branch.contains('6') { + res += 32 + } + if branch.contains('7') { + res += 64 + } + if branch.contains('8') { + res += 128 + } + return res +} diff --git a/cmd/tools/vcover/testdata/example2/runtime_condition_test.v b/cmd/tools/vcover/testdata/example2/runtime_condition_test.v new file mode 100644 index 00000000000000..a798f2b9d11686 --- /dev/null +++ b/cmd/tools/vcover/testdata/example2/runtime_condition_test.v @@ -0,0 +1,5 @@ +import example2 + +fn test_runtime_condition() { + println(example2.runtime_condition()) +} From f450a556f79f30899c77330297b8fbbb46af56b6 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 04:47:14 +0300 Subject: [PATCH 28/38] Support relative locations in the --hotspots listing too --- cmd/tools/vcover/main.v | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/tools/vcover/main.v b/cmd/tools/vcover/main.v index 6bec1f3e51b142..3340c9c1c0da49 100644 --- a/cmd/tools/vcover/main.v +++ b/cmd/tools/vcover/main.v @@ -111,7 +111,11 @@ fn (mut ctx Context) show_report() ! { continue } } - println('${hits:-8} ${location}') + mut final_path := normalize_path(location) + if !ctx.use_absolute_paths { + final_path = location.all_after_first('${ctx.working_folder}/') + } + println('${hits:-8} ${final_path}') } } if ctx.show_percentages { From 19229fa23b59d793a1de5b1737a1b6ff5f4813fd Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 05:16:56 +0300 Subject: [PATCH 29/38] ci: fix `v build-tools`, since now `v cover` has multiple .v files. --- cmd/tools/vbuild-tools.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/tools/vbuild-tools.v b/cmd/tools/vbuild-tools.v index 1c04d7a277203f..cfd368595741ac 100644 --- a/cmd/tools/vbuild-tools.v +++ b/cmd/tools/vbuild-tools.v @@ -11,7 +11,7 @@ import v.util // should be compiled (v folder). // To implement that, these folders are initially skipped, then added // as a whole *after the testing.prepare_test_session call*. -const tools_in_subfolders = ['vast', 'vcreate', 'vdoc', 'vpm', 'vsymlink', 'vvet', 'vwhere'] +const tools_in_subfolders = ['vast', 'vcreate', 'vdoc', 'vpm', 'vsymlink', 'vvet', 'vwhere', 'vcover'] // non_packaged_tools are tools that should not be packaged with // prebuild versions of V, to keep the size smaller. From 07499c8431d28f541637d4765479fa2e24c750e9 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 12:13:56 +0300 Subject: [PATCH 30/38] ci: fix `./v -autofree -o v2 cmd/v` in the misc-tooling job --- vlib/v/gen/c/coverage.v | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 0ea5fe60af34c0..94810908bc9813 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -60,11 +60,13 @@ fn (mut g Gen) write_coverage_stats() { } mut fmeta := os.create(metadata_coverage_fpath) or { continue } fmeta.writeln('{') or { continue } - fmeta.writeln(' "file": "${jesc(os.real_path(cov.file.path))}", "fhash": "${jesc(cov.fhash)}",') or { - continue - } - fmeta.writeln(' "v_version": "${jesc(version.full_v_version(true))}",') or { continue } - fmeta.writeln(' "build_options": "${jesc(cov.build_options)}",') or { continue } + jfilepath := jesc(os.real_path(cov.file.path)) + jfhash := jesc(cov.fhash) + jversion := jesc(version.full_v_version(true)) + jboptions := jesc(cov.build_options) + fmeta.writeln(' "file": "${jfilepath}", "fhash": "${jfhash}",') or { continue } + fmeta.writeln(' "v_version": "${jversion}",') or { continue } + fmeta.writeln(' "build_options": "${jboptions}",') or { continue } fmeta.writeln(' "npoints": ${cov.points.len},') or { continue } fmeta.write_string(' "points": [ ') or { continue } for idx, p in cov.points { @@ -81,8 +83,10 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('\tclock_gettime(CLOCK_MONOTONIC, &ts);') g.cov_declarations.writeln('\tsnprintf(cov_filename, sizeof(cov_filename), "%s/vcounters_${counter_ulid}.%ld.%ld.csv", cov_dir, ts.tv_sec, ts.tv_nsec);') g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "wb+");') - g.cov_declarations.writeln('\tfprintf(fp, "# path: ${cesc(os.real_path(g.pref.path))}\\n");') - g.cov_declarations.writeln('\tfprintf(fp, "# build_options: ${cesc(build_options)}\\n");') + cprefpath := cesc(os.real_path(g.pref.path)) + cboptions := cesc(build_options) + g.cov_declarations.writeln('\tfprintf(fp, "# path: ${cprefpath}\\n");') + g.cov_declarations.writeln('\tfprintf(fp, "# build_options: ${cboptions}\\n");') g.cov_declarations.writeln('\tfprintf(fp, "meta,point,hits\\n");') for k, cov in g.coverage_files { nr_points := cov.points.len From bceb1160ff577f9fa076dd21545525eab4f2ada9 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 13:43:26 +0300 Subject: [PATCH 31/38] add more tests --- cmd/tools/vcover/cover_test.v | 98 ++++++++++++++++++++++ cmd/tools/vcover/testdata/simple/simple.v | 26 ++++++ cmd/tools/vcover/testdata/simple/t1_test.v | 7 ++ cmd/tools/vcover/testdata/simple/t2_test.v | 7 ++ cmd/tools/vcover/testdata/simple/v.mod | 0 5 files changed, 138 insertions(+) create mode 100644 cmd/tools/vcover/cover_test.v create mode 100644 cmd/tools/vcover/testdata/simple/simple.v create mode 100644 cmd/tools/vcover/testdata/simple/t1_test.v create mode 100644 cmd/tools/vcover/testdata/simple/t2_test.v create mode 100644 cmd/tools/vcover/testdata/simple/v.mod diff --git a/cmd/tools/vcover/cover_test.v b/cmd/tools/vcover/cover_test.v new file mode 100644 index 00000000000000..0ad71352344956 --- /dev/null +++ b/cmd/tools/vcover/cover_test.v @@ -0,0 +1,98 @@ +import os + +const vexe = @VEXE +const vroot = os.dir(vexe) +const tfolder = os.join_path(os.vtmp_dir(), 'cover_test') + +fn testsuite_begin() { + os.chdir(vroot)! + os.rmdir_all(tfolder) or {} + os.mkdir(tfolder) or {} +} + +fn testsuite_end() { + os.rmdir_all(tfolder) or {} +} + +fn test_help() { + res := os.execute('${os.quoted_path(vexe)} cover -h') + assert res.exit_code == 0 + assert res.output.contains('Usage: v cover') + assert res.output.contains('Description: Analyze & make reports') + assert res.output.contains('Options:') + assert res.output.contains('-h, --help Show this help text.') + assert res.output.contains('-v, --verbose Be more verbose while processing the coverages.') + assert res.output.contains('-H, --hotspots Show most frequently executed covered lines.') + assert res.output.contains('-P, --percentages Show coverage percentage per file.') + assert res.output.contains('-S, --show_test_files Show `_test.v` files as well (normally filtered).') + assert res.output.contains('-A, --absolute Use absolute paths for all files') +} + +fn test_simple() { + t1 := os.join_path(tfolder, 't1') + t2 := os.join_path(tfolder, 't2') + t3 := os.join_path(tfolder, 't3') + assert !os.exists(t1) + assert !os.exists(t2) + assert !os.exists(t3) + + r1 := os.execute('${os.quoted_path(vexe)} -coverage ${os.quoted_path(t1)} cmd/tools/vcover/testdata/simple/t1_test.v') + assert r1.exit_code == 0, r1.str() + assert r1.output.trim_space() == '10', r1.str() + assert os.exists(t1), t1 + filter1 := os.execute('${os.quoted_path(vexe)} cover ${os.quoted_path(t1)} --filter vcover/testdata/simple/') + assert filter1.exit_code == 0, filter1.output + assert filter1.output.contains('cmd/tools/vcover/testdata/simple/simple.v') + assert filter1.output.trim_space().ends_with('| 4 | 9 | 44.44%'), filter1.output + hfilter1 := os.execute('${os.quoted_path(vexe)} cover ${os.quoted_path(t1)} --filter vcover/testdata/simple/ -H -P false') + assert hfilter1.exit_code == 0, hfilter1.output + assert !hfilter1.output.contains('%'), hfilter1.output + houtput1 := hfilter1.output.trim_space().split_into_lines() + zeros1 := houtput1.filter(it.starts_with('0 ')) + nzeros1 := houtput1.filter(!it.starts_with('0 ')) + assert zeros1.len > 0 + assert zeros1.any(it.contains('simple.v:12')), zeros1.str() + assert zeros1.any(it.contains('simple.v:14')), zeros1.str() + assert zeros1.any(it.contains('simple.v:17')), zeros1.str() + assert zeros1.any(it.contains('simple.v:18')), zeros1.str() + assert zeros1.any(it.contains('simple.v:19')), zeros1.str() + assert nzeros1.len > 0 + assert nzeros1.any(it.contains('simple.v:4')), nzeros1.str() + assert nzeros1.any(it.contains('simple.v:6')), nzeros1.str() + assert nzeros1.any(it.contains('simple.v:8')), nzeros1.str() + assert nzeros1.any(it.contains('simple.v:25')), nzeros1.str() + + r2 := os.execute('${os.quoted_path(vexe)} -coverage ${os.quoted_path(t2)} cmd/tools/vcover/testdata/simple/t2_test.v') + assert r2.exit_code == 0, r2.str() + assert r2.output.trim_space() == '24', r2.str() + assert os.exists(t2), t2 + filter2 := os.execute('${os.quoted_path(vexe)} cover ${os.quoted_path(t2)} --filter vcover/testdata/simple') + assert filter2.exit_code == 0, filter2.output + assert filter2.output.contains('cmd/tools/vcover/testdata/simple/simple.v') + assert filter2.output.trim_space().ends_with('| 6 | 9 | 66.67%'), filter2.output + hfilter2 := os.execute('${os.quoted_path(vexe)} cover ${os.quoted_path(t2)} --filter testdata/simple -H -P false') + assert hfilter2.exit_code == 0, hfilter2.output + assert !hfilter2.output.contains('%'), hfilter2.output + houtput2 := hfilter2.output.trim_space().split_into_lines() + zeros2 := houtput2.filter(it.starts_with('0 ')) + nzeros2 := houtput2.filter(!it.starts_with('0 ')) + assert zeros2.len > 0 + assert zeros2.any(it.contains('simple.v:4')), zeros2.str() + assert zeros2.any(it.contains('simple.v:6')), zeros2.str() + assert zeros2.any(it.contains('simple.v:8')), zeros2.str() + assert nzeros2.len > 0 + assert nzeros2.any(it.contains('simple.v:17')), nzeros2.str() + assert nzeros2.any(it.contains('simple.v:18')), nzeros2.str() + assert nzeros2.any(it.contains('simple.v:19')), nzeros2.str() + assert nzeros2.any(it.contains('simple.v:25')), nzeros2.str() + + // Run both tests. The coverage should be combined and == 100% + r3 := os.execute('${os.quoted_path(vexe)} -coverage ${os.quoted_path(t3)} test cmd/tools/vcover/testdata/simple/') + assert r3.exit_code == 0, r3.str() + assert r3.output.trim_space().contains('Summary for all V _test.v files: 2 passed'), r3.str() + assert os.exists(t3), t3 + filter3 := os.execute('${os.quoted_path(vexe)} cover ${os.quoted_path(t3)} --filter simple/') + assert filter3.exit_code == 0, filter3.str() + assert filter3.output.contains('cmd/tools/vcover/testdata/simple/simple.v'), filter3.str() + assert filter3.output.trim_space().match_glob('*cmd/tools/vcover/testdata/simple/simple.v *| 9 | 9 | 100.00%'), filter3.str() +} diff --git a/cmd/tools/vcover/testdata/simple/simple.v b/cmd/tools/vcover/testdata/simple/simple.v new file mode 100644 index 00000000000000..72e0c353d565d6 --- /dev/null +++ b/cmd/tools/vcover/testdata/simple/simple.v @@ -0,0 +1,26 @@ +module simple + +pub fn sum() int { + mut res := 0 + for i in 1 .. 5 { + res += i + } + return res +} + +pub fn mul() int { + mut res := 1 + for i in 1 .. 5 { + res *= i + } + // the lines here are just to introduce an asymmetry in the reported coverage lines + c := res * 2 + _ := c * 10 + return res +} + +pub const a_const = f() + +fn f() int { + return 50 // this should be executed always +} diff --git a/cmd/tools/vcover/testdata/simple/t1_test.v b/cmd/tools/vcover/testdata/simple/t1_test.v new file mode 100644 index 00000000000000..e4ce9691eb6009 --- /dev/null +++ b/cmd/tools/vcover/testdata/simple/t1_test.v @@ -0,0 +1,7 @@ +import simple + +fn test_sum() { + s := simple.sum() + assert s == 10 + println(s) +} diff --git a/cmd/tools/vcover/testdata/simple/t2_test.v b/cmd/tools/vcover/testdata/simple/t2_test.v new file mode 100644 index 00000000000000..c46c38fa6d8639 --- /dev/null +++ b/cmd/tools/vcover/testdata/simple/t2_test.v @@ -0,0 +1,7 @@ +import simple + +fn test_mul() { + m := simple.mul() + assert m == 24 + println(m) +} diff --git a/cmd/tools/vcover/testdata/simple/v.mod b/cmd/tools/vcover/testdata/simple/v.mod new file mode 100644 index 00000000000000..e69de29bb2d1d6 From 26f1b659327462a6a40584ddcc180d80bd97bd8e Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 13:44:57 +0300 Subject: [PATCH 32/38] fix `v test cmd/tools/` --- cmd/tools/vcover/cover_test.v | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/tools/vcover/cover_test.v b/cmd/tools/vcover/cover_test.v index 0ad71352344956..6134714048b815 100644 --- a/cmd/tools/vcover/cover_test.v +++ b/cmd/tools/vcover/cover_test.v @@ -5,6 +5,7 @@ const vroot = os.dir(vexe) const tfolder = os.join_path(os.vtmp_dir(), 'cover_test') fn testsuite_begin() { + os.setenv('VCOLORS', 'never', true) os.chdir(vroot)! os.rmdir_all(tfolder) or {} os.mkdir(tfolder) or {} From 189d3ca796f35e4b9388c1f82b60949d4acf7fff Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 14:35:40 +0300 Subject: [PATCH 33/38] ci: fix `v -os windows -coverage folder file.v` failure, use GetTickCount/0 on windows and clock_gettime/2 everywhere else --- vlib/v/gen/c/coverage.v | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 94810908bc9813..e694261061e9d4 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -79,9 +79,20 @@ fn (mut g Gen) write_coverage_stats() { fmeta.writeln('}') or { continue } fmeta.close() } + g.cov_declarations.writeln('\tlong int secs = 0;') + g.cov_declarations.writeln('\tlong int nsecs = 0;') + g.cov_declarations.writeln('\t#if defined(_WIN32)') + g.cov_declarations.writeln('\tlong int ticks_passed = GetTickCount();') + g.cov_declarations.writeln('\nsecs = ticks_passed / 1000;') + g.cov_declarations.writeln('\nnsecs = (ticks_passed % 1000) * 1000000;') + g.cov_declarations.writeln('\t#endif') + g.cov_declarations.writeln('\t#if !defined(_WIN32)') g.cov_declarations.writeln('\tstruct timespec ts;') g.cov_declarations.writeln('\tclock_gettime(CLOCK_MONOTONIC, &ts);') - g.cov_declarations.writeln('\tsnprintf(cov_filename, sizeof(cov_filename), "%s/vcounters_${counter_ulid}.%ld.%ld.csv", cov_dir, ts.tv_sec, ts.tv_nsec);') + g.cov_declarations.writeln('\tsecs = ts.tv_sec;') + g.cov_declarations.writeln('\nsecs = ts.tv_nsec;') + g.cov_declarations.writeln('\t#endif') + g.cov_declarations.writeln('\tsnprintf(cov_filename, sizeof(cov_filename), "%s/vcounters_${counter_ulid}.%07ld.%09ld.csv", cov_dir, secs, nsecs);') g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "wb+");') cprefpath := cesc(os.real_path(g.pref.path)) cboptions := cesc(build_options) From dbfb62e4bd43f89c7bcc3ad8ebeaa3a796b307e3 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 15:48:38 +0300 Subject: [PATCH 34/38] ci: use just os.walk_ext/2, instead of os.glob/1 (the windows implementation of os.glob is less well tested and differs from the unix one, unlike os.walk_ext/2) --- cmd/tools/vcover/main.v | 2 +- cmd/tools/vcover/reporting.v | 91 ------------------------------------ 2 files changed, 1 insertion(+), 92 deletions(-) delete mode 100644 cmd/tools/vcover/reporting.v diff --git a/cmd/tools/vcover/main.v b/cmd/tools/vcover/main.v index 3340c9c1c0da49..8d2c0d91a09c76 100644 --- a/cmd/tools/vcover/main.v +++ b/cmd/tools/vcover/main.v @@ -177,7 +177,7 @@ fn main() { continue } if os.is_dir(t) { - found_counter_files := os.glob('${t}/${vcounter_glob_pattern}')! + found_counter_files := os.walk_ext(t, '.csv') if found_counter_files.len == 0 { log.error('Skipping ${t}, since there are 0 ${vcounter_glob_pattern} files in it') continue diff --git a/cmd/tools/vcover/reporting.v b/cmd/tools/vcover/reporting.v deleted file mode 100644 index 75aaf758e2ee44..00000000000000 --- a/cmd/tools/vcover/reporting.v +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2024 Felipe Pena and Delyan Angelov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module main - -import json -import os -import arrays - -@[heap] -struct VCoverData { - file string // file name -mut: - hits u64 // file coverage hits - points u64 // file counter -} - -pub fn (covdata []&VCoverData) find(filename string) ?&VCoverData { - for data in covdata { - if data.file == filename { - return unsafe { data } - } - } - return none -} - -pub fn (covdata []VCoverData) filter_files(filters []string, remove_test bool) []VCoverData { - mut arr := []VCoverData{} - for data in covdata { - if remove_test && data.file.ends_with('_test.v') { - continue - } - if filters.len == 0 || filters.any(data.file.contains(it)) { - arr << data - } - } - return arr -} - -fn display_result(filter string, data []VCoverData) ! { - mut covdata := data.clone() - if filter != '' { - filters := filter.split(',') - covdata = covdata.filter_files(filters, true) - if covdata.len == 0 { - return error('No result found with such filter') - } - } else { - covdata = covdata.filter_files([], true) - } - hits := arrays.sum(covdata.map(it.hits))! - points := arrays.sum(covdata.map(it.points))! - for lineinfo in covdata { - println('${(f64(lineinfo.hits) / lineinfo.points) * 100:7.2f} | ${lineinfo.hits:4u} | ${lineinfo.points:4u} | ${lineinfo.file}') - } - println('Total coverage: ${covdata.len} files, ${(f64(hits) / points) * 100:.2f}% coverage') -} - -fn report_file(filter string, covfile string) ! { - json_content := os.read_file(covfile)! - data := json.decode([]VCoverData, json_content)! - display_result(filter, data)! -} - -fn summarize_coverage(covfile string, mut covdata []&VCoverData) ! { - json_content := os.read_file(covfile)! - data := json.decode([]VCoverData, json_content)! - for lineinfo in data { - if mut fileinfo := covdata.find(lineinfo.file) { - if fileinfo.hits < lineinfo.hits { - fileinfo.hits = lineinfo.hits - } - } else { - covdata << &VCoverData{ - ...lineinfo - } - } - } -} - -fn process_file_or_folder(filter string, covfile string, covdir string) ! { - if covfile != '' { - report_file(filter, covfile) or { println('Error: ${err}') } - } else if covdir != '' { - mut sum_data := []&VCoverData{} - for coverfile in os.glob(covdir + '/vcover.*')! { - summarize_coverage(coverfile, mut sum_data)! - } - display_result(filter, sum_data.map(*it)) or { println('Error: ${err}') } - } -} From adf49d24e2b80b203c7e46572f26e7576ade5af3 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Sun, 26 May 2024 15:43:18 -0300 Subject: [PATCH 35/38] fix --- vlib/v/gen/c/cgen.v | 9 +-------- vlib/v/gen/c/coverage.v | 4 ++++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index a1ae5829f528f4..6c9443d73ce76f 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2096,14 +2096,7 @@ fn (mut g Gen) write_v_source_line_info_stmt(stmt ast.Stmt) { g.write_v_source_line_info_pos(stmt.pos) if g.inside_ternary == 0 && g.pref.is_coverage && !g.inside_for_c_stmt && stmt !in [ast.FnDecl, ast.ForCStmt, ast.ForInStmt, ast.ForStmt] { - if stmt is ast.ExprStmt { - if !g.inside_assign - && stmt.expr !in [ast.CastExpr, ast.IndexExpr, ast.StringInterLiteral, ast.StringLiteral, ast.Ident, ast.UnsafeExpr] { - g.write_coverage_point(stmt.pos) - } - } else { - g.write_coverage_point(stmt.pos) - } + g.write_coverage_point(stmt.pos) } } diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index e694261061e9d4..34fbcacb15c405 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -38,7 +38,11 @@ fn (mut g Gen) write_coverage_point(pos token.Pos) { if curr_line !in curr_cov.points { curr_cov.points << curr_line } + stmt_str := g.go_before_last_stmt().trim_space() + g.empty_line = true g.writeln('_v_cov[_v_cov_file_offset_${g.unique_file_path_hash}+${curr_cov.points.len - 1}]++;') + g.set_current_pos_as_last_stmt_pos() + g.write(stmt_str) } } From ec57f1035d455fda0873dc97fb88509b9855867d Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 22:21:12 +0300 Subject: [PATCH 36/38] ci: fix windows failure (unescaped path in the generated source) --- vlib/v/gen/c/coverage.v | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index 34fbcacb15c405..c19489981255c5 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -56,7 +56,8 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('') g.cov_declarations.writeln('void vprint_coverage_stats() {') g.cov_declarations.writeln('\tchar cov_filename[2048];') - g.cov_declarations.writeln('\tchar *cov_dir = "${os.real_path(g.pref.coverage_dir)}";') + covdir := cesc(os.real_path(g.pref.coverage_dir)) + g.cov_declarations.writeln('\tchar *cov_dir = "${covdir}";') for _, mut cov in g.coverage_files { metadata_coverage_fpath := os.join_path(coverage_meta_folder, '${cov.fhash}.json') if os.exists(metadata_coverage_fpath) { From a7c05704d079fced0f456acacf0e5998f18fdc06 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Sun, 26 May 2024 23:31:16 +0300 Subject: [PATCH 37/38] ci: normalise paths on windows to the unix ones for uniformity and consistent behaviour of the tests --- cmd/tools/vcover/cover_test.v | 21 +++++++++++++-------- cmd/tools/vcover/main.v | 3 ++- vlib/v/gen/c/coverage.v | 10 ++++++---- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/cmd/tools/vcover/cover_test.v b/cmd/tools/vcover/cover_test.v index 6134714048b815..1a42dea112e334 100644 --- a/cmd/tools/vcover/cover_test.v +++ b/cmd/tools/vcover/cover_test.v @@ -29,21 +29,26 @@ fn test_help() { assert res.output.contains('-A, --absolute Use absolute paths for all files') } +fn np(path string) string { + return path.replace('\\', '/') +} + fn test_simple() { - t1 := os.join_path(tfolder, 't1') - t2 := os.join_path(tfolder, 't2') - t3 := os.join_path(tfolder, 't3') - assert !os.exists(t1) - assert !os.exists(t2) - assert !os.exists(t3) + t1 := np(os.join_path(tfolder, 't1')) + t2 := np(os.join_path(tfolder, 't2')) + t3 := np(os.join_path(tfolder, 't3')) + assert !os.exists(t1), t1 + assert !os.exists(t2), t2 + assert !os.exists(t3), t3 r1 := os.execute('${os.quoted_path(vexe)} -coverage ${os.quoted_path(t1)} cmd/tools/vcover/testdata/simple/t1_test.v') assert r1.exit_code == 0, r1.str() assert r1.output.trim_space() == '10', r1.str() assert os.exists(t1), t1 - filter1 := os.execute('${os.quoted_path(vexe)} cover ${os.quoted_path(t1)} --filter vcover/testdata/simple/') + cmd := '${os.quoted_path(vexe)} cover ${os.quoted_path(t1)} --filter vcover/testdata/simple/' + filter1 := os.execute(cmd) assert filter1.exit_code == 0, filter1.output - assert filter1.output.contains('cmd/tools/vcover/testdata/simple/simple.v') + assert filter1.output.contains('cmd/tools/vcover/testdata/simple/simple.v'), filter1.output assert filter1.output.trim_space().ends_with('| 4 | 9 | 44.44%'), filter1.output hfilter1 := os.execute('${os.quoted_path(vexe)} cover ${os.quoted_path(t1)} --filter vcover/testdata/simple/ -H -P false') assert hfilter1.exit_code == 0, hfilter1.output diff --git a/cmd/tools/vcover/main.v b/cmd/tools/vcover/main.v index 8d2c0d91a09c76..6d3c7ff954b4cb 100644 --- a/cmd/tools/vcover/main.v +++ b/cmd/tools/vcover/main.v @@ -35,7 +35,8 @@ const metadata_extension = '.json' const vcounter_glob_pattern = 'vcounters_*.csv' fn (mut ctx Context) load_meta(folder string) { - for mfile in os.walk_ext(folder, metadata_extension) { + for omfile in os.walk_ext(folder, metadata_extension) { + mfile := omfile.replace('\\', '/') content := os.read_file(mfile) or { '' } meta := os.file_name(mfile.replace(metadata_extension, '')) data := json.decode(MetaData, content) or { diff --git a/vlib/v/gen/c/coverage.v b/vlib/v/gen/c/coverage.v index c19489981255c5..37ae9dd0012758 100644 --- a/vlib/v/gen/c/coverage.v +++ b/vlib/v/gen/c/coverage.v @@ -48,7 +48,8 @@ fn (mut g Gen) write_coverage_point(pos token.Pos) { fn (mut g Gen) write_coverage_stats() { build_options := g.pref.build_options.join(' ') - coverage_meta_folder := os.join_path(g.pref.coverage_dir, 'meta') + coverage_dir := os.real_path(g.pref.coverage_dir).replace('\\', '/') + coverage_meta_folder := '${coverage_dir}/meta' if !os.exists(coverage_meta_folder) { os.mkdir_all(coverage_meta_folder) or {} } @@ -56,16 +57,17 @@ fn (mut g Gen) write_coverage_stats() { g.cov_declarations.writeln('') g.cov_declarations.writeln('void vprint_coverage_stats() {') g.cov_declarations.writeln('\tchar cov_filename[2048];') - covdir := cesc(os.real_path(g.pref.coverage_dir)) + covdir := cesc(coverage_dir) g.cov_declarations.writeln('\tchar *cov_dir = "${covdir}";') for _, mut cov in g.coverage_files { - metadata_coverage_fpath := os.join_path(coverage_meta_folder, '${cov.fhash}.json') + metadata_coverage_fpath := '${coverage_meta_folder}/${cov.fhash}.json' + filepath := os.real_path(cov.file.path).replace('\\', '/') if os.exists(metadata_coverage_fpath) { continue } mut fmeta := os.create(metadata_coverage_fpath) or { continue } fmeta.writeln('{') or { continue } - jfilepath := jesc(os.real_path(cov.file.path)) + jfilepath := jesc(filepath) jfhash := jesc(cov.fhash) jversion := jesc(version.full_v_version(true)) jboptions := jesc(cov.build_options) From 9f34dec32b5eeb1effb5a4e55a4ba14ade0917f8 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Mon, 27 May 2024 08:55:16 -0300 Subject: [PATCH 38/38] fix case where assert breaks --- vlib/v/gen/c/cgen.v | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 586037a9a1e612..eebc54d07c9ce5 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -2096,7 +2096,13 @@ fn (mut g Gen) write_v_source_line_info_stmt(stmt ast.Stmt) { g.write_v_source_line_info_pos(stmt.pos) if g.inside_ternary == 0 && g.pref.is_coverage && !g.inside_for_c_stmt && stmt !in [ast.FnDecl, ast.ForCStmt, ast.ForInStmt, ast.ForStmt] { - g.write_coverage_point(stmt.pos) + if stmt is ast.AssertStmt { + if stmt.expr !in [ast.InfixExpr, ast.MatchExpr] { + g.write_coverage_point(stmt.pos) + } + } else { + g.write_coverage_point(stmt.pos) + } } }