simplecov 0.22.0 → 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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +173 -875
  4. data/exe/simplecov +8 -0
  5. data/lib/minitest/simplecov_plugin.rb +18 -7
  6. data/lib/simplecov/atomic_file.rb +67 -0
  7. data/lib/simplecov/autostart.rb +9 -0
  8. data/lib/simplecov/baseline/parser.rb +58 -0
  9. data/lib/simplecov/baseline.rb +170 -0
  10. data/lib/simplecov/cli/affected/changed_files.rb +57 -0
  11. data/lib/simplecov/cli/affected/selection.rb +117 -0
  12. data/lib/simplecov/cli/affected.rb +148 -0
  13. data/lib/simplecov/cli/annotations.rb +166 -0
  14. data/lib/simplecov/cli/badge/svg.rb +70 -0
  15. data/lib/simplecov/cli/badge.rb +62 -0
  16. data/lib/simplecov/cli/clean.rb +86 -0
  17. data/lib/simplecov/cli/command_helpers.rb +91 -0
  18. data/lib/simplecov/cli/completions/scripts.rb +114 -0
  19. data/lib/simplecov/cli/completions.rb +83 -0
  20. data/lib/simplecov/cli/coverage.rb +83 -0
  21. data/lib/simplecov/cli/coverage_file.rb +109 -0
  22. data/lib/simplecov/cli/dead_code/output.rb +100 -0
  23. data/lib/simplecov/cli/dead_code.rb +139 -0
  24. data/lib/simplecov/cli/diff/output.rb +45 -0
  25. data/lib/simplecov/cli/diff.rb +115 -0
  26. data/lib/simplecov/cli/dotfile.rb +156 -0
  27. data/lib/simplecov/cli/git.rb +41 -0
  28. data/lib/simplecov/cli/history/output.rb +135 -0
  29. data/lib/simplecov/cli/history.rb +67 -0
  30. data/lib/simplecov/cli/merge.rb +115 -0
  31. data/lib/simplecov/cli/open.rb +45 -0
  32. data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
  33. data/lib/simplecov/cli/patch/output.rb +151 -0
  34. data/lib/simplecov/cli/patch.rb +175 -0
  35. data/lib/simplecov/cli/ratchet/output.rb +44 -0
  36. data/lib/simplecov/cli/ratchet.rb +83 -0
  37. data/lib/simplecov/cli/report.rb +91 -0
  38. data/lib/simplecov/cli/run.rb +35 -0
  39. data/lib/simplecov/cli/serve/report_preparer.rb +30 -0
  40. data/lib/simplecov/cli/serve/static_file_handler.rb +128 -0
  41. data/lib/simplecov/cli/serve.rb +88 -0
  42. data/lib/simplecov/cli/show/annotator.rb +87 -0
  43. data/lib/simplecov/cli/show/sweep.rb +36 -0
  44. data/lib/simplecov/cli/show.rb +133 -0
  45. data/lib/simplecov/cli/status/facts.rb +87 -0
  46. data/lib/simplecov/cli/status.rb +93 -0
  47. data/lib/simplecov/cli/tests/redundancy.rb +78 -0
  48. data/lib/simplecov/cli/tests.rb +148 -0
  49. data/lib/simplecov/cli/uncovered/misses.rb +33 -0
  50. data/lib/simplecov/cli/uncovered.rb +127 -0
  51. data/lib/simplecov/cli/usage.rb +153 -0
  52. data/lib/simplecov/cli/watch/live_report.rb +73 -0
  53. data/lib/simplecov/cli/watch/narrator.rb +55 -0
  54. data/lib/simplecov/cli/watch/poller.rb +45 -0
  55. data/lib/simplecov/cli/watch/session.rb +134 -0
  56. data/lib/simplecov/cli/watch/test_plan.rb +43 -0
  57. data/lib/simplecov/cli/watch.rb +85 -0
  58. data/lib/simplecov/cli.rb +114 -0
  59. data/lib/simplecov/color.rb +65 -0
  60. data/lib/simplecov/combine/branches_combiner.rb +44 -21
  61. data/lib/simplecov/combine/coverage_accumulator.rb +175 -0
  62. data/lib/simplecov/combine/identity_interner.rb +27 -0
  63. data/lib/simplecov/combine/interned_counts.rb +28 -0
  64. data/lib/simplecov/combine/lines_combiner.rb +35 -29
  65. data/lib/simplecov/combine/methods_combiner.rb +48 -0
  66. data/lib/simplecov/combine/results_combiner.rb +9 -49
  67. data/lib/simplecov/combine.rb +0 -24
  68. data/lib/simplecov/command_guesser.rb +99 -42
  69. data/lib/simplecov/configuration/baseline.rb +31 -0
  70. data/lib/simplecov/configuration/coverage.rb +184 -0
  71. data/lib/simplecov/configuration/coverage_criteria.rb +143 -0
  72. data/lib/simplecov/configuration/deprecations.rb +21 -0
  73. data/lib/simplecov/configuration/eval_coverage.rb +37 -0
  74. data/lib/simplecov/configuration/filters.rb +131 -0
  75. data/lib/simplecov/configuration/formatting.rb +129 -0
  76. data/lib/simplecov/configuration/groups.rb +48 -0
  77. data/lib/simplecov/configuration/history.rb +44 -0
  78. data/lib/simplecov/configuration/ignored_entries.rb +72 -0
  79. data/lib/simplecov/configuration/merging.rb +197 -0
  80. data/lib/simplecov/configuration/missed_caps.rb +51 -0
  81. data/lib/simplecov/configuration/production.rb +23 -0
  82. data/lib/simplecov/configuration/test_tracking.rb +48 -0
  83. data/lib/simplecov/configuration/thresholds.rb +150 -0
  84. data/lib/simplecov/configuration/view_coverage.rb +37 -0
  85. data/lib/simplecov/configuration.rb +100 -443
  86. data/lib/simplecov/configuration_error.rb +5 -0
  87. data/lib/simplecov/context_map/union.rb +81 -0
  88. data/lib/simplecov/context_map.rb +187 -0
  89. data/lib/simplecov/coverage_json.rb +21 -0
  90. data/lib/simplecov/coverage_statistics.rb +24 -28
  91. data/lib/simplecov/coverage_violations.rb +254 -0
  92. data/lib/simplecov/current_run.rb +61 -0
  93. data/lib/simplecov/defaults.rb +20 -28
  94. data/lib/simplecov/deprecation.rb +63 -0
  95. data/lib/simplecov/directive/erb.rb +44 -0
  96. data/lib/simplecov/directive/haml.rb +33 -0
  97. data/lib/simplecov/directive/indented_template.rb +37 -0
  98. data/lib/simplecov/directive/slim.rb +35 -0
  99. data/lib/simplecov/directive/template.rb +29 -0
  100. data/lib/simplecov/directive.rb +152 -0
  101. data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
  102. data/lib/simplecov/exit_codes/check.rb +39 -0
  103. data/lib/simplecov/exit_codes/exit_code_handling.rb +24 -5
  104. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +17 -67
  105. data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
  106. data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
  107. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +27 -0
  108. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +20 -38
  109. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +28 -0
  110. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +37 -34
  111. data/lib/simplecov/exit_codes.rb +26 -0
  112. data/lib/simplecov/exit_handling.rb +163 -0
  113. data/lib/simplecov/file_list.rb +63 -48
  114. data/lib/simplecov/filter.rb +98 -42
  115. data/lib/simplecov/formatter/base.rb +100 -0
  116. data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
  117. data/lib/simplecov/formatter/coverage_json_writer.rb +95 -0
  118. data/lib/simplecov/formatter/html_formatter/public/index.html +93 -0
  119. data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +153 -0
  120. data/lib/simplecov/formatter/html_formatter.rb +88 -0
  121. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +122 -0
  122. data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
  123. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +132 -0
  124. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +97 -0
  125. data/lib/simplecov/formatter/json_formatter.rb +40 -0
  126. data/lib/simplecov/formatter/multi_formatter.rb +8 -10
  127. data/lib/simplecov/formatter/simple_formatter.rb +12 -15
  128. data/lib/simplecov/formatter.rb +21 -1
  129. data/lib/simplecov/group_names.rb +33 -0
  130. data/lib/simplecov/history.rb +115 -0
  131. data/lib/simplecov/last_run.rb +16 -5
  132. data/lib/simplecov/lines_classifier.rb +67 -19
  133. data/lib/simplecov/load_global_config.rb +12 -4
  134. data/lib/simplecov/parallel_adapters/base.rb +50 -0
  135. data/lib/simplecov/parallel_adapters/generic.rb +33 -0
  136. data/lib/simplecov/parallel_adapters/parallel_tests.rb +72 -0
  137. data/lib/simplecov/parallel_adapters.rb +54 -0
  138. data/lib/simplecov/parallel_coordination.rb +124 -0
  139. data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
  140. data/lib/simplecov/parallel_result_merger.rb +171 -0
  141. data/lib/simplecov/process.rb +34 -12
  142. data/lib/simplecov/production/error.rb +11 -0
  143. data/lib/simplecov/production/file_sink.rb +130 -0
  144. data/lib/simplecov/production.rb +249 -0
  145. data/lib/simplecov/profiles/bundler_filter.rb +1 -1
  146. data/lib/simplecov/profiles/hidden_filter.rb +1 -1
  147. data/lib/simplecov/profiles/rails.rb +13 -10
  148. data/lib/simplecov/profiles/root_filter.rb +2 -5
  149. data/lib/simplecov/profiles/strict.rb +22 -0
  150. data/lib/simplecov/profiles/test_frameworks.rb +1 -4
  151. data/lib/simplecov/profiles.rb +26 -19
  152. data/lib/simplecov/report_deferral.rb +45 -0
  153. data/lib/simplecov/report_stamp.rb +25 -0
  154. data/lib/simplecov/result/filter_config.rb +19 -0
  155. data/lib/simplecov/result/missing_source_files_reporter.rb +48 -0
  156. data/lib/simplecov/result/serialization.rb +43 -0
  157. data/lib/simplecov/result/source_file_builder.rb +46 -0
  158. data/lib/simplecov/result.rb +123 -47
  159. data/lib/simplecov/result_adapter.rb +130 -11
  160. data/lib/simplecov/result_merger/contexts.rb +22 -0
  161. data/lib/simplecov/result_merger/legacy_format_adapter.rb +24 -0
  162. data/lib/simplecov/result_merger/resultset_file.rb +67 -0
  163. data/lib/simplecov/result_merger/resultset_run_identity.rb +65 -0
  164. data/lib/simplecov/result_merger/resultset_store.rb +62 -0
  165. data/lib/simplecov/result_merger/unloaded_files.rb +95 -0
  166. data/lib/simplecov/result_merger.rb +120 -124
  167. data/lib/simplecov/result_processing.rb +188 -0
  168. data/lib/simplecov/run_identity.rb +75 -0
  169. data/lib/simplecov/simulate_coverage.rb +72 -17
  170. data/lib/simplecov/source_file/branch.rb +8 -35
  171. data/lib/simplecov/source_file/branch_builder.rb +95 -0
  172. data/lib/simplecov/source_file/builder_context.rb +28 -0
  173. data/lib/simplecov/source_file/line.rb +17 -32
  174. data/lib/simplecov/source_file/line_builder.rb +37 -0
  175. data/lib/simplecov/source_file/method.rb +54 -0
  176. data/lib/simplecov/source_file/method_builder.rb +51 -0
  177. data/lib/simplecov/source_file/ruby_data_parser.rb +103 -0
  178. data/lib/simplecov/source_file/skip_chunks.rb +72 -0
  179. data/lib/simplecov/source_file/source_loader.rb +89 -0
  180. data/lib/simplecov/source_file/statistics.rb +76 -0
  181. data/lib/simplecov/source_file.rb +64 -260
  182. data/lib/simplecov/static_coverage_extractor/condition_folding.rb +155 -0
  183. data/lib/simplecov/static_coverage_extractor/location_conventions.rb +191 -0
  184. data/lib/simplecov/static_coverage_extractor/method_collector.rb +45 -0
  185. data/lib/simplecov/static_coverage_extractor/prism_compat.rb +46 -0
  186. data/lib/simplecov/static_coverage_extractor/value_position.rb +61 -0
  187. data/lib/simplecov/static_coverage_extractor/visitor.rb +163 -0
  188. data/lib/simplecov/static_coverage_extractor.rb +78 -0
  189. data/lib/simplecov/test_tracker/accessors.rb +34 -0
  190. data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
  191. data/lib/simplecov/test_tracker/delta.rb +69 -0
  192. data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
  193. data/lib/simplecov/test_tracker.rb +162 -0
  194. data/lib/simplecov/unloaded_file_injector.rb +62 -0
  195. data/lib/simplecov/useless_results_remover.rb +17 -7
  196. data/lib/simplecov/version.rb +1 -1
  197. data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
  198. data/lib/simplecov/view_coverage.rb +55 -0
  199. data/lib/simplecov-html.rb +5 -0
  200. data/lib/simplecov.rb +143 -394
  201. data/lib/simplecov_json_formatter.rb +5 -0
  202. data/man/simplecov.1 +354 -0
  203. data/sig/simplecov.rbs +1710 -0
  204. metadata +183 -61
  205. data/CHANGELOG.md +0 -191
  206. data/doc/alternate-formatters.md +0 -71
  207. data/doc/commercial-services.md +0 -25
  208. data/doc/editor-integration.md +0 -18
  209. data/lib/simplecov/combine/files_combiner.rb +0 -24
  210. data/lib/simplecov/default_formatter.rb +0 -20
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
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).
9
+ module SourceLoader
10
+ SHEBANG_REGEX = /\A#!/
11
+ RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX = /\A#\s*(?:-\*-)?\s*(?:en)?coding:\s*(\S+)\s*(?:-\*-)?\s*\z/
12
+
13
+ extend self
14
+
15
+ def call(filename)
16
+ lines = [] #: Array[String]
17
+ open_file(filename, "rb:UTF-8") do |file|
18
+ read_lines(file, lines, scrub_invalid(file.gets))
19
+ end
20
+ end
21
+
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.
34
+ def scrub_invalid(line)
35
+ return line if line.nil? || line.valid_encoding?
36
+
37
+ line.scrub
38
+ end
39
+
40
+ def shebang?(line)
41
+ SHEBANG_REGEX.match?(line)
42
+ end
43
+
44
+ def read_lines(file, lines, current_line)
45
+ return lines unless current_line
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
+
53
+ set_encoding_based_on_magic_comment(file, current_line)
54
+ lines.concat([current_line], ensure_remove_undefs(file.readlines))
55
+ end
56
+
57
+ # An encoding magic comment must be placed on the first line, except after a
58
+ # shebang.
59
+ def set_encoding_based_on_magic_comment(file, line)
60
+ if (match = RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX.match(line))
61
+ file.set_encoding(match[1], "UTF-8")
62
+ end
63
+ end
64
+
65
+ # Setting invalid/undef options on `file.set_encoding` doesn't work
66
+ # properly, and transcoding here also works around a JRuby incompatibility.
67
+ def ensure_remove_undefs(file_lines)
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)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
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.
8
+ class Statistics
9
+ def initialize(source_file)
10
+ @source_file = source_file
11
+ end
12
+
13
+ def call
14
+ {
15
+ **line_statistics,
16
+ **branch_statistics,
17
+ **method_statistics
18
+ }
19
+ end
20
+
21
+ private
22
+
23
+ def line_statistics
24
+ {
25
+ line: coverage_statistics(
26
+ @source_file.covered_lines,
27
+ @source_file.missed_lines,
28
+ omitted: @source_file.never_lines.size
29
+ )
30
+ }
31
+ end
32
+
33
+ def branch_statistics
34
+ sf = @source_file
35
+ covered = sf.covered_branches
36
+ missed = sf.missed_branches
37
+ percent = 0.0 if covered.empty? && unaccounted?(sf, "branches")
38
+
39
+ {branch: coverage_statistics(covered, missed, percent: percent)}
40
+ end
41
+
42
+ def method_statistics
43
+ sf = @source_file
44
+ covered = sf.covered_methods
45
+ missed = sf.missed_methods
46
+ percent = 0.0 if covered.empty? && unaccounted?(sf, "methods")
47
+
48
+ {method: coverage_statistics(covered, missed, percent: percent)}
49
+ end
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
+
65
+ def coverage_statistics(covered, missed, omitted: 0, percent: nil)
66
+ CoverageStatistics.new(
67
+ total_strength: covered.sum { |item| item.coverage.to_i },
68
+ covered: covered.size,
69
+ missed: missed.size,
70
+ omitted: omitted,
71
+ percent: percent
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,102 +1,99 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "directive"
4
+ require_relative "static_coverage_extractor"
5
+ require_relative "source_file/ruby_data_parser"
6
+ require_relative "source_file/source_loader"
7
+ require_relative "source_file/skip_chunks"
8
+ require_relative "source_file/builder_context"
9
+ require_relative "source_file/line_builder"
10
+ require_relative "source_file/branch_builder"
11
+ require_relative "source_file/method_builder"
12
+ require_relative "source_file/statistics"
13
+
3
14
  module SimpleCov
4
- #
5
- # Representation of a source file including it's coverage data, source code,
6
- # source lines and featuring helpers to interpret that data.
7
- #
8
15
  class SourceFile
9
- # The full path to this source file (e.g. /User/colszowka/projects/simplecov/lib/simplecov/source_file.rb)
10
- attr_reader :filename
11
- # The array of coverage data received from the Coverage.result
12
- attr_reader :coverage_data
16
+ include BuilderContext
17
+
18
+ attr_reader :filename, :coverage_data
13
19
 
14
- def initialize(filename, coverage_data)
20
+ def initialize(filename, coverage_data, loaded: true)
15
21
  @filename = filename
16
22
  @coverage_data = coverage_data
23
+ @loaded = loaded
17
24
  end
18
25
 
19
- # The path to this source file relative to the projects directory
20
26
  def project_filename
21
- @filename.delete_prefix(SimpleCov.root)
27
+ @filename.delete_prefix(SimpleCov.root).sub(%r{\A[/\\]}, "")
22
28
  end
23
29
 
24
- # The source code for this file. Aliased as :source
30
+ # Read lazily to suppress reading unused source code.
25
31
  def src
26
- # We intentionally read source code lazily to
27
- # suppress reading unused source code.
28
- @src ||= load_source
32
+ @src ||= SourceLoader.call(filename)
29
33
  end
30
- alias source src
31
-
32
- def coverage_statistics
33
- @coverage_statistics ||=
34
- {
35
- **line_coverage_statistics,
36
- **branch_coverage_statistics
37
- }
34
+ alias_method :source, :src
35
+
36
+ # The per-criterion coverage statistics for this file. With no argument
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.
41
+ def coverage_statistics(criterion = nil)
42
+ stats = (@coverage_statistics ||= Statistics.new(self).call)
43
+ criterion ? stats[criterion] : stats
38
44
  end
39
45
 
40
- # Returns all source lines for this file as instances of SimpleCov::SourceFile::Line,
41
- # and thus including coverage data. Aliased as :source_lines
42
46
  def lines
43
- @lines ||= build_lines
47
+ @lines ||= LineBuilder.new(self).call
44
48
  end
45
- alias source_lines lines
49
+ alias_method :source_lines, :lines
46
50
 
47
- # Returns all covered lines as SimpleCov::SourceFile::Line
48
51
  def covered_lines
49
52
  @covered_lines ||= lines.select(&:covered?)
50
53
  end
51
54
 
52
- # Returns all lines that should have been, but were not covered
53
- # as instances of SimpleCov::SourceFile::Line
54
55
  def missed_lines
55
56
  @missed_lines ||= lines.select(&:missed?)
56
57
  end
57
58
 
58
- # Returns all lines that are not relevant for coverage as
59
- # SimpleCov::SourceFile::Line instances
60
59
  def never_lines
61
60
  @never_lines ||= lines.select(&:never?)
62
61
  end
63
62
 
64
- # Returns all lines that were skipped as SimpleCov::SourceFile::Line instances
65
63
  def skipped_lines
66
64
  @skipped_lines ||= lines.select(&:skipped?)
67
65
  end
68
66
 
69
- # Returns the number of relevant lines (covered + missed)
70
67
  def lines_of_code
71
- coverage_statistics[:line]&.total
68
+ coverage_statistics[:line]&.total || 0
72
69
  end
73
70
 
74
- # Access SimpleCov::SourceFile::Line source lines by line number
71
+ # Answers nil for a number this file has no line for.
75
72
  def line(number)
76
- lines[number - 1]
73
+ lines.at(number - 1)
77
74
  end
78
75
 
79
- # The coverage for this file in percent. 0 if the file has no coverage lines
80
- def covered_percent
81
- coverage_statistics[:line]&.percent
76
+ # Nil if the criterion was not measured.
77
+ def covered_percent(criterion = :line)
78
+ coverage_statistics(criterion)&.percent
82
79
  end
83
80
 
84
- def covered_strength
85
- coverage_statistics[:line]&.strength
81
+ def covered_strength(criterion = :line)
82
+ coverage_statistics(criterion)&.strength
86
83
  end
87
84
 
85
+ # Every line the file has is irrelevant, a file with no lines at all
86
+ # included.
88
87
  def no_lines?
89
- lines.length.zero? || (lines.length == never_lines.size)
88
+ lines.all?(&:never?)
90
89
  end
91
90
 
92
91
  def relevant_lines
93
92
  lines.size - never_lines.size - skipped_lines.size
94
93
  end
95
94
 
96
- #
97
- # Return all the branches inside current source file
98
95
  def branches
99
- @branches ||= build_branches
96
+ @branches ||= BranchBuilder.new(self).call
100
97
  end
101
98
 
102
99
  def no_branches?
@@ -104,37 +101,27 @@ module SimpleCov
104
101
  end
105
102
 
106
103
  def branches_coverage_percent
107
- coverage_statistics[:branch]&.percent
104
+ Deprecation.warn("`SimpleCov::SourceFile#branches_coverage_percent` is deprecated. " \
105
+ "Use `covered_percent(:branch)`.")
106
+ covered_percent(:branch)
108
107
  end
109
108
 
110
- #
111
- # Return the relevant branches to source file
112
109
  def total_branches
113
110
  @total_branches ||= covered_branches + missed_branches
114
111
  end
115
112
 
116
- #
117
- # Return hash with key of line number and branch coverage count as value
118
113
  def branches_report
119
- @branches_report ||= build_branches_report
114
+ @branches_report ||=
115
+ branches.reject(&:skipped?).group_by(&:report_line).transform_values { |bs| bs.map(&:report) }
120
116
  end
121
117
 
122
- #
123
- # Select the covered branches
124
- # Here we user tree schema because some conditions like case may have additional
125
- # else that is not in declared inside the code but given by default by coverage report
126
- #
127
- # @return [Array]
128
- #
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.
129
121
  def covered_branches
130
122
  @covered_branches ||= branches.select(&:covered?)
131
123
  end
132
124
 
133
- #
134
- # Select the missed branches with coverage equal to zero
135
- #
136
- # @return [Array]
137
- #
138
125
  def missed_branches
139
126
  @missed_branches ||= branches.select(&:missed?)
140
127
  end
@@ -143,213 +130,30 @@ module SimpleCov
143
130
  branches_report.fetch(line_number, [])
144
131
  end
145
132
 
146
- #
147
- # Check if any branches missing on given line number
148
- #
149
- # @param [Integer] line_number
150
- #
151
- # @return [Boolean]
152
- #
153
133
  def line_with_missed_branch?(line_number)
154
- branches_for_line(line_number).select { |_type, count| count.zero? }.any?
155
- end
156
-
157
- private
158
-
159
- # no_cov_chunks is zero indexed to work directly with the array holding the lines
160
- def no_cov_chunks
161
- @no_cov_chunks ||= build_no_cov_chunks
162
- end
163
-
164
- def build_no_cov_chunks
165
- no_cov_lines = src.map.with_index(1).select { |line_src, _index| LinesClassifier.no_cov_line?(line_src) }
166
-
167
- # if we have an uneven number of nocovs we assume they go to the
168
- # end of the file, the source doesn't really matter
169
- # Can't deal with this within the each_slice due to differing
170
- # behavior in JRuby: jruby/jruby#6048
171
- no_cov_lines << ["", src.size] if no_cov_lines.size.odd?
172
-
173
- no_cov_lines.each_slice(2).map do |(_line_src_start, index_start), (_line_src_end, index_end)|
174
- index_start..index_end
175
- end
176
- end
177
-
178
- def load_source
179
- lines = []
180
- # The default encoding is UTF-8
181
- File.open(filename, "rb:UTF-8") do |file|
182
- current_line = file.gets
183
-
184
- if shebang?(current_line)
185
- lines << current_line
186
- current_line = file.gets
187
- end
188
-
189
- read_lines(file, lines, current_line)
190
- end
191
- end
192
-
193
- SHEBANG_REGEX = /\A#!/.freeze
194
- def shebang?(line)
195
- SHEBANG_REGEX.match?(line)
196
- end
197
-
198
- def read_lines(file, lines, current_line)
199
- return lines unless current_line
200
-
201
- set_encoding_based_on_magic_comment(file, current_line)
202
- lines.concat([current_line], ensure_remove_undefs(file.readlines))
203
- end
204
-
205
- RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX = /\A#\s*(?:-\*-)?\s*(?:en)?coding:\s*(\S+)\s*(?:-\*-)?\s*\z/.freeze
206
- def set_encoding_based_on_magic_comment(file, line)
207
- # Check for encoding magic comment
208
- # Encoding magic comment must be placed at first line except for shebang
209
- if (match = RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX.match(line))
210
- file.set_encoding(match[1], "UTF-8")
211
- end
134
+ branches_for_line(line_number).any? { |_type, count| count.zero? }
212
135
  end
213
136
 
214
- def ensure_remove_undefs(file_lines)
215
- # invalid/undef replace are technically not really necessary but nice to
216
- # have and work around a JRuby incompatibility. Also moved here from
217
- # simplecov-html to have encoding shenaningans in one place. See #866
218
- # also setting these option on `file.set_encoding` doesn't seem to work
219
- # properly so it has to be done here.
220
- file_lines.each do |line|
221
- if line.encoding == Encoding::UTF_8
222
- line
223
- else
224
- line.encode!("UTF-8", invalid: :replace, undef: :replace)
225
- end
226
- end
137
+ def methods
138
+ @methods ||= MethodBuilder.new(self).call
227
139
  end
228
140
 
229
- def build_lines
230
- coverage_exceeding_source_warn if coverage_data["lines"].size > src.size
231
- lines = src.map.with_index(1) do |src, i|
232
- SimpleCov::SourceFile::Line.new(src, i, coverage_data["lines"][i - 1])
233
- end
234
- process_skipped_lines(lines)
141
+ def covered_methods
142
+ @covered_methods ||= methods.select(&:covered?)
235
143
  end
236
144
 
237
- def process_skipped_lines(lines)
238
- # the array the lines are kept in is 0-based whereas the line numbers in the nocov
239
- # chunks are 1-based and are expected to be like this in other parts (and it's also
240
- # arguably more understandable)
241
- no_cov_chunks.each { |chunk| lines[(chunk.begin - 1)..(chunk.end - 1)].each(&:skipped!) }
242
-
243
- lines
244
- end
245
-
246
- def lines_strength
247
- lines.sum { |line| line.coverage.to_i }
248
- end
249
-
250
- # Warning to identify condition from Issue #56
251
- def coverage_exceeding_source_warn
252
- warn "Warning: coverage data provided by Coverage [#{coverage_data['lines'].size}] exceeds number of lines in #{filename} [#{src.size}]"
253
- end
254
-
255
- #
256
- # Build full branches report
257
- # Root branches represent the wrapper of all condition state that
258
- # have inside the branches
259
- #
260
- # @return [Hash]
261
- #
262
- def build_branches_report
263
- branches.reject(&:skipped?).each_with_object({}) do |branch, coverage_statistics|
264
- coverage_statistics[branch.report_line] ||= []
265
- coverage_statistics[branch.report_line] << branch.report
266
- end
267
- end
268
-
269
- #
270
- # Call recursive method that transform our static hash to array of objects
271
- # @return [Array]
272
- #
273
- def build_branches
274
- coverage_branch_data = coverage_data.fetch("branches", {})
275
- branches = coverage_branch_data.flat_map do |condition, coverage_branches|
276
- build_branches_from(condition, coverage_branches)
277
- end
278
-
279
- process_skipped_branches(branches)
280
- end
281
-
282
- def process_skipped_branches(branches)
283
- return branches if no_cov_chunks.empty?
284
-
285
- branches.each do |branch|
286
- branch.skipped! if no_cov_chunks.any? { |no_cov_chunk| branch.overlaps_with?(no_cov_chunk) }
287
- end
288
-
289
- branches
290
- end
291
-
292
- # Since we are dumping to and loading from JSON, and we have arrays as keys those
293
- # don't make their way back to us intact e.g. just as a string
294
- #
295
- # We should probably do something different here, but as it stands these are
296
- # our data structures that we write so eval isn't _too_ bad.
297
- #
298
- # See #801
299
- #
300
- def restore_ruby_data_structure(structure)
301
- # Tests use the real data structures (except for integration tests) so no need to
302
- # put them through here.
303
- return structure if structure.is_a?(Array)
304
-
305
- # rubocop:disable Security/Eval
306
- eval structure
307
- # rubocop:enable Security/Eval
308
- end
309
-
310
- def build_branches_from(condition, branches)
311
- # the format handed in from the coverage data is like this:
312
- #
313
- # [:then, 4, 6, 6, 6, 10]
314
- #
315
- # which is [type, id, start_line, start_col, end_line, end_col]
316
- _condition_type, _condition_id, condition_start_line, * = restore_ruby_data_structure(condition)
317
-
318
- branches.map do |branch_data, hit_count|
319
- branch_data = restore_ruby_data_structure(branch_data)
320
- build_branch(branch_data, hit_count, condition_start_line)
321
- end
322
- end
323
-
324
- def build_branch(branch_data, hit_count, condition_start_line)
325
- type, _id, start_line, _start_col, end_line, _end_col = branch_data
326
-
327
- SourceFile::Branch.new(
328
- start_line: start_line,
329
- end_line: end_line,
330
- coverage: hit_count,
331
- inline: start_line == condition_start_line,
332
- type: type
333
- )
145
+ def missed_methods
146
+ @missed_methods ||= methods.select(&:missed?)
334
147
  end
335
148
 
336
- def line_coverage_statistics
337
- {
338
- line: CoverageStatistics.new(
339
- total_strength: lines_strength,
340
- covered: covered_lines.size,
341
- missed: missed_lines.size
342
- )
343
- }
149
+ def methods_coverage_percent
150
+ Deprecation.warn("`SimpleCov::SourceFile#methods_coverage_percent` is deprecated. " \
151
+ "Use `covered_percent(:method)`.")
152
+ covered_percent(:method)
344
153
  end
345
154
 
346
- def branch_coverage_statistics
347
- {
348
- branch: CoverageStatistics.new(
349
- covered: covered_branches.size,
350
- missed: missed_branches.size
351
- )
352
- }
155
+ def not_loaded?
156
+ !@loaded
353
157
  end
354
158
  end
355
159
  end