simplecov 0.19.0 → 0.21.2

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.
@@ -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