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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/bake/covered/policy.rb +2 -22
- data/bake/covered/validate.rb +4 -4
- data/context/configuration.md +10 -10
- data/context/getting-started.md +6 -6
- data/context/usage.md +6 -6
- data/lib/covered/autostart.rb +3 -0
- data/lib/covered/brief_summary.rb +6 -0
- data/lib/covered/capture.rb +10 -1
- data/lib/covered/config.rb +51 -2
- data/lib/covered/coverage.rb +73 -0
- data/lib/covered/files.rb +63 -1
- data/lib/covered/forks.rb +12 -0
- data/lib/covered/markdown_summary.rb +23 -1
- data/lib/covered/minitest.rb +3 -0
- data/lib/covered/partial_summary.rb +12 -2
- data/lib/covered/persist.rb +29 -0
- data/lib/covered/policy.rb +33 -0
- data/lib/covered/rspec.rb +8 -0
- data/lib/covered/source.rb +32 -0
- data/lib/covered/statistics.rb +42 -0
- data/lib/covered/summary.rb +40 -1
- data/lib/covered/sus.rb +8 -1
- data/lib/covered/version.rb +2 -1
- data/lib/covered/wrapper.rb +51 -0
- data/license.md +1 -1
- data/readme.md +20 -0
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +3 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz: '
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '009f56de49059187634630092d8e8256fc32e59a309c155d46676f3a87002581'
|
|
4
|
+
data.tar.gz: 3f91b876b34f65800ab6b92a6a991eba898131dee199d476c2f82620a864d3d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d11ce0723dc200027f6a319972baab7492599d40fc383cc2c1deba7ef88c9e600231809ce6e4de701a11fec78c0b5de4ac136c82110b503ebf5f59a9c6d7591e
|
|
7
|
+
data.tar.gz: f848f0fde6e08847fa8194c8dc6854ecc80053279ea1f97cd99028840f9892f102964a52d901a63ffd9393e00bc917ecbfefb247db94ab0c65b59272e1a63188
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/bake/covered/policy.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2023-
|
|
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
|
-
|
|
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.
|
data/bake/covered/validate.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2022-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
+
input.call(STDOUT)
|
|
30
30
|
|
|
31
31
|
# Validate statistics and raise an error if they are not met:
|
|
32
32
|
statistics.validate!(minimum)
|
data/context/configuration.md
CHANGED
|
@@ -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(
|
|
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[
|
|
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
|
|
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[
|
|
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)
|
|
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[
|
|
285
|
-
when
|
|
286
|
-
when
|
|
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[
|
|
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[
|
|
347
|
+
if ENV["DEBUG_COVERAGE"]
|
|
348
348
|
puts "Ignore paths: #{ignore_paths}"
|
|
349
349
|
puts "Include patterns: #{include_patterns}"
|
|
350
350
|
puts "Root: #{@root}"
|
data/context/getting-started.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
42
|
-
require
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
93
|
-
require
|
|
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
|
|
99
|
+
require_relative "test_helper"
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
|
data/lib/covered/autostart.rb
CHANGED
|
@@ -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
|
|
data/lib/covered/capture.rb
CHANGED
|
@@ -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
|
|
data/lib/covered/config.rb
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2019-
|
|
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
|
-
# @
|
|
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)
|
data/lib/covered/coverage.rb
CHANGED
|
@@ -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
|