simplecov 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +148 -5
  3. data/exe/simplecov +0 -3
  4. data/lib/minitest/simplecov_plugin.rb +12 -9
  5. data/lib/simplecov/atomic_file.rb +16 -19
  6. data/lib/simplecov/autostart.rb +5 -7
  7. data/lib/simplecov/baseline/parser.rb +58 -0
  8. data/lib/simplecov/baseline.rb +170 -0
  9. data/lib/simplecov/cli/affected/changed_files.rb +57 -0
  10. data/lib/simplecov/cli/affected/selection.rb +117 -0
  11. data/lib/simplecov/cli/affected.rb +148 -0
  12. data/lib/simplecov/cli/annotations.rb +166 -0
  13. data/lib/simplecov/cli/badge/svg.rb +70 -0
  14. data/lib/simplecov/cli/badge.rb +62 -0
  15. data/lib/simplecov/cli/clean.rb +23 -24
  16. data/lib/simplecov/cli/command_helpers.rb +55 -19
  17. data/lib/simplecov/cli/completions/scripts.rb +114 -0
  18. data/lib/simplecov/cli/completions.rb +83 -0
  19. data/lib/simplecov/cli/coverage.rb +25 -28
  20. data/lib/simplecov/cli/coverage_file.rb +58 -14
  21. data/lib/simplecov/cli/dead_code/output.rb +100 -0
  22. data/lib/simplecov/cli/dead_code.rb +139 -0
  23. data/lib/simplecov/cli/diff/output.rb +45 -0
  24. data/lib/simplecov/cli/diff.rb +35 -67
  25. data/lib/simplecov/cli/dotfile.rb +83 -38
  26. data/lib/simplecov/cli/git.rb +41 -0
  27. data/lib/simplecov/cli/history/output.rb +135 -0
  28. data/lib/simplecov/cli/history.rb +67 -0
  29. data/lib/simplecov/cli/merge.rb +34 -39
  30. data/lib/simplecov/cli/open.rb +12 -15
  31. data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
  32. data/lib/simplecov/cli/patch/output.rb +151 -0
  33. data/lib/simplecov/cli/patch.rb +175 -0
  34. data/lib/simplecov/cli/ratchet/output.rb +44 -0
  35. data/lib/simplecov/cli/ratchet.rb +83 -0
  36. data/lib/simplecov/cli/report.rb +18 -24
  37. data/lib/simplecov/cli/run.rb +8 -9
  38. data/lib/simplecov/cli/serve/report_preparer.rb +7 -6
  39. data/lib/simplecov/cli/serve/static_file_handler.rb +62 -56
  40. data/lib/simplecov/cli/serve.rb +27 -24
  41. data/lib/simplecov/cli/show/annotator.rb +87 -0
  42. data/lib/simplecov/cli/show/sweep.rb +36 -0
  43. data/lib/simplecov/cli/show.rb +133 -0
  44. data/lib/simplecov/cli/status/facts.rb +87 -0
  45. data/lib/simplecov/cli/status.rb +93 -0
  46. data/lib/simplecov/cli/tests/redundancy.rb +78 -0
  47. data/lib/simplecov/cli/tests.rb +148 -0
  48. data/lib/simplecov/cli/uncovered/misses.rb +33 -0
  49. data/lib/simplecov/cli/uncovered.rb +58 -34
  50. data/lib/simplecov/cli/usage.rb +153 -0
  51. data/lib/simplecov/cli/watch/live_report.rb +73 -0
  52. data/lib/simplecov/cli/watch/narrator.rb +55 -0
  53. data/lib/simplecov/cli/watch/poller.rb +45 -0
  54. data/lib/simplecov/cli/watch/session.rb +134 -0
  55. data/lib/simplecov/cli/watch/test_plan.rb +43 -0
  56. data/lib/simplecov/cli/watch.rb +85 -0
  57. data/lib/simplecov/cli.rb +43 -92
  58. data/lib/simplecov/color.rb +19 -37
  59. data/lib/simplecov/combine/branches_combiner.rb +7 -31
  60. data/lib/simplecov/combine/coverage_accumulator.rb +49 -142
  61. data/lib/simplecov/combine/identity_interner.rb +10 -13
  62. data/lib/simplecov/combine/interned_counts.rb +7 -9
  63. data/lib/simplecov/combine/lines_combiner.rb +20 -44
  64. data/lib/simplecov/combine/methods_combiner.rb +9 -35
  65. data/lib/simplecov/combine/results_combiner.rb +4 -21
  66. data/lib/simplecov/combine.rb +0 -6
  67. data/lib/simplecov/command_guesser.rb +20 -34
  68. data/lib/simplecov/configuration/baseline.rb +31 -0
  69. data/lib/simplecov/configuration/coverage.rb +91 -79
  70. data/lib/simplecov/configuration/coverage_criteria.rb +43 -54
  71. data/lib/simplecov/configuration/deprecations.rb +21 -0
  72. data/lib/simplecov/configuration/eval_coverage.rb +7 -11
  73. data/lib/simplecov/configuration/filters.rb +40 -73
  74. data/lib/simplecov/configuration/formatting.rb +74 -83
  75. data/lib/simplecov/configuration/groups.rb +19 -13
  76. data/lib/simplecov/configuration/history.rb +44 -0
  77. data/lib/simplecov/configuration/ignored_entries.rb +31 -22
  78. data/lib/simplecov/configuration/merging.rb +94 -62
  79. data/lib/simplecov/configuration/missed_caps.rb +51 -0
  80. data/lib/simplecov/configuration/production.rb +23 -0
  81. data/lib/simplecov/configuration/test_tracking.rb +48 -0
  82. data/lib/simplecov/configuration/thresholds.rb +33 -62
  83. data/lib/simplecov/configuration/view_coverage.rb +37 -0
  84. data/lib/simplecov/configuration.rb +70 -76
  85. data/lib/simplecov/configuration_error.rb +5 -0
  86. data/lib/simplecov/context_map/union.rb +81 -0
  87. data/lib/simplecov/context_map.rb +187 -0
  88. data/lib/simplecov/coverage_json.rb +2 -5
  89. data/lib/simplecov/coverage_statistics.rb +14 -24
  90. data/lib/simplecov/coverage_violations.rb +160 -65
  91. data/lib/simplecov/current_run.rb +61 -0
  92. data/lib/simplecov/defaults.rb +4 -23
  93. data/lib/simplecov/deprecation.rb +45 -27
  94. data/lib/simplecov/directive/erb.rb +44 -0
  95. data/lib/simplecov/directive/haml.rb +33 -0
  96. data/lib/simplecov/directive/indented_template.rb +37 -0
  97. data/lib/simplecov/directive/slim.rb +35 -0
  98. data/lib/simplecov/directive/template.rb +29 -0
  99. data/lib/simplecov/directive.rb +47 -62
  100. data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
  101. data/lib/simplecov/exit_codes/check.rb +17 -11
  102. data/lib/simplecov/exit_codes/exit_code_handling.rb +21 -8
  103. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +7 -10
  104. data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
  105. data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
  106. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +7 -12
  107. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +11 -10
  108. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +7 -9
  109. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +16 -17
  110. data/lib/simplecov/exit_codes.rb +16 -10
  111. data/lib/simplecov/exit_handling.rb +94 -59
  112. data/lib/simplecov/file_list.rb +20 -51
  113. data/lib/simplecov/filter.rb +43 -66
  114. data/lib/simplecov/formatter/base.rb +37 -55
  115. data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
  116. data/lib/simplecov/formatter/coverage_json_writer.rb +29 -31
  117. data/lib/simplecov/formatter/html_formatter/public/index.html +15 -9
  118. data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +74 -17
  119. data/lib/simplecov/formatter/html_formatter.rb +24 -41
  120. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +50 -16
  121. data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
  122. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +43 -29
  123. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +14 -10
  124. data/lib/simplecov/formatter/json_formatter.rb +7 -13
  125. data/lib/simplecov/formatter/multi_formatter.rb +5 -11
  126. data/lib/simplecov/formatter/simple_formatter.rb +2 -8
  127. data/lib/simplecov/formatter.rb +14 -10
  128. data/lib/simplecov/group_names.rb +13 -12
  129. data/lib/simplecov/history.rb +115 -0
  130. data/lib/simplecov/last_run.rb +6 -9
  131. data/lib/simplecov/lines_classifier.rb +31 -16
  132. data/lib/simplecov/load_global_config.rb +7 -7
  133. data/lib/simplecov/parallel_adapters/base.rb +13 -41
  134. data/lib/simplecov/parallel_adapters/generic.rb +7 -19
  135. data/lib/simplecov/parallel_adapters/parallel_tests.rb +21 -42
  136. data/lib/simplecov/parallel_adapters.rb +24 -55
  137. data/lib/simplecov/parallel_coordination.rb +39 -49
  138. data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
  139. data/lib/simplecov/parallel_result_merger.rb +67 -126
  140. data/lib/simplecov/process.rb +14 -32
  141. data/lib/simplecov/production/error.rb +11 -0
  142. data/lib/simplecov/production/file_sink.rb +130 -0
  143. data/lib/simplecov/production.rb +249 -0
  144. data/lib/simplecov/profiles/rails.rb +4 -15
  145. data/lib/simplecov/profiles/root_filter.rb +0 -4
  146. data/lib/simplecov/profiles/strict.rb +11 -21
  147. data/lib/simplecov/profiles.rb +11 -33
  148. data/lib/simplecov/report_deferral.rb +11 -15
  149. data/lib/simplecov/report_stamp.rb +6 -9
  150. data/lib/simplecov/result/filter_config.rb +19 -0
  151. data/lib/simplecov/result/missing_source_files_reporter.rb +8 -9
  152. data/lib/simplecov/result/serialization.rb +43 -0
  153. data/lib/simplecov/result/source_file_builder.rb +7 -12
  154. data/lib/simplecov/result.rb +74 -117
  155. data/lib/simplecov/result_adapter.rb +56 -68
  156. data/lib/simplecov/result_merger/contexts.rb +22 -0
  157. data/lib/simplecov/result_merger/legacy_format_adapter.rb +4 -8
  158. data/lib/simplecov/result_merger/resultset_file.rb +20 -27
  159. data/lib/simplecov/result_merger/resultset_run_identity.rb +23 -25
  160. data/lib/simplecov/result_merger/resultset_store.rb +23 -14
  161. data/lib/simplecov/result_merger/unloaded_files.rb +24 -32
  162. data/lib/simplecov/result_merger.rb +59 -58
  163. data/lib/simplecov/result_processing.rb +92 -122
  164. data/lib/simplecov/run_identity.rb +16 -18
  165. data/lib/simplecov/simulate_coverage.rb +34 -50
  166. data/lib/simplecov/source_file/branch.rb +8 -33
  167. data/lib/simplecov/source_file/branch_builder.rb +28 -47
  168. data/lib/simplecov/source_file/builder_context.rb +13 -13
  169. data/lib/simplecov/source_file/line.rb +13 -33
  170. data/lib/simplecov/source_file/line_builder.rb +9 -15
  171. data/lib/simplecov/source_file/method.rb +3 -7
  172. data/lib/simplecov/source_file/method_builder.rb +6 -15
  173. data/lib/simplecov/source_file/ruby_data_parser.rb +59 -66
  174. data/lib/simplecov/source_file/skip_chunks.rb +29 -29
  175. data/lib/simplecov/source_file/source_loader.rb +45 -35
  176. data/lib/simplecov/source_file/statistics.rb +20 -9
  177. data/lib/simplecov/source_file.rb +21 -51
  178. data/lib/simplecov/static_coverage_extractor/condition_folding.rb +55 -139
  179. data/lib/simplecov/static_coverage_extractor/location_conventions.rb +76 -121
  180. data/lib/simplecov/static_coverage_extractor/method_collector.rb +9 -26
  181. data/lib/simplecov/static_coverage_extractor/prism_compat.rb +15 -24
  182. data/lib/simplecov/static_coverage_extractor/value_position.rb +25 -38
  183. data/lib/simplecov/static_coverage_extractor/visitor.rb +45 -65
  184. data/lib/simplecov/static_coverage_extractor.rb +43 -75
  185. data/lib/simplecov/test_tracker/accessors.rb +34 -0
  186. data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
  187. data/lib/simplecov/test_tracker/delta.rb +69 -0
  188. data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
  189. data/lib/simplecov/test_tracker.rb +162 -0
  190. data/lib/simplecov/unloaded_file_injector.rb +18 -31
  191. data/lib/simplecov/useless_results_remover.rb +7 -6
  192. data/lib/simplecov/version.rb +1 -1
  193. data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
  194. data/lib/simplecov/view_coverage.rb +55 -0
  195. data/lib/simplecov-html.rb +1 -0
  196. data/lib/simplecov.rb +57 -113
  197. data/lib/simplecov_json_formatter.rb +1 -0
  198. data/man/simplecov.1 +354 -0
  199. data/sig/simplecov.rbs +528 -595
  200. metadata +79 -6
@@ -2,15 +2,9 @@
2
2
 
3
3
  module SimpleCov
4
4
  class SourceFile
5
- # Computes the set of line ranges that should be excluded from a
6
- # SourceFile's coverage for each criterion. Two sources contribute:
7
- #
8
- # * The deprecated `# :nocov:` block toggle (lines wrapped between
9
- # even-numbered pairs of nocov markers are excluded from line and
10
- # branch coverage).
11
- # * `# simplecov:disable` / `# simplecov:enable` block directives,
12
- # which can be scoped per-criterion (`# simplecov:disable branch`,
13
- # etc.) — see `SimpleCov::Directive`.
5
+ # The line ranges excluded from a SourceFile's coverage per criterion. Two
6
+ # sources contribute: the deprecated nocov block toggle, and the
7
+ # `SimpleCov::Directive` block directives, which can be scoped per criterion.
14
8
  class SkipChunks
15
9
  @nocov_warned = Set.new
16
10
  class << self
@@ -22,43 +16,49 @@ module SimpleCov
22
16
  @src = src
23
17
  end
24
18
 
25
- # Every criterion honors the deprecated all-criteria `# :nocov:`
26
- # chunks plus its own per-criterion directive ranges. Methods
27
- # included: they carry a source range, and nocov has always meant
28
- # "exclude everything here".
29
- def for(criterion)
19
+ # Every criterion honors the deprecated all-criteria nocov chunks plus its
20
+ # own per-criterion directive ranges, methods included: they carry a
21
+ # source range, and nocov has always meant "exclude everything here". The
22
+ # ranges are 1-based line numbers, so consumers subtract 1 to index into
23
+ # the zero-based lines array.
24
+ def chunks_for(criterion)
30
25
  nocov_chunks + directive_chunks.fetch(criterion)
31
26
  end
32
27
 
33
- # Ranges of 1-based line numbers (see `with_index(1)` below);
34
- # consumers subtract 1 to index into the zero-based lines array.
35
28
  def nocov_chunks
36
29
  @nocov_chunks ||= build_nocov_chunks
37
30
  end
38
31
 
39
32
  def directive_chunks
40
- @directive_chunks ||= Directive.disabled_ranges(@src)
33
+ @directive_chunks ||= Directive.disabled_ranges(ruby_lines)
41
34
  end
42
35
 
43
- private
36
+ private
44
37
 
38
+ # The Ruby the file holds, which for a template means the Ruby inside its
39
+ # tags or script lines with its own comments turned into Ruby comments.
40
+ # Both kinds of marker are found there, so a toggle in a template's
41
+ # comment syntax gets the same treatment, and the same deprecation
42
+ # warning, as one in a Ruby file.
43
+ def ruby_lines
44
+ @ruby_lines ||= Directive::Template.ruby_lines(@filename, @src)
45
+ end
46
+
47
+ # An uneven number of nocovs is assumed to run to the end of the file. It
48
+ # cannot be handled inside the each_slice because JRuby behaves differently
49
+ # there (jruby/jruby#6048).
45
50
  def build_nocov_chunks
46
- no_cov_lines = @src.map.with_index(1).select { |line_src, _index| LinesClassifier.no_cov_line?(line_src) }
51
+ no_cov_lines = ruby_lines.filter_map.with_index(1) do |line_src, index|
52
+ index if LinesClassifier.no_cov_line?(line_src)
53
+ end
47
54
 
48
- warn_nocov_deprecation(no_cov_lines.first.last) if no_cov_lines.any?
55
+ warn_nocov_deprecation(no_cov_lines.first) if no_cov_lines.any?
49
56
 
50
- # If we have an uneven number of nocovs we assume they go to the
51
- # end of the file, the source doesn't really matter. Can't deal
52
- # with this within the each_slice due to differing behavior in
53
- # JRuby: jruby/jruby#6048
54
- no_cov_lines << ["", @src.size] if no_cov_lines.size.odd?
57
+ no_cov_lines << @src.size if no_cov_lines.size.odd?
55
58
 
56
- no_cov_lines.each_slice(2).map do |(_line_src_start, index_start), (_line_src_end, index_end)|
57
- index_start..index_end
58
- end
59
+ no_cov_lines.each_slice(2).map { |chunk_start, chunk_end| chunk_start..chunk_end }
59
60
  end
60
61
 
61
- # Emit a one-time-per-file deprecation warning pointing the user
62
62
  # at the `# simplecov:disable` / `# simplecov:enable` replacement.
63
63
  def warn_nocov_deprecation(first_line_number)
64
64
  return unless self.class.nocov_warned.add?(@filename)
@@ -2,36 +2,35 @@
2
2
 
3
3
  module SimpleCov
4
4
  class SourceFile
5
- # Reads a source file into an array of lines, honoring the source's
6
- # shebang and `coding:` magic comment when present. Always
7
- # transcodes to UTF-8 with invalid/undef bytes replaced both for
8
- # JRuby compatibility and to keep encoding shenanigans in one place
9
- # (see #866).
5
+ # Reads a source file into an array of lines, honoring the source's shebang
6
+ # and `coding:` magic comment when present. Always transcodes to UTF-8 with
7
+ # invalid and undefined bytes replaced, both for JRuby compatibility and to
8
+ # keep encoding shenanigans in one place (#866).
10
9
  module SourceLoader
11
10
  SHEBANG_REGEX = /\A#!/
12
11
  RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX = /\A#\s*(?:-\*-)?\s*(?:en)?coding:\s*(\S+)\s*(?:-\*-)?\s*\z/
13
12
 
14
- module_function
13
+ extend self
15
14
 
16
15
  def call(filename)
17
16
  lines = [] #: Array[String]
18
- # The default encoding is UTF-8
19
- File.open(filename, "rb:UTF-8") do |file|
20
- current_line = scrub_invalid(file.gets)
21
-
22
- if current_line && shebang?(current_line)
23
- lines << current_line
24
- current_line = scrub_invalid(file.gets)
25
- end
26
-
27
- read_lines(file, lines, current_line)
17
+ open_file(filename, "rb:UTF-8") do |file|
18
+ read_lines(file, lines, scrub_invalid(file.gets))
28
19
  end
29
20
  end
30
21
 
31
- # A line read as UTF-8 can still carry invalid bytes (a Latin-1
32
- # source file without a magic comment, say). Replace them before
33
- # any regex sees the line: the shebang and magic-comment checks
34
- # would otherwise raise ArgumentError and take the report down.
22
+ # mutant:disable Ruby 4.0 removed Kernel#open's leading-pipe
23
+ # command mode, so no test can tell `File.open` from `open` here
24
+ # any more. The explicit receiver is kept: on older rubies it is
25
+ # what refuses to run a filename as a command.
26
+ def open_file(name, mode, &)
27
+ File.open(name, mode, &)
28
+ end
29
+
30
+ # A line read as UTF-8 can still carry invalid bytes, from a Latin-1 source
31
+ # file without a magic comment, say. They are replaced before any regex sees
32
+ # the line: the shebang and magic-comment checks would otherwise raise
33
+ # ArgumentError and take the report down.
35
34
  def scrub_invalid(line)
36
35
  return line if line.nil? || line.valid_encoding?
37
36
 
@@ -45,11 +44,17 @@ module SimpleCov
45
44
  def read_lines(file, lines, current_line)
46
45
  return lines unless current_line
47
46
 
47
+ if shebang?(current_line)
48
+ lines << current_line
49
+ current_line = scrub_invalid(file.gets)
50
+ return lines unless current_line
51
+ end
52
+
48
53
  set_encoding_based_on_magic_comment(file, current_line)
49
54
  lines.concat([current_line], ensure_remove_undefs(file.readlines))
50
55
  end
51
56
 
52
- # Encoding magic comment must be placed at first line except for
57
+ # An encoding magic comment must be placed on the first line, except after a
53
58
  # shebang.
54
59
  def set_encoding_based_on_magic_comment(file, line)
55
60
  if (match = RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX.match(line))
@@ -57,21 +62,26 @@ module SimpleCov
57
62
  end
58
63
  end
59
64
 
60
- # Guarantee every line leaves the loader as valid UTF-8, replacing
61
- # what can't be represented: transcode non-UTF-8 lines (setting
62
- # invalid/undef options on `file.set_encoding` doesn't work
63
- # properly, and this also works around a JRuby incompatibility)
64
- # and scrub UTF-8-tagged lines that carry invalid bytes, which
65
- # would otherwise raise from every regex the classifier runs.
65
+ # Setting invalid/undef options on `file.set_encoding` doesn't work
66
+ # properly, and transcoding here also works around a JRuby incompatibility.
66
67
  def ensure_remove_undefs(file_lines)
67
- file_lines.each do |line|
68
- # simplecov:disable — defensive: only fires for non-UTF-8 source files
69
- if line.encoding == Encoding::UTF_8
70
- line.scrub! unless line.valid_encoding?
71
- else
72
- line.encode!("UTF-8", invalid: :replace, undef: :replace)
73
- end
74
- # simplecov:enable
68
+ file_lines.each { |line| make_utf8(line) }
69
+ end
70
+
71
+ # mutant:disable — on CRuby the two arms answer alike, because
72
+ # converting to the encoding a string already carries scrubs it
73
+ # when `invalid: :replace` is given. The arms are kept apart for
74
+ # the engines where that conversion is the documented no-op it
75
+ # reads as, which is the same reason the transcode is here rather
76
+ # than on `file.set_encoding`.
77
+ # Encodings are singletons, so identity is the whole of the question. A line
78
+ # already tagged UTF-8 has nothing to transcode, only invalid bytes to
79
+ # replace, and scrubbing a line that has none of those leaves it alone.
80
+ def make_utf8(line)
81
+ if line.encoding.equal?(Encoding::UTF_8)
82
+ line.scrub!
83
+ else
84
+ line.encode!("UTF-8", invalid: :replace, undef: :replace)
75
85
  end
76
86
  end
77
87
  end
@@ -2,10 +2,9 @@
2
2
 
3
3
  module SimpleCov
4
4
  class SourceFile
5
- # Builds the `CoverageStatistics` triple (`:line`, `:branch`, `:method`)
6
- # for a SourceFile, regardless of which criteria were actually enabled
7
- # during the run disabled or empty criteria collapse to 0/0/0 so
8
- # downstream consumers don't have to special-case enable-state.
5
+ # Builds the `CoverageStatistics` triple for a SourceFile regardless of which
6
+ # criteria were enabled during the run: disabled or empty criteria collapse to
7
+ # 0/0/0 so downstream consumers don't have to special-case enable-state.
9
8
  class Statistics
10
9
  def initialize(source_file)
11
10
  @source_file = source_file
@@ -19,7 +18,7 @@ module SimpleCov
19
18
  }
20
19
  end
21
20
 
22
- private
21
+ private
23
22
 
24
23
  def line_statistics
25
24
  {
@@ -31,13 +30,11 @@ module SimpleCov
31
30
  }
32
31
  end
33
32
 
34
- # Files added via track_files but never loaded have no branch/method
35
- # data. Report 0% instead of the empty-set default of 100% (see #902).
36
33
  def branch_statistics
37
34
  sf = @source_file
38
35
  covered = sf.covered_branches
39
36
  missed = sf.missed_branches
40
- percent = 0.0 if sf.not_loaded? && covered.empty? && missed.empty?
37
+ percent = 0.0 if covered.empty? && unaccounted?(sf, "branches")
41
38
 
42
39
  {branch: coverage_statistics(covered, missed, percent: percent)}
43
40
  end
@@ -46,11 +43,25 @@ module SimpleCov
46
43
  sf = @source_file
47
44
  covered = sf.covered_methods
48
45
  missed = sf.missed_methods
49
- percent = 0.0 if sf.not_loaded? && covered.empty? && missed.empty?
46
+ percent = 0.0 if covered.empty? && unaccounted?(sf, "methods")
50
47
 
51
48
  {method: coverage_statistics(covered, missed, percent: percent)}
52
49
  end
53
50
 
51
+ # A file tracked but never loaded once carried no branch or method tuples
52
+ # at all, so an empty set meant "nobody knows", and answering the
53
+ # empty-set default of 100% overstated it (#902). Simulation now
54
+ # synthesizes those tuples statically, so a file carrying a table at all,
55
+ # empty or not, has been accounted for: it has no branches, or a
56
+ # directive skipped the ones it has, and it is as covered as a loaded
57
+ # file with none. Only a file carrying no table for the criterion is
58
+ # still unaccounted for. A file with missed entries and none covered
59
+ # already computes to 0% either way, so only one that really has covered
60
+ # entries keeps its computed percentage.
61
+ def unaccounted?(source_file, table)
62
+ source_file.not_loaded? && source_file.coverage_data[table].nil?
63
+ end
64
+
54
65
  def coverage_statistics(covered, missed, omitted: 0, percent: nil)
55
66
  CoverageStatistics.new(
56
67
  total_strength: covered.sum { |item| item.coverage.to_i },
@@ -12,17 +12,10 @@ require_relative "source_file/method_builder"
12
12
  require_relative "source_file/statistics"
13
13
 
14
14
  module SimpleCov
15
- #
16
- # Representation of a source file including it's coverage data, source code,
17
- # source lines and featuring helpers to interpret that data.
18
- #
19
15
  class SourceFile
20
16
  include BuilderContext
21
17
 
22
- # The full path to this source file (e.g. /User/colszowka/projects/simplecov/lib/simplecov/source_file.rb)
23
- attr_reader :filename
24
- # The array of coverage data received from the Coverage.result
25
- attr_reader :coverage_data
18
+ attr_reader :filename, :coverage_data
26
19
 
27
20
  def initialize(filename, coverage_data, loaded: true)
28
21
  @filename = filename
@@ -30,72 +23,57 @@ module SimpleCov
30
23
  @loaded = loaded
31
24
  end
32
25
 
33
- # The path to this source file relative to the projects directory
34
26
  def project_filename
35
27
  @filename.delete_prefix(SimpleCov.root).sub(%r{\A[/\\]}, "")
36
28
  end
37
29
 
38
- # The source code for this file. Aliased as :source.
39
- # Intentionally read lazily to suppress reading unused source code.
30
+ # Read lazily to suppress reading unused source code.
40
31
  def src
41
32
  @src ||= SourceLoader.call(filename)
42
33
  end
43
- alias source src
34
+ alias_method :source, :src
44
35
 
45
- # Returns a hash keyed by every supported coverage criterion. Each
46
- # value is a CoverageStatistics, even for criteria that weren't
47
- # enabled during the run — those collapse to 0/0/0. Consumers
48
- # (FileList, formatters) decide which keys to surface based on
49
- # `SimpleCov.coverage_criterion_enabled?`.
50
36
  # The per-criterion coverage statistics for this file. With no argument
51
- # returns the `{line:, branch:, method:}` Hash; pass a criterion symbol
52
- # (`:line` / `:branch` / `:method`) to get that one CoverageStatistics.
37
+ # answers the `{line:, branch:, method:}` Hash, and with a criterion symbol
38
+ # that one CoverageStatistics. Every criterion is present even when it
39
+ # wasn't enabled during the run, collapsed to 0/0/0; `FileList` is the
40
+ # layer that filters to the enabled set.
53
41
  def coverage_statistics(criterion = nil)
54
42
  stats = (@coverage_statistics ||= Statistics.new(self).call)
55
43
  criterion ? stats[criterion] : stats
56
44
  end
57
45
 
58
- # Returns all source lines for this file as instances of SimpleCov::SourceFile::Line,
59
- # and thus including coverage data. Aliased as :source_lines
60
46
  def lines
61
47
  @lines ||= LineBuilder.new(self).call
62
48
  end
63
- alias source_lines lines
49
+ alias_method :source_lines, :lines
64
50
 
65
- # Returns all covered lines as SimpleCov::SourceFile::Line
66
51
  def covered_lines
67
52
  @covered_lines ||= lines.select(&:covered?)
68
53
  end
69
54
 
70
- # Returns all lines that should have been, but were not covered
71
- # as instances of SimpleCov::SourceFile::Line
72
55
  def missed_lines
73
56
  @missed_lines ||= lines.select(&:missed?)
74
57
  end
75
58
 
76
- # Returns all lines that are not relevant for coverage as
77
- # SimpleCov::SourceFile::Line instances
78
59
  def never_lines
79
60
  @never_lines ||= lines.select(&:never?)
80
61
  end
81
62
 
82
- # Returns all lines that were skipped as SimpleCov::SourceFile::Line instances
83
63
  def skipped_lines
84
64
  @skipped_lines ||= lines.select(&:skipped?)
85
65
  end
86
66
 
87
- # Returns the number of relevant lines (covered + missed)
88
67
  def lines_of_code
89
68
  coverage_statistics[:line]&.total || 0
90
69
  end
91
70
 
92
- # Access SimpleCov::SourceFile::Line source lines by line number
71
+ # Answers nil for a number this file has no line for.
93
72
  def line(number)
94
- lines[number - 1]
73
+ lines.at(number - 1)
95
74
  end
96
75
 
97
- # The coverage for this file in percent, for the given criterion (line by
98
- # default). Returns nil if the criterion was not measured.
76
+ # Nil if the criterion was not measured.
99
77
  def covered_percent(criterion = :line)
100
78
  coverage_statistics(criterion)&.percent
101
79
  end
@@ -104,15 +82,16 @@ module SimpleCov
104
82
  coverage_statistics(criterion)&.strength
105
83
  end
106
84
 
85
+ # Every line the file has is irrelevant, a file with no lines at all
86
+ # included.
107
87
  def no_lines?
108
- lines.empty? || (lines.length == never_lines.size)
88
+ lines.all?(&:never?)
109
89
  end
110
90
 
111
91
  def relevant_lines
112
92
  lines.size - never_lines.size - skipped_lines.size
113
93
  end
114
94
 
115
- # Return all the branches inside current source file
116
95
  def branches
117
96
  @branches ||= BranchBuilder.new(self).call
118
97
  end
@@ -121,33 +100,28 @@ module SimpleCov
121
100
  total_branches.empty?
122
101
  end
123
102
 
124
- # DEPRECATED: use `covered_percent(:branch)`.
125
103
  def branches_coverage_percent
126
- SimpleCov::Deprecation.warn("`SimpleCov::SourceFile#branches_coverage_percent` is deprecated. " \
127
- "Use `covered_percent(:branch)`.")
104
+ Deprecation.warn("`SimpleCov::SourceFile#branches_coverage_percent` is deprecated. " \
105
+ "Use `covered_percent(:branch)`.")
128
106
  covered_percent(:branch)
129
107
  end
130
108
 
131
- # Return the relevant branches to source file
132
109
  def total_branches
133
110
  @total_branches ||= covered_branches + missed_branches
134
111
  end
135
112
 
136
- # Return hash with key of line number and branch coverage count as value
137
113
  def branches_report
138
114
  @branches_report ||=
139
115
  branches.reject(&:skipped?).group_by(&:report_line).transform_values { |bs| bs.map(&:report) }
140
116
  end
141
117
 
142
- # Select the covered branches. We use a tree schema here because
143
- # some conditions like `case` may have an additional `else` that
144
- # isn't declared in code but is given by default by the coverage
145
- # report.
118
+ # A tree schema, because some conditions like `case` may have an additional
119
+ # `else` that isn't declared in code but is given by default by the
120
+ # coverage report.
146
121
  def covered_branches
147
122
  @covered_branches ||= branches.select(&:covered?)
148
123
  end
149
124
 
150
- # Select the missed branches with coverage equal to zero
151
125
  def missed_branches
152
126
  @missed_branches ||= branches.select(&:missed?)
153
127
  end
@@ -156,12 +130,10 @@ module SimpleCov
156
130
  branches_report.fetch(line_number, [])
157
131
  end
158
132
 
159
- # Check if any branches missing on given line number
160
133
  def line_with_missed_branch?(line_number)
161
134
  branches_for_line(line_number).any? { |_type, count| count.zero? }
162
135
  end
163
136
 
164
- # Return all methods detected in this source file
165
137
  def methods
166
138
  @methods ||= MethodBuilder.new(self).call
167
139
  end
@@ -174,14 +146,12 @@ module SimpleCov
174
146
  @missed_methods ||= methods.select(&:missed?)
175
147
  end
176
148
 
177
- # DEPRECATED: use `covered_percent(:method)`.
178
149
  def methods_coverage_percent
179
- SimpleCov::Deprecation.warn("`SimpleCov::SourceFile#methods_coverage_percent` is deprecated. " \
180
- "Use `covered_percent(:method)`.")
150
+ Deprecation.warn("`SimpleCov::SourceFile#methods_coverage_percent` is deprecated. " \
151
+ "Use `covered_percent(:method)`.")
181
152
  covered_percent(:method)
182
153
  end
183
154
 
184
- # Whether this file was added via track_files but never loaded/required.
185
155
  def not_loaded?
186
156
  !@loaded
187
157
  end