Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cgen,pref: add -coverage support + vcover tool #21154

Merged
merged 42 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
5cad749
fix
felipensp Mar 31, 2024
c255914
fix
felipensp Apr 1, 2024
f9ed550
improvement
felipensp Apr 1, 2024
a1c524c
fix
felipensp Apr 1, 2024
68bd90d
improvement
felipensp Apr 1, 2024
f05811a
improve
felipensp May 13, 2024
322d2f4
Merge remote-tracking branch 'origin/master' into v_coverage
felipensp May 13, 2024
5a8ecb8
fix merge
felipensp May 13, 2024
8344d02
Merge remote-tracking branch 'origin/master' into v_coverage
felipensp May 23, 2024
0adcd12
wip
felipensp May 23, 2024
b5d9fa6
wip
felipensp May 24, 2024
cf6bb6f
fix
felipensp May 24, 2024
0f4f4a7
Merge remote-tracking branch 'origin/master' into v_coverage
felipensp May 24, 2024
c223eb9
wip
felipensp May 24, 2024
791d7ba
wip
felipensp May 24, 2024
a561426
wip
felipensp May 24, 2024
01ed4d7
improve
felipensp May 25, 2024
1096fb5
fix
felipensp May 25, 2024
c80b978
added -filter
felipensp May 25, 2024
73c3878
ignore test funcs
felipensp May 25, 2024
0d6665f
improve filtering
felipensp May 25, 2024
d16866f
change format to csv, add more details, add cmd/tools/vcover/testdata…
spytheman May 25, 2024
2f4dbb1
make test runs of cmd/tools/vcover/testdata/example1/ silent for now
spytheman May 25, 2024
622ed4a
cleanup output format for both the csv counters, and the json meta da…
spytheman May 25, 2024
def6f9b
add the build options to the .csv file too, as a comment (for easier …
spytheman May 25, 2024
44b0c08
move condition_test.v to a separate folder example2/
spytheman May 25, 2024
0b03e87
fix `# build_options` in cgen for `v -cc clang -cstrict -gc none -cov…
spytheman May 25, 2024
729ecc9
escape the paths and build options involved in coverage reports, when…
spytheman May 25, 2024
f08eafb
update copyright
spytheman May 25, 2024
7f9c0d6
draft of `v cover` working with the updated .csv and .json coverage data
spytheman May 25, 2024
f450a55
Support relative locations in the --hotspots listing too
spytheman May 26, 2024
19229fa
ci: fix `v build-tools`, since now `v cover` has multiple .v files.
spytheman May 26, 2024
07499c8
ci: fix `./v -autofree -o v2 cmd/v` in the misc-tooling job
spytheman May 26, 2024
bceb116
add more tests
spytheman May 26, 2024
26f1b65
fix `v test cmd/tools/`
spytheman May 26, 2024
189d3ca
ci: fix `v -os windows -coverage folder file.v` failure, use GetTickC…
spytheman May 26, 2024
dbfb62e
ci: use just os.walk_ext/2, instead of os.glob/1 (the windows impleme…
spytheman May 26, 2024
adf49d2
fix
felipensp May 26, 2024
ec57f10
ci: fix windows failure (unescaped path in the generated source)
spytheman May 26, 2024
a7c0570
ci: normalise paths on windows to the unix ones for uniformity and co…
spytheman May 26, 2024
2f76f7b
Merge branch 'master' into v_coverage
spytheman May 27, 2024
9f34dec
fix case where assert breaks
felipensp May 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion vlib/v/gen/c/assert.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
86 changes: 73 additions & 13 deletions vlib/v/gen/c/cgen.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -69,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
Expand Down Expand Up @@ -119,6 +129,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 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<</+=/-= infix expr
Expand Down Expand Up @@ -299,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)
Expand Down Expand Up @@ -379,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) }
Expand Down Expand Up @@ -411,6 +424,9 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) (str
for k, v in g.sumtype_definitions {
global_g.sumtype_definitions[k] = v
}
for k, v in g.coverage_files {
global_g.coverage_files[k] = v
}
global_g.json_forward_decls.write(g.json_forward_decls) or { panic(err) }
global_g.enum_typedefs.write(g.enum_typedefs) or { panic(err) }
global_g.channel_definitions.write(g.channel_definitions) or { panic(err) }
Expand Down Expand Up @@ -505,6 +521,15 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) (str
g.write_init_function()
}

if g.pref.is_coverage {
mut total_code_points := 0
for k, cov in g.coverage_files {
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};')
}

// insert for options forward
if g.out_options_forward.len > 0 || g.out_results_forward.len > 0 {
tail := g.type_definitions.cut_to(g.options_pos_forward)
Expand Down Expand Up @@ -540,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:')
Expand Down Expand Up @@ -654,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)
Expand Down Expand Up @@ -724,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()
Expand Down Expand Up @@ -2049,7 +2080,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}"'
Expand All @@ -2060,6 +2091,31 @@ 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] {
g.write_coverage_point(node.pos())
}
}

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

fn (mut g Gen) stmt(node ast.Stmt) {
$if trace_cgen_stmt ? {
ntype := typeof(node).replace('v.ast.', '')
Expand All @@ -2075,19 +2131,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 {
Expand All @@ -2097,11 +2153,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 {
Expand All @@ -2121,7 +2177,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)
Expand Down Expand Up @@ -2161,7 +2217,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)
Expand All @@ -2177,7 +2233,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)
Expand All @@ -2193,7 +2249,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)
Expand All @@ -2206,7 +2262,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 {
Expand Down Expand Up @@ -5266,7 +5322,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 {
Expand Down Expand Up @@ -6410,6 +6466,10 @@ fn (mut g Gen) write_init_function() {
if g.pref.use_coroutines {
g.writeln('\tdelete_photon_work_pool();')
}
if g.pref.is_coverage {
g.write_coverage_stats()
g.writeln('\tvprint_coverage_stats();')
}
g.writeln('}')
if g.pref.printfn_list.len > 0 && '_vcleanup' in g.pref.printfn_list {
println(g.out.after(fn_vcleanup_start_pos))
Expand Down
67 changes: 67 additions & 0 deletions vlib/v/gen/c/coverage.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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

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('\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('\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('\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('\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}')
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('}')
}
2 changes: 1 addition & 1 deletion vlib/v/gen/c/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
12 changes: 6 additions & 6 deletions vlib/v/gen/c/match.v
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,15 @@ 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 {
if j > 0 || sumtype_index > 0 {
if use_ternary {
g.write(' : ')
} else {
g.write_v_source_line_info(branch.pos)
g.write_v_source_line_info(branch)
g.write('else ')
}
}
Expand All @@ -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)
Expand Down Expand Up @@ -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 {')
}
}
Expand All @@ -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 ')
}
}
Expand All @@ -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 {
Expand Down