simplecov 0.19.0 → 0.21.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,16 +9,18 @@ module SimpleCov
9
9
  end
10
10
 
11
11
  def failing?
12
- covered_percentages.any? { |p| p < minimum_coverage_by_file }
12
+ minimum_violations.any?
13
13
  end
14
14
 
15
15
  def report
16
- $stderr.printf(
17
- "File (%<file>s) is only (%<least_covered_percentage>.2f%%) covered. This is below the expected minimum coverage per file of (%<min_coverage>.2f%%).\n",
18
- file: result.least_covered_file,
19
- least_covered_percentage: covered_percentages.min,
20
- min_coverage: minimum_coverage_by_file
21
- )
16
+ minimum_violations.each do |violation|
17
+ $stderr.printf(
18
+ "%<criterion>s coverage by file (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
19
+ covered: SimpleCov.round_coverage(violation.fetch(:actual)),
20
+ minimum_coverage: violation.fetch(:minimum_expected),
21
+ criterion: violation.fetch(:criterion).capitalize
22
+ )
23
+ end
22
24
  end
23
25
 
24
26
  def exit_code
@@ -29,9 +31,23 @@ module SimpleCov
29
31
 
30
32
  attr_reader :result, :minimum_coverage_by_file
31
33
 
32
- def covered_percentages
33
- @covered_percentages ||=
34
- result.covered_percentages.map { |percentage| SimpleCov.round_coverage(percentage) }
34
+ def minimum_violations
35
+ @minimum_violations ||=
36
+ compute_minimum_coverage_data.select do |achieved|
37
+ achieved.fetch(:actual) < achieved.fetch(:minimum_expected)
38
+ end
39
+ end
40
+
41
+ def compute_minimum_coverage_data
42
+ minimum_coverage_by_file.flat_map do |criterion, expected_percent|
43
+ result.coverage_statistics_by_file.fetch(criterion).map do |actual_coverage|
44
+ {
45
+ criterion: criterion,
46
+ minimum_expected: expected_percent,
47
+ actual: SimpleCov.round_coverage(actual_coverage.percent)
48
+ }
49
+ end
50
+ end
35
51
  end
36
52
  end
37
53
  end
@@ -27,6 +27,10 @@ module SimpleCov
27
27
  @coverage_statistics ||= compute_coverage_statistics
28
28
  end
29
29
 
30
+ def coverage_statistics_by_file
31
+ @coverage_statistics_by_file ||= compute_coverage_statistics_by_file
32
+ end
33
+
30
34
  # Returns the count of lines that have coverage
31
35
  def covered_lines
32
36
  coverage_statistics[:line]&.covered
@@ -100,14 +104,16 @@ module SimpleCov
100
104
 
101
105
  private
102
106
 
103
- def compute_coverage_statistics
104
- total_coverage_statistics = @files.each_with_object(line: [], branch: []) do |file, together|
105
- together[:line] << file.coverage_statistics[:line]
106
- together[:branch] << file.coverage_statistics[:branch] if SimpleCov.branch_coverage?
107
+ def compute_coverage_statistics_by_file
108
+ @files.each_with_object(line: [], branch: []) do |file, together|
109
+ together[:line] << file.coverage_statistics.fetch(:line)
110
+ together[:branch] << file.coverage_statistics.fetch(:branch) if SimpleCov.branch_coverage?
107
111
  end
112
+ end
108
113
 
109
- coverage_statistics = {line: CoverageStatistics.from(total_coverage_statistics[:line])}
110
- coverage_statistics[:branch] = CoverageStatistics.from(total_coverage_statistics[:branch]) if SimpleCov.branch_coverage?
114
+ def compute_coverage_statistics
115
+ coverage_statistics = {line: CoverageStatistics.from(coverage_statistics_by_file[:line])}
116
+ coverage_statistics[:branch] = CoverageStatistics.from(coverage_statistics_by_file[:branch]) if SimpleCov.branch_coverage?
111
117
  coverage_statistics
112
118
  end
113
119
  end
@@ -17,14 +17,14 @@ module SimpleCov
17
17
  end
18
18
 
19
19
  def self.no_cov_line?(line)
20
- line =~ no_cov_line
20
+ no_cov_line.match?(line)
21
21
  rescue ArgumentError
22
22
  # E.g., line contains an invalid byte sequence in UTF-8
23
23
  false
24
24
  end
25
25
 
26
26
  def self.whitespace_line?(line)
27
- line =~ WHITESPACE_OR_COMMENT_LINE
27
+ WHITESPACE_OR_COMMENT_LINE.match?(line)
28
28
  rescue ArgumentError
29
29
  # E.g., line contains an invalid byte sequence in UTF-8
30
30
  false
@@ -20,13 +20,13 @@ module SimpleCov
20
20
  # Explicitly set the command name that was used for this coverage result. Defaults to SimpleCov.command_name
21
21
  attr_writer :command_name
22
22
 
23
- def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength, :covered_lines, :missed_lines, :total_branches, :covered_branches, :missed_branches, :coverage_statistics
23
+ def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength, :covered_lines, :missed_lines, :total_branches, :covered_branches, :missed_branches, :coverage_statistics, :coverage_statistics_by_file
24
24
  def_delegator :files, :lines_of_code, :total_lines
25
25
 
26
26
  # Initialize a new SimpleCov::Result from given Coverage.result (a Hash of filenames each containing an array of
27
27
  # coverage data)
28
28
  def initialize(original_result, command_name: nil, created_at: nil)
29
- result = adapt_result(original_result)
29
+ result = original_result
30
30
  @original_result = result.freeze
31
31
  @command_name = command_name
32
32
  @created_at = created_at
@@ -72,10 +72,6 @@ module SimpleCov
72
72
  }
73
73
  end
74
74
 
75
- def time_since_creation
76
- Time.now - created_at
77
- end
78
-
79
75
  # Loads a SimpleCov::Result#to_hash dump
80
76
  def self.from_hash(hash)
81
77
  hash.map do |command_name, data|
@@ -85,31 +81,6 @@ module SimpleCov
85
81
 
86
82
  private
87
83
 
88
- # We changed the format of the raw result data in simplecov, as people are likely
89
- # to have "old" resultsets lying around (but not too old so that they're still
90
- # considered we can adapt them).
91
- # See https://github.com/simplecov-ruby/simplecov/pull/824#issuecomment-576049747
92
- def adapt_result(result)
93
- if pre_simplecov_0_18_result?(result)
94
- adapt_pre_simplecov_0_18_result(result)
95
- else
96
- result
97
- end
98
- end
99
-
100
- # pre 0.18 coverage data pointed from file directly to an array of line coverage
101
- def pre_simplecov_0_18_result?(result)
102
- _key, data = result.first
103
-
104
- data.is_a?(Array)
105
- end
106
-
107
- def adapt_pre_simplecov_0_18_result(result)
108
- result.transform_values do |line_coverage_data|
109
- {"lines" => line_coverage_data}
110
- end
111
- end
112
-
113
84
  def coverage
114
85
  keys = original_result.keys & filenames
115
86
  Hash[keys.zip(original_result.values_at(*keys))]
@@ -19,81 +19,130 @@ module SimpleCov
19
19
  File.join(SimpleCov.coverage_path, ".resultset.json.lock")
20
20
  end
21
21
 
22
- # Loads the cached resultset from JSON and returns it as a Hash,
23
- # caching it for subsequent accesses.
24
- def resultset
25
- @resultset ||= begin
26
- data = stored_data
27
- if data
28
- begin
29
- JSON.parse(data) || {}
30
- rescue StandardError
31
- {}
32
- end
33
- else
34
- {}
35
- end
22
+ def merge_and_store(*file_paths, ignore_timeout: false)
23
+ result = merge_results(*file_paths, ignore_timeout: ignore_timeout)
24
+ store_result(result) if result
25
+ result
26
+ end
27
+
28
+ def merge_results(*file_paths, ignore_timeout: false)
29
+ # It is intentional here that files are only read in and parsed one at a time.
30
+ #
31
+ # In big CI setups you might deal with 100s of CI jobs and each one producing Megabytes
32
+ # of data. Reading them all in easily produces Gigabytes of memory consumption which
33
+ # we want to avoid.
34
+ #
35
+ # For similar reasons a SimpleCov::Result is only created in the end as that'd create
36
+ # even more data especially when it also reads in all source files.
37
+ initial_memo = valid_results(file_paths.shift, ignore_timeout: ignore_timeout)
38
+
39
+ command_names, coverage = file_paths.reduce(initial_memo) do |memo, file_path|
40
+ merge_coverage(memo, valid_results(file_path, ignore_timeout: ignore_timeout))
36
41
  end
42
+
43
+ create_result(command_names, coverage)
37
44
  end
38
45
 
39
- # Returns the contents of the resultset cache as a string or if the file is missing or empty nil
40
- def stored_data
41
- synchronize_resultset do
42
- return unless File.exist?(resultset_path)
46
+ def valid_results(file_path, ignore_timeout: false)
47
+ results = parse_file(file_path)
48
+ merge_valid_results(results, ignore_timeout: ignore_timeout)
49
+ end
50
+
51
+ def parse_file(path)
52
+ data = read_file(path)
53
+ parse_json(data)
54
+ end
43
55
 
44
- data = File.read(resultset_path)
45
- return if data.nil? || data.length < 2
56
+ def read_file(path)
57
+ return unless File.exist?(path)
46
58
 
47
- data
59
+ data = File.read(path)
60
+ return if data.nil? || data.length < 2
61
+
62
+ data
63
+ end
64
+
65
+ def parse_json(content)
66
+ return {} unless content
67
+
68
+ JSON.parse(content) || {}
69
+ rescue StandardError
70
+ warn "[SimpleCov]: Warning! Parsing JSON content of resultset file failed"
71
+ {}
72
+ end
73
+
74
+ def merge_valid_results(results, ignore_timeout: false)
75
+ results = results.select { |_command_name, data| within_merge_timeout?(data) } unless ignore_timeout
76
+
77
+ command_plus_coverage = results.map do |command_name, data|
78
+ [[command_name], adapt_result(data.fetch("coverage"))]
48
79
  end
80
+
81
+ # one file itself _might_ include multiple test runs
82
+ merge_coverage(*command_plus_coverage)
49
83
  end
50
84
 
51
- # Gets the resultset hash and re-creates all included instances
52
- # of SimpleCov::Result from that.
53
- # All results that are above the SimpleCov.merge_timeout will be
54
- # dropped. Returns an array of SimpleCov::Result items.
55
- def results
56
- results = Result.from_hash(resultset)
57
- results.select { |result| result.time_since_creation < SimpleCov.merge_timeout }
85
+ def within_merge_timeout?(data)
86
+ time_since_result_creation(data) < SimpleCov.merge_timeout
58
87
  end
59
88
 
60
- def merge_and_store(*results)
61
- result = merge_results(*results)
62
- store_result(result) if result
63
- result
89
+ def time_since_result_creation(data)
90
+ Time.now - Time.at(data.fetch("timestamp"))
64
91
  end
65
92
 
66
- # Merge two or more SimpleCov::Results into a new one with merged
67
- # coverage data and the command_name for the result consisting of a join
68
- # on all source result's names
69
- def merge_results(*results)
70
- parsed_results = JSON.parse(JSON.dump(results.map(&:original_result)))
71
- combined_result = SimpleCov::Combine::ResultsCombiner.combine(*parsed_results)
72
- result = SimpleCov::Result.new(combined_result)
73
- # Specify the command name
74
- result.command_name = results.map(&:command_name).sort.join(", ")
75
- result
93
+ def create_result(command_names, coverage)
94
+ return nil unless coverage
95
+
96
+ command_name = command_names.reject(&:empty?).sort.join(", ")
97
+ SimpleCov::Result.new(coverage, command_name: command_name)
98
+ end
99
+
100
+ def merge_coverage(*results)
101
+ return [[""], nil] if results.empty?
102
+ return results.first if results.size == 1
103
+
104
+ results.reduce do |(memo_command, memo_coverage), (command, coverage)|
105
+ # timestamp is dropped here, which is intentional (we merge it, it gets a new time stamp as of now)
106
+ merged_coverage = Combine.combine(Combine::ResultsCombiner, memo_coverage, coverage)
107
+ merged_command = memo_command + command
108
+
109
+ [merged_command, merged_coverage]
110
+ end
76
111
  end
77
112
 
78
113
  #
79
- # Gets all SimpleCov::Results from cache, merges them and produces a new
114
+ # Gets all SimpleCov::Results stored in resultset, merges them and produces a new
80
115
  # SimpleCov::Result with merged coverage data and the command_name
81
116
  # for the result consisting of a join on all source result's names
82
- #
83
117
  def merged_result
84
- merge_results(*results)
118
+ # conceptually this is just doing `merge_results(resultset_path)`
119
+ # it's more involved to make syre `synchronize_resultset` is only used around reading
120
+ resultset_hash = read_resultset
121
+ command_names, coverage = merge_valid_results(resultset_hash)
122
+
123
+ create_result(command_names, coverage)
124
+ end
125
+
126
+ def read_resultset
127
+ resultset_content =
128
+ synchronize_resultset do
129
+ read_file(resultset_path)
130
+ end
131
+
132
+ parse_json(resultset_content)
85
133
  end
86
134
 
87
135
  # Saves the given SimpleCov::Result in the resultset cache
88
136
  def store_result(result)
89
137
  synchronize_resultset do
90
138
  # Ensure we have the latest, in case it was already cached
91
- clear_resultset
92
- new_set = resultset
139
+ new_resultset = read_resultset
140
+
141
+ # A single result only ever has one command_name, see `SimpleCov::Result#to_hash`
93
142
  command_name, data = result.to_hash.first
94
- new_set[command_name] = data
143
+ new_resultset[command_name] = data
95
144
  File.open(resultset_path, "w+") do |f_|
96
- f_.puts JSON.pretty_generate(new_set)
145
+ f_.puts JSON.pretty_generate(new_resultset)
97
146
  end
98
147
  end
99
148
  true
@@ -116,9 +165,29 @@ module SimpleCov
116
165
  end
117
166
  end
118
167
 
119
- # Clear out the previously cached .resultset
120
- def clear_resultset
121
- @resultset = nil
168
+ # We changed the format of the raw result data in simplecov, as people are likely
169
+ # to have "old" resultsets lying around (but not too old so that they're still
170
+ # considered we can adapt them).
171
+ # See https://github.com/simplecov-ruby/simplecov/pull/824#issuecomment-576049747
172
+ def adapt_result(result)
173
+ if pre_simplecov_0_18_result?(result)
174
+ adapt_pre_simplecov_0_18_result(result)
175
+ else
176
+ result
177
+ end
178
+ end
179
+
180
+ # pre 0.18 coverage data pointed from file directly to an array of line coverage
181
+ def pre_simplecov_0_18_result?(result)
182
+ _key, data = result.first
183
+
184
+ data.is_a?(Array)
185
+ end
186
+
187
+ def adapt_pre_simplecov_0_18_result(result)
188
+ result.transform_values do |line_coverage_data|
189
+ {"lines" => line_coverage_data}
190
+ end
122
191
  end
123
192
  end
124
193
  end
@@ -56,7 +56,7 @@ module SimpleCov
56
56
  # Returns true if this line was skipped, false otherwise. Lines are skipped if they are wrapped with
57
57
  # # :nocov: comment lines.
58
58
  def skipped?
59
- !!skipped
59
+ skipped
60
60
  end
61
61
 
62
62
  # The status of this line - either covered, missed, skipped or never. Useful i.e. for direct use
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- VERSION = "0.19.0"
4
+ VERSION = "0.21.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simplecov
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.0
4
+ version: 0.21.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christoph Olszowka
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-08-16 00:00:00.000000000 Z
12
+ date: 2021-01-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: docile
@@ -39,6 +39,20 @@ dependencies:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0.11'
42
+ - !ruby/object:Gem::Dependency
43
+ name: simplecov_json_formatter
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '0.1'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '0.1'
42
56
  description: Code coverage for Ruby with a powerful configuration library and automatic
43
57
  merging of coverage across test suites
44
58
  email:
@@ -49,9 +63,6 @@ extensions: []
49
63
  extra_rdoc_files: []
50
64
  files:
51
65
  - CHANGELOG.md
52
- - CODE_OF_CONDUCT.md
53
- - CONTRIBUTING.md
54
- - ISSUE_TEMPLATE.md
55
66
  - LICENSE
56
67
  - README.md
57
68
  - doc/alternate-formatters.md
@@ -67,6 +78,7 @@ files:
67
78
  - lib/simplecov/command_guesser.rb
68
79
  - lib/simplecov/configuration.rb
69
80
  - lib/simplecov/coverage_statistics.rb
81
+ - lib/simplecov/default_formatter.rb
70
82
  - lib/simplecov/defaults.rb
71
83
  - lib/simplecov/exit_codes.rb
72
84
  - lib/simplecov/exit_codes/exit_code_handling.rb
@@ -104,9 +116,9 @@ licenses:
104
116
  metadata:
105
117
  bug_tracker_uri: https://github.com/simplecov-ruby/simplecov/issues
106
118
  changelog_uri: https://github.com/simplecov-ruby/simplecov/blob/main/CHANGELOG.md
107
- documentation_uri: https://www.rubydoc.info/gems/simplecov/0.19.0
119
+ documentation_uri: https://www.rubydoc.info/gems/simplecov/0.21.2
108
120
  mailing_list_uri: https://groups.google.com/forum/#!forum/simplecov
109
- source_code_uri: https://github.com/simplecov-ruby/simplecov/tree/v0.19.0
121
+ source_code_uri: https://github.com/simplecov-ruby/simplecov/tree/v0.21.2
110
122
  post_install_message:
111
123
  rdoc_options: []
112
124
  require_paths:
@@ -122,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
134
  - !ruby/object:Gem::Version
123
135
  version: '0'
124
136
  requirements: []
125
- rubygems_version: 3.1.2
137
+ rubygems_version: 3.2.3
126
138
  signing_key:
127
139
  specification_version: 4
128
140
  summary: Code coverage for Ruby