simplecov 0.18.0.beta1 → 0.18.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 +4 -4
- data/CHANGELOG.md +43 -1
- data/README.md +103 -18
- data/lib/simplecov/combine/files_combiner.rb +3 -4
- data/lib/simplecov/combine/results_combiner.rb +10 -10
- data/lib/simplecov/configuration.rb +43 -11
- data/lib/simplecov/coverage_statistics.rb +56 -0
- data/lib/simplecov/defaults.rb +3 -1
- data/lib/simplecov/file_list.rb +48 -22
- data/lib/simplecov/last_run.rb +1 -1
- data/lib/simplecov/result.rb +35 -19
- data/lib/simplecov/result_adapter.rb +1 -1
- data/lib/simplecov/result_merger.rb +7 -1
- data/lib/simplecov/simulate_coverage.rb +2 -2
- data/lib/simplecov/source_file/branch.rb +4 -26
- data/lib/simplecov/source_file.rb +134 -147
- data/lib/simplecov/useless_results_remover.rb +1 -1
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov.rb +103 -21
- metadata +9 -8
|
@@ -62,11 +62,17 @@ module SimpleCov
|
|
|
62
62
|
results
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
def merge_and_store(*results)
|
|
66
|
+
result = merge_results(*results)
|
|
67
|
+
store_result(result) if result
|
|
68
|
+
result
|
|
69
|
+
end
|
|
70
|
+
|
|
65
71
|
# Merge two or more SimpleCov::Results into a new one with merged
|
|
66
72
|
# coverage data and the command_name for the result consisting of a join
|
|
67
73
|
# on all source result's names
|
|
68
74
|
def merge_results(*results)
|
|
69
|
-
parsed_results = JSON.parse(JSON.dump(results.map(&:original_result))
|
|
75
|
+
parsed_results = JSON.parse(JSON.dump(results.map(&:original_result)))
|
|
70
76
|
combined_result = SimpleCov::Combine::ResultsCombiner.combine(*parsed_results)
|
|
71
77
|
result = SimpleCov::Result.new(combined_result)
|
|
72
78
|
# Specify the command name
|
|
@@ -19,10 +19,10 @@ module SimpleCov
|
|
|
19
19
|
lines = File.foreach(absolute_path)
|
|
20
20
|
|
|
21
21
|
{
|
|
22
|
-
|
|
22
|
+
"lines" => LinesClassifier.new.classify(lines),
|
|
23
23
|
# we don't want to parse branches ourselves...
|
|
24
24
|
# requiring files can have side effects and we don't want to trigger that
|
|
25
|
-
|
|
25
|
+
"branches" => {}
|
|
26
26
|
}
|
|
27
27
|
end
|
|
28
28
|
end
|
|
@@ -6,15 +6,15 @@ module SimpleCov
|
|
|
6
6
|
# Representing single branch that has been detected in coverage report.
|
|
7
7
|
# Give us support methods that handle needed calculations.
|
|
8
8
|
class Branch
|
|
9
|
-
attr_reader :start_line, :end_line, :coverage
|
|
9
|
+
attr_reader :start_line, :end_line, :coverage, :type
|
|
10
10
|
|
|
11
11
|
# rubocop:disable Metrics/ParameterLists
|
|
12
|
-
def initialize(start_line:, end_line:, coverage:, inline:,
|
|
12
|
+
def initialize(start_line:, end_line:, coverage:, inline:, type:)
|
|
13
13
|
@start_line = start_line
|
|
14
14
|
@end_line = end_line
|
|
15
15
|
@coverage = coverage
|
|
16
16
|
@inline = inline
|
|
17
|
-
@
|
|
17
|
+
@type = type
|
|
18
18
|
@skipped = false
|
|
19
19
|
end
|
|
20
20
|
# rubocop:enable Metrics/ParameterLists
|
|
@@ -23,19 +23,6 @@ module SimpleCov
|
|
|
23
23
|
@inline
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def positive?
|
|
27
|
-
@positive
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
#
|
|
31
|
-
# Branch is negative
|
|
32
|
-
#
|
|
33
|
-
# @return [Boolean]
|
|
34
|
-
#
|
|
35
|
-
def negative?
|
|
36
|
-
!positive?
|
|
37
|
-
end
|
|
38
|
-
|
|
39
26
|
#
|
|
40
27
|
# Return true if there is relevant count defined > 0
|
|
41
28
|
#
|
|
@@ -54,15 +41,6 @@ module SimpleCov
|
|
|
54
41
|
!skipped? && coverage.zero?
|
|
55
42
|
end
|
|
56
43
|
|
|
57
|
-
#
|
|
58
|
-
# Return the sign depends on branch is positive or negative
|
|
59
|
-
#
|
|
60
|
-
# @return [String]
|
|
61
|
-
#
|
|
62
|
-
def badge
|
|
63
|
-
positive? ? "+" : "-"
|
|
64
|
-
end
|
|
65
|
-
|
|
66
44
|
# The line on which we want to report the coverage
|
|
67
45
|
#
|
|
68
46
|
# Usually we choose the line above the start of the branch (so that it shows up
|
|
@@ -99,7 +77,7 @@ module SimpleCov
|
|
|
99
77
|
# @return [Array]
|
|
100
78
|
#
|
|
101
79
|
def report
|
|
102
|
-
[
|
|
80
|
+
[type, coverage]
|
|
103
81
|
end
|
|
104
82
|
end
|
|
105
83
|
end
|
|
@@ -9,11 +9,11 @@ module SimpleCov
|
|
|
9
9
|
# The full path to this source file (e.g. /User/colszowka/projects/simplecov/lib/simplecov/source_file.rb)
|
|
10
10
|
attr_reader :filename
|
|
11
11
|
# The array of coverage data received from the Coverage.result
|
|
12
|
-
attr_reader :
|
|
12
|
+
attr_reader :coverage_data
|
|
13
13
|
|
|
14
|
-
def initialize(filename,
|
|
15
|
-
@filename = filename
|
|
16
|
-
@
|
|
14
|
+
def initialize(filename, coverage_data)
|
|
15
|
+
@filename = filename
|
|
16
|
+
@coverage_data = coverage_data
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# The path to this source file relative to the projects directory
|
|
@@ -29,6 +29,14 @@ module SimpleCov
|
|
|
29
29
|
end
|
|
30
30
|
alias source src
|
|
31
31
|
|
|
32
|
+
def coverage_statistics
|
|
33
|
+
@coverage_statistics ||=
|
|
34
|
+
{
|
|
35
|
+
**line_coverage_statistics,
|
|
36
|
+
**branch_coverage_statistics
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
32
40
|
# Returns all source lines for this file as instances of SimpleCov::SourceFile::Line,
|
|
33
41
|
# and thus including coverage data. Aliased as :source_lines
|
|
34
42
|
def lines
|
|
@@ -60,44 +68,7 @@ module SimpleCov
|
|
|
60
68
|
|
|
61
69
|
# Returns the number of relevant lines (covered + missed)
|
|
62
70
|
def lines_of_code
|
|
63
|
-
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def build_lines
|
|
67
|
-
coverage_exceeding_source_warn if coverage[:lines].size > src.size
|
|
68
|
-
lines = src.map.with_index(1) do |src, i|
|
|
69
|
-
SimpleCov::SourceFile::Line.new(src, i, coverage[:lines][i - 1])
|
|
70
|
-
end
|
|
71
|
-
process_skipped_lines(lines)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# no_cov_chunks is zero indexed to work directly with the array holding the lines
|
|
75
|
-
def no_cov_chunks
|
|
76
|
-
@no_cov_chunks ||= build_no_cov_chunks
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def build_no_cov_chunks
|
|
80
|
-
no_cov_lines = src.map.with_index(1).select { |line, _index| LinesClassifier.no_cov_line?(line) }
|
|
81
|
-
|
|
82
|
-
warn "uneven number of nocov comments detected" if no_cov_lines.size.odd?
|
|
83
|
-
|
|
84
|
-
no_cov_lines.each_slice(2).map do |(_line_start, index_start), (_line_end, index_end)|
|
|
85
|
-
index_start..index_end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def process_skipped_lines(lines)
|
|
90
|
-
# the array the lines are kept in is 0-based whereas the line numbers in the nocov
|
|
91
|
-
# chunks are 1-based and are expected to be like this in other parts (and it's also
|
|
92
|
-
# arguably more understandable)
|
|
93
|
-
no_cov_chunks.each { |chunk| lines[(chunk.begin - 1)..(chunk.end - 1)].each(&:skipped!) }
|
|
94
|
-
|
|
95
|
-
lines
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Warning to identify condition from Issue #56
|
|
99
|
-
def coverage_exceeding_source_warn
|
|
100
|
-
warn "Warning: coverage data provided by Coverage [#{coverage[:lines].size}] exceeds number of lines in #{filename} [#{src.size}]"
|
|
71
|
+
coverage_statistics[:line]&.total
|
|
101
72
|
end
|
|
102
73
|
|
|
103
74
|
# Access SimpleCov::SourceFile::Line source lines by line number
|
|
@@ -107,27 +78,17 @@ module SimpleCov
|
|
|
107
78
|
|
|
108
79
|
# The coverage for this file in percent. 0 if the file has no coverage lines
|
|
109
80
|
def covered_percent
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return 0.0 if relevant_lines.zero?
|
|
113
|
-
|
|
114
|
-
Float(covered_lines.size * 100.0 / relevant_lines.to_f)
|
|
81
|
+
coverage_statistics[:line]&.percent
|
|
115
82
|
end
|
|
116
83
|
|
|
117
84
|
def covered_strength
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
(lines_strength / relevant_lines.to_f).round(1)
|
|
85
|
+
coverage_statistics[:line]&.strength
|
|
121
86
|
end
|
|
122
87
|
|
|
123
88
|
def no_lines?
|
|
124
89
|
lines.length.zero? || (lines.length == never_lines.size)
|
|
125
90
|
end
|
|
126
91
|
|
|
127
|
-
def lines_strength
|
|
128
|
-
lines.map(&:coverage).compact.reduce(:+)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
92
|
def relevant_lines
|
|
132
93
|
lines.size - never_lines.size - skipped_lines.size
|
|
133
94
|
end
|
|
@@ -143,16 +104,13 @@ module SimpleCov
|
|
|
143
104
|
end
|
|
144
105
|
|
|
145
106
|
def branches_coverage_percent
|
|
146
|
-
|
|
147
|
-
return 0.0 if covered_branches.empty?
|
|
148
|
-
|
|
149
|
-
Float(covered_branches.size * 100.0 / total_branches.size.to_f)
|
|
107
|
+
coverage_statistics[:branch]&.percent
|
|
150
108
|
end
|
|
151
109
|
|
|
152
110
|
#
|
|
153
111
|
# Return the relevant branches to source file
|
|
154
112
|
def total_branches
|
|
155
|
-
covered_branches + missed_branches
|
|
113
|
+
@total_branches ||= covered_branches + missed_branches
|
|
156
114
|
end
|
|
157
115
|
|
|
158
116
|
#
|
|
@@ -161,14 +119,104 @@ module SimpleCov
|
|
|
161
119
|
@branches_report ||= build_branches_report
|
|
162
120
|
end
|
|
163
121
|
|
|
164
|
-
|
|
122
|
+
#
|
|
123
|
+
# Select the covered branches
|
|
124
|
+
# Here we user tree schema because some conditions like case may have additional
|
|
125
|
+
# else that is not in declared inside the code but given by default by coverage report
|
|
126
|
+
#
|
|
127
|
+
# @return [Array]
|
|
128
|
+
#
|
|
129
|
+
def covered_branches
|
|
130
|
+
@covered_branches ||= branches.select(&:covered?)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
#
|
|
134
|
+
# Select the missed branches with coverage equal to zero
|
|
135
|
+
#
|
|
136
|
+
# @return [Array]
|
|
137
|
+
#
|
|
138
|
+
def missed_branches
|
|
139
|
+
@missed_branches ||= branches.select(&:missed?)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def branches_for_line(line_number)
|
|
143
|
+
branches_report.fetch(line_number, [])
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
#
|
|
147
|
+
# Check if any branches missing on given line number
|
|
148
|
+
#
|
|
149
|
+
# @param [Integer] line_number
|
|
150
|
+
#
|
|
151
|
+
# @return [Boolean]
|
|
152
|
+
#
|
|
153
|
+
def line_with_missed_branch?(line_number)
|
|
154
|
+
branches_for_line(line_number).select { |_type, count| count.zero? }.any?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
# no_cov_chunks is zero indexed to work directly with the array holding the lines
|
|
160
|
+
def no_cov_chunks
|
|
161
|
+
@no_cov_chunks ||= build_no_cov_chunks
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def build_no_cov_chunks
|
|
165
|
+
no_cov_lines = src.map.with_index(1).select { |line, _index| LinesClassifier.no_cov_line?(line) }
|
|
166
|
+
|
|
167
|
+
warn "uneven number of nocov comments detected" if no_cov_lines.size.odd?
|
|
168
|
+
|
|
169
|
+
no_cov_lines.each_slice(2).map do |(_line_start, index_start), (_line_end, index_end)|
|
|
170
|
+
index_start..index_end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def build_lines
|
|
175
|
+
coverage_exceeding_source_warn if coverage_data["lines"].size > src.size
|
|
176
|
+
lines = src.map.with_index(1) do |src, i|
|
|
177
|
+
SimpleCov::SourceFile::Line.new(src, i, coverage_data["lines"][i - 1])
|
|
178
|
+
end
|
|
179
|
+
process_skipped_lines(lines)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def process_skipped_lines(lines)
|
|
183
|
+
# the array the lines are kept in is 0-based whereas the line numbers in the nocov
|
|
184
|
+
# chunks are 1-based and are expected to be like this in other parts (and it's also
|
|
185
|
+
# arguably more understandable)
|
|
186
|
+
no_cov_chunks.each { |chunk| lines[(chunk.begin - 1)..(chunk.end - 1)].each(&:skipped!) }
|
|
187
|
+
|
|
188
|
+
lines
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def lines_strength
|
|
192
|
+
lines.map(&:coverage).compact.reduce(:+)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Warning to identify condition from Issue #56
|
|
196
|
+
def coverage_exceeding_source_warn
|
|
197
|
+
warn "Warning: coverage data provided by Coverage [#{coverage_data['lines'].size}] exceeds number of lines in #{filename} [#{src.size}]"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
#
|
|
201
|
+
# Build full branches report
|
|
202
|
+
# Root branches represent the wrapper of all condition state that
|
|
203
|
+
# have inside the branches
|
|
204
|
+
#
|
|
205
|
+
# @return [Hash]
|
|
206
|
+
#
|
|
207
|
+
def build_branches_report
|
|
208
|
+
branches.reject(&:skipped?).each_with_object({}) do |branch, coverage_statistics|
|
|
209
|
+
coverage_statistics[branch.report_line] ||= []
|
|
210
|
+
coverage_statistics[branch.report_line] << branch.report
|
|
211
|
+
end
|
|
212
|
+
end
|
|
165
213
|
|
|
166
214
|
#
|
|
167
215
|
# Call recursive method that transform our static hash to array of objects
|
|
168
216
|
# @return [Array]
|
|
169
217
|
#
|
|
170
218
|
def build_branches
|
|
171
|
-
coverage_branch_data =
|
|
219
|
+
coverage_branch_data = coverage_data.fetch("branches", {})
|
|
172
220
|
branches = coverage_branch_data.flat_map do |condition, coverage_branches|
|
|
173
221
|
build_branches_from(condition, coverage_branches)
|
|
174
222
|
end
|
|
@@ -187,7 +235,7 @@ module SimpleCov
|
|
|
187
235
|
end
|
|
188
236
|
|
|
189
237
|
# Since we are dumping to and loading from JSON, and we have arrays as keys those
|
|
190
|
-
# don't make their way back to us intact e.g. just as a string
|
|
238
|
+
# don't make their way back to us intact e.g. just as a string
|
|
191
239
|
#
|
|
192
240
|
# We should probably do something different here, but as it stands these are
|
|
193
241
|
# our data structures that we write so eval isn't _too_ bad.
|
|
@@ -199,9 +247,8 @@ module SimpleCov
|
|
|
199
247
|
# put them through here.
|
|
200
248
|
return structure if structure.is_a?(Array)
|
|
201
249
|
|
|
202
|
-
# as of right now the keys are still symbolized
|
|
203
250
|
# rubocop:disable Security/Eval
|
|
204
|
-
eval structure
|
|
251
|
+
eval structure
|
|
205
252
|
# rubocop:enable Security/Eval
|
|
206
253
|
end
|
|
207
254
|
|
|
@@ -211,103 +258,43 @@ module SimpleCov
|
|
|
211
258
|
# [:then, 4, 6, 6, 6, 10]
|
|
212
259
|
#
|
|
213
260
|
# which is [type, id, start_line, start_col, end_line, end_col]
|
|
214
|
-
|
|
261
|
+
_condition_type, _condition_id, condition_start_line, * = restore_ruby_data_structure(condition)
|
|
215
262
|
|
|
216
|
-
branches
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
263
|
+
branches.map do |branch_data, hit_count|
|
|
264
|
+
branch_data = restore_ruby_data_structure(branch_data)
|
|
265
|
+
build_branch(branch_data, hit_count, condition_start_line)
|
|
266
|
+
end
|
|
220
267
|
end
|
|
221
268
|
|
|
222
|
-
def build_branch(branch_data, hit_count, condition_start_line
|
|
223
|
-
type,
|
|
269
|
+
def build_branch(branch_data, hit_count, condition_start_line)
|
|
270
|
+
type, _id, start_line, _start_col, end_line, _end_col = branch_data
|
|
224
271
|
|
|
225
272
|
SourceFile::Branch.new(
|
|
226
|
-
# rubocop these are keyword args please let me keep them, thank you
|
|
227
|
-
# rubocop:disable Style/HashSyntax
|
|
228
273
|
start_line: start_line,
|
|
229
274
|
end_line: end_line,
|
|
230
275
|
coverage: hit_count,
|
|
231
276
|
inline: start_line == condition_start_line,
|
|
232
|
-
|
|
233
|
-
# rubocop:enable Style/HashSyntax
|
|
277
|
+
type: type
|
|
234
278
|
)
|
|
235
279
|
end
|
|
236
280
|
|
|
237
|
-
def
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
branch_type == :else &&
|
|
246
|
-
condition_start_line == branch_start_line
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
#
|
|
250
|
-
# Branch is positive or negative.
|
|
251
|
-
# For `case` conditions, `when` always supposed as positive branch.
|
|
252
|
-
# For `if, else` conditions:
|
|
253
|
-
# coverage returns matrices ex: [:if, 0,..] => {[:then, 1,..], [:else, 2,..]},
|
|
254
|
-
# positive branch always has id equals to condition id incremented by 1.
|
|
255
|
-
#
|
|
256
|
-
# @return [Boolean]
|
|
257
|
-
#
|
|
258
|
-
def positive_branch?(condition_id, branch_id, branch_type)
|
|
259
|
-
return true if branch_type == :when
|
|
260
|
-
|
|
261
|
-
branch_id == (1 + condition_id)
|
|
281
|
+
def line_coverage_statistics
|
|
282
|
+
{
|
|
283
|
+
line: CoverageStatistics.new(
|
|
284
|
+
total_strength: lines_strength,
|
|
285
|
+
covered: covered_lines.size,
|
|
286
|
+
missed: missed_lines.size
|
|
287
|
+
)
|
|
288
|
+
}
|
|
262
289
|
end
|
|
263
290
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
def covered_branches
|
|
272
|
-
@covered_branches ||= branches.select(&:covered?)
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
#
|
|
276
|
-
# Select the missed branches with coverage equal to zero
|
|
277
|
-
#
|
|
278
|
-
# @return [Array]
|
|
279
|
-
#
|
|
280
|
-
def missed_branches
|
|
281
|
-
@missed_branches ||= branches.select(&:missed?)
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
def branches_for_line(line_number)
|
|
285
|
-
branches_report.fetch(line_number, [])
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
#
|
|
289
|
-
# Check if any branches missing on given line number
|
|
290
|
-
#
|
|
291
|
-
# @param [Integer] line_number
|
|
292
|
-
#
|
|
293
|
-
# @return [Boolean]
|
|
294
|
-
#
|
|
295
|
-
def line_with_missed_branch?(line_number)
|
|
296
|
-
branches_for_line(line_number).select { |count, _sign| count.zero? }.any?
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
#
|
|
300
|
-
# Build full branches report
|
|
301
|
-
# Root branches represent the wrapper of all condition state that
|
|
302
|
-
# have inside the branches
|
|
303
|
-
#
|
|
304
|
-
# @return [Hash]
|
|
305
|
-
#
|
|
306
|
-
def build_branches_report
|
|
307
|
-
branches.reject(&:skipped?).each_with_object({}) do |branch, coverage_statistics|
|
|
308
|
-
coverage_statistics[branch.report_line] ||= []
|
|
309
|
-
coverage_statistics[branch.report_line] << branch.report
|
|
310
|
-
end
|
|
291
|
+
def branch_coverage_statistics
|
|
292
|
+
{
|
|
293
|
+
branch: CoverageStatistics.new(
|
|
294
|
+
covered: covered_branches.size,
|
|
295
|
+
missed: missed_branches.size
|
|
296
|
+
)
|
|
297
|
+
}
|
|
311
298
|
end
|
|
312
299
|
end
|
|
313
300
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
#
|
|
5
|
-
#
|
|
5
|
+
# Select the files that related to working scope directory of SimpleCov
|
|
6
6
|
#
|
|
7
7
|
module UselessResultsRemover
|
|
8
8
|
ROOT_REGX = /\A#{Regexp.escape(SimpleCov.root + File::SEPARATOR)}/io.freeze
|
data/lib/simplecov/version.rb
CHANGED