simplecov 0.16.1 → 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 +5 -5
- data/CHANGELOG.md +92 -0
- data/CODE_OF_CONDUCT.md +76 -0
- data/README.md +271 -112
- data/doc/alternate-formatters.md +10 -0
- data/lib/simplecov/combine/branches_combiner.rb +32 -0
- data/lib/simplecov/combine/files_combiner.rb +24 -0
- data/lib/simplecov/combine/lines_combiner.rb +43 -0
- data/lib/simplecov/combine/results_combiner.rb +60 -0
- data/lib/simplecov/combine.rb +30 -0
- data/lib/simplecov/command_guesser.rb +6 -3
- data/lib/simplecov/configuration.rb +110 -9
- data/lib/simplecov/coverage_statistics.rb +56 -0
- data/lib/simplecov/defaults.rb +6 -2
- data/lib/simplecov/file_list.rb +66 -13
- data/lib/simplecov/filter.rb +2 -1
- data/lib/simplecov/formatter/multi_formatter.rb +2 -2
- data/lib/simplecov/formatter/simple_formatter.rb +4 -4
- data/lib/simplecov/last_run.rb +3 -1
- data/lib/simplecov/lines_classifier.rb +2 -2
- data/lib/simplecov/profiles/hidden_filter.rb +5 -0
- data/lib/simplecov/profiles/rails.rb +1 -1
- data/lib/simplecov/profiles.rb +9 -7
- data/lib/simplecov/result.rb +39 -6
- data/lib/simplecov/result_adapter.rb +30 -0
- data/lib/simplecov/result_merger.rb +18 -11
- data/lib/simplecov/simulate_coverage.rb +29 -0
- data/lib/simplecov/source_file/branch.rb +84 -0
- data/lib/simplecov/source_file/line.rb +72 -0
- data/lib/simplecov/source_file.rb +223 -126
- data/lib/simplecov/useless_results_remover.rb +16 -0
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov.rb +248 -63
- metadata +31 -53
- data/lib/simplecov/jruby_fix.rb +0 -44
- data/lib/simplecov/railtie.rb +0 -9
- data/lib/simplecov/railties/tasks.rake +0 -13
- data/lib/simplecov/raw_coverage.rb +0 -41
|
@@ -7,7 +7,7 @@ SimpleCov.profiles.define "rails" do
|
|
|
7
7
|
add_filter %r{^/db/}
|
|
8
8
|
|
|
9
9
|
add_group "Controllers", "app/controllers"
|
|
10
|
-
add_group "Channels", "app/channels"
|
|
10
|
+
add_group "Channels", "app/channels"
|
|
11
11
|
add_group "Models", "app/models"
|
|
12
12
|
add_group "Mailers", "app/mailers"
|
|
13
13
|
add_group "Helpers", "app/helpers"
|
data/lib/simplecov/profiles.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
# Profiles are SimpleCov configuration procs that can be easily
|
|
5
|
-
# loaded using SimpleCov.start :rails and defined using
|
|
6
|
-
# SimpleCov.profiles.define :foo do
|
|
7
|
-
# # SimpleCov configuration here, same as in SimpleCov.configure
|
|
8
|
-
# end
|
|
9
|
-
#
|
|
10
3
|
module SimpleCov
|
|
4
|
+
#
|
|
5
|
+
# Profiles are SimpleCov configuration procs that can be easily
|
|
6
|
+
# loaded using SimpleCov.start :rails and defined using
|
|
7
|
+
# SimpleCov.profiles.define :foo do
|
|
8
|
+
# # SimpleCov configuration here, same as in SimpleCov.configure
|
|
9
|
+
# end
|
|
10
|
+
#
|
|
11
11
|
class Profiles < Hash
|
|
12
12
|
#
|
|
13
13
|
# Define a SimpleCov profile:
|
|
@@ -18,6 +18,7 @@ module SimpleCov
|
|
|
18
18
|
def define(name, &blk)
|
|
19
19
|
name = name.to_sym
|
|
20
20
|
raise "SimpleCov Profile '#{name}' is already defined" unless self[name].nil?
|
|
21
|
+
|
|
21
22
|
self[name] = blk
|
|
22
23
|
end
|
|
23
24
|
|
|
@@ -27,6 +28,7 @@ module SimpleCov
|
|
|
27
28
|
def load(name)
|
|
28
29
|
name = name.to_sym
|
|
29
30
|
raise "Could not find SimpleCov Profile called '#{name}'" unless key?(name)
|
|
31
|
+
|
|
30
32
|
SimpleCov.configure(&self[name])
|
|
31
33
|
end
|
|
32
34
|
end
|
data/lib/simplecov/result.rb
CHANGED
|
@@ -5,7 +5,7 @@ require "forwardable"
|
|
|
5
5
|
|
|
6
6
|
module SimpleCov
|
|
7
7
|
#
|
|
8
|
-
# A simplecov code coverage result, initialized from the Hash Ruby
|
|
8
|
+
# A simplecov code coverage result, initialized from the Hash Ruby's built-in coverage
|
|
9
9
|
# library generates (Coverage.result).
|
|
10
10
|
#
|
|
11
11
|
class Result
|
|
@@ -20,15 +20,16 @@ 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
|
|
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
|
|
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)
|
|
29
|
-
|
|
30
|
-
@
|
|
31
|
-
|
|
29
|
+
result = adapt_result(original_result)
|
|
30
|
+
@original_result = result.freeze
|
|
31
|
+
@files = SimpleCov::FileList.new(result.map do |filename, coverage|
|
|
32
|
+
SimpleCov::SourceFile.new(filename, JSON.parse(JSON.dump(coverage))) if File.file?(filename)
|
|
32
33
|
end.compact.sort_by(&:filename))
|
|
33
34
|
filter!
|
|
34
35
|
end
|
|
@@ -61,13 +62,20 @@ module SimpleCov
|
|
|
61
62
|
|
|
62
63
|
# Returns a hash representation of this Result that can be used for marshalling it into JSON
|
|
63
64
|
def to_hash
|
|
64
|
-
{
|
|
65
|
+
{
|
|
66
|
+
command_name => {
|
|
67
|
+
"coverage" => coverage,
|
|
68
|
+
"timestamp" => created_at.to_i
|
|
69
|
+
}
|
|
70
|
+
}
|
|
65
71
|
end
|
|
66
72
|
|
|
67
73
|
# Loads a SimpleCov::Result#to_hash dump
|
|
68
74
|
def self.from_hash(hash)
|
|
69
75
|
command_name, data = hash.first
|
|
76
|
+
|
|
70
77
|
result = SimpleCov::Result.new(data["coverage"])
|
|
78
|
+
|
|
71
79
|
result.command_name = command_name
|
|
72
80
|
result.created_at = Time.at(data["timestamp"])
|
|
73
81
|
result
|
|
@@ -75,6 +83,31 @@ module SimpleCov
|
|
|
75
83
|
|
|
76
84
|
private
|
|
77
85
|
|
|
86
|
+
# We changed the format of the raw result data in simplecov, as people are likely
|
|
87
|
+
# to have "old" resultsets lying around (but not too old so that they're still
|
|
88
|
+
# considered we can adapt them).
|
|
89
|
+
# See https://github.com/colszowka/simplecov/pull/824#issuecomment-576049747
|
|
90
|
+
def adapt_result(result)
|
|
91
|
+
if pre_simplecov_0_18_result?(result)
|
|
92
|
+
adapt_pre_simplecov_0_18_result(result)
|
|
93
|
+
else
|
|
94
|
+
result
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# pre 0.18 coverage data pointed from file directly to an array of line coverage
|
|
99
|
+
def pre_simplecov_0_18_result?(result)
|
|
100
|
+
_key, data = result.first
|
|
101
|
+
|
|
102
|
+
data.is_a?(Array)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def adapt_pre_simplecov_0_18_result(result)
|
|
106
|
+
result.map do |file_path, line_coverage_data|
|
|
107
|
+
[file_path, {"lines" => line_coverage_data}]
|
|
108
|
+
end.to_h
|
|
109
|
+
end
|
|
110
|
+
|
|
78
111
|
def coverage
|
|
79
112
|
keys = original_result.keys & filenames
|
|
80
113
|
Hash[keys.zip(original_result.values_at(*keys))]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
#
|
|
5
|
+
# Responsible for adapting the format of the coverage result whether it's default or with statistics
|
|
6
|
+
#
|
|
7
|
+
class ResultAdapter
|
|
8
|
+
attr_reader :result
|
|
9
|
+
|
|
10
|
+
def initialize(result)
|
|
11
|
+
@result = result
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.call(*args)
|
|
15
|
+
new(*args).adapt
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def adapt
|
|
19
|
+
return unless result
|
|
20
|
+
|
|
21
|
+
result.each_with_object({}) do |(file_name, cover_statistic), adapted_result|
|
|
22
|
+
if cover_statistic.is_a?(Array)
|
|
23
|
+
adapted_result.merge!(file_name => {"lines" => cover_statistic})
|
|
24
|
+
else
|
|
25
|
+
adapted_result.merge!(file_name => cover_statistic)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
# Singleton that is responsible for caching, loading and merging
|
|
7
|
-
# SimpleCov::Results into a single result for coverage analysis based
|
|
8
|
-
# upon multiple test suites.
|
|
9
|
-
#
|
|
10
5
|
module SimpleCov
|
|
6
|
+
#
|
|
7
|
+
# Singleton that is responsible for caching, loading and merging
|
|
8
|
+
# SimpleCov::Results into a single result for coverage analysis based
|
|
9
|
+
# upon multiple test suites.
|
|
10
|
+
#
|
|
11
11
|
module ResultMerger
|
|
12
12
|
class << self
|
|
13
13
|
# The path to the .resultset.json cache file
|
|
@@ -27,7 +27,7 @@ module SimpleCov
|
|
|
27
27
|
if data
|
|
28
28
|
begin
|
|
29
29
|
JSON.parse(data) || {}
|
|
30
|
-
rescue
|
|
30
|
+
rescue StandardError
|
|
31
31
|
{}
|
|
32
32
|
end
|
|
33
33
|
else
|
|
@@ -40,8 +40,10 @@ module SimpleCov
|
|
|
40
40
|
def stored_data
|
|
41
41
|
synchronize_resultset do
|
|
42
42
|
return unless File.exist?(resultset_path)
|
|
43
|
+
|
|
43
44
|
data = File.read(resultset_path)
|
|
44
45
|
return if data.nil? || data.length < 2
|
|
46
|
+
|
|
45
47
|
data
|
|
46
48
|
end
|
|
47
49
|
end
|
|
@@ -55,19 +57,24 @@ module SimpleCov
|
|
|
55
57
|
resultset.each do |command_name, data|
|
|
56
58
|
result = SimpleCov::Result.from_hash(command_name => data)
|
|
57
59
|
# Only add result if the timeout is above the configured threshold
|
|
58
|
-
if (Time.now - result.created_at) < SimpleCov.merge_timeout
|
|
59
|
-
results << result
|
|
60
|
-
end
|
|
60
|
+
results << result if (Time.now - result.created_at) < SimpleCov.merge_timeout
|
|
61
61
|
end
|
|
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
|
-
|
|
70
|
-
|
|
75
|
+
parsed_results = JSON.parse(JSON.dump(results.map(&:original_result)))
|
|
76
|
+
combined_result = SimpleCov::Combine::ResultsCombiner.combine(*parsed_results)
|
|
77
|
+
result = SimpleCov::Result.new(combined_result)
|
|
71
78
|
# Specify the command name
|
|
72
79
|
result.command_name = results.map(&:command_name).sort.join(", ")
|
|
73
80
|
result
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
#
|
|
5
|
+
# Responsible for producing file coverage metrics.
|
|
6
|
+
#
|
|
7
|
+
module SimulateCoverage
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# Simulate normal file coverage report on
|
|
12
|
+
# ruby 2.5 and return similar hash with lines and branches keys
|
|
13
|
+
#
|
|
14
|
+
# Happens when a file wasn't required but still tracked.
|
|
15
|
+
#
|
|
16
|
+
# @return [Hash]
|
|
17
|
+
#
|
|
18
|
+
def call(absolute_path)
|
|
19
|
+
lines = File.foreach(absolute_path)
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
"lines" => LinesClassifier.new.classify(lines),
|
|
23
|
+
# we don't want to parse branches ourselves...
|
|
24
|
+
# requiring files can have side effects and we don't want to trigger that
|
|
25
|
+
"branches" => {}
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
5
|
+
#
|
|
6
|
+
# Representing single branch that has been detected in coverage report.
|
|
7
|
+
# Give us support methods that handle needed calculations.
|
|
8
|
+
class Branch
|
|
9
|
+
attr_reader :start_line, :end_line, :coverage, :type
|
|
10
|
+
|
|
11
|
+
# rubocop:disable Metrics/ParameterLists
|
|
12
|
+
def initialize(start_line:, end_line:, coverage:, inline:, type:)
|
|
13
|
+
@start_line = start_line
|
|
14
|
+
@end_line = end_line
|
|
15
|
+
@coverage = coverage
|
|
16
|
+
@inline = inline
|
|
17
|
+
@type = type
|
|
18
|
+
@skipped = false
|
|
19
|
+
end
|
|
20
|
+
# rubocop:enable Metrics/ParameterLists
|
|
21
|
+
|
|
22
|
+
def inline?
|
|
23
|
+
@inline
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#
|
|
27
|
+
# Return true if there is relevant count defined > 0
|
|
28
|
+
#
|
|
29
|
+
# @return [Boolean]
|
|
30
|
+
#
|
|
31
|
+
def covered?
|
|
32
|
+
!skipped? && coverage.positive?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
#
|
|
36
|
+
# Check if branche missed or not
|
|
37
|
+
#
|
|
38
|
+
# @return [Boolean]
|
|
39
|
+
#
|
|
40
|
+
def missed?
|
|
41
|
+
!skipped? && coverage.zero?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The line on which we want to report the coverage
|
|
45
|
+
#
|
|
46
|
+
# Usually we choose the line above the start of the branch (so that it shows up
|
|
47
|
+
# at if/else) because that
|
|
48
|
+
# * highlights the condition
|
|
49
|
+
# * makes it distinguishable if the first line of the branch is an inline branch
|
|
50
|
+
# (see the nested_branches fixture)
|
|
51
|
+
#
|
|
52
|
+
def report_line
|
|
53
|
+
if inline?
|
|
54
|
+
start_line
|
|
55
|
+
else
|
|
56
|
+
start_line - 1
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Flags the branch as skipped
|
|
61
|
+
def skipped!
|
|
62
|
+
@skipped = true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns true if the branch was marked skipped by virtue of nocov comments.
|
|
66
|
+
def skipped?
|
|
67
|
+
@skipped
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def overlaps_with?(line_range)
|
|
71
|
+
start_line <= line_range.end && end_line >= line_range.begin
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#
|
|
75
|
+
# Return array with coverage count and badge
|
|
76
|
+
#
|
|
77
|
+
# @return [Array]
|
|
78
|
+
#
|
|
79
|
+
def report
|
|
80
|
+
[type, coverage]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
5
|
+
# Representation of a single line in a source file including
|
|
6
|
+
# this specific line's source code, line_number and code coverage,
|
|
7
|
+
# with the coverage being either nil (coverage not applicable, e.g. comment
|
|
8
|
+
# line), 0 (line not covered) or >1 (the amount of times the line was
|
|
9
|
+
# executed)
|
|
10
|
+
class Line
|
|
11
|
+
# The source code for this line. Aliased as :source
|
|
12
|
+
attr_reader :src
|
|
13
|
+
# The line number in the source file. Aliased as :line, :number
|
|
14
|
+
attr_reader :line_number
|
|
15
|
+
# The coverage data for this line: either nil (never), 0 (missed) or >=1 (times covered)
|
|
16
|
+
attr_reader :coverage
|
|
17
|
+
# Whether this line was skipped
|
|
18
|
+
attr_reader :skipped
|
|
19
|
+
|
|
20
|
+
# Lets grab some fancy aliases, shall we?
|
|
21
|
+
alias source src
|
|
22
|
+
alias line line_number
|
|
23
|
+
alias number line_number
|
|
24
|
+
|
|
25
|
+
def initialize(src, line_number, coverage)
|
|
26
|
+
raise ArgumentError, "Only String accepted for source" unless src.is_a?(String)
|
|
27
|
+
raise ArgumentError, "Only Integer accepted for line_number" unless line_number.is_a?(Integer)
|
|
28
|
+
raise ArgumentError, "Only Integer and nil accepted for coverage" unless coverage.is_a?(Integer) || coverage.nil?
|
|
29
|
+
|
|
30
|
+
@src = src
|
|
31
|
+
@line_number = line_number
|
|
32
|
+
@coverage = coverage
|
|
33
|
+
@skipped = false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns true if this is a line that should have been covered, but was not
|
|
37
|
+
def missed?
|
|
38
|
+
!never? && !skipped? && coverage.zero?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns true if this is a line that has been covered
|
|
42
|
+
def covered?
|
|
43
|
+
!never? && !skipped? && coverage.positive?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns true if this line is not relevant for coverage
|
|
47
|
+
def never?
|
|
48
|
+
!skipped? && coverage.nil?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Flags this line as skipped
|
|
52
|
+
def skipped!
|
|
53
|
+
@skipped = true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns true if this line was skipped, false otherwise. Lines are skipped if they are wrapped with
|
|
57
|
+
# # :nocov: comment lines.
|
|
58
|
+
def skipped?
|
|
59
|
+
!!skipped
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The status of this line - either covered, missed, skipped or never. Useful i.e. for direct use
|
|
63
|
+
# as a css class in report generation
|
|
64
|
+
def status
|
|
65
|
+
return "skipped" if skipped?
|
|
66
|
+
return "never" if never?
|
|
67
|
+
return "missed" if missed?
|
|
68
|
+
return "covered" if covered?
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|