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
data/lib/covered/files.rb
CHANGED
|
@@ -9,35 +9,57 @@ require_relative "wrapper"
|
|
|
9
9
|
require "set"
|
|
10
10
|
|
|
11
11
|
module Covered
|
|
12
|
+
# Collects coverage information keyed by source path.
|
|
12
13
|
class Files < Base
|
|
14
|
+
# Initialize an empty coverage collection.
|
|
13
15
|
def initialize(*)
|
|
14
16
|
super
|
|
15
17
|
|
|
16
18
|
@paths = {}
|
|
17
19
|
end
|
|
18
20
|
|
|
21
|
+
# @attribute [Hash(String, Covered::Coverage)] Coverage indexed by expanded source path.
|
|
19
22
|
attr_accessor :paths
|
|
20
23
|
|
|
24
|
+
# Get or create coverage for the given path.
|
|
25
|
+
# @parameter path [String] The source path.
|
|
26
|
+
# @returns [Covered::Coverage] The coverage object for the path.
|
|
21
27
|
def [](path)
|
|
22
28
|
@paths[path] ||= Coverage.for(path)
|
|
23
29
|
end
|
|
24
30
|
|
|
31
|
+
# Whether there are no tracked paths.
|
|
32
|
+
# @returns [Boolean] Whether no coverage paths are tracked.
|
|
25
33
|
def empty?
|
|
26
34
|
@paths.empty?
|
|
27
35
|
end
|
|
28
36
|
|
|
37
|
+
# Mark a line in the given path as executed.
|
|
38
|
+
# @parameter path [String] The source path.
|
|
39
|
+
# @parameter line_number [Integer] The line number to mark.
|
|
40
|
+
# @parameter value [Integer | Array(Integer)] The execution count or counts to add.
|
|
29
41
|
def mark(path, line_number, value)
|
|
30
42
|
self[path].mark(line_number, value)
|
|
31
43
|
end
|
|
32
44
|
|
|
45
|
+
# Add an annotation to a line in the given path.
|
|
46
|
+
# @parameter path [String] The source path.
|
|
47
|
+
# @parameter line_number [Integer] The line number to annotate.
|
|
48
|
+
# @parameter value [String] The annotation text.
|
|
33
49
|
def annotate(path, line_number, value)
|
|
34
50
|
self[path].annotate(line_number, value)
|
|
35
51
|
end
|
|
36
52
|
|
|
53
|
+
# Merge coverage for the given path into this collection.
|
|
54
|
+
# @parameter coverage [Covered::Coverage] The coverage object to merge.
|
|
37
55
|
def add(coverage)
|
|
38
56
|
self[coverage.path].merge!(coverage)
|
|
39
57
|
end
|
|
40
58
|
|
|
59
|
+
# Enumerate tracked coverage objects.
|
|
60
|
+
# @yields {|coverage| ...} Each tracked coverage object.
|
|
61
|
+
# @parameter coverage [Covered::Coverage] The current coverage object.
|
|
62
|
+
# @returns [Enumerator | Nil] An enumerator without a block.
|
|
41
63
|
def each
|
|
42
64
|
return to_enum unless block_given?
|
|
43
65
|
|
|
@@ -46,12 +68,19 @@ module Covered
|
|
|
46
68
|
end
|
|
47
69
|
end
|
|
48
70
|
|
|
71
|
+
# Remove all tracked coverage data.
|
|
72
|
+
# @returns [Hash] The cleared path map.
|
|
49
73
|
def clear
|
|
50
74
|
@paths.clear
|
|
51
75
|
end
|
|
52
76
|
end
|
|
53
77
|
|
|
78
|
+
# Includes coverage for files matching a glob pattern.
|
|
54
79
|
class Include < Wrapper
|
|
80
|
+
# Initialize an include filter for the given glob pattern.
|
|
81
|
+
# @parameter output [Covered::Base] The output to wrap.
|
|
82
|
+
# @parameter pattern [String] The glob pattern to include.
|
|
83
|
+
# @parameter base [String] The base path used to expand the glob.
|
|
55
84
|
def initialize(output, pattern, base = "")
|
|
56
85
|
super(output)
|
|
57
86
|
|
|
@@ -59,8 +88,11 @@ module Covered
|
|
|
59
88
|
@base = base
|
|
60
89
|
end
|
|
61
90
|
|
|
91
|
+
# @attribute [String] The glob pattern to include.
|
|
62
92
|
attr :pattern
|
|
63
93
|
|
|
94
|
+
# Resolve the include pattern to real file paths.
|
|
95
|
+
# @returns [Set(String)] The real paths matched by the include pattern.
|
|
64
96
|
def glob
|
|
65
97
|
paths = Set.new
|
|
66
98
|
root = self.expand_path(@base)
|
|
@@ -75,6 +107,9 @@ module Covered
|
|
|
75
107
|
return paths
|
|
76
108
|
end
|
|
77
109
|
|
|
110
|
+
# Enumerate existing coverage and synthesize empty coverage for unmatched included files.
|
|
111
|
+
# @yields {|coverage| ...} Each existing or synthesized coverage object.
|
|
112
|
+
# @parameter coverage [Covered::Coverage] The current coverage object.
|
|
78
113
|
def each(&block)
|
|
79
114
|
paths = glob
|
|
80
115
|
|
|
@@ -90,17 +125,22 @@ module Covered
|
|
|
90
125
|
end
|
|
91
126
|
end
|
|
92
127
|
|
|
128
|
+
# Excludes coverage for paths matching a pattern.
|
|
93
129
|
class Skip < Filter
|
|
130
|
+
# Initialize a skip filter for the given pattern.
|
|
131
|
+
# @parameter output [Covered::Base] The output to wrap.
|
|
132
|
+
# @parameter pattern [Regexp] The pattern to exclude.
|
|
94
133
|
def initialize(output, pattern)
|
|
95
134
|
super(output)
|
|
96
135
|
|
|
97
136
|
@pattern = pattern
|
|
98
137
|
end
|
|
99
138
|
|
|
139
|
+
# @attribute [Regexp] The pattern to exclude.
|
|
100
140
|
attr :pattern
|
|
101
141
|
|
|
102
142
|
if Regexp.instance_methods.include? :match?
|
|
103
|
-
# This is better as it doesn't allocate a MatchData instance which is essentially useless
|
|
143
|
+
# This is better as it doesn't allocate a MatchData instance which is essentially useless:
|
|
104
144
|
def match? path
|
|
105
145
|
!@pattern.match?(path)
|
|
106
146
|
end
|
|
@@ -111,33 +151,52 @@ module Covered
|
|
|
111
151
|
end
|
|
112
152
|
end
|
|
113
153
|
|
|
154
|
+
# Only includes coverage for paths matching a pattern.
|
|
114
155
|
class Only < Filter
|
|
156
|
+
# Initialize an only filter for the given pattern.
|
|
157
|
+
# @parameter output [Covered::Base] The output to wrap.
|
|
158
|
+
# @parameter pattern [Object] The pattern matched with `===`.
|
|
115
159
|
def initialize(output, pattern)
|
|
116
160
|
super(output)
|
|
117
161
|
|
|
118
162
|
@pattern = pattern
|
|
119
163
|
end
|
|
120
164
|
|
|
165
|
+
# @attribute [Object] The pattern matched with `===`.
|
|
121
166
|
attr :pattern
|
|
122
167
|
|
|
168
|
+
# Whether the given path matches the only pattern.
|
|
169
|
+
# @parameter path [String] The source path.
|
|
170
|
+
# @returns [Boolean] Whether the path matches the pattern.
|
|
123
171
|
def match?(path)
|
|
124
172
|
@pattern === path
|
|
125
173
|
end
|
|
126
174
|
end
|
|
127
175
|
|
|
176
|
+
# Restricts coverage to a project root and converts paths relative to it.
|
|
128
177
|
class Root < Filter
|
|
178
|
+
# Initialize a root filter for the given path.
|
|
179
|
+
# @parameter output [Covered::Base] The output to wrap.
|
|
180
|
+
# @parameter path [String] The root path.
|
|
129
181
|
def initialize(output, path)
|
|
130
182
|
super(output)
|
|
131
183
|
|
|
132
184
|
@path = path
|
|
133
185
|
end
|
|
134
186
|
|
|
187
|
+
# @attribute [String] The root path.
|
|
135
188
|
attr :path
|
|
136
189
|
|
|
190
|
+
# Expand a path relative to this root.
|
|
191
|
+
# @parameter path [String] The path to expand.
|
|
192
|
+
# @returns [String] The expanded path.
|
|
137
193
|
def expand_path(path)
|
|
138
194
|
File.expand_path(super, @path)
|
|
139
195
|
end
|
|
140
196
|
|
|
197
|
+
# Convert a path under this root to a relative path.
|
|
198
|
+
# @parameter path [String] The path to relativize.
|
|
199
|
+
# @returns [String] The relative path when under this root, otherwise the wrapped output result.
|
|
141
200
|
def relative_path(path)
|
|
142
201
|
if path.start_with?(@path)
|
|
143
202
|
path.slice(@path.size+1, path.size)
|
|
@@ -146,6 +205,9 @@ module Covered
|
|
|
146
205
|
end
|
|
147
206
|
end
|
|
148
207
|
|
|
208
|
+
# Whether the given path is under this root.
|
|
209
|
+
# @parameter path [String] The source path.
|
|
210
|
+
# @returns [Boolean] Whether the path starts with this root.
|
|
149
211
|
def match?(path)
|
|
150
212
|
path.start_with?(@path)
|
|
151
213
|
end
|
data/lib/covered/forks.rb
CHANGED
|
@@ -6,25 +6,33 @@
|
|
|
6
6
|
require_relative "wrapper"
|
|
7
7
|
|
|
8
8
|
module Covered
|
|
9
|
+
# Tracks coverage across forked child processes.
|
|
9
10
|
class Forks < Wrapper
|
|
11
|
+
# Start tracking coverage and install the fork handler.
|
|
10
12
|
def start
|
|
11
13
|
super
|
|
12
14
|
|
|
13
15
|
Handler.start(self)
|
|
14
16
|
end
|
|
15
17
|
|
|
18
|
+
# Stop tracking coverage and remove the fork handler state.
|
|
16
19
|
def finish
|
|
17
20
|
Handler.finish
|
|
18
21
|
|
|
19
22
|
super
|
|
20
23
|
end
|
|
21
24
|
|
|
25
|
+
# Handles `Process.fork` so child processes record coverage independently.
|
|
22
26
|
module Handler
|
|
23
27
|
LOCK = Mutex.new
|
|
24
28
|
|
|
25
29
|
class << self
|
|
30
|
+
# @attribute [Covered::Forks | Nil] The currently registered coverage wrapper.
|
|
26
31
|
attr :coverage
|
|
27
32
|
|
|
33
|
+
# Register coverage for fork handling.
|
|
34
|
+
# @parameter coverage [Covered::Forks] The coverage wrapper to use in forked children.
|
|
35
|
+
# @raises [ArgumentError] If coverage is already registered.
|
|
28
36
|
def start(coverage)
|
|
29
37
|
LOCK.synchronize do
|
|
30
38
|
if @coverage
|
|
@@ -35,12 +43,14 @@ module Covered
|
|
|
35
43
|
end
|
|
36
44
|
end
|
|
37
45
|
|
|
46
|
+
# Clear the registered coverage.
|
|
38
47
|
def finish
|
|
39
48
|
LOCK.synchronize do
|
|
40
49
|
@coverage = nil
|
|
41
50
|
end
|
|
42
51
|
end
|
|
43
52
|
|
|
53
|
+
# Reset coverage in a forked child and save it at exit.
|
|
44
54
|
def after_fork
|
|
45
55
|
return unless coverage = Handler.coverage
|
|
46
56
|
pid = Process.pid
|
|
@@ -57,6 +67,8 @@ module Covered
|
|
|
57
67
|
end
|
|
58
68
|
end
|
|
59
69
|
|
|
70
|
+
# Intercept `Process.fork` and initialize coverage in the child.
|
|
71
|
+
# @returns [Integer | Nil] The process ID in the parent, and `0` or `nil` in the child depending on Ruby's fork semantics.
|
|
60
72
|
def _fork
|
|
61
73
|
pid = super
|
|
62
74
|
|
|
@@ -9,11 +9,19 @@ require_relative "wrapper"
|
|
|
9
9
|
require "console/output"
|
|
10
10
|
|
|
11
11
|
module Covered
|
|
12
|
+
# Generates a Markdown coverage summary.
|
|
12
13
|
class MarkdownSummary
|
|
14
|
+
# Initialize the report with an optional coverage threshold.
|
|
15
|
+
# @parameter threshold [Numeric | Nil] The minimum ratio a file must meet to be omitted from the detailed output.
|
|
13
16
|
def initialize(threshold: 1.0)
|
|
14
17
|
@threshold = threshold
|
|
15
18
|
end
|
|
16
19
|
|
|
20
|
+
# Enumerate coverage below the threshold and return aggregate statistics.
|
|
21
|
+
# @parameter wrapper [Covered::Base] The coverage wrapper to enumerate.
|
|
22
|
+
# @yields {|coverage| ...} Coverage whose ratio is below the configured threshold.
|
|
23
|
+
# @parameter coverage [Covered::Coverage] The coverage object below the threshold.
|
|
24
|
+
# @returns [Covered::Statistics] Statistics for all coverage objects, including omitted ones.
|
|
17
25
|
def each(wrapper)
|
|
18
26
|
statistics = Statistics.new
|
|
19
27
|
|
|
@@ -28,6 +36,11 @@ module Covered
|
|
|
28
36
|
return statistics
|
|
29
37
|
end
|
|
30
38
|
|
|
39
|
+
# Print any annotations for the given line.
|
|
40
|
+
# @parameter output [IO] The output stream.
|
|
41
|
+
# @parameter coverage [Covered::Coverage] The coverage being rendered.
|
|
42
|
+
# @parameter line [String] The source line.
|
|
43
|
+
# @parameter line_offset [Integer] The current line number.
|
|
31
44
|
def print_annotations(output, coverage, line, line_offset)
|
|
32
45
|
if annotations = coverage.annotations[line_offset]
|
|
33
46
|
prefix = "#{line_offset}|".rjust(8) + "*|".rjust(8)
|
|
@@ -38,10 +51,17 @@ module Covered
|
|
|
38
51
|
end
|
|
39
52
|
end
|
|
40
53
|
|
|
54
|
+
# Print the line and hit-count header.
|
|
55
|
+
# @parameter output [IO] The output stream.
|
|
41
56
|
def print_line_header(output)
|
|
42
57
|
output.puts "Line|".rjust(8) + "Hits|".rjust(8)
|
|
43
58
|
end
|
|
44
59
|
|
|
60
|
+
# Print a single source line.
|
|
61
|
+
# @parameter output [IO] The output stream.
|
|
62
|
+
# @parameter line [String] The source line.
|
|
63
|
+
# @parameter line_offset [Integer] The current line number.
|
|
64
|
+
# @parameter count [Integer | Nil] The execution count for the line.
|
|
45
65
|
def print_line(output, line, line_offset, count)
|
|
46
66
|
prefix = "#{line_offset}|".rjust(8) + "#{count}|".rjust(8)
|
|
47
67
|
|
|
@@ -54,7 +74,9 @@ module Covered
|
|
|
54
74
|
end
|
|
55
75
|
end
|
|
56
76
|
|
|
57
|
-
# A coverage array gives, for each line, the number of line
|
|
77
|
+
# A coverage array gives, for each line, the number of line executions by the interpreter. A `nil` value means coverage is finished for this line (lines like `else` and `end`).
|
|
78
|
+
# @parameter wrapper [Covered::Base] The coverage wrapper to report.
|
|
79
|
+
# @parameter output [IO] The output stream.
|
|
58
80
|
def call(wrapper, output = $stdout)
|
|
59
81
|
output.puts "# Coverage Report"
|
|
60
82
|
output.puts
|
data/lib/covered/minitest.rb
CHANGED
|
@@ -11,7 +11,10 @@ require "minitest"
|
|
|
11
11
|
$covered = Covered::Config.load
|
|
12
12
|
|
|
13
13
|
module Covered
|
|
14
|
+
# Integrates coverage tracking with Minitest.
|
|
14
15
|
module Minitest
|
|
16
|
+
# Start coverage before running the Minitest suite.
|
|
17
|
+
# @returns [Object] The result of Minitest's original `run` method.
|
|
15
18
|
def run(*)
|
|
16
19
|
$covered.start
|
|
17
20
|
|
|
@@ -6,7 +6,13 @@
|
|
|
6
6
|
require_relative "summary"
|
|
7
7
|
|
|
8
8
|
module Covered
|
|
9
|
+
# Generates a report containing only lines near missing coverage.
|
|
9
10
|
class PartialSummary < Summary
|
|
11
|
+
# Print coverage snippets around uncovered lines.
|
|
12
|
+
# @parameter terminal [Console::Terminal] The terminal to write to.
|
|
13
|
+
# @parameter coverage [Covered::Coverage] The coverage to render.
|
|
14
|
+
# @parameter before [Integer] The number of lines before an uncovered line to show.
|
|
15
|
+
# @parameter after [Integer] The number of lines after an uncovered line to show.
|
|
10
16
|
def print_coverage(terminal, coverage, before: 4, after: 4)
|
|
11
17
|
return if coverage.zero?
|
|
12
18
|
|
|
@@ -38,6 +44,10 @@ module Covered
|
|
|
38
44
|
end
|
|
39
45
|
end
|
|
40
46
|
|
|
47
|
+
# Print the partial coverage report.
|
|
48
|
+
# @parameter wrapper [Covered::Base] The coverage wrapper to report.
|
|
49
|
+
# @parameter output [IO] The output stream.
|
|
50
|
+
# @parameter options [Hash] Options forwarded to {print_coverage}.
|
|
41
51
|
def call(wrapper, output = $stdout, **options)
|
|
42
52
|
terminal = self.terminal(output)
|
|
43
53
|
complete_files = []
|
|
@@ -59,7 +69,7 @@ module Covered
|
|
|
59
69
|
coverage.print(output)
|
|
60
70
|
end
|
|
61
71
|
|
|
62
|
-
# Collect files with 100% coverage that were not shown
|
|
72
|
+
# Collect files with 100% coverage that were not shown:
|
|
63
73
|
wrapper.each do |coverage|
|
|
64
74
|
if coverage.ratio >= 1.0
|
|
65
75
|
complete_files << wrapper.relative_path(coverage.path)
|
|
@@ -69,7 +79,7 @@ module Covered
|
|
|
69
79
|
terminal.puts
|
|
70
80
|
statistics.print(output)
|
|
71
81
|
|
|
72
|
-
# Only show information about files with 100% coverage if there were files with partial coverage shown above
|
|
82
|
+
# Only show information about files with 100% coverage if there were files with partial coverage shown above:
|
|
73
83
|
if complete_files.any? && partial_files_count > 0
|
|
74
84
|
terminal.puts ""
|
|
75
85
|
if complete_files.size == 1
|
data/lib/covered/persist.rb
CHANGED
|
@@ -10,15 +10,24 @@ require "msgpack"
|
|
|
10
10
|
require "time"
|
|
11
11
|
|
|
12
12
|
module Covered
|
|
13
|
+
# Persists coverage records to a MessagePack database.
|
|
13
14
|
class Persist < Wrapper
|
|
14
15
|
DEFAULT_PATH = ".covered.db"
|
|
15
16
|
|
|
17
|
+
# Initialize persistence for the given output and database path.
|
|
18
|
+
# @parameter output [Covered::Base] The output to wrap.
|
|
19
|
+
# @parameter path [String] The coverage database path.
|
|
16
20
|
def initialize(output, path = DEFAULT_PATH)
|
|
17
21
|
super(output)
|
|
18
22
|
|
|
19
23
|
@path = self.expand_path(path)
|
|
20
24
|
end
|
|
21
25
|
|
|
26
|
+
# Apply a persisted record to the output.
|
|
27
|
+
# Records with stale source modification times are ignored unless `ignore_mtime` is true.
|
|
28
|
+
# @parameter record [Hash] The persisted coverage record.
|
|
29
|
+
# @parameter ignore_mtime [Boolean] Whether to apply records even if their source appears stale.
|
|
30
|
+
# @returns [Boolean] Whether the record was applied.
|
|
22
31
|
def apply(record, ignore_mtime: false)
|
|
23
32
|
if coverage = record[:coverage]
|
|
24
33
|
if path = record[:path]
|
|
@@ -35,6 +44,9 @@ module Covered
|
|
|
35
44
|
return false
|
|
36
45
|
end
|
|
37
46
|
|
|
47
|
+
# Convert coverage into a database record.
|
|
48
|
+
# @parameter coverage [Covered::Coverage] The coverage object to serialize.
|
|
49
|
+
# @returns [Hash] A MessagePack-compatible record.
|
|
38
50
|
def serialize(coverage)
|
|
39
51
|
{
|
|
40
52
|
# We want to use relative paths so that moving the repo won't break everything:
|
|
@@ -45,6 +57,9 @@ module Covered
|
|
|
45
57
|
}
|
|
46
58
|
end
|
|
47
59
|
|
|
60
|
+
# Load persisted coverage records into the output.
|
|
61
|
+
# @parameter options [Hash] Options forwarded to {apply}.
|
|
62
|
+
# @raises [LoadError] If the database exists but cannot be decoded.
|
|
48
63
|
def load!(**options)
|
|
49
64
|
return unless File.exist?(@path)
|
|
50
65
|
|
|
@@ -61,6 +76,7 @@ module Covered
|
|
|
61
76
|
raise LoadError, "Failed to load coverage from #{@path}, maybe old format or corrupt!"
|
|
62
77
|
end
|
|
63
78
|
|
|
79
|
+
# Save all output coverage records to the database.
|
|
64
80
|
def save!
|
|
65
81
|
# Dump all coverage:
|
|
66
82
|
File.open(@path, "ab") do |file|
|
|
@@ -77,12 +93,17 @@ module Covered
|
|
|
77
93
|
end
|
|
78
94
|
end
|
|
79
95
|
|
|
96
|
+
# Finish the wrapped output and save the coverage database.
|
|
80
97
|
def finish
|
|
81
98
|
super
|
|
82
99
|
|
|
83
100
|
self.save!
|
|
84
101
|
end
|
|
85
102
|
|
|
103
|
+
# Reload persisted coverage and enumerate the wrapped output.
|
|
104
|
+
# @yields {|coverage| ...} Each coverage object from the reloaded output.
|
|
105
|
+
# @parameter coverage [Covered::Coverage] The current coverage object.
|
|
106
|
+
# @returns [Enumerator | Nil] An enumerator without a block.
|
|
86
107
|
def each(&block)
|
|
87
108
|
return to_enum unless block_given?
|
|
88
109
|
|
|
@@ -92,6 +113,8 @@ module Covered
|
|
|
92
113
|
super
|
|
93
114
|
end
|
|
94
115
|
|
|
116
|
+
# Build the MessagePack factory used for coverage records.
|
|
117
|
+
# @returns [MessagePack::Factory] The configured MessagePack factory.
|
|
95
118
|
def make_factory
|
|
96
119
|
factory = MessagePack::Factory.new
|
|
97
120
|
|
|
@@ -117,10 +140,16 @@ module Covered
|
|
|
117
140
|
return factory
|
|
118
141
|
end
|
|
119
142
|
|
|
143
|
+
# Build a MessagePack packer for the given IO.
|
|
144
|
+
# @parameter io [IO] The IO to write packed records to.
|
|
145
|
+
# @returns [MessagePack::Packer] A packer configured for coverage records.
|
|
120
146
|
def make_packer(io)
|
|
121
147
|
return make_factory.packer(io)
|
|
122
148
|
end
|
|
123
149
|
|
|
150
|
+
# Build a MessagePack unpacker for the given IO.
|
|
151
|
+
# @parameter io [IO] The IO to read packed records from.
|
|
152
|
+
# @returns [MessagePack::Unpacker] An unpacker configured for coverage records.
|
|
124
153
|
def make_unpacker(io)
|
|
125
154
|
return make_factory.unpacker(io)
|
|
126
155
|
end
|
data/lib/covered/policy.rb
CHANGED
|
@@ -10,7 +10,9 @@ require_relative "persist"
|
|
|
10
10
|
require_relative "forks"
|
|
11
11
|
|
|
12
12
|
module Covered
|
|
13
|
+
# Configures coverage collection, filtering, persistence and reports.
|
|
13
14
|
class Policy < Wrapper
|
|
15
|
+
# Initialize a policy with an empty file collection.
|
|
14
16
|
def initialize
|
|
15
17
|
super(Files.new)
|
|
16
18
|
|
|
@@ -18,8 +20,11 @@ module Covered
|
|
|
18
20
|
@capture = nil
|
|
19
21
|
end
|
|
20
22
|
|
|
23
|
+
# @attribute [Covered::Base] The current output pipeline.
|
|
21
24
|
attr :output
|
|
22
25
|
|
|
26
|
+
# Freeze the policy and eagerly build the capture pipeline.
|
|
27
|
+
# @returns [Covered::Policy] This frozen policy.
|
|
23
28
|
def freeze
|
|
24
29
|
return self if frozen?
|
|
25
30
|
|
|
@@ -29,47 +34,67 @@ module Covered
|
|
|
29
34
|
super
|
|
30
35
|
end
|
|
31
36
|
|
|
37
|
+
# Include files matching the given pattern in coverage results.
|
|
38
|
+
# Arguments are forwarded to {Covered::Include#initialize}.
|
|
32
39
|
def include(...)
|
|
33
40
|
@output = Include.new(@output, ...)
|
|
34
41
|
end
|
|
35
42
|
|
|
43
|
+
# Exclude files matching the given pattern from coverage results.
|
|
44
|
+
# Arguments are forwarded to {Covered::Skip#initialize}.
|
|
36
45
|
def skip(...)
|
|
37
46
|
@output = Skip.new(@output, ...)
|
|
38
47
|
end
|
|
39
48
|
|
|
49
|
+
# Restrict coverage results to files matching the given pattern.
|
|
50
|
+
# Arguments are forwarded to {Covered::Only#initialize}.
|
|
40
51
|
def only(...)
|
|
41
52
|
@output = Only.new(@output, ...)
|
|
42
53
|
end
|
|
43
54
|
|
|
55
|
+
# Restrict coverage results to the given project root.
|
|
56
|
+
# Arguments are forwarded to {Covered::Root#initialize}.
|
|
44
57
|
def root(...)
|
|
45
58
|
@output = Root.new(@output, ...)
|
|
46
59
|
end
|
|
47
60
|
|
|
61
|
+
# Persist coverage results to a database.
|
|
62
|
+
# Arguments are forwarded to {Covered::Persist#initialize}.
|
|
48
63
|
def persist!(...)
|
|
49
64
|
@output = Persist.new(@output, ...)
|
|
50
65
|
end
|
|
51
66
|
|
|
67
|
+
# The runtime capture pipeline for this policy.
|
|
68
|
+
# @returns [Covered::Forks] The memoized capture pipeline.
|
|
52
69
|
def capture
|
|
53
70
|
@capture ||= Forks.new(
|
|
54
71
|
Capture.new(@output)
|
|
55
72
|
)
|
|
56
73
|
end
|
|
57
74
|
|
|
75
|
+
# Start collecting coverage.
|
|
58
76
|
def start
|
|
59
77
|
capture.start
|
|
60
78
|
end
|
|
61
79
|
|
|
80
|
+
# Finish collecting coverage.
|
|
62
81
|
def finish
|
|
63
82
|
capture.finish
|
|
64
83
|
end
|
|
65
84
|
|
|
85
|
+
# @attribute [Array] The configured report objects or autoloaders.
|
|
66
86
|
attr :reports
|
|
67
87
|
|
|
88
|
+
# Lazily loads a report class when it is first used.
|
|
68
89
|
class Autoload
|
|
90
|
+
# Initialize an autoloaded report with the given constant name.
|
|
91
|
+
# @parameter name [String] The report class name under {Covered}.
|
|
69
92
|
def initialize(name)
|
|
70
93
|
@name = name
|
|
71
94
|
end
|
|
72
95
|
|
|
96
|
+
# Instantiate the report class.
|
|
97
|
+
# @returns [Object] A new report instance.
|
|
73
98
|
def new
|
|
74
99
|
begin
|
|
75
100
|
klass = Covered.const_get(@name)
|
|
@@ -82,10 +107,14 @@ module Covered
|
|
|
82
107
|
return klass.new
|
|
83
108
|
end
|
|
84
109
|
|
|
110
|
+
# Instantiate the report and call it.
|
|
111
|
+
# Arguments are forwarded to the report.
|
|
85
112
|
def call(...)
|
|
86
113
|
self.new.call(...)
|
|
87
114
|
end
|
|
88
115
|
|
|
116
|
+
# A human-readable representation of this autoloaded report.
|
|
117
|
+
# @returns [String] A debug representation of the autoloader.
|
|
89
118
|
def to_s
|
|
90
119
|
"\#<#{self.class} loading #{@name}>"
|
|
91
120
|
end
|
|
@@ -101,6 +130,8 @@ module Covered
|
|
|
101
130
|
end
|
|
102
131
|
end
|
|
103
132
|
|
|
133
|
+
# Configure reports from names, booleans, arrays or report objects.
|
|
134
|
+
# @parameter reports [String | Boolean | Array | Object | Nil] The reports to configure.
|
|
104
135
|
def reports!(reports)
|
|
105
136
|
if reports.is_a?(String)
|
|
106
137
|
names = reports.split(",")
|
|
@@ -124,6 +155,8 @@ module Covered
|
|
|
124
155
|
end
|
|
125
156
|
end
|
|
126
157
|
|
|
158
|
+
# Generate all configured reports.
|
|
159
|
+
# Arguments are forwarded to each report.
|
|
127
160
|
def call(...)
|
|
128
161
|
@reports.each do |report|
|
|
129
162
|
report.call(self, ...)
|
data/lib/covered/rspec.rb
CHANGED
|
@@ -9,18 +9,26 @@ require "rspec/core/formatters"
|
|
|
9
9
|
$covered = Covered::Config.load
|
|
10
10
|
|
|
11
11
|
module Covered
|
|
12
|
+
# Integrates coverage tracking with RSpec.
|
|
12
13
|
module RSpec
|
|
14
|
+
# Extends RSpec configuration with coverage tracking.
|
|
13
15
|
module Policy
|
|
16
|
+
# Start coverage before loading spec files.
|
|
17
|
+
# @returns [Object] The result of RSpec's original `load_spec_files` method.
|
|
14
18
|
def load_spec_files
|
|
15
19
|
$covered.start
|
|
16
20
|
|
|
17
21
|
super
|
|
18
22
|
end
|
|
19
23
|
|
|
24
|
+
# The active coverage configuration.
|
|
25
|
+
# @returns [Covered::Config] The active coverage configuration.
|
|
20
26
|
def covered
|
|
21
27
|
$covered
|
|
22
28
|
end
|
|
23
29
|
|
|
30
|
+
# Assign the active coverage configuration.
|
|
31
|
+
# @parameter policy [Covered::Config] The replacement coverage configuration.
|
|
24
32
|
def covered= policy
|
|
25
33
|
$covered = policy
|
|
26
34
|
end
|
data/lib/covered/source.rb
CHANGED
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
# Copyright, 2018-2023, by Samuel Williams.
|
|
5
5
|
|
|
6
6
|
module Covered
|
|
7
|
+
# Source code metadata for a covered file or generated template.
|
|
7
8
|
class Source
|
|
9
|
+
# Build source metadata for the given path.
|
|
10
|
+
# Records the current file modification time when the path exists.
|
|
11
|
+
# @parameter path [String] The source path.
|
|
12
|
+
# @parameter options [Hash] Options forwarded to {initialize}.
|
|
13
|
+
# @returns [Covered::Source] The source metadata.
|
|
8
14
|
def self.for(path, **options)
|
|
9
15
|
if File.exist?(path)
|
|
10
16
|
# options[:code] ||= File.read(path)
|
|
@@ -14,6 +20,11 @@ module Covered
|
|
|
14
20
|
self.new(path, **options)
|
|
15
21
|
end
|
|
16
22
|
|
|
23
|
+
# Initialize source metadata.
|
|
24
|
+
# @parameter path [String] The source path.
|
|
25
|
+
# @parameter code [String | Nil] Optional generated source code.
|
|
26
|
+
# @parameter line_offset [Integer] The starting line offset.
|
|
27
|
+
# @parameter modified_time [Time | Nil] The source modification time.
|
|
17
28
|
def initialize(path, code: nil, line_offset: 1, modified_time: nil)
|
|
18
29
|
@path = path
|
|
19
30
|
@code = code
|
|
@@ -21,15 +32,28 @@ module Covered
|
|
|
21
32
|
@modified_time = modified_time
|
|
22
33
|
end
|
|
23
34
|
|
|
35
|
+
# @attribute [String] The source path.
|
|
24
36
|
attr_accessor :path
|
|
37
|
+
|
|
38
|
+
# @attribute [String | Nil] Optional generated source code.
|
|
25
39
|
attr :code
|
|
40
|
+
|
|
41
|
+
# @attribute [Integer] The starting line offset for generated source code.
|
|
26
42
|
attr :line_offset
|
|
43
|
+
|
|
44
|
+
# @attribute [Time | Nil] The recorded source modification time.
|
|
27
45
|
attr :modified_time
|
|
28
46
|
|
|
47
|
+
# A human-readable representation of this source.
|
|
48
|
+
# @returns [String] A summary containing the source path.
|
|
29
49
|
def to_s
|
|
30
50
|
"\#<#{self.class} path=#{path}>"
|
|
31
51
|
end
|
|
32
52
|
|
|
53
|
+
# Read the source code from disk.
|
|
54
|
+
# @yields {|file| ...} If a block is given, yields an open source file.
|
|
55
|
+
# @parameter file [File] The open source file.
|
|
56
|
+
# @returns [String | Object] The source contents without a block, or the block result with a block.
|
|
33
57
|
def read(&block)
|
|
34
58
|
if block_given?
|
|
35
59
|
File.open(self.path, "r", &block)
|
|
@@ -39,14 +63,19 @@ module Covered
|
|
|
39
63
|
end
|
|
40
64
|
|
|
41
65
|
# The actual code which is being covered. If a template generates the source, this is the generated code, while the path refers to the template itself.
|
|
66
|
+
# @returns [String] The generated code when present, otherwise the file contents.
|
|
42
67
|
def code!
|
|
43
68
|
self.code || self.read
|
|
44
69
|
end
|
|
45
70
|
|
|
71
|
+
# Whether generated source code is present.
|
|
72
|
+
# @returns [Boolean] Whether this source has generated code.
|
|
46
73
|
def code?
|
|
47
74
|
!!self.code
|
|
48
75
|
end
|
|
49
76
|
|
|
77
|
+
# Serialize this source with the given packer.
|
|
78
|
+
# @parameter packer [Object] The MessagePack-compatible packer.
|
|
50
79
|
def serialize(packer)
|
|
51
80
|
packer.write(self.path)
|
|
52
81
|
packer.write(self.code)
|
|
@@ -54,6 +83,9 @@ module Covered
|
|
|
54
83
|
packer.write(self.modified_time)
|
|
55
84
|
end
|
|
56
85
|
|
|
86
|
+
# Deserialize a source from the given unpacker.
|
|
87
|
+
# @parameter unpacker [Object] The MessagePack-compatible unpacker.
|
|
88
|
+
# @returns [Covered::Source] The deserialized source metadata.
|
|
57
89
|
def self.deserialize(unpacker)
|
|
58
90
|
path = unpacker.read
|
|
59
91
|
code = unpacker.read
|