@@ -5,12 +5,19 @@ use super::reroot_path;
5
5
use clap:: * ;
6
6
use move_compiler:: compiled_unit:: NamedCompiledModule ;
7
7
use move_coverage:: {
8
- coverage_map:: CoverageMap , format_csv_summary, format_human_summary,
9
- source_coverage:: SourceCoverageBuilder , summary:: summarize_inst_cov,
8
+ coverage_map:: CoverageMap , differential_coverage , format_csv_summary, format_human_summary,
9
+ lcov , source_coverage:: SourceCoverageBuilder , summary:: summarize_inst_cov,
10
10
} ;
11
11
use move_disassembler:: disassembler:: Disassembler ;
12
12
use move_package:: BuildConfig ;
13
- use std:: path:: Path ;
13
+ use move_trace_format:: format:: MoveTraceReader ;
14
+ use std:: {
15
+ fs:: File ,
16
+ path:: { Path , PathBuf } ,
17
+ } ;
18
+
19
+ const COVERAGE_FILE_NAME : & str = "lcov.info" ;
20
+ const DIFFERENTIAL : & str = "diff" ;
14
21
15
22
#[ derive( Parser ) ]
16
23
pub enum CoverageSummaryOptions {
@@ -36,6 +43,19 @@ pub enum CoverageSummaryOptions {
36
43
#[ clap( long = "module" ) ]
37
44
module_name : String ,
38
45
} ,
46
+ #[ clap( name = "lcov" ) ]
47
+ Lcov {
48
+ /// Compute differential coverage for the provided test name. Lines that are hit by this
49
+ /// test only will show as covered, and lines that are hit by both this test and all other
50
+ /// tests will show as "uncovered". Otherwise lines are not annotated with coverage
51
+ /// information.
52
+ #[ clap( long = "differential-test" ) ]
53
+ differential : Option < String > ,
54
+ /// Compute coverage for the provided test name. Only this test will contribute to the
55
+ /// coverage calculation.
56
+ #[ clap( long = "only-test" , conflicts_with = "differential" ) ]
57
+ test : Option < String > ,
58
+ } ,
39
59
}
40
60
41
61
/// Inspect test coverage for this package. A previous test run with the `--coverage` flag must
@@ -50,9 +70,16 @@ pub struct Coverage {
50
70
impl Coverage {
51
71
pub fn execute ( self , path : Option < & Path > , config : BuildConfig ) -> anyhow:: Result < ( ) > {
52
72
let path = reroot_path ( path) ?;
53
- let coverage_map = CoverageMap :: from_binary_file ( path. join ( ".coverage_map.mvcov" ) ) ?;
73
+
74
+ // We treat lcov-format coverage differently because it requires traces to be present, and
75
+ // we don't use the old trace format for it.
76
+ if let CoverageSummaryOptions :: Lcov { differential, test } = self . options {
77
+ return Self :: output_lcov_coverage ( path, config, differential, test) ;
78
+ }
79
+
54
80
let package = config. compile_package ( & path, & mut Vec :: new ( ) ) ?;
55
81
let modules = package. root_modules ( ) . map ( |unit| & unit. unit . module ) ;
82
+ let coverage_map = CoverageMap :: from_binary_file ( path. join ( ".coverage_map.mvcov" ) ) ?;
56
83
match self . options {
57
84
CoverageSummaryOptions :: Source { module_name } => {
58
85
let unit = package. get_module_by_name_from_root ( & module_name) ?;
@@ -95,7 +122,119 @@ impl Coverage {
95
122
disassembler. add_coverage_map ( coverage_map. to_unified_exec_map ( ) ) ;
96
123
println ! ( "{}" , disassembler. disassemble( ) ?) ;
97
124
}
125
+ CoverageSummaryOptions :: Lcov { .. } => {
126
+ unreachable ! ( )
127
+ }
98
128
}
99
129
Ok ( ( ) )
100
130
}
131
+
132
+ pub fn output_lcov_coverage (
133
+ path : PathBuf ,
134
+ mut config : BuildConfig ,
135
+ differential : Option < String > ,
136
+ test : Option < String > ,
137
+ ) -> anyhow:: Result < ( ) > {
138
+ // Make sure we always compile the package in test mode so we get correct source maps.
139
+ config. test_mode = true ;
140
+ let package = config. compile_package ( & path, & mut Vec :: new ( ) ) ?;
141
+ let units: Vec < _ > = package
142
+ . all_modules ( )
143
+ . cloned ( )
144
+ . map ( |unit| ( unit. unit , unit. source_path ) )
145
+ . collect ( ) ;
146
+ let traces = path. join ( "traces" ) ;
147
+ let sanitize_name = |s : & str | s. replace ( "::" , "__" ) ;
148
+ let trace_of_test = |test_name : & str | {
149
+ let trace_substr_name = format ! ( "{}." , sanitize_name( test_name) ) ;
150
+ std:: fs:: read_dir ( & traces) ?
151
+ . filter_map ( |entry| {
152
+ let entry = entry. unwrap ( ) ;
153
+ let path = entry. path ( ) ;
154
+ if path. is_file ( )
155
+ && path
156
+ . file_name ( )
157
+ . unwrap ( )
158
+ . to_str ( )
159
+ . unwrap ( )
160
+ . contains ( & trace_substr_name)
161
+ {
162
+ Some ( path)
163
+ } else {
164
+ None
165
+ }
166
+ } )
167
+ . next ( )
168
+ . ok_or_else ( || {
169
+ anyhow:: anyhow!(
170
+ "No trace found for test {}. Please run with `--coverage` to generate traces." ,
171
+ test_name
172
+ )
173
+ } )
174
+ } ;
175
+
176
+ if let Some ( test_name) = test {
177
+ let mut coverage = lcov:: PackageRecordKeeper :: new ( units, package. file_map . clone ( ) ) ;
178
+ let trace_path = trace_of_test ( & test_name) ?;
179
+ let file = File :: open ( & trace_path) ?;
180
+ let move_trace_reader = MoveTraceReader :: new ( file) ?;
181
+ coverage. calculate_coverage ( move_trace_reader) ;
182
+ std:: fs:: write (
183
+ & path. join ( format ! (
184
+ "{}.{COVERAGE_FILE_NAME}" ,
185
+ sanitize_name( & test_name)
186
+ ) ) ,
187
+ coverage. lcov_record_string ( ) ,
188
+ ) ?;
189
+ } else {
190
+ let mut coverage =
191
+ lcov:: PackageRecordKeeper :: new ( units. clone ( ) , package. file_map . clone ( ) ) ;
192
+ let differential_test_path = differential
193
+ . as_ref ( )
194
+ . map ( |s| trace_of_test ( s) )
195
+ . transpose ( ) ?;
196
+
197
+ for entry in std:: fs:: read_dir ( & traces) ? {
198
+ let entry = entry?;
199
+ let path = entry. path ( ) ;
200
+ if path. is_file ( )
201
+ && differential_test_path
202
+ . as_ref ( )
203
+ . is_none_or ( |diff_path| diff_path != & path)
204
+ {
205
+ let file = File :: open ( & path) ?;
206
+ let move_trace_reader = MoveTraceReader :: new ( file) ?;
207
+ coverage. calculate_coverage ( move_trace_reader) ;
208
+ }
209
+ }
210
+
211
+ if let Some ( differential_test_name) = differential {
212
+ let trace_path =
213
+ differential_test_path. expect ( "Differential test path is already computed" ) ;
214
+ let file = File :: open ( & trace_path) ?;
215
+ let move_trace_reader = MoveTraceReader :: new ( file) ?;
216
+ let mut test_coverage =
217
+ lcov:: PackageRecordKeeper :: new ( units, package. file_map . clone ( ) ) ;
218
+ test_coverage. calculate_coverage ( move_trace_reader) ;
219
+
220
+ let differential_string =
221
+ differential_coverage:: differential_report ( & coverage, & test_coverage) ?;
222
+
223
+ std:: fs:: write (
224
+ & path. join ( format ! (
225
+ "{}.{DIFFERENTIAL}.{COVERAGE_FILE_NAME}" ,
226
+ sanitize_name( & differential_test_name)
227
+ ) ) ,
228
+ differential_string,
229
+ ) ?;
230
+ } else {
231
+ std:: fs:: write (
232
+ & path. join ( COVERAGE_FILE_NAME ) ,
233
+ coverage. lcov_record_string ( ) ,
234
+ ) ?;
235
+ }
236
+ } ;
237
+
238
+ Ok ( ( ) )
239
+ }
101
240
}
0 commit comments