covered 0.23.0 → 0.24.0

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: 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