covered 0.23.0 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a79659c74af1e795823f64d18c4829240e5be7cdcf03b3f719680b9d29f9522
4
- data.tar.gz: f0d8ad9bb7330adf479b0a028bc7a03e73ff35f091d79c4308f461f984f65f77
3
+ metadata.gz: 6101920cc470784c2969e8d78252c81678104f6cd882038a306e633f3ec34267
4
+ data.tar.gz: 213bc88224aba333120b869360a663f02355ace3f1dcb4106c5fcdbebd61cd7e
5
5
  SHA512:
6
- metadata.gz: d205944455af6959144bc22e7456ed165d308fde5989b5e6b781458aead3ded563fc7efcea3fa3ede628e85d0b678dcad82f0caa4ce851c3fa02ebd2f3432a08
7
- data.tar.gz: 5f1d51e92917387f004e38e4e7ad0754ff1063ea348a7a4a36d52b27703fc1bb66ecd8dbc8a5815aeeaeaf71663e49c6faf072e7e7fec984c5c7c368d0035719
6
+ metadata.gz: aabfd2999173af2de001ce8a4718483c9801497bb064b27c6892d8c6cb80dc8b2207db0de41a0eb8e4116deac5a2eaf8f68cc2febe8acbea84c4795b666992c1
7
+ data.tar.gz: 302d49b96fcad976a40bc86422976e1526d0b410ed72112190787a8c1ea75309015fe0c6c2fa8359b8d3cf60f9b93f63b021dab063b8e3c1a08d24cb9b79fb59
checksums.yaml.gz.sig CHANGED
Binary file
@@ -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
@@ -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,65 @@ module Covered
29
29
  self.new(Source.for(path, **options))
30
30
  end
31
31
 
32
- def initialize(source, counts = [], annotations = {}, total = nil)
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
+ other.counts.each_with_index do |count, index|
71
+ if count
72
+ @counts[index] ||= 0
73
+ @counts[index] += count
74
+ end
75
+ end
36
76
 
37
- @total = total || counts.sum{|count| count || 0}
38
-
39
- # Memoized metrics:
40
- @executable_lines = nil
41
- @executed_lines = nil
77
+ @annotations.merge!(other.annotations) do |line_number, a, b|
78
+ Array(a) + Array(b)
79
+ end
42
80
  end
43
81
 
44
82
  # 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
83
  # @parameter line_numbers [Array(Integer)] The line numbers to include in the new coverage object.
46
84
  def for_lines(line_numbers)
47
- self.class.new(@source, @counts.values_at(*line_numbers), @annotations)
85
+ counts = [nil] * @counts.size
86
+ line_numbers.each do |line_number|
87
+ counts[line_number] = @counts[line_number]
88
+ end
89
+
90
+ self.class.new(@source, counts, @annotations)
48
91
  end
49
92
 
50
93
  def path
@@ -74,13 +117,6 @@ module Covered
74
117
  return false
75
118
  end
76
119
 
77
- attr_accessor :source
78
-
79
- attr :counts
80
- attr :total
81
-
82
- attr :annotations
83
-
84
120
  def read(&block)
85
121
  @source.read(&block)
86
122
  end
@@ -99,15 +135,15 @@ module Covered
99
135
  end
100
136
 
101
137
  def zero?
102
- @total.zero?
138
+ total.zero?
103
139
  end
104
140
 
105
- def [] lineno
106
- @counts[lineno]
141
+ def [] line_number
142
+ @counts[line_number]
107
143
  end
108
144
 
109
145
  def executable_lines
110
- @executable_lines ||= @counts.compact
146
+ @counts.compact
111
147
  end
112
148
 
113
149
  def executable_count
@@ -115,7 +151,7 @@ module Covered
115
151
  end
116
152
 
117
153
  def executed_lines
118
- @executed_lines ||= executable_lines.reject(&:zero?)
154
+ executable_lines.reject(&:zero?)
119
155
  end
120
156
 
121
157
  def executed_count
@@ -131,23 +167,30 @@ module Covered
131
167
  end
132
168
 
133
169
  def to_s
134
- "\#<#{self.class} path=#{self.path} #{self.summary.percentage.to_f.round(2)}% covered>"
170
+ "\#<#{self.class} path=#{self.path} #{self.percentage.to_f.round(2)}% covered>"
171
+ end
172
+
173
+ def as_json
174
+ {
175
+ counts: counts,
176
+ executable_count: executable_count,
177
+ executed_count: executed_count,
178
+ percentage: percentage.to_f.round(2),
179
+ }
135
180
  end
136
181
 
137
182
  def serialize(packer)
138
183
  packer.write(@source)
139
184
  packer.write(@counts)
140
185
  packer.write(@annotations)
141
- packer.write(@total)
142
186
  end
143
187
 
144
188
  def self.deserialize(unpacker)
145
189
  source = unpacker.read
146
190
  counts = unpacker.read
147
191
  annotations = unpacker.read
148
- total = unpacker.read
149
192
 
150
- self.new(source, counts, annotations, total)
193
+ self.new(source, counts, annotations)
151
194
  end
152
195
  end
153
196
  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] ||= State.for(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, lineno, value)
84
- self[path].mark(lineno, value)
29
+ def mark(path, line_number, value)
30
+ self[path].mark(line_number, value)
85
31
  end
86
32
 
87
- def annotate(path, lineno, value)
88
- self[path].annotate(lineno, value)
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 |state|
99
- yield state.coverage
44
+ @paths.each_value do |coverage|
45
+ yield coverage
100
46
  end
101
47
  end
102
48
 
@@ -10,34 +10,86 @@ module Covered
10
10
  class CoverageError < StandardError
11
11
  end
12
12
 
13
- class Statistics < Wrapper
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
- @count = 0
22
- @executable_count = 0
23
- @executed_count = 0
62
+ @total = Aggregate.new
63
+ @paths = Hash.new
24
64
  end
25
65
 
26
- # Total number of files added.
27
- attr :count
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
- # The number of lines which could have been executed.
30
- attr :executable_count
78
+ def << coverage
79
+ @total << coverage
80
+ (@paths[coverage.path] ||= coverage.empty).merge!(coverage)
81
+ end
31
82
 
32
- # The number of lines that were executed.
33
- attr :executed_count
83
+ attr :total
84
+
85
+ def [](path)
86
+ @paths[path]
87
+ end
34
88
 
35
89
  def as_json
36
90
  {
37
- count: count,
38
- executable_count: executable_count,
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
- def << coverage
49
- @count += 1
50
-
51
- @executable_count += coverage.executable_count
52
- @executed_count += coverage.executed_count
53
- end
54
-
55
- include Ratio
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 "* #{count} files checked; #{executed_count}/#{executable_count} lines executed; #{percentage.to_f.round(2)}% covered."
114
+ output.puts "#{count} files checked; #{@total.executed_count}/#{@total.executable_count} lines executed; #{@total.percentage.to_f.round(2)}% covered."
59
115
 
60
- # Could output funny message here, especially for 100% coverage.
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 self.ratio < minimum
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
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2018-2023, by Samuel Williams.
5
5
 
6
6
  module Covered
7
- VERSION = "0.23.0"
7
+ VERSION = "0.24.0"
8
8
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: covered
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.0
4
+ version: 0.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
metadata.gz.sig CHANGED
Binary file