covered 0.19.0 → 0.20.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: d7f641ef55ad67441aee4c21d0f7221b955f17340baf9e51d7c133c58c5343ae
4
- data.tar.gz: 43ccf1cf59d39fef08a271a2a3b97c4756b9e08916a4fb31efcd5903d8ff14cc
3
+ metadata.gz: e865810f0e3c42f28a7bf1569449240d46f6304a0803b2d34006c3d8ecb4b17a
4
+ data.tar.gz: b0da723e52175e6766fae5c9d4e9c61b4619fd19867ee6ce0970008c61bc7330
5
5
  SHA512:
6
- metadata.gz: cf1576281c6fb28953788771f793c88ed0cb96b26795d78fbbd15a60f216140c69f24b3a5621279e5bde25fb00e77bb3a8c398f2512c465d7fa62f85ff35a792
7
- data.tar.gz: bfe434cc0c0d3162e87192b714d3459341178649907e187ec9d3aa93e2567f9eea98b9e0cc8831379a647913b9d1862d51e3dc852c42a7ffb37277f04640ff5b
6
+ metadata.gz: 4858522284e9e50cff92558194a7caf5a08604b45602bd83b6137d71e0a6537a3569bae82270efde1f3ee6fefa58015fbcac036c8eba36a72f52f3f41cfc1078
7
+ data.tar.gz: ee3779802478b0d9097e8ec00c93a2cc25c6a01e72963c762f0e463759b5284b43820d3213b0927e9402db77c31a639f04ac4fc3beceab5b9f199a3b84028c30
checksums.yaml.gz.sig CHANGED
@@ -1,4 +1,5 @@
1
- (K�������y6��ȫ�L4[Q׺��2<�V
2
- T{��|���EB��D�Wݒ��%д�+�?T6��y�Z�X�rw.m�H�
3
- �^�ڠIQ4
4
- �S ��;&:"�Gw?B^1�5c�S��}_�y����Px�� ��SPn�������<`��tS��}O�u�:*Mze�|jm�uД|@f�|А�kf�b�=ٗ�3@�>9?���Q6>���@ftT_��u��~]߼�X��N���E�`B�O���(�ܳ�қ��
1
+ ��j}�Xo7򊡔�)��iD���:J��
2
+ �!kG^]��*��;�8e~����>��|�~�\(mZ��Ed ��
3
+ B�
4
+ a��ƛ���N�؇�kS1
5
+ “����ʃ2�
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2022-2023, by Samuel Williams.
5
5
 
6
6
  def initialize(context)
7
7
  super
@@ -23,11 +23,11 @@ def parse(paths: [], execute: false)
23
23
 
24
24
  if execute
25
25
  capture = Covered::Capture.new(output)
26
- capture.enable
26
+ capture.start
27
27
  paths.each do |path|
28
28
  load path
29
29
  end
30
- capture.disable
30
+ capture.finish
31
31
 
32
32
  files.paths = files.paths.slice(*paths)
33
33
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2022-2023, by Samuel Williams.
5
5
 
6
6
  def initialize(context)
7
7
  super
@@ -20,8 +20,6 @@ def validate(paths: nil, minimum: 1.0)
20
20
  Covered::Persist.new(config.output, path).load!(ignore_mtime: true)
21
21
  end
22
22
 
23
- config.flush
24
-
25
23
  statistics = Covered::Statistics.new
26
24
 
27
25
  config.each do |coverage|
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require_relative 'config'
7
+
8
+ module Coverage
9
+ module Autostart
10
+ # Start recording coverage information.
11
+ # Usage: RUBYOPT=-rcovered/autostart ruby my_script.rb
12
+ def self.autostart!
13
+ config = Covered::Config.load
14
+ config.start
15
+
16
+ pid = Process.pid
17
+
18
+ at_exit do
19
+ # Don't break forked children:
20
+ if Process.pid == pid
21
+ config.finish
22
+
23
+ if config.report?
24
+ config.call($stderr)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ Coverage::Autostart.autostart!
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2022, by Samuel Williams.
4
+ # Copyright, 2018-2023, by Samuel Williams.
5
5
 
6
6
  require_relative 'wrapper'
7
7
 
@@ -9,32 +9,39 @@ require 'coverage'
9
9
 
10
10
  module Covered
11
11
  class Capture < Wrapper
12
- def enable
12
+ def start
13
13
  super
14
14
 
15
15
  ::Coverage.start(lines: true, eval: true)
16
16
  end
17
17
 
18
- def disable
18
+ def clear
19
+ super
20
+
21
+ ::Coverage.result(stop: false, clear: true)
22
+ end
23
+
24
+ def finish
19
25
  results = ::Coverage.result
20
-
26
+
21
27
  results.each do |path, result|
22
28
  lines = result[:lines]
23
-
29
+ path = self.expand_path(path)
30
+
24
31
  lines.each_with_index do |count, lineno|
25
32
  @output.mark(path, lineno+1, count) if count
26
33
  end
27
34
  end
28
-
35
+
29
36
  super
30
37
  end
31
-
38
+
32
39
  def execute(source, binding: TOPLEVEL_BINDING)
33
- enable
40
+ start
34
41
 
35
42
  eval(source.code!, binding, source.path, source.line_offset)
36
43
  ensure
37
- disable
44
+ finish
38
45
  end
39
46
  end
40
47
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2022-2023, by Samuel Williams.
5
5
 
6
6
  require_relative 'policy'
7
7
 
@@ -9,8 +9,12 @@ module Covered
9
9
  class Config
10
10
  PATH = "config/covered.rb"
11
11
 
12
+ def self.root
13
+ ENV['COVERED_ROOT'] || Dir.pwd
14
+ end
15
+
12
16
  def self.path(root)
13
- path = ::File.join(root, PATH)
17
+ path = ::File.expand_path(PATH, root)
14
18
 
15
19
  if ::File.exist?(path)
16
20
  return path
@@ -21,7 +25,7 @@ module Covered
21
25
  ENV['COVERAGE']
22
26
  end
23
27
 
24
- def self.load(root: Dir.pwd, coverage: self.coverage)
28
+ def self.load(root: self.root, coverage: self.coverage)
25
29
  derived = Class.new(self)
26
30
 
27
31
  if path = self.path(root)
@@ -37,12 +41,16 @@ module Covered
37
41
  @root = root
38
42
  @coverage = coverage
39
43
  @policy = nil
44
+
45
+ @environment = nil
40
46
  end
41
47
 
42
- def record?
48
+ def report?
43
49
  !!@coverage
44
50
  end
45
51
 
52
+ alias :record? :report?
53
+
46
54
  attr :coverage
47
55
 
48
56
  def policy
@@ -53,16 +61,22 @@ module Covered
53
61
  policy.output
54
62
  end
55
63
 
56
- def enable
57
- policy.enable
58
- end
59
-
60
- def disable
61
- policy.disable
64
+ def start
65
+ # Save and setup the environment:
66
+ @environment = ENV.to_h
67
+ autostart!
68
+
69
+ # Start coverage tracking:
70
+ policy.start
62
71
  end
63
72
 
64
- def flush
65
- policy.flush
73
+ def finish
74
+ # Finish coverage tracking:
75
+ policy.finish
76
+
77
+ # Restore the environment:
78
+ ENV.replace(@environment)
79
+ @environment = nil
66
80
  end
67
81
 
68
82
  def call(output)
@@ -73,15 +87,22 @@ module Covered
73
87
  policy.each(&block)
74
88
  end
75
89
 
90
+ def ignore_paths
91
+ ['test/', 'fixtures/', 'spec/', 'vendor/', 'config/']
92
+ end
93
+
94
+
76
95
  # Override this method to implement your own policy.
77
96
  def make_policy(policy)
78
- policy.cache!
79
-
80
97
  # Only files in the root would be tracked:
81
98
  policy.root(@root)
82
99
 
100
+ patterns = ignore_paths.map do |path|
101
+ File.join(@root, path)
102
+ end
103
+
83
104
  # We will ignore any files in the test or spec directory:
84
- policy.skip(/^.*\/(test|fixtures|spec|vendor|config)\//)
105
+ policy.skip(Regexp.union(patterns))
85
106
 
86
107
  # We will include all files under lib, even if they aren't loaded:
87
108
  policy.include("lib/**/*.rb")
@@ -90,5 +111,26 @@ module Covered
90
111
 
91
112
  policy.reports!(@coverage)
92
113
  end
114
+
115
+ protected
116
+
117
+ REQUIRE_COVERED_AUTOSTART = '-rcovered/autostart'
118
+
119
+ def autostart!
120
+ if rubyopt = ENV['RUBYOPT'] and !rubyopt.empty?
121
+ rubyopt = [rubyopt.strip, REQUIRE_COVERED_AUTOSTART].join(' ')
122
+ else
123
+ rubyopt = REQUIRE_COVERED_AUTOSTART
124
+ end
125
+
126
+ ENV['RUBYOPT'] = rubyopt
127
+
128
+ unless ENV['COVERED_ROOT']
129
+ ENV['COVERED_ROOT'] = @root
130
+ end
131
+
132
+ # Don't report coverage in child processes:
133
+ ENV.delete('COVERAGE')
134
+ end
93
135
  end
94
136
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2022, by Samuel Williams.
4
+ # Copyright, 2018-2023, by Samuel Williams.
5
5
 
6
6
  module Covered
7
7
  module Ratio
@@ -20,45 +20,76 @@ module Covered
20
20
  end
21
21
  end
22
22
 
23
- class Coverage
24
- Source = Struct.new(:path, :code, :line_offset) do
25
- def to_s
26
- "\#<#{self.class} path=#{path}>"
27
- end
28
-
29
- def read(&block)
30
- if block_given?
31
- File.open(self.path, "r", &block)
32
- else
33
- File.read(self.path)
34
- end
35
- end
36
-
37
- # 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.
38
- def code!
39
- self.code || self.read
40
- end
41
-
42
- def code?
43
- !!self.code
23
+ class Source
24
+ def self.for(path, code, line_offset)
25
+ self.new(path, code: code, line_offset: line_offset)
26
+ end
27
+
28
+ def initialize(path, code: nil, line_offset: 1, modified_time: nil)
29
+ @path = path
30
+ @code = code
31
+ @line_offset = line_offset
32
+ @modified_time = modified_time
33
+ end
34
+
35
+ attr_accessor :path
36
+ attr :code
37
+ attr :line_offset
38
+ attr :modified_time
39
+
40
+ def to_s
41
+ "\#<#{self.class} path=#{path}>"
42
+ end
43
+
44
+ def read(&block)
45
+ if block_given?
46
+ File.open(self.path, "r", &block)
47
+ else
48
+ File.read(self.path)
44
49
  end
45
50
  end
46
51
 
47
- def self.source(path, code = nil, line_offset = 1)
48
- Source.new(path, code, line_offset)
52
+ # 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.
53
+ def code!
54
+ self.code || self.read
49
55
  end
50
56
 
51
- def self.for(path, code = nil, line_offset = 1)
52
- self.new(Source.new(path, code, line_offset))
57
+ def code?
58
+ !!self.code
53
59
  end
54
60
 
55
- def initialize(source, counts = [])
61
+ def serialize(packer)
62
+ packer.write(self.path)
63
+ packer.write(self.code)
64
+ packer.write(self.line_offset)
65
+ packer.write(self.modified_time)
66
+ end
67
+
68
+ def self.deserialize(unpacker)
69
+ path = unpacker.read
70
+ code = unpacker.read
71
+ line_offset = unpacker.read
72
+ modified_time = unpacker.read
73
+
74
+ self.new(path, code: code, line_offset: line_offset, modified_time: modified_time)
75
+ end
76
+ end
77
+
78
+ class Coverage
79
+ include Ratio
80
+
81
+ def self.for(path, **options)
82
+ self.new(Source.new(path, **options))
83
+ end
84
+
85
+ def initialize(source, counts = [], annotations = {}, total = nil)
56
86
  @source = source
57
87
  @counts = counts
58
- @total = 0
88
+ @annotations = annotations
59
89
 
60
- @annotations = {}
90
+ @total = total || counts.sum{|count| count || 0}
61
91
 
92
+ # Memoized metrics:
62
93
  @executable_lines = nil
63
94
  @executed_lines = nil
64
95
  end
@@ -67,6 +98,29 @@ module Covered
67
98
  @source.path
68
99
  end
69
100
 
101
+ def path= value
102
+ @source.path = value
103
+ end
104
+
105
+ def fresh?
106
+ if @source.modified_time.nil?
107
+ # We don't know when the file was last modified, so we assume it is fresh:
108
+ return true
109
+ end
110
+
111
+ unless File.exist?(@source.path)
112
+ # The file no longer exists, so we assume it is stale:
113
+ return false
114
+ end
115
+
116
+ if @source.modified_time >= File.mtime(@source.path)
117
+ # The file has not been modified since we last processed it, so we assume it is fresh:
118
+ return true
119
+ end
120
+
121
+ return false
122
+ end
123
+
70
124
  attr_accessor :source
71
125
 
72
126
  attr :counts
@@ -84,9 +138,6 @@ module Covered
84
138
  @counts.freeze
85
139
  @annotations.freeze
86
140
 
87
- executable_lines
88
- executed_lines
89
-
90
141
  super
91
142
  end
92
143
 
@@ -102,21 +153,6 @@ module Covered
102
153
  @counts[lineno]
103
154
  end
104
155
 
105
- def annotate(lineno, annotation)
106
- @annotations[lineno] ||= []
107
- @annotations[lineno] << annotation
108
- end
109
-
110
- def mark(lineno, value = 1)
111
- @total += value
112
-
113
- if @counts[lineno]
114
- @counts[lineno] += value
115
- else
116
- @counts[lineno] = value
117
- end
118
- end
119
-
120
156
  def executable_lines
121
157
  @executable_lines ||= @counts.compact
122
158
  end
@@ -137,14 +173,28 @@ module Covered
137
173
  executable_count - executed_count
138
174
  end
139
175
 
140
- include Ratio
141
-
142
176
  def print(output)
143
177
  output.puts "** #{executed_count}/#{executable_count} lines executed; #{percentage.to_f.round(2)}% covered."
144
178
  end
145
179
 
146
180
  def to_s
147
- "\#<#{self.class} path=#{@path} #{percentage.to_f.round(2)}% covered>"
181
+ "\#<#{self.class} path=#{self.path} #{self.summary.percentage.to_f.round(2)}% covered>"
182
+ end
183
+
184
+ def serialize(packer)
185
+ packer.write(@source)
186
+ packer.write(@counts)
187
+ packer.write(@annotations)
188
+ packer.write(@total)
189
+ end
190
+
191
+ def self.deserialize(unpacker)
192
+ source = unpacker.read
193
+ counts = unpacker.read
194
+ annotations = unpacker.read
195
+ total = unpacker.read
196
+
197
+ self.new(source, counts, annotations, total)
148
198
  end
149
199
  end
150
200
  end
data/lib/covered/files.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2022, by Samuel Williams.
4
+ # Copyright, 2018-2023, by Samuel Williams.
5
5
 
6
6
  require_relative 'coverage'
7
7
  require_relative 'wrapper'
@@ -10,6 +10,55 @@ 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.new(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
+ if @counts[lineno]
38
+ @counts[lineno] += value
39
+ else
40
+ @counts[lineno] = value
41
+ end
42
+ end
43
+
44
+ def merge!(coverage)
45
+ coverage.counts.each_with_index do |count, index|
46
+ if count
47
+ @counts[index] ||= 0
48
+ @counts[index] += count
49
+ end
50
+ end
51
+
52
+ @annotations.merge!(coverage.annotations) do |lineno, a, b|
53
+ Array(a) + Array(b)
54
+ end
55
+ end
56
+
57
+ def coverage
58
+ Coverage.new(@source, @counts, @annotations)
59
+ end
60
+ end
61
+
13
62
  def initialize(*)
14
63
  super
15
64
 
@@ -19,7 +68,7 @@ module Covered
19
68
  attr_accessor :paths
20
69
 
21
70
  def [](path)
22
- @paths[path]
71
+ @paths[path] ||= State.for(path)
23
72
  end
24
73
 
25
74
  def empty?
@@ -27,19 +76,27 @@ module Covered
27
76
  end
28
77
 
29
78
  def mark(path, lineno, value)
30
- coverage = (@paths[path] ||= Coverage.for(path))
31
-
32
- coverage.mark(lineno, value)
33
-
34
- return coverage
79
+ self[path].mark(lineno, value)
35
80
  end
36
81
 
37
- def add(source)
38
- @paths[source.path] ||= Coverage.new(source)
82
+ def annotate(path, lineno, value)
83
+ self[path].annotate(lineno, value)
39
84
  end
40
85
 
41
- def each(&block)
42
- @paths.each_value(&block)
86
+ def add(coverage)
87
+ self[coverage.path].merge!(coverage)
88
+ end
89
+
90
+ def each
91
+ return to_enum unless block_given?
92
+
93
+ @paths.each_value do |state|
94
+ yield state.coverage
95
+ end
96
+ end
97
+
98
+ def clear
99
+ @paths.clear
43
100
  end
44
101
  end
45
102
 
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require_relative 'wrapper'
7
+
8
+ module Covered
9
+ class Forks < Wrapper
10
+ def start
11
+ super
12
+
13
+ Handler.start(self)
14
+ end
15
+
16
+ def finish
17
+ Handler.finish
18
+
19
+ super
20
+ end
21
+
22
+ module Handler
23
+ LOCK = Mutex.new
24
+
25
+ class << self
26
+ attr :coverage
27
+
28
+ def start(coverage)
29
+ LOCK.synchronize do
30
+ if @coverage
31
+ raise ArgumentError, "Coverage is already being tracked!"
32
+ end
33
+
34
+ @coverage = coverage
35
+ end
36
+ end
37
+
38
+ def finish
39
+ LOCK.synchronize do
40
+ @coverage = nil
41
+ end
42
+ end
43
+
44
+ def after_fork
45
+ return unless coverage = Handler.coverage
46
+ pid = Process.pid
47
+
48
+ # Any pre-existing coverage is being tracked by the parent process, so discard it.
49
+ coverage.clear
50
+
51
+ at_exit do
52
+ # Don't break forked children:
53
+ if Process.pid == pid
54
+ coverage.finish
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def _fork
61
+ pid = super
62
+
63
+ if pid.zero?
64
+ Handler.after_fork
65
+ end
66
+
67
+ return pid
68
+ end
69
+
70
+ ::Process.singleton_class.prepend(self)
71
+ end
72
+
73
+ private_constant :Handler
74
+ end
75
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2022, by Samuel Williams.
4
+ # Copyright, 2021-2023, by Samuel Williams.
5
5
 
6
6
  require_relative 'statistics'
7
7
  require_relative 'wrapper'
@@ -54,7 +54,7 @@ module Covered
54
54
  end
55
55
  end
56
56
 
57
- # A coverage array gives, for each line, the number of line execution by the interpreter. A nil value means coverage is disabled for this line (lines like else and end).
57
+ # A coverage array gives, for each line, the number of line execution by the interpreter. A nil value means coverage is finishd for this line (lines like else and end).
58
58
  def call(wrapper, output = $stdout)
59
59
  output.puts '# Coverage Report'
60
60
  output.puts
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2022, by Samuel Williams.
4
+ # Copyright, 2019-2023, by Samuel Williams.
5
5
  # Copyright, 2022, by Adam Daniels.
6
6
 
7
7
  require_relative 'config'
@@ -13,7 +13,7 @@ $covered = Covered::Config.load
13
13
  module Covered
14
14
  module Minitest
15
15
  def run(*)
16
- $covered.enable
16
+ $covered.start
17
17
 
18
18
  super
19
19
  end
@@ -26,7 +26,7 @@ if $covered.record?
26
26
  end
27
27
 
28
28
  Minitest.after_run do
29
- $covered.disable
29
+ $covered.finish
30
30
  $covered.call($stderr)
31
31
  end
32
32
  end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2022, by Samuel Williams.
4
+ # Copyright, 2019-2023, by Samuel Williams.
5
+ # Copyright, 2023, by Stephen Ierodiaconou.
5
6
 
6
7
  require_relative 'wrapper'
7
8
 
8
9
  require 'msgpack'
9
10
  require 'time'
10
- require 'set'
11
11
 
12
12
  module Covered
13
13
  class Persist < Wrapper
@@ -16,45 +16,27 @@ module Covered
16
16
  def initialize(output, path = DEFAULT_PATH)
17
17
  super(output)
18
18
 
19
- @path = path
20
- @touched = Set.new
19
+ @path = self.expand_path(path)
21
20
  end
22
21
 
23
22
  def apply(record, ignore_mtime: false)
24
- # The file must still exist:
25
- return unless path = expand_path(record[:path])
26
-
27
- unless File.exist?(path)
28
- # Ignore this coverage, the file no longer exists.
29
- return
30
- end
31
-
32
- # If the file has been modified since... we can't use the coverage.
33
- return unless mtime = record[:mtime]
34
-
35
- unless ignore_mtime
36
- if File.mtime(path).to_f > record[:mtime]
37
- # Ignore this coverage, the file has been modified since it was recorded.
38
- return
23
+ if coverage = record[:coverage]
24
+ if path = record[:path]
25
+ path = self.expand_path(path)
26
+ coverage.path = path
39
27
  end
40
- end
41
-
42
- if source = record[:source]
43
- @output.add(source)
44
- end
45
-
46
- record[:counts].each_with_index do |count, index|
47
- @output.mark(path, index, count) if count
28
+
29
+ add(coverage)
48
30
  end
49
31
  end
50
32
 
51
33
  def serialize(coverage)
52
34
  {
53
35
  # We want to use relative paths so that moving the repo won't break everything:
36
+ pid: Process.pid,
54
37
  path: relative_path(coverage.path),
55
- mtime: File.mtime(coverage.path).to_f,
56
- counts: coverage.counts,
57
- source: coverage.source,
38
+ # relative_path: relative_path(coverage.path),
39
+ coverage: coverage,
58
40
  }
59
41
  end
60
42
 
@@ -66,21 +48,23 @@ module Covered
66
48
  file.flock(File::LOCK_SH)
67
49
 
68
50
  make_unpacker(file).each do |record|
51
+ # pp load: record
69
52
  self.apply(record, **options)
70
53
  end
71
54
  end
72
- rescue => error
55
+ rescue
73
56
  raise LoadError, "Failed to load coverage from #{@path}, maybe old format or corrupt!"
74
57
  end
75
58
 
76
59
  def save!
77
60
  # Dump all coverage:
78
- File.open(@path, "wb") do |file|
61
+ File.open(@path, "ab") do |file|
79
62
  file.flock(File::LOCK_EX)
80
63
 
81
64
  packer = make_packer(file)
82
65
 
83
- self.each do |coverage|
66
+ @output.each do |coverage|
67
+ # pp save: coverage
84
68
  packer.write(serialize(coverage))
85
69
  end
86
70
 
@@ -88,64 +72,52 @@ module Covered
88
72
  end
89
73
  end
90
74
 
91
- def mark(file, line, count)
92
- @touched << file
93
-
94
- super
95
- end
96
-
97
- def enable
75
+ def finish
98
76
  super
99
77
 
100
- load!
78
+ self.save!
101
79
  end
102
80
 
103
- def flush
104
- load!
81
+ def each(&block)
82
+ return to_enum unless block_given?
83
+
84
+ @output.clear
85
+ self.load!
105
86
 
106
87
  super
107
88
  end
108
89
 
109
- def disable
110
- super
90
+ def make_factory
91
+ factory = MessagePack::Factory.new
92
+
93
+ factory.register_type(0x00, Symbol)
94
+
95
+ factory.register_type(0x01, Time,
96
+ packer: MessagePack::Time::Packer,
97
+ unpacker: MessagePack::Time::Unpacker
98
+ )
99
+
100
+ factory.register_type(0x20, Source,
101
+ recursive: true,
102
+ packer: :serialize,
103
+ unpacker: :deserialize,
104
+ )
111
105
 
112
- # @touched.each do |path|
113
- # if @output.accept?(path)
114
- # puts "Updated #{path} coverage."
115
- # end
116
- # end
106
+ factory.register_type(0x21, Coverage,
107
+ recursive: true,
108
+ packer: :serialize,
109
+ unpacker: :deserialize,
110
+ )
117
111
 
118
- save!
112
+ return factory
119
113
  end
120
114
 
121
- # def each
122
- # super do |coverage|
123
- # if @touched.include?(coverage.path)
124
- # yield coverage
125
- # end
126
- # end
127
- # end
128
-
129
115
  def make_packer(io)
130
- packer = MessagePack::Packer.new(io)
131
- packer.register_type(0x00, Symbol, :to_msgpack_ext)
132
- packer.register_type(0x01, Time) {|object| object.to_s}
133
- packer.register_type(0x0F, Coverage::Source) do |object|
134
- object.to_a.to_msgpack
135
- end
136
-
137
- return packer
116
+ return make_factory.packer(io)
138
117
  end
139
118
 
140
119
  def make_unpacker(io)
141
- unpacker = MessagePack::Unpacker.new(io)
142
- unpacker.register_type(0x00, Symbol, :from_msgpack_ext)
143
- unpacker.register_type(0x01, Time, :parse)
144
- unpacker.register_type(0x0F) do |data|
145
- Coverage::Source.new(*MessagePack.unpack(data))
146
- end
147
-
148
- return unpacker
120
+ return make_factory.unpacker(io)
149
121
  end
150
122
  end
151
123
  end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2022, by Samuel Williams.
4
+ # Copyright, 2018-2023, by Samuel Williams.
5
5
 
6
6
  require_relative "summary"
7
7
  require_relative "files"
8
8
  require_relative "capture"
9
- require_relative "cache"
10
9
  require_relative "persist"
10
+ require_relative "forks"
11
11
 
12
12
  module Covered
13
13
  class Policy < Wrapper
@@ -15,6 +15,7 @@ module Covered
15
15
  super(Files.new)
16
16
 
17
17
  @reports = []
18
+ @capture = nil
18
19
  end
19
20
 
20
21
  attr :output
@@ -28,44 +29,38 @@ module Covered
28
29
  super
29
30
  end
30
31
 
31
- def include(*args)
32
- @output = Include.new(@output, *args)
32
+ def include(...)
33
+ @output = Include.new(@output, ...)
33
34
  end
34
35
 
35
- def skip(*args)
36
- @output = Skip.new(@output, *args)
36
+ def skip(...)
37
+ @output = Skip.new(@output, ...)
37
38
  end
38
39
 
39
- def only(*args)
40
- @output = Only.new(@output, *args)
40
+ def only(...)
41
+ @output = Only.new(@output, ...)
41
42
  end
42
43
 
43
- def root(*args)
44
- @output = Root.new(@output, *args)
44
+ def root(...)
45
+ @output = Root.new(@output, ...)
45
46
  end
46
47
 
47
- def cache!
48
- @output = Cache.new(@output)
49
- end
50
-
51
- def persist!
52
- @output = Persist.new(@output)
48
+ def persist!(...)
49
+ @output = Persist.new(@output, ...)
53
50
  end
54
51
 
55
52
  def capture
56
- @capture ||= Capture.new(@output)
57
- end
58
-
59
- def enable
60
- capture.enable
53
+ @capture ||= Forks.new(
54
+ Capture.new(@output)
55
+ )
61
56
  end
62
57
 
63
- def disable
64
- capture.disable
58
+ def start
59
+ capture.start
65
60
  end
66
61
 
67
- def flush
68
- @output.flush
62
+ def finish
63
+ capture.finish
69
64
  end
70
65
 
71
66
  attr :reports
@@ -87,8 +82,8 @@ module Covered
87
82
  return klass.new
88
83
  end
89
84
 
90
- def call(*args)
91
- self.new.call(*args)
85
+ def call(...)
86
+ self.new.call(...)
92
87
  end
93
88
 
94
89
  def to_s
@@ -123,9 +118,9 @@ module Covered
123
118
  end
124
119
  end
125
120
 
126
- def call(*args)
121
+ def call(...)
127
122
  @reports.each do |report|
128
- report.call(self, *args)
123
+ report.call(self, ...)
129
124
  end
130
125
  end
131
126
  end
data/lib/covered/rspec.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2022, by Samuel Williams.
4
+ # Copyright, 2018-2023, by Samuel Williams.
5
5
 
6
6
  require_relative 'config'
7
7
  require 'rspec/core/formatters'
@@ -12,7 +12,7 @@ module Covered
12
12
  module RSpec
13
13
  module Policy
14
14
  def load_spec_files
15
- $covered.enable
15
+ $covered.start
16
16
 
17
17
  super
18
18
  end
@@ -33,7 +33,7 @@ if $covered.record?
33
33
 
34
34
  RSpec.configure do |config|
35
35
  config.after(:suite) do
36
- $covered.disable
36
+ $covered.finish
37
37
  $covered.call(config.output_stream)
38
38
  end
39
39
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2022, by Samuel Williams.
4
+ # Copyright, 2018-2023, by Samuel Williams.
5
5
 
6
6
  require_relative 'wrapper'
7
7
  require_relative 'coverage'
@@ -28,6 +28,7 @@ module Covered
28
28
 
29
29
  def << coverage
30
30
  @count += 1
31
+
31
32
  @executable_count += coverage.executable_count
32
33
  @executed_count += coverage.executed_count
33
34
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2022, by Samuel Williams.
4
+ # Copyright, 2018-2023, by Samuel Williams.
5
5
 
6
6
  require_relative 'statistics'
7
7
  require_relative 'wrapper'
@@ -82,7 +82,7 @@ module Covered
82
82
  end
83
83
  end
84
84
 
85
- # A coverage array gives, for each line, the number of line execution by the interpreter. A nil value means coverage is disabled for this line (lines like else and end).
85
+ # A coverage array gives, for each line, the number of line execution by the interpreter. A nil value means coverage is finishd for this line (lines like else and end).
86
86
  def call(wrapper, output = $stdout)
87
87
  terminal = self.terminal(output)
88
88
 
@@ -116,6 +116,12 @@ module Covered
116
116
  end
117
117
  end
118
118
 
119
+ class FullSummary < Summary
120
+ def initialize
121
+ super(threshold: nil)
122
+ end
123
+ end
124
+
119
125
  class BriefSummary < Summary
120
126
  def call(wrapper, output = $stdout, before: 4, after: 4)
121
127
  terminal = self.terminal(output)
data/lib/covered/sus.rb CHANGED
@@ -1,20 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2022-2023, by Samuel Williams.
5
5
 
6
6
  module Covered
7
7
  module Sus
8
8
  def initialize(...)
9
9
  super
10
10
 
11
- # Defer loading the coverage configuration unless we are actually running with coverage enabled to avoid performance cost/overhead.
11
+ # Defer loading the coverage configuration unless we are actually running with coverage startd to avoid performance cost/overhead.
12
12
  if ENV['COVERAGE']
13
13
  require_relative 'config'
14
14
 
15
15
  @covered = Covered::Config.load(root: self.root)
16
16
  if @covered.record?
17
- @covered.enable
17
+ @covered.start
18
18
  end
19
19
  else
20
20
  @covered = nil
@@ -25,7 +25,7 @@ module Covered
25
25
  super(assertions)
26
26
 
27
27
  if @covered&.record?
28
- @covered.disable
28
+ @covered.finish
29
29
  @covered.call(self.output.io)
30
30
  end
31
31
  end
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2018-2022, by Samuel Williams.
5
5
 
6
6
  module Covered
7
- VERSION = "0.19.0"
7
+ VERSION = "0.20.0"
8
8
  end
@@ -1,17 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2022, by Samuel Williams.
4
+ # Copyright, 2018-2023, by Samuel Williams.
5
5
 
6
6
  module Covered
7
7
  class Base
8
- def enable
8
+ # Start tracking coverage.
9
+ def start
9
10
  end
10
11
 
11
- def disable
12
+ # Discard any coverage data and restart tracking.
13
+ def clear
12
14
  end
13
15
 
14
- def flush
16
+ # Stop tracking coverage.
17
+ def finish
15
18
  end
16
19
 
17
20
  def accept?(path)
@@ -21,9 +24,12 @@ module Covered
21
24
  def mark(path, lineno, value)
22
25
  end
23
26
 
24
- def add(source)
27
+ def add(coverage)
25
28
  end
26
29
 
30
+ # Enumerate the coverage data.
31
+ # @yields {|coverage| ...}
32
+ # @parameter coverage [Coverage] The coverage data, including the source file and execution counts.
27
33
  def each
28
34
  end
29
35
 
@@ -43,16 +49,16 @@ module Covered
43
49
 
44
50
  attr :output
45
51
 
46
- def enable
47
- @output.enable
52
+ def start
53
+ @output.start
48
54
  end
49
55
 
50
- def disable
51
- @output.disable
56
+ def clear
57
+ @output.clear
52
58
  end
53
59
 
54
- def flush
55
- @output.flush
60
+ def finish
61
+ @output.finish
56
62
  end
57
63
 
58
64
  def accept?(path)
@@ -63,8 +69,8 @@ module Covered
63
69
  @output.mark(path, lineno, value)
64
70
  end
65
71
 
66
- def add(source)
67
- @output.add(source)
72
+ def add(coverage)
73
+ @output.add(coverage)
68
74
  end
69
75
 
70
76
  # @yield [Coverage] the path to the file, and the execution counts.
@@ -81,7 +87,7 @@ module Covered
81
87
  end
82
88
 
83
89
  def to_h
84
- @output.to_enum(:each).collect{|coverage| [coverage.path, coverage]}.to_h
90
+ to_enum(:each).collect{|coverage| [coverage.path, coverage]}.to_h
85
91
  end
86
92
  end
87
93
 
data/license.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2018-2022, by Samuel Williams.
3
+ Copyright, 2018-2023, by Samuel Williams.
4
4
  Copyright, 2018, by Shannon Skipper.
5
5
  Copyright, 2018, by chocolateboy.
6
6
  Copyright, 2019, by Cyril Roelandt.
7
7
  Copyright, 2022, by Adam Daniels.
8
+ Copyright, 2022, by Felix Yan.
9
+ Copyright, 2023, by Stephen Ierodiaconou.
8
10
 
9
11
  Permission is hereby granted, free of charge, to any person obtaining a copy
10
12
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -59,7 +59,7 @@ When running `rspec`, you can specify the kind of coverage analysis you would li
59
59
 
60
60
  COVERAGE=Summary rspec
61
61
 
62
- If no `COVERAGE` is specified, coverage tracking will be disabled.
62
+ If no `COVERAGE` is specified, coverage tracking will be finishd.
63
63
 
64
64
  ### Partial Summary
65
65
 
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,13 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: covered
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.0
4
+ version: 0.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  - Adam Daniels
9
9
  - Cyril Roelandt
10
+ - Felix Yan
10
11
  - Shannon Skipper
12
+ - Stephen Ierodiaconou
11
13
  - chocolateboy
12
14
  autorequire:
13
15
  bindir: bin
@@ -41,7 +43,7 @@ cert_chain:
41
43
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
42
44
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
43
45
  -----END CERTIFICATE-----
44
- date: 2022-12-29 00:00:00.000000000 Z
46
+ date: 2023-02-10 00:00:00.000000000 Z
45
47
  dependencies:
46
48
  - !ruby/object:Gem::Dependency
47
49
  name: console
@@ -136,11 +138,12 @@ files:
136
138
  - bake/covered/debug.rb
137
139
  - bake/covered/validate.rb
138
140
  - lib/covered.rb
139
- - lib/covered/cache.rb
141
+ - lib/covered/autostart.rb
140
142
  - lib/covered/capture.rb
141
143
  - lib/covered/config.rb
142
144
  - lib/covered/coverage.rb
143
145
  - lib/covered/files.rb
146
+ - lib/covered/forks.rb
144
147
  - lib/covered/markdown_summary.rb
145
148
  - lib/covered/minitest.rb
146
149
  - lib/covered/persist.rb
@@ -173,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
176
  - !ruby/object:Gem::Version
174
177
  version: '0'
175
178
  requirements: []
176
- rubygems_version: 3.4.1
179
+ rubygems_version: 3.4.6
177
180
  signing_key:
178
181
  specification_version: 4
179
182
  summary: A modern approach to code coverage.
metadata.gz.sig CHANGED
Binary file
data/lib/covered/cache.rb DELETED
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
5
-
6
- require_relative 'wrapper'
7
-
8
- require 'coverage'
9
-
10
- module Covered
11
- class Cache < Wrapper
12
- def initialize(output)
13
- super(output)
14
-
15
- @marks = nil
16
- end
17
-
18
- def mark(path, lineno, count = 1)
19
- if @marks
20
- @marks.push(path, lineno, count)
21
- else
22
- super
23
- end
24
- end
25
-
26
- def enable
27
- @marks = []
28
-
29
- super
30
- end
31
-
32
- def flush
33
- if @marks
34
- @marks.each_slice(3) do |path, lineno, count|
35
- @output.mark(path, lineno, count)
36
- end
37
-
38
- @marks = nil
39
- end
40
-
41
- super
42
- end
43
-
44
- def disable
45
- super
46
-
47
- flush
48
- end
49
- end
50
- end