TER1 (Statement): 100.00%
TER2 (Branch): 83.33%
TER3 (LCSAJ): 100.0% (3/3)
Approximate LCSAJ segments: 13
● Covered — this LCSAJ path was executed during testing.
● Not covered — this LCSAJ path was never executed. These are the paths to focus on.
Multiple dots on a line indicate that multiple control-flow paths begin at that line. Hovering over any dot shows:
start → end → jump
Uncovered paths show [NOT COVERED] in the tooltip.
1: package App::Test::Generator::LCSAJ::Coverage; 2: 3: use strict; 4: use warnings; 5: 6: use autodie qw(:all); 7: use Carp qw(croak); 8: use JSON::MaybeXS; 9: 10: our $VERSION = '0.36'; 11: 12: =head1 NAME 13: 14: App::Test::Generator::LCSAJ::Coverage - Merge LCSAJ path data with runtime hits 15: 16: =head1 VERSION 17: 18: Version 0.36 19: 20: =head1 DESCRIPTION 21: 22: Merges static LCSAJ path data produced by L<App::Test::Generator::LCSAJ> 23: with runtime line hit data to determine which LCSAJ paths were covered 24: during test execution. The merged result is written as JSON for 25: consumption by C<bin/test-generator-index>. 26: 27: =head2 merge 28: 29: Merge a static LCSAJ path JSON file with a runtime line hits JSON file 30: and write the annotated result to an output file. 31: 32: App::Test::Generator::LCSAJ::Coverage::merge( 33: 'cover_db/lcsaj/MyModule.pm.lcsaj.json', 34: 'cover_db/lcsaj/MyModule.pm.hits.json', 35: 'cover_db/lcsaj/MyModule.pm.covered.json', 36: ); 37: 38: =head3 Arguments 39: 40: =over 4 41: 42: =item * C<$lcsaj_file> 43: 44: Path to the C<.lcsaj.json> file produced by 45: L<App::Test::Generator::LCSAJ>. Required. 46: 47: =item * C<$hits_file> 48: 49: Path to a JSON file mapping line numbers (as strings) to hit counts, 50: as produced by L<Devel::App::Test::Generator::LCSAJ::Runtime>. 51: Required. 52: 53: =item * C<$out_file> 54: 55: Path to write the merged output JSON file. Required. 56: 57: =back 58: 59: =head3 Returns 60: 61: Nothing. Writes the annotated LCSAJ path data to C<$out_file>, with 62: a C<covered> key added to each path record. 63: 64: =head3 Side effects 65: 66: Writes to C<$out_file>. Croaks if any file cannot be read or written. 67: 68: =head3 Notes 69: 70: A path is considered covered if any line in the range C<start..end> 71: was executed at least once. This is a conservative approximation â 72: it does not verify that the jump target was actually reached. As a 73: result, coverage may be slightly overstated for paths where only the 74: beginning of the sequence was executed. 75: 76: =head3 API specification 77: 78: =head4 input 79: 80: { 81: lcsaj_file => { type => SCALAR }, 82: hits_file => { type => SCALAR }, 83: out_file => { type => SCALAR }, 84: } 85: 86: =head4 output 87: 88: { type => UNDEF } 89: 90: =cut 91: 92: sub merge { ●93 → 108 → 122●93 → 108 → 0 93: my ($lcsaj_file, $hits_file, $out_file) = @_; 94: 95: # Validate all three file arguments before attempting any IO 96: croak 'lcsaj_file required' unless defined $lcsaj_file; 97: croak 'hits_file required' unless defined $hits_file; 98: croak 'out_file required' unless defined $out_file; 99: 100: # Load static LCSAJ path data extracted by App::Test::Generator::LCSAJ 101: my $paths = decode_json(_slurp($lcsaj_file)); 102: 103: # Load runtime line hit counts from Devel::App::Test::Generator::LCSAJ::Runtime 104: my $hits = decode_json(_slurp($hits_file)); 105: 106: # Annotate each path with a covered flag â a path is considered 107: # covered if any line in the start..end range was executed 108: for my $path (@{$paths}) { 109: my $covered = 0; 110: 111: for my $line ($path->{start} .. $path->{end}) { 112: if($hits->{$line}) {Mutants (Total: 1, Killed: 1, Survived: 0)
113: $covered = 1; 114: last; 115: } 116: } 117: 118: $path->{covered} = $covered; 119: } 120: 121: # Write the annotated paths to the output file ●122 → 124 → 0 122: open my $fh, '>', $out_file or croak "Cannot write coverage output to $out_file: $!"; 123: print $fh encode_json($paths); 124: close $fh; 125: } 126: 127: # -------------------------------------------------- 128: # _slurp 129: # 130: # Purpose: Read the entire contents of a file and 131: # return it as a string. 132: # 133: # Entry: $file - path to the file to read. 134: # 135: # Exit: Returns the file contents as a scalar 136: # string. Croaks if the file cannot be 137: # opened. 138: # 139: # Side effects: None beyond opening and closing the 140: # file handle. 141: # 142: # Notes: Uses three-argument open for safety with 143: # filenames containing special characters. 144: # Sets $/ to undef to slurp the whole file 145: # in one read, localised to avoid affecting 146: # other code. 147: # -------------------------------------------------- 148: sub _slurp { 149: my $file = $_[0]; 150: 151: open my $fh, '<', $file or croak "Cannot read $file: $!"; 152: 153: # Localise $/ to undef to slurp entire file in one read 154: local $/; 155: return <$fh>; 156: } 157: 158: 1;