v2 / vlib / v / gen / c / coverage.v
130 lines · 124 sloc · 5.08 KB · e2e5cf8db56f3562c7baa735061690be936bdf3e
Raw
1// Copyright (c) 2024 Felipe Pena and Delyan Angelov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module c
5
6import os
7import rand
8import v.ast
9import v.token
10import v.util.version
11import hash
12
13// V coverage info
14@[heap]
15struct CoverageInfo {
16mut:
17 idx int // index
18 points []u64 // code point line nr
19 file &ast.File = unsafe { nil }
20 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
21 build_options string
22}
23
24fn (mut g Gen) write_coverage_point(pos token.Pos) {
25 if g.unique_file_path_hash !in g.coverage_files {
26 build_options := g.pref.build_options.join(' ')
27 fhash := hash.sum64_string('${build_options}:${g.unique_file_path_hash}', 32).hex_full()
28 g.coverage_files[g.unique_file_path_hash] = &CoverageInfo{
29 points: []
30 file: g.file
31 fhash: fhash
32 build_options: build_options
33 }
34 }
35 if g.fn_decl != unsafe { nil } {
36 curr_line := u64(pos.line_nr)
37 mut curr_cov := unsafe { g.coverage_files[g.unique_file_path_hash] }
38 if curr_line !in curr_cov.points {
39 curr_cov.points << curr_line
40 }
41 stmt_str := g.go_before_last_stmt().trim_space()
42 g.empty_line = true
43 g.writeln('_v_cov[_v_cov_file_offset_${g.unique_file_path_hash}+${curr_cov.points.len - 1}]++;')
44 g.set_current_pos_as_last_stmt_pos()
45 g.write(stmt_str)
46 }
47}
48
49fn (mut g Gen) write_coverage_stats() {
50 build_options := g.pref.build_options.join(' ')
51 coverage_dir := os.real_path(g.pref.coverage_dir).replace('\\', '/')
52 coverage_meta_folder := '${coverage_dir}/meta'
53 if !os.exists(coverage_meta_folder) {
54 os.mkdir_all(coverage_meta_folder) or {}
55 }
56 counter_ulid :=
57 rand.ulid() // rand.ulid provides a hash+timestamp, so that a collision is extremely unlikely
58 g.cov_declarations.writeln('')
59 g.cov_declarations.writeln('void vprint_coverage_stats() {')
60 g.cov_declarations.writeln('\tchar cov_filename[2048];')
61 covdir := cesc(coverage_dir)
62 g.cov_declarations.writeln('\tchar *cov_dir = "${covdir}";')
63 for _, mut cov in g.coverage_files {
64 metadata_coverage_fpath := '${coverage_meta_folder}/${cov.fhash}.json'
65 filepath := os.real_path(cov.file.path).replace('\\', '/')
66 if os.exists(metadata_coverage_fpath) {
67 continue
68 }
69 mut fmeta := os.create(metadata_coverage_fpath) or { continue }
70 fmeta.writeln('{') or { continue }
71 jfilepath := jesc(filepath)
72 jfhash := jesc(cov.fhash)
73 jversion := jesc(version.full_v_version(true))
74 jboptions := jesc(cov.build_options)
75 fmeta.writeln(' "file": "${jfilepath}", "fhash": "${jfhash}",') or { continue }
76 fmeta.writeln(' "v_version": "${jversion}",') or { continue }
77 fmeta.writeln(' "build_options": "${jboptions}",') or { continue }
78 fmeta.writeln(' "npoints": ${cov.points.len},') or { continue }
79 fmeta.write_string(' "points": [ ') or { continue }
80 for idx, p in cov.points {
81 fmeta.write_string('${p + 1}') or { continue }
82 if idx < cov.points.len - 1 {
83 fmeta.write_string(',') or { continue }
84 }
85 }
86 fmeta.writeln(' ]') or { continue }
87 fmeta.writeln('}') or { continue }
88 fmeta.close()
89 }
90 g.cov_declarations.writeln('\tint secs = 0;')
91 g.cov_declarations.writeln('\tint nsecs = 0;')
92 g.cov_declarations.writeln('\t#if defined(_WIN32)')
93 g.cov_declarations.writeln('\tint ticks_passed = GetTickCount();')
94 g.cov_declarations.writeln('\nsecs = ticks_passed / 1000;')
95 g.cov_declarations.writeln('\nnsecs = (ticks_passed % 1000) * 1000000;')
96 g.cov_declarations.writeln('\t#endif')
97 g.cov_declarations.writeln('\t#if !defined(_WIN32)')
98 g.cov_declarations.writeln('\tstruct timespec ts;')
99 g.cov_declarations.writeln('\tclock_gettime(CLOCK_MONOTONIC, &ts);')
100 g.cov_declarations.writeln('\tsecs = ts.tv_sec;')
101 g.cov_declarations.writeln('\nsecs = ts.tv_nsec;')
102 g.cov_declarations.writeln('\t#endif')
103 g.cov_declarations.writeln('\tsnprintf(cov_filename, sizeof(cov_filename), "%s/vcounters_${counter_ulid}.%07ld.%09ld.csv", cov_dir, secs, nsecs);')
104 g.cov_declarations.writeln('\tFILE *fp = fopen(cov_filename, "wb+");')
105 cprefpath := cesc(os.real_path(g.pref.path))
106 cboptions := cesc(build_options)
107 g.cov_declarations.writeln('\tfprintf(fp, "# path: ${cprefpath}\\n");')
108 g.cov_declarations.writeln('\tfprintf(fp, "# build_options: ${cboptions}\\n");')
109 g.cov_declarations.writeln('\tfprintf(fp, "meta,point,hits\\n");')
110 for k, cov in g.coverage_files {
111 nr_points := cov.points.len
112 g.cov_declarations.writeln('\t{')
113 g.cov_declarations.writeln('\t\tfor (${ast.int_type_name} i = 0; i < ${nr_points}; ++i) {')
114 g.cov_declarations.writeln('\t\t\tif (_v_cov[_v_cov_file_offset_${k}+i]) {')
115 g.cov_declarations.writeln("\t\t\t\tfprintf(fp, \"%s,%d,%ld\\n\", \"${cov.fhash}\", i, _v_cov[_v_cov_file_offset_${k}+i]);")
116 g.cov_declarations.writeln('\t\t\t}')
117 g.cov_declarations.writeln('\t\t}')
118 g.cov_declarations.writeln('\t}')
119 }
120 g.cov_declarations.writeln('\tfclose(fp);')
121 g.cov_declarations.writeln('}')
122}
123
124fn cesc(s string) string {
125 return cescape_nonascii(cestring(s))
126}
127
128fn jesc(s string) string {
129 return escape_quotes(s)
130}
131