covered 0.28.1 → 0.28.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '050825f1f7f7966b0592e4fa5864ccd53dfbb0027795e422e47deb32d7300be5'
4
- data.tar.gz: 27984bc6a74fb8cd375e2277ea59592a544f6d3617034cc0626dee085fec4ac0
3
+ metadata.gz: '009f56de49059187634630092d8e8256fc32e59a309c155d46676f3a87002581'
4
+ data.tar.gz: 3f91b876b34f65800ab6b92a6a991eba898131dee199d476c2f82620a864d3d1
5
5
  SHA512:
6
- metadata.gz: 12300d334169f20ed66a8f6429e6294b7fcd75306fc4c589b3ebf71c2fed2a6feb29a41f502729a15a77bceefc3213ba9c65033d4cd14a7859b49318e58aaeb7
7
- data.tar.gz: e822368a201045b58b653a87d37951a8e2ca773da45f16537fea1fbafe70a968047acf28d4b369111a68384afda93514c796648173ada8c61053fb65b4f6bc84
6
+ metadata.gz: d11ce0723dc200027f6a319972baab7492599d40fc383cc2c1deba7ef88c9e600231809ce6e4de701a11fec78c0b5de4ac136c82110b503ebf5f59a9c6d7591e
7
+ data.tar.gz: f848f0fde6e08847fa8194c8dc6854ecc80053279ea1f97cd99028840f9892f102964a52d901a63ffd9393e00bc917ecbfefb247db94ab0c65b59272e1a63188
checksums.yaml.gz.sig CHANGED
Binary file
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2025, by Samuel Williams.
4
+ # Copyright, 2023-2026, by Samuel Williams.
5
5
  # Copyright, 2023, by Michael Adams.
6
6
 
7
7
  def initialize(context)
@@ -14,27 +14,7 @@ end
14
14
  # Defaults to the default coverage path if no paths are specified.
15
15
  # @parameter paths [Array(String)] The coverage database paths.
16
16
  def current(paths: nil, reports: Covered::Config.reports)
17
- policy = Covered::Policy.new
18
-
19
- # Load the default path if no paths are specified:
20
- paths ||= Dir.glob(Covered::Persist::DEFAULT_PATH, base: context.root)
21
-
22
- # If no paths are specified, raise an error:
23
- if paths.empty?
24
- raise ArgumentError, "No coverage paths specified!"
25
- end
26
-
27
- # Load all coverage information:
28
- paths.each do |path|
29
- # It would be nice to have a better algorithm here than just ignoring mtime - perhaps using checksums?
30
- Covered::Persist.new(policy.output, path).load!(ignore_mtime: true)
31
- end
32
-
33
- if reports
34
- policy.reports!(reports)
35
- end
36
-
37
- return policy
17
+ return Covered::Config.load(root: context.root, reports: reports).policy_for(paths)
38
18
  end
39
19
 
40
20
  # Validate the coverage of multiple test runs.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022-2025, by Samuel Williams.
4
+ # Copyright, 2022-2026, by Samuel Williams.
5
5
 
6
6
  def initialize(context)
7
7
  super
@@ -14,19 +14,19 @@ end
14
14
  # @parameter minimum [Float] The minimum required coverage in order to pass.
15
15
  # @parameter input [Covered::Policy] The input policy to validate.
16
16
  def validate(paths: nil, minimum: 1.0, input:)
17
- policy ||= context.lookup("covered:policy:current").call(paths: paths)
17
+ input ||= context.lookup("covered:policy:current").call(paths: paths)
18
18
 
19
19
  # Calculate statistics:
20
20
  statistics = Covered::Statistics.new
21
21
 
22
- policy.each do |coverage|
22
+ input.each do |coverage|
23
23
  statistics << coverage
24
24
  end
25
25
 
26
26
  # Print statistics:
27
27
  statistics.print($stderr)
28
28
 
29
- policy.call(STDOUT)
29
+ input.call(STDOUT)
30
30
 
31
31
  # Validate statistics and raise an error if they are not met:
32
32
  statistics.validate!(minimum)
@@ -146,13 +146,13 @@ def make_policy(policy)
146
146
  policy.only(/^app\//)
147
147
 
148
148
  # Set custom root
149
- policy.root(File.expand_path('..', __dir__))
149
+ policy.root(File.expand_path("..", __dir__))
150
150
 
151
151
  # Enable persistent coverage across runs
152
152
  policy.persist!
153
153
 
154
154
  # Configure reports programmatically
155
- if ENV['CI']
155
+ if ENV["CI"]
156
156
  policy.reports << Covered::MarkdownSummary.new
157
157
  else
158
158
  policy.reports << Covered::PartialSummary.new
@@ -245,7 +245,7 @@ def make_policy(policy)
245
245
  super
246
246
 
247
247
  # Ensure template coverage is enabled
248
- require 'covered/erb' if defined?(ERB)
248
+ require "covered/erb" if defined?(ERB)
249
249
  end
250
250
  ```
251
251
 
@@ -257,14 +257,14 @@ end
257
257
  def make_policy(policy)
258
258
  super
259
259
 
260
- if ENV['GITHUB_ACTIONS']
260
+ if ENV["GITHUB_ACTIONS"]
261
261
  # Use markdown format for GitHub
262
262
  policy.reports << Covered::MarkdownSummary.new
263
263
 
264
264
  # Fail build on low coverage
265
265
  policy.reports << Class.new do
266
266
  def call(wrapper, output = $stdout)
267
- statistics = wrapper.each.inject(Statistics.new) { |s, c| s << c }
267
+ statistics = wrapper.each.inject(Statistics.new){|s, c| s << c}
268
268
  if statistics.ratio < 0.90
269
269
  exit 1
270
270
  end
@@ -281,9 +281,9 @@ def make_policy(policy)
281
281
  super
282
282
 
283
283
  # Different thresholds for different environments
284
- threshold = case ENV['RAILS_ENV']
285
- when 'production' then 0.95
286
- when 'staging' then 0.90
284
+ threshold = case ENV["RAILS_ENV"]
285
+ when "production" then 0.95
286
+ when "staging" then 0.90
287
287
  else 0.80
288
288
  end
289
289
 
@@ -305,7 +305,7 @@ def make_policy(policy)
305
305
  policy.skip(/\/coverage\//)
306
306
 
307
307
  # Use more efficient reports for large projects
308
- if Dir['**/*.rb'].length > 1000
308
+ if Dir["**/*.rb"].length > 1000
309
309
  policy.reports << Covered::BriefSummary.new
310
310
  else
311
311
  policy.reports << Covered::PartialSummary.new
@@ -344,7 +344,7 @@ def make_policy(policy)
344
344
  super
345
345
 
346
346
  # Debug: print current configuration
347
- if ENV['DEBUG_COVERAGE']
347
+ if ENV["DEBUG_COVERAGE"]
348
348
  puts "Ignore paths: #{ignore_paths}"
349
349
  puts "Include patterns: #{include_patterns}"
350
350
  puts "Root: #{@root}"
@@ -7,7 +7,7 @@ This guide explains how to get started with `covered` and integrate it with your
7
7
  Add this line to your application's `Gemfile`:
8
8
 
9
9
  ``` ruby
10
- gem 'covered'
10
+ gem "covered"
11
11
  ```
12
12
 
13
13
  ### Sus Integration
@@ -15,7 +15,7 @@ gem 'covered'
15
15
  In your `config/sus.rb` add the following:
16
16
 
17
17
  ``` ruby
18
- require 'covered/sus'
18
+ require "covered/sus"
19
19
  include Covered::Sus
20
20
  ```
21
21
 
@@ -24,7 +24,7 @@ include Covered::Sus
24
24
  In your `spec/spec_helper.rb` add the following before loading any other code:
25
25
 
26
26
  ``` ruby
27
- require 'covered/rspec'
27
+ require "covered/rspec"
28
28
  ```
29
29
 
30
30
  Ensure that you have a `.rspec` file with `--require spec_helper`:
@@ -38,14 +38,14 @@ Ensure that you have a `.rspec` file with `--require spec_helper`:
38
38
  In your `test/test_helper.rb` add the following before loading any other code:
39
39
 
40
40
  ``` ruby
41
- require 'covered/minitest'
42
- require 'minitest/autorun'
41
+ require "covered/minitest"
42
+ require "minitest/autorun"
43
43
  ```
44
44
 
45
45
  In your test files, e.g. `test/dummy_test.rb` add the following at the top:
46
46
 
47
47
  ``` ruby
48
- require_relative 'test_helper'
48
+ require_relative "test_helper"
49
49
  ```
50
50
 
51
51
  ### Template Coverage
data/context/usage.md CHANGED
@@ -13,7 +13,7 @@ more accurate than classic line-count tools like SimpleCov.
13
13
  Add this line to your application's `Gemfile`:
14
14
 
15
15
  ```ruby
16
- gem 'covered'
16
+ gem "covered"
17
17
  ```
18
18
 
19
19
  ## Configuration
@@ -65,7 +65,7 @@ One possibly helpful functionality to take note of is that you can override the
65
65
  In your `config/sus.rb` add the following:
66
66
 
67
67
  ```ruby
68
- require 'covered/sus'
68
+ require "covered/sus"
69
69
  include Covered::Sus
70
70
  ```
71
71
  ### RSpec Integration
@@ -73,7 +73,7 @@ include Covered::Sus
73
73
  In your `spec/spec_helper.rb` add the following before loading any other code:
74
74
 
75
75
  ```ruby
76
- require 'covered/rspec'
76
+ require "covered/rspec"
77
77
  ```
78
78
 
79
79
  Ensure that you have a `.rspec` file with `--require spec_helper`:
@@ -89,14 +89,14 @@ Ensure that you have a `.rspec` file with `--require spec_helper`:
89
89
  In your `test/test_helper.rb` add the following before loading any other code:
90
90
 
91
91
  ```ruby
92
- require 'covered/minitest'
93
- require 'minitest/autorun'
92
+ require "covered/minitest"
93
+ require "minitest/autorun"
94
94
  ```
95
95
 
96
96
  In your test files, e.g. `test/dummy_test.rb` add the following at the top:
97
97
 
98
98
  ```ruby
99
- require_relative 'test_helper'
99
+ require_relative "test_helper"
100
100
  ```
101
101
 
102
102
 
@@ -5,10 +5,13 @@
5
5
 
6
6
  require_relative "config"
7
7
 
8
+ # @namespace
8
9
  module Coverage
10
+ # Integrates `covered` with Ruby's `Coverage.autostart!` hook.
9
11
  module Autostart
10
12
  # Start recording coverage information.
11
13
  # Usage: RUBYOPT=-rcovered/autostart ruby my_script.rb
14
+ # Registers an `at_exit` hook which finishes coverage and writes reports for the original process.
12
15
  def self.autostart!
13
16
  config = Covered::Config.load
14
17
  config.start
@@ -6,7 +6,13 @@
6
6
  require_relative "summary"
7
7
 
8
8
  module Covered
9
+ # Generates a short coverage report with the least-covered files.
9
10
  class BriefSummary < Summary
11
+ # Print aggregate statistics and the files with the most missing lines.
12
+ # @parameter wrapper [Covered::Base] The coverage wrapper to report.
13
+ # @parameter output [IO] The output stream.
14
+ # @parameter before [Integer] Reserved for compatibility with other summaries.
15
+ # @parameter after [Integer] Reserved for compatibility with other summaries.
10
16
  def call(wrapper, output = $stdout, before: 4, after: 4)
11
17
  terminal = self.terminal(output)
12
18
 
@@ -8,13 +8,16 @@ require_relative "wrapper"
8
8
  require "coverage"
9
9
 
10
10
  module Covered
11
+ # Captures Ruby coverage data and forwards it to another coverage output.
11
12
  class Capture < Wrapper
13
+ # Start Ruby coverage collection.
12
14
  def start
13
15
  super
14
16
 
15
17
  ::Coverage.start(lines: true, eval: true)
16
18
  end
17
19
 
20
+ # Clear any collected coverage data without stopping coverage.
18
21
  def clear
19
22
  super
20
23
 
@@ -27,6 +30,8 @@ module Covered
27
30
  "eval" => true
28
31
  }
29
32
 
33
+ # Stop coverage collection and add the collected results to the output.
34
+ # Ignores Ruby's anonymous eval paths and files that no longer exist.
30
35
  def finish
31
36
  results = ::Coverage.result
32
37
 
@@ -35,7 +40,7 @@ module Covered
35
40
 
36
41
  path = self.expand_path(path)
37
42
 
38
- # Skip files which don't exist. This can happen if `eval` is used with an invalid/incorrect path.
43
+ # Skip files which don't exist. This can happen if `eval` is used with an invalid/incorrect path:
39
44
  if File.exist?(path)
40
45
  @output.mark(path, 1, result[:lines])
41
46
  else
@@ -47,6 +52,10 @@ module Covered
47
52
  super
48
53
  end
49
54
 
55
+ # Execute the given source while capturing coverage for it.
56
+ # @parameter source [Covered::Source] The source to execute.
57
+ # @parameter binding [Binding] The binding used to evaluate the source.
58
+ # @returns [Object] The result of evaluating the source.
50
59
  def execute(source, binding: TOPLEVEL_BINDING)
51
60
  start
52
61
 
@@ -1,18 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2025, by Samuel Williams.
4
+ # Copyright, 2019-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "policy"
7
7
 
8
8
  module Covered
9
+ # Loads project coverage configuration and controls a configured policy.
9
10
  class Config
10
11
  PATH = "config/covered.rb"
11
12
 
13
+ # The root directory used for coverage configuration.
14
+ # @returns [String] The value of `COVERED_ROOT`, or the current working directory.
12
15
  def self.root
13
16
  ENV["COVERED_ROOT"] || Dir.pwd
14
17
  end
15
18
 
19
+ # The coverage configuration path under the given root.
20
+ # @parameter root [String] The project root.
21
+ # @returns [String | Nil] The expanded configuration path if it exists.
16
22
  def self.path(root)
17
23
  path = ::File.expand_path(PATH, root)
18
24
 
@@ -21,10 +27,16 @@ module Covered
21
27
  end
22
28
  end
23
29
 
30
+ # The report names requested by the environment.
31
+ # @returns [String | Nil] The `COVERAGE` environment value.
24
32
  def self.reports
25
33
  ENV["COVERAGE"]
26
34
  end
27
35
 
36
+ # Load the project coverage configuration for the given root.
37
+ # @parameter root [String] The project root.
38
+ # @parameter reports [String | Boolean | Array | Object | Nil] The report configuration.
39
+ # @returns [Covered::Config] The loaded configuration instance.
28
40
  def self.load(root: self.root, reports: self.reports)
29
41
  derived = Class.new(self)
30
42
 
@@ -37,6 +49,9 @@ module Covered
37
49
  return derived.new(root, reports)
38
50
  end
39
51
 
52
+ # Initialize the configuration for a project root and reports.
53
+ # @parameter root [String] The project root.
54
+ # @parameter reports [String | Boolean | Array | Object | Nil] The report configuration.
40
55
  def initialize(root, reports)
41
56
  @root = root
42
57
  @reports = reports
@@ -45,23 +60,31 @@ module Covered
45
60
  @environment = nil
46
61
  end
47
62
 
63
+ # Whether reports should be generated.
64
+ # @returns [Boolean] Whether reporting is enabled.
48
65
  def report?
49
66
  !!@reports
50
67
  end
51
68
 
52
69
  alias :record? :report?
53
70
 
71
+ # @attribute [Covered::Policy | Nil] The active coverage policy, if assigned by an integration.
54
72
  attr :coverage
55
73
 
74
+ # The configured coverage policy.
75
+ # @returns [Covered::Policy] The memoized, frozen policy.
56
76
  def policy
57
77
  @policy ||= Policy.new.tap{|policy| make_policy(policy)}.freeze
58
78
  end
59
79
 
80
+ # The configured policy output wrapper.
81
+ # @returns [Covered::Base] The output wrapper at the end of the policy pipeline.
60
82
  def output
61
83
  policy.output
62
84
  end
63
85
 
64
86
  # Start coverage tracking.
87
+ # Stores the current environment, configures child process autostart, and starts the policy capture pipeline.
65
88
  def start
66
89
  # Save and setup the environment:
67
90
  @environment = ENV.to_h
@@ -72,6 +95,7 @@ module Covered
72
95
  end
73
96
 
74
97
  # Finish coverage tracking.
98
+ # Stops the policy capture pipeline and restores the environment saved by {start}.
75
99
  def finish
76
100
  # Finish coverage tracking:
77
101
  policy.finish
@@ -82,15 +106,39 @@ module Covered
82
106
  end
83
107
 
84
108
  # Generate coverage reports to the given output.
85
- # @param output [IO] The output stream to write the coverage report to.
109
+ # @parameter output [IO] The output stream to write the coverage report to.
86
110
  def call(output)
87
111
  policy.call(output)
88
112
  end
89
113
 
114
+ # Enumerate the coverage data from the configured policy.
115
+ # @yields {|coverage| ...} Each coverage object from the policy.
116
+ # @parameter coverage [Covered::Coverage] The current coverage object.
90
117
  def each(&block)
91
118
  policy.each(&block)
92
119
  end
93
120
 
121
+ # Build a configured policy using coverage data from persistent storage.
122
+ #
123
+ # @parameter paths [Array(String)] The coverage database paths.
124
+ # @parameter ignore_mtime [Boolean] Whether to ignore source file modification times.
125
+ # @returns [Covered::Policy] The configured policy with loaded coverage data.
126
+ def policy_for(paths = nil, ignore_mtime: true)
127
+ paths ||= Dir.glob(Persist::DEFAULT_PATH, base: @root)
128
+ paths = Array(paths)
129
+
130
+ if paths.empty?
131
+ raise ArgumentError, "No coverage paths specified!"
132
+ end
133
+
134
+ paths.each do |path|
135
+ # It would be nice to have a better algorithm here than just ignoring mtime - perhaps using checksums:
136
+ Persist.new(policy.output, path).load!(ignore_mtime: ignore_mtime)
137
+ end
138
+
139
+ return policy
140
+ end
141
+
94
142
  # Which paths to ignore when computing coverage for a given project.
95
143
  # @returns [Array(String)] An array of relative paths to ignore.
96
144
  def ignore_paths
@@ -104,6 +152,7 @@ module Covered
104
152
  end
105
153
 
106
154
  # Override this method to implement your own policy.
155
+ # @parameter policy [Covered::Policy] The policy to configure.
107
156
  def make_policy(policy)
108
157
  # Only files in the root would be tracked:
109
158
  policy.root(@root)
@@ -6,53 +6,83 @@
6
6
  require_relative "source"
7
7
 
8
8
  module Covered
9
+ # Computes common coverage ratios from executed and executable line counts.
9
10
  module Ratio
11
+ # The fraction of executable lines that were executed.
12
+ # @returns [Rational | Float] The executed-to-executable line ratio, or `1.0` when there are no executable lines.
10
13
  def ratio
11
14
  return 1.0 if executable_count.zero?
12
15
 
13
16
  Rational(executed_count, executable_count)
14
17
  end
15
18
 
19
+ # Whether all executable lines were executed.
20
+ # @returns [Boolean] Whether `executed_count` equals `executable_count`.
16
21
  def complete?
17
22
  executed_count == executable_count
18
23
  end
19
24
 
25
+ # The coverage ratio as a percentage.
26
+ # @returns [Numeric] The coverage ratio multiplied by `100`.
20
27
  def percentage
21
28
  ratio * 100
22
29
  end
23
30
  end
24
31
 
32
+ # Stores line execution counts and source metadata for a single file.
25
33
  class Coverage
26
34
  include Ratio
27
35
 
36
+ # Build coverage for the given source path.
37
+ # @parameter path [String] The source path.
38
+ # @parameter options [Hash] Options forwarded to {Covered::Source.for}.
39
+ # @returns [Covered::Coverage] The coverage object for the source path.
28
40
  def self.for(path, **options)
29
41
  self.new(Source.for(path, **options))
30
42
  end
31
43
 
44
+ # Initialize coverage with the given source, line counts and annotations.
45
+ # @parameter source [Covered::Source] The covered source metadata.
46
+ # @parameter counts [Array(Integer | Nil)] Line execution counts indexed by line number.
47
+ # @parameter annotations [Hash(Integer, Array(String))] Line annotations indexed by line number.
32
48
  def initialize(source, counts = [], annotations = {})
33
49
  @source = source
34
50
  @counts = counts
35
51
  @annotations = annotations
36
52
  end
37
53
 
54
+ # @attribute [Covered::Source] The covered source metadata.
38
55
  attr_accessor :source
56
+
57
+ # @attribute [Array(Integer | Nil)] Line execution counts indexed by line number.
39
58
  attr :counts
59
+
60
+ # @attribute [Hash(Integer, Array(String))] Line annotations indexed by line number.
40
61
  attr :annotations
41
62
 
63
+ # The total number of executions across all tracked lines.
64
+ # @returns [Integer] The sum of all non-`nil` execution counts.
42
65
  def total
43
66
  counts.sum{|count| count || 0}
44
67
  end
45
68
 
46
69
  # Create an empty coverage with the same source.
70
+ # @returns [Covered::Coverage] Coverage with the same source and `nil` counts.
47
71
  def empty
48
72
  self.class.new(@source, [nil] * @counts.size)
49
73
  end
50
74
 
75
+ # Add an annotation to the given line number.
76
+ # @parameter line_number [Integer] The line number to annotate.
77
+ # @parameter annotation [String] The annotation text.
51
78
  def annotate(line_number, annotation)
52
79
  @annotations[line_number] ||= []
53
80
  @annotations[line_number] << annotation
54
81
  end
55
82
 
83
+ # Add the given execution count to one or more line numbers.
84
+ # @parameter line_number [Integer] The first line number to mark.
85
+ # @parameter value [Integer | Array(Integer)] The execution count or counts to add.
56
86
  def mark(line_number, value = 1)
57
87
  # As currently implemented, @counts is base-zero rather than base-one.
58
88
  # Line numbers generally start at line 1, so the first line, line 1, is at index 1. This means that index[0] is usually nil.
@@ -66,6 +96,8 @@ module Covered
66
96
  end
67
97
  end
68
98
 
99
+ # Merge another coverage object into this coverage object.
100
+ # @parameter other [Covered::Coverage] The coverage object to merge.
69
101
  def merge!(other)
70
102
  # If the counts are non-zero and don't match, that can indicate a problem.
71
103
 
@@ -83,6 +115,7 @@ module Covered
83
115
 
84
116
  # Construct a new coverage object for the given line numbers. Only the given line numbers will be considered for the purposes of computing coverage.
85
117
  # @parameter line_numbers [Array(Integer)] The line numbers to include in the new coverage object.
118
+ # @returns [Covered::Coverage] A coverage object containing counts for the selected lines.
86
119
  def for_lines(line_numbers)
87
120
  counts = [nil] * @counts.size
88
121
  line_numbers.each do |line_number|
@@ -92,14 +125,20 @@ module Covered
92
125
  self.class.new(@source, counts, @annotations)
93
126
  end
94
127
 
128
+ # The covered source path.
129
+ # @returns [String] The covered source path.
95
130
  def path
96
131
  @source.path
97
132
  end
98
133
 
134
+ # Assign the covered source path.
135
+ # @parameter value [String] The new covered source path.
99
136
  def path= value
100
137
  @source.path = value
101
138
  end
102
139
 
140
+ # Whether the source file has not changed since this coverage was recorded.
141
+ # @returns [Boolean] Whether the source exists and its modification time is not newer than the recorded time.
103
142
  def fresh?
104
143
  if @source.modified_time.nil?
105
144
  # We don't know when the file was last modified, so we assume it is stale:
@@ -119,10 +158,16 @@ module Covered
119
158
  return false
120
159
  end
121
160
 
161
+ # Read the covered source.
162
+ # @yields {|file| ...} If a block is given, yields an open source file.
163
+ # @parameter file [File] The open source file.
164
+ # @returns [String | Object] The source contents without a block, or the block result with a block.
122
165
  def read(&block)
123
166
  @source.read(&block)
124
167
  end
125
168
 
169
+ # Freeze this coverage and its mutable collections.
170
+ # @returns [Covered::Coverage] This frozen coverage object.
126
171
  def freeze
127
172
  return self if frozen?
128
173
 
@@ -132,46 +177,69 @@ module Covered
132
177
  super
133
178
  end
134
179
 
180
+ # The raw coverage counts array.
181
+ # @returns [Array(Integer | Nil)] The raw coverage counts.
135
182
  def to_a
136
183
  @counts
137
184
  end
138
185
 
186
+ # Whether this coverage has no executions.
187
+ # @returns [Boolean] Whether the total execution count is zero.
139
188
  def zero?
140
189
  total.zero?
141
190
  end
142
191
 
192
+ # The raw coverage count for the given line number.
193
+ # @parameter line_number [Integer] The line number to query.
194
+ # @returns [Integer | Nil] The execution count for the line.
143
195
  def [] line_number
144
196
  @counts[line_number]
145
197
  end
146
198
 
199
+ # Counts for lines that are executable.
200
+ # @returns [Array(Integer)] Execution counts for executable lines.
147
201
  def executable_lines
148
202
  @counts.compact
149
203
  end
150
204
 
205
+ # The number of executable lines.
206
+ # @returns [Integer] The number of executable lines.
151
207
  def executable_count
152
208
  executable_lines.count
153
209
  end
154
210
 
211
+ # Counts for executable lines that were executed.
212
+ # @returns [Array(Integer)] Non-zero execution counts for executable lines.
155
213
  def executed_lines
156
214
  executable_lines.reject(&:zero?)
157
215
  end
158
216
 
217
+ # The number of executable lines that were executed.
218
+ # @returns [Integer] The number of executed lines.
159
219
  def executed_count
160
220
  executed_lines.count
161
221
  end
162
222
 
223
+ # The number of executable lines that were not executed.
224
+ # @returns [Integer] The number of missing executable lines.
163
225
  def missing_count
164
226
  executable_count - executed_count
165
227
  end
166
228
 
229
+ # Print a human-readable coverage summary.
230
+ # @parameter output [IO] The output stream.
167
231
  def print(output)
168
232
  output.puts "** #{executed_count}/#{executable_count} lines executed; #{percentage.to_f.round(2)}% covered."
169
233
  end
170
234
 
235
+ # A human-readable representation of this coverage object.
236
+ # @returns [String] A summary including the source path and percentage.
171
237
  def to_s
172
238
  "\#<#{self.class} path=#{self.path} #{self.percentage.to_f.round(2)}% covered>"
173
239
  end
174
240
 
241
+ # A JSON-compatible representation of this coverage object.
242
+ # @returns [Hash] The coverage counts and summary statistics.
175
243
  def as_json
176
244
  {
177
245
  counts: counts,
@@ -181,12 +249,17 @@ module Covered
181
249
  }
182
250
  end
183
251
 
252
+ # Serialize this coverage object with the given packer.
253
+ # @parameter packer [Object] The MessagePack-compatible packer.
184
254
  def serialize(packer)
185
255
  packer.write(@source)
186
256
  packer.write(@counts)
187
257
  packer.write(@annotations)
188
258
  end
189
259
 
260
+ # Deserialize a coverage object from the given unpacker.
261
+ # @parameter unpacker [Object] The MessagePack-compatible unpacker.
262
+ # @returns [Covered::Coverage] The deserialized coverage object.
190
263
  def self.deserialize(unpacker)
191
264
  source = unpacker.read
192
265
  counts = unpacker.read