covered 0.23.0 → 0.24.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/bake/covered/policy.rb +17 -0
- data/lib/covered/coverage.rb +68 -23
- data/lib/covered/files.rb +7 -61
- data/lib/covered/source.rb +1 -1
- data/lib/covered/statistics.rb +83 -25
- data/lib/covered/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +1 -1
- 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: 05e2a8d6cec9c00fffb320cdb4e6a2e7da8fd9a5361ee762d00353d576c98343
|
4
|
+
data.tar.gz: b167be6adaafcbaca11040da20e8593588c810cc20baeacccf344dad73315638
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc31923a4d1dfeaed6be0e7b460c3fd4dff9b9a4d5aaad7b5a6868047eb075c5be0569f981fd274ce4e0548d8c18797dd0fc05ff0ef10a4994edda2a0e1990ab
|
7
|
+
data.tar.gz: 1a298bd1af4ac74b56418f46f4cac2d2c0d7618a34c836a404481cc6c31e38795047a7b427ac93d2a2ae85ed3f52eec4b02292e97ad3b8ce8ce26bf1bb9a17d2
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/bake/covered/policy.rb
CHANGED
@@ -29,3 +29,20 @@ def current(paths: nil)
|
|
29
29
|
|
30
30
|
return policy
|
31
31
|
end
|
32
|
+
|
33
|
+
# Validate the coverage of multiple test runs.
|
34
|
+
# @parameter paths [Array(String)] The coverage database paths.
|
35
|
+
# @parameter minimum [Float] The minimum required coverage in order to pass.
|
36
|
+
# @parameter input [Covered::Policy] The input policy to validate.
|
37
|
+
def statistics(paths: nil, minimum: 1.0, input:)
|
38
|
+
policy ||= context.lookup("covered:policy:current").call(paths: paths)
|
39
|
+
|
40
|
+
# Calculate statistics:
|
41
|
+
statistics = Covered::Statistics.new
|
42
|
+
|
43
|
+
policy.each do |coverage|
|
44
|
+
statistics << coverage
|
45
|
+
end
|
46
|
+
|
47
|
+
return statistics
|
48
|
+
end
|
data/lib/covered/coverage.rb
CHANGED
@@ -8,7 +8,7 @@ require_relative 'source'
|
|
8
8
|
module Covered
|
9
9
|
module Ratio
|
10
10
|
def ratio
|
11
|
-
return 0 if executable_count.zero?
|
11
|
+
return 1.0 if executable_count.zero?
|
12
12
|
|
13
13
|
Rational(executed_count, executable_count)
|
14
14
|
end
|
@@ -29,22 +29,67 @@ module Covered
|
|
29
29
|
self.new(Source.for(path, **options))
|
30
30
|
end
|
31
31
|
|
32
|
-
def initialize(source, counts = [], annotations = {}
|
32
|
+
def initialize(source, counts = [], annotations = {})
|
33
33
|
@source = source
|
34
34
|
@counts = counts
|
35
35
|
@annotations = annotations
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_accessor :source
|
39
|
+
attr :counts
|
40
|
+
attr :annotations
|
41
|
+
|
42
|
+
def total
|
43
|
+
counts.sum{|count| count || 0}
|
44
|
+
end
|
45
|
+
|
46
|
+
# Create an empty coverage with the same source.
|
47
|
+
def empty
|
48
|
+
self.class.new(@source, [nil] * @counts.size)
|
49
|
+
end
|
50
|
+
|
51
|
+
def annotate(line_number, annotation)
|
52
|
+
@annotations[line_number] ||= []
|
53
|
+
@annotations[line_number] << annotation
|
54
|
+
end
|
55
|
+
|
56
|
+
def mark(line_number, value = 1)
|
57
|
+
# As currently implemented, @counts is base-zero rather than base-one.
|
58
|
+
# 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.
|
59
|
+
Array(value).each_with_index do |value, index|
|
60
|
+
offset = line_number + index
|
61
|
+
if @counts[offset]
|
62
|
+
@counts[offset] += value
|
63
|
+
else
|
64
|
+
@counts[offset] = value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def merge!(other)
|
70
|
+
# If the counts are non-zero and don't match, that can indicate a problem.
|
36
71
|
|
37
|
-
|
72
|
+
other.counts.each_with_index do |count, index|
|
73
|
+
if count
|
74
|
+
@counts[index] ||= 0
|
75
|
+
@counts[index] += count
|
76
|
+
end
|
77
|
+
end
|
38
78
|
|
39
|
-
|
40
|
-
|
41
|
-
|
79
|
+
@annotations.merge!(other.annotations) do |line_number, a, b|
|
80
|
+
Array(a) + Array(b)
|
81
|
+
end
|
42
82
|
end
|
43
83
|
|
44
84
|
# Construct a new coverage object for the given line numbers. Only the given line numbers will be considered for the purposes of computing coverage.
|
45
85
|
# @parameter line_numbers [Array(Integer)] The line numbers to include in the new coverage object.
|
46
86
|
def for_lines(line_numbers)
|
47
|
-
|
87
|
+
counts = [nil] * @counts.size
|
88
|
+
line_numbers.each do |line_number|
|
89
|
+
counts[line_number] = @counts[line_number]
|
90
|
+
end
|
91
|
+
|
92
|
+
self.class.new(@source, counts, @annotations)
|
48
93
|
end
|
49
94
|
|
50
95
|
def path
|
@@ -74,13 +119,6 @@ module Covered
|
|
74
119
|
return false
|
75
120
|
end
|
76
121
|
|
77
|
-
attr_accessor :source
|
78
|
-
|
79
|
-
attr :counts
|
80
|
-
attr :total
|
81
|
-
|
82
|
-
attr :annotations
|
83
|
-
|
84
122
|
def read(&block)
|
85
123
|
@source.read(&block)
|
86
124
|
end
|
@@ -99,15 +137,15 @@ module Covered
|
|
99
137
|
end
|
100
138
|
|
101
139
|
def zero?
|
102
|
-
|
140
|
+
total.zero?
|
103
141
|
end
|
104
142
|
|
105
|
-
def []
|
106
|
-
@counts[
|
143
|
+
def [] line_number
|
144
|
+
@counts[line_number]
|
107
145
|
end
|
108
146
|
|
109
147
|
def executable_lines
|
110
|
-
@
|
148
|
+
@counts.compact
|
111
149
|
end
|
112
150
|
|
113
151
|
def executable_count
|
@@ -115,7 +153,7 @@ module Covered
|
|
115
153
|
end
|
116
154
|
|
117
155
|
def executed_lines
|
118
|
-
|
156
|
+
executable_lines.reject(&:zero?)
|
119
157
|
end
|
120
158
|
|
121
159
|
def executed_count
|
@@ -131,23 +169,30 @@ module Covered
|
|
131
169
|
end
|
132
170
|
|
133
171
|
def to_s
|
134
|
-
"\#<#{self.class} path=#{self.path} #{self.
|
172
|
+
"\#<#{self.class} path=#{self.path} #{self.percentage.to_f.round(2)}% covered>"
|
173
|
+
end
|
174
|
+
|
175
|
+
def as_json
|
176
|
+
{
|
177
|
+
counts: counts,
|
178
|
+
executable_count: executable_count,
|
179
|
+
executed_count: executed_count,
|
180
|
+
percentage: percentage.to_f.round(2),
|
181
|
+
}
|
135
182
|
end
|
136
183
|
|
137
184
|
def serialize(packer)
|
138
185
|
packer.write(@source)
|
139
186
|
packer.write(@counts)
|
140
187
|
packer.write(@annotations)
|
141
|
-
packer.write(@total)
|
142
188
|
end
|
143
189
|
|
144
190
|
def self.deserialize(unpacker)
|
145
191
|
source = unpacker.read
|
146
192
|
counts = unpacker.read
|
147
193
|
annotations = unpacker.read
|
148
|
-
total = unpacker.read
|
149
194
|
|
150
|
-
self.new(source, counts, annotations
|
195
|
+
self.new(source, counts, annotations)
|
151
196
|
end
|
152
197
|
end
|
153
198
|
end
|
data/lib/covered/files.rb
CHANGED
@@ -10,60 +10,6 @@ require 'set'
|
|
10
10
|
|
11
11
|
module Covered
|
12
12
|
class Files < Base
|
13
|
-
class State
|
14
|
-
def self.for(path, **options)
|
15
|
-
self.new(Source.for(path, **options))
|
16
|
-
end
|
17
|
-
|
18
|
-
def initialize(source)
|
19
|
-
@source = source
|
20
|
-
@counts = []
|
21
|
-
@annotations = {}
|
22
|
-
end
|
23
|
-
|
24
|
-
def [](lineno)
|
25
|
-
@counts[lineno]
|
26
|
-
end
|
27
|
-
|
28
|
-
attr :counts
|
29
|
-
attr :annotations
|
30
|
-
|
31
|
-
def annotate(lineno, annotation)
|
32
|
-
@annotations[lineno] ||= []
|
33
|
-
@annotations[lineno] << annotation
|
34
|
-
end
|
35
|
-
|
36
|
-
def mark(lineno, value = 1)
|
37
|
-
# As currently implemented, @counts is base-zero rather than base-one.
|
38
|
-
# 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.
|
39
|
-
Array(value).each_with_index do |value, index|
|
40
|
-
offset = lineno + index
|
41
|
-
if @counts[offset]
|
42
|
-
@counts[offset] += value
|
43
|
-
else
|
44
|
-
@counts[offset] = value
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def merge!(coverage)
|
50
|
-
coverage.counts.each_with_index do |count, index|
|
51
|
-
if count
|
52
|
-
@counts[index] ||= 0
|
53
|
-
@counts[index] += count
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
@annotations.merge!(coverage.annotations) do |lineno, a, b|
|
58
|
-
Array(a) + Array(b)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def coverage
|
63
|
-
Coverage.new(@source, @counts, @annotations)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
13
|
def initialize(*)
|
68
14
|
super
|
69
15
|
|
@@ -73,19 +19,19 @@ module Covered
|
|
73
19
|
attr_accessor :paths
|
74
20
|
|
75
21
|
def [](path)
|
76
|
-
@paths[path] ||=
|
22
|
+
@paths[path] ||= Coverage.for(path)
|
77
23
|
end
|
78
24
|
|
79
25
|
def empty?
|
80
26
|
@paths.empty?
|
81
27
|
end
|
82
28
|
|
83
|
-
def mark(path,
|
84
|
-
self[path].mark(
|
29
|
+
def mark(path, line_number, value)
|
30
|
+
self[path].mark(line_number, value)
|
85
31
|
end
|
86
32
|
|
87
|
-
def annotate(path,
|
88
|
-
self[path].annotate(
|
33
|
+
def annotate(path, line_number, value)
|
34
|
+
self[path].annotate(line_number, value)
|
89
35
|
end
|
90
36
|
|
91
37
|
def add(coverage)
|
@@ -95,8 +41,8 @@ module Covered
|
|
95
41
|
def each
|
96
42
|
return to_enum unless block_given?
|
97
43
|
|
98
|
-
@paths.each_value do |
|
99
|
-
yield
|
44
|
+
@paths.each_value do |coverage|
|
45
|
+
yield coverage
|
100
46
|
end
|
101
47
|
end
|
102
48
|
|
data/lib/covered/source.rb
CHANGED
data/lib/covered/statistics.rb
CHANGED
@@ -10,34 +10,86 @@ module Covered
|
|
10
10
|
class CoverageError < StandardError
|
11
11
|
end
|
12
12
|
|
13
|
-
class Statistics
|
13
|
+
class Statistics
|
14
|
+
include Ratio
|
15
|
+
|
14
16
|
def self.for(coverage)
|
15
17
|
self.new.tap do |statistics|
|
16
18
|
statistics << coverage
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
22
|
+
class Aggregate
|
23
|
+
include Ratio
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@count = 0
|
27
|
+
@executable_count = 0
|
28
|
+
@executed_count = 0
|
29
|
+
end
|
30
|
+
|
31
|
+
# Total number of files added.
|
32
|
+
attr :count
|
33
|
+
|
34
|
+
# The number of lines which could have been executed.
|
35
|
+
attr :executable_count
|
36
|
+
|
37
|
+
# The number of lines that were executed.
|
38
|
+
attr :executed_count
|
39
|
+
|
40
|
+
def as_json
|
41
|
+
{
|
42
|
+
count: count,
|
43
|
+
executable_count: executable_count,
|
44
|
+
executed_count: executed_count,
|
45
|
+
percentage: percentage.to_f.round(2),
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_json(options)
|
50
|
+
as_json.to_json(options)
|
51
|
+
end
|
52
|
+
|
53
|
+
def << coverage
|
54
|
+
@count += 1
|
55
|
+
|
56
|
+
@executable_count += coverage.executable_count
|
57
|
+
@executed_count += coverage.executed_count
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
20
61
|
def initialize
|
21
|
-
@
|
22
|
-
@
|
23
|
-
@executed_count = 0
|
62
|
+
@total = Aggregate.new
|
63
|
+
@paths = Hash.new
|
24
64
|
end
|
25
65
|
|
26
|
-
|
27
|
-
|
66
|
+
def count
|
67
|
+
@paths.size
|
68
|
+
end
|
69
|
+
|
70
|
+
def executable_count
|
71
|
+
@total.executable_count
|
72
|
+
end
|
73
|
+
|
74
|
+
def executed_count
|
75
|
+
@total.executed_count
|
76
|
+
end
|
28
77
|
|
29
|
-
|
30
|
-
|
78
|
+
def << coverage
|
79
|
+
@total << coverage
|
80
|
+
(@paths[coverage.path] ||= coverage.empty).merge!(coverage)
|
81
|
+
end
|
31
82
|
|
32
|
-
|
33
|
-
|
83
|
+
attr :total
|
84
|
+
|
85
|
+
def [](path)
|
86
|
+
@paths[path]
|
87
|
+
end
|
34
88
|
|
35
89
|
def as_json
|
36
90
|
{
|
37
|
-
|
38
|
-
|
39
|
-
executed_count: executed_count,
|
40
|
-
percentage: percentage.to_f.round(2),
|
91
|
+
total: total.as_json,
|
92
|
+
paths: @paths.map{|path, coverage| [path, coverage.as_json]}.to_h,
|
41
93
|
}
|
42
94
|
end
|
43
95
|
|
@@ -45,23 +97,29 @@ module Covered
|
|
45
97
|
as_json.to_json(options)
|
46
98
|
end
|
47
99
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
100
|
+
COMPLETE = [
|
101
|
+
"Enter the code dojo: 100% coverage attained, bugs defeated with one swift strike.",
|
102
|
+
"Nirvana reached: 100% code coverage, where bugs meditate and vanish like a passing cloud.",
|
103
|
+
"With 100% coverage, your code has unlocked the path to enlightenment – bugs have no place to hide.",
|
104
|
+
"In the realm of code serenity, 100% coverage is your ticket to coding enlightenment.",
|
105
|
+
"100% coverage, where code and bugs coexist in perfect harmony, like Yin and Yang.",
|
106
|
+
"Achieving the Zen of code coverage, your code is a peaceful garden where bugs find no shelter.",
|
107
|
+
"Congratulations on coding enlightenment! 100% coverage means your code is one with the universe.",
|
108
|
+
"With 100% coverage, your code is a tranquil pond where bugs cause no ripples.",
|
109
|
+
"At the peak of code mastery: 100% coverage, where bugs bow down before the wisdom of your code.",
|
110
|
+
"100% code coverage: Zen achieved! Bugs in harmony, code at peace.",
|
111
|
+
]
|
56
112
|
|
57
113
|
def print(output)
|
58
|
-
output.puts "
|
114
|
+
output.puts "#{count} files checked; #{@total.executed_count}/#{@total.executable_count} lines executed; #{@total.percentage.to_f.round(2)}% covered."
|
59
115
|
|
60
|
-
|
116
|
+
if self.complete?
|
117
|
+
output.puts "🧘 #{COMPLETE.sample}"
|
118
|
+
end
|
61
119
|
end
|
62
120
|
|
63
121
|
def validate!(minimum = 1.0)
|
64
|
-
if
|
122
|
+
if total.ratio < minimum
|
65
123
|
raise CoverageError, "Coverage of #{self.percentage.to_f.round(2)}% is less than required minimum of #{(minimum * 100.0).round(2)}%!"
|
66
124
|
end
|
67
125
|
end
|
data/lib/covered/version.rb
CHANGED
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
metadata.gz.sig
CHANGED
Binary file
|