v/vlib/v/gen/c/coverage.v
2025-09-04 22:00:02 +08:00

129 lines
5.1 KiB
V

// 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
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
}
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 } {
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
}
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)
}
}
fn (mut g Gen) write_coverage_stats() {
build_options := g.pref.build_options.join(' ')
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 {}
}
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('\tchar cov_filename[2048];')
covdir := cesc(coverage_dir)
g.cov_declarations.writeln('\tchar *cov_dir = "${covdir}";')
for _, mut cov in g.coverage_files {
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(filepath)
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 {
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('\tint secs = 0;')
g.cov_declarations.writeln('\tint nsecs = 0;')
g.cov_declarations.writeln('\t#if defined(_WIN32)')
g.cov_declarations.writeln('\tint 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('\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)
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
g.cov_declarations.writeln('\t{')
g.cov_declarations.writeln('\t\tfor (${ast.int_type_name} 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,%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}')
}
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)
}