simplecov 0.14.1 → 0.15.1
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/.rubocop.yml +6 -2
- data/.travis.yml +4 -4
- data/CHANGELOG.md +24 -1
- data/Gemfile +2 -2
- data/MIT-LICENSE +1 -1
- data/README.md +52 -7
- data/Rakefile +0 -2
- data/doc/alternate-formatters.md +6 -1
- data/doc/editor-integration.md +6 -1
- data/features/config_tracked_files.feature +1 -1
- data/features/config_tracked_files_relevant_lines.feature +31 -0
- data/features/step_definitions/transformers.rb +1 -1
- data/features/support/aruba_freedom_patch.rb +53 -0
- data/features/support/env.rb +5 -5
- data/lib/simplecov/configuration.rb +5 -9
- data/lib/simplecov/defaults.rb +5 -5
- data/lib/simplecov/file_list.rb +5 -5
- data/lib/simplecov/filter.rb +39 -4
- data/lib/simplecov/formatter/simple_formatter.rb +1 -1
- data/lib/simplecov/lines_classifier.rb +32 -0
- data/lib/simplecov/result_merger.rb +42 -13
- data/lib/simplecov/source_file.rb +6 -1
- data/lib/simplecov/version.rb +1 -23
- data/lib/simplecov.rb +16 -10
- data/spec/defaults_spec.rb +41 -0
- data/spec/filters_spec.rb +104 -0
- data/spec/lines_classifier_spec.rb +103 -0
- data/spec/result_merger_spec.rb +89 -6
- data/spec/simplecov_spec.rb +109 -0
- data/spec/source_file_spec.rb +4 -0
- metadata +21 -15
|
@@ -17,25 +17,31 @@ module SimpleCov
|
|
|
17
17
|
File.join(SimpleCov.coverage_path, ".resultset.json.lock")
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# Loads the cached resultset from JSON and returns it as a Hash
|
|
20
|
+
# Loads the cached resultset from JSON and returns it as a Hash,
|
|
21
|
+
# caching it for subsequent accesses.
|
|
21
22
|
def resultset
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
@resultset ||= begin
|
|
24
|
+
data = stored_data
|
|
25
|
+
if data
|
|
26
|
+
begin
|
|
27
|
+
JSON.parse(data) || {}
|
|
28
|
+
rescue
|
|
29
|
+
{}
|
|
30
|
+
end
|
|
31
|
+
else
|
|
26
32
|
{}
|
|
27
33
|
end
|
|
28
|
-
else
|
|
29
|
-
{}
|
|
30
34
|
end
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
# Returns the contents of the resultset cache as a string or if the file is missing or empty nil
|
|
34
38
|
def stored_data
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
synchronize_resultset do
|
|
40
|
+
return unless File.exist?(resultset_path)
|
|
41
|
+
data = File.read(resultset_path)
|
|
42
|
+
return if data.nil? || data.length < 2
|
|
43
|
+
data
|
|
44
|
+
end
|
|
39
45
|
end
|
|
40
46
|
|
|
41
47
|
# Gets the resultset hash and re-creates all included instances
|
|
@@ -76,8 +82,9 @@ module SimpleCov
|
|
|
76
82
|
|
|
77
83
|
# Saves the given SimpleCov::Result in the resultset cache
|
|
78
84
|
def store_result(result)
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
synchronize_resultset do
|
|
86
|
+
# Ensure we have the latest, in case it was already cached
|
|
87
|
+
clear_resultset
|
|
81
88
|
new_set = resultset
|
|
82
89
|
command_name, data = result.to_hash.first
|
|
83
90
|
new_set[command_name] = data
|
|
@@ -87,6 +94,28 @@ module SimpleCov
|
|
|
87
94
|
end
|
|
88
95
|
true
|
|
89
96
|
end
|
|
97
|
+
|
|
98
|
+
# Ensure only one process is reading or writing the resultset at any
|
|
99
|
+
# given time
|
|
100
|
+
def synchronize_resultset
|
|
101
|
+
# make it reentrant
|
|
102
|
+
return yield if defined?(@resultset_locked) && @resultset_locked
|
|
103
|
+
|
|
104
|
+
begin
|
|
105
|
+
@resultset_locked = true
|
|
106
|
+
File.open(resultset_writelock, "w+") do |f|
|
|
107
|
+
f.flock(File::LOCK_EX)
|
|
108
|
+
yield
|
|
109
|
+
end
|
|
110
|
+
ensure
|
|
111
|
+
@resultset_locked = false
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Clear out the previously cached .resultset
|
|
116
|
+
def clear_resultset
|
|
117
|
+
@resultset = nil
|
|
118
|
+
end
|
|
90
119
|
end
|
|
91
120
|
end
|
|
92
121
|
end
|
|
@@ -80,6 +80,11 @@ module SimpleCov
|
|
|
80
80
|
@coverage = coverage
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
+
# The path to this source file relative to the projects directory
|
|
84
|
+
def project_filename
|
|
85
|
+
@filename.sub(/^#{SimpleCov.root}/, "")
|
|
86
|
+
end
|
|
87
|
+
|
|
83
88
|
# The source code for this file. Aliased as :source
|
|
84
89
|
def src
|
|
85
90
|
# We intentionally read source code lazily to
|
|
@@ -175,7 +180,7 @@ module SimpleCov
|
|
|
175
180
|
skipping = false
|
|
176
181
|
|
|
177
182
|
lines.each do |line|
|
|
178
|
-
if line.src =~
|
|
183
|
+
if line.src =~ SimpleCov::LinesClassifier.no_cov_line
|
|
179
184
|
skipping = !skipping
|
|
180
185
|
line.skipped!
|
|
181
186
|
elsif skipping
|
data/lib/simplecov/version.rb
CHANGED
|
@@ -1,25 +1,3 @@
|
|
|
1
1
|
module SimpleCov
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def version.to_a
|
|
5
|
-
split(".").map(&:to_i)
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def version.major
|
|
9
|
-
to_a[0]
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def version.minor
|
|
13
|
-
to_a[1]
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def version.patch
|
|
17
|
-
to_a[2]
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def version.pre
|
|
21
|
-
to_a[3]
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
VERSION = version
|
|
2
|
+
VERSION = "0.15.1".freeze
|
|
25
3
|
end
|
data/lib/simplecov.rb
CHANGED
|
@@ -55,7 +55,7 @@ module SimpleCov
|
|
|
55
55
|
|
|
56
56
|
#
|
|
57
57
|
# Finds files that were to be tracked but were not loaded and initializes
|
|
58
|
-
#
|
|
58
|
+
# the line-by-line coverage to zero (if relevant) or nil (comments / whitespace etc).
|
|
59
59
|
#
|
|
60
60
|
def add_not_loaded_files(result)
|
|
61
61
|
if tracked_files
|
|
@@ -63,7 +63,7 @@ module SimpleCov
|
|
|
63
63
|
Dir[tracked_files].each do |file|
|
|
64
64
|
absolute = File.expand_path(file)
|
|
65
65
|
|
|
66
|
-
result[absolute] ||=
|
|
66
|
+
result[absolute] ||= LinesClassifier.new.classify(File.foreach(absolute))
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
@@ -75,23 +75,21 @@ module SimpleCov
|
|
|
75
75
|
# from cache using SimpleCov::ResultMerger if use_merging is activated (default)
|
|
76
76
|
#
|
|
77
77
|
def result
|
|
78
|
-
|
|
79
|
-
@result = nil unless defined?(@result)
|
|
78
|
+
return @result if result?
|
|
80
79
|
|
|
81
80
|
# Collect our coverage result
|
|
82
|
-
if running
|
|
81
|
+
if running
|
|
83
82
|
@result = SimpleCov::Result.new add_not_loaded_files(Coverage.result)
|
|
84
83
|
end
|
|
85
84
|
|
|
86
85
|
# If we're using merging of results, store the current result
|
|
87
|
-
# first, then merge the results and return those
|
|
86
|
+
# first (if there is one), then merge the results and return those
|
|
88
87
|
if use_merging
|
|
89
88
|
SimpleCov::ResultMerger.store_result(@result) if result?
|
|
90
|
-
|
|
91
|
-
SimpleCov::ResultMerger.merged_result
|
|
92
|
-
else
|
|
93
|
-
@result
|
|
89
|
+
@result = SimpleCov::ResultMerger.merged_result
|
|
94
90
|
end
|
|
91
|
+
|
|
92
|
+
@result
|
|
95
93
|
ensure
|
|
96
94
|
self.running = false
|
|
97
95
|
end
|
|
@@ -158,6 +156,13 @@ module SimpleCov
|
|
|
158
156
|
false
|
|
159
157
|
end
|
|
160
158
|
end
|
|
159
|
+
|
|
160
|
+
#
|
|
161
|
+
# Clear out the previously cached .result. Primarily useful in testing
|
|
162
|
+
#
|
|
163
|
+
def clear_result
|
|
164
|
+
@result = nil
|
|
165
|
+
end
|
|
161
166
|
end
|
|
162
167
|
end
|
|
163
168
|
|
|
@@ -172,6 +177,7 @@ require "simplecov/result"
|
|
|
172
177
|
require "simplecov/filter"
|
|
173
178
|
require "simplecov/formatter"
|
|
174
179
|
require "simplecov/last_run"
|
|
180
|
+
require "simplecov/lines_classifier"
|
|
175
181
|
require "simplecov/raw_coverage"
|
|
176
182
|
require "simplecov/result_merger"
|
|
177
183
|
require "simplecov/command_guesser"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require "helper"
|
|
2
|
+
|
|
3
|
+
if SimpleCov.usable?
|
|
4
|
+
describe SimpleCov do
|
|
5
|
+
skip "requires the default configuration" if ENV["SIMPLECOV_NO_DEFAULTS"]
|
|
6
|
+
|
|
7
|
+
context "profiles" do
|
|
8
|
+
let(:config_class) do
|
|
9
|
+
Class.new do
|
|
10
|
+
include SimpleCov::Configuration
|
|
11
|
+
|
|
12
|
+
def load_profile(name)
|
|
13
|
+
configure(&SimpleCov.profiles[name.to_sym])
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
let(:config) { config_class.new }
|
|
19
|
+
|
|
20
|
+
def filtered?(config, filename)
|
|
21
|
+
path = File.join(SimpleCov.root, filename)
|
|
22
|
+
file = SimpleCov::SourceFile.new(path, [nil, 1, 1, 1, nil, nil, 1, 0, nil, nil])
|
|
23
|
+
config.filters.any? { |filter| filter.matches?(file) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "provides a sensible test_frameworks profile" do
|
|
27
|
+
config.load_profile(:test_frameworks)
|
|
28
|
+
expect(filtered?(config, "foo.rb")).not_to be
|
|
29
|
+
expect(filtered?(config, "test/foo.rb")).to be
|
|
30
|
+
expect(filtered?(config, "spec/bar.rb")).to be
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "provides a sensible rails profile" do
|
|
34
|
+
config.load_profile(:rails)
|
|
35
|
+
expect(filtered?(config, "app/models/user.rb")).not_to be
|
|
36
|
+
expect(filtered?(config, "db/schema.rb")).to be
|
|
37
|
+
expect(filtered?(config, "config/environment.rb")).to be
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/spec/filters_spec.rb
CHANGED
|
@@ -26,6 +26,27 @@ if SimpleCov.usable?
|
|
|
26
26
|
expect(SimpleCov::StringFilter.new("sample.rb")).to be_matches subject
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
it "doesn't match a parent directory with a new SimpleCov::StringFilter" do
|
|
30
|
+
parent_dir_name = File.basename(File.expand_path("..", File.dirname(__FILE__)))
|
|
31
|
+
expect(SimpleCov::StringFilter.new(parent_dir_name)).not_to be_matches subject
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "matches a new SimpleCov::StringFilter '/fixtures/'" do
|
|
35
|
+
expect(SimpleCov::StringFilter.new("sample.rb")).to be_matches subject
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "matches a new SimpleCov::RegexFilter /\/fixtures\//" do
|
|
39
|
+
expect(SimpleCov::RegexFilter.new(/\/fixtures\//)).to be_matches subject
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "doesn't match a new SimpleCov::RegexFilter /^\/fixtures\//" do
|
|
43
|
+
expect(SimpleCov::RegexFilter.new(/^\/fixtures\//)).not_to be_matches subject
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "matches a new SimpleCov::RegexFilter /^\/spec\//" do
|
|
47
|
+
expect(SimpleCov::RegexFilter.new(/^\/spec\//)).to be_matches subject
|
|
48
|
+
end
|
|
49
|
+
|
|
29
50
|
it "doesn't match a new SimpleCov::BlockFilter that is not applicable" do
|
|
30
51
|
expect(SimpleCov::BlockFilter.new(proc { |s| File.basename(s.filename) == "foo.rb" })).not_to be_matches subject
|
|
31
52
|
end
|
|
@@ -46,6 +67,45 @@ if SimpleCov.usable?
|
|
|
46
67
|
expect(SimpleCov::ArrayFilter.new(["sample.rb", "other_file.rb"])).to be_matches subject
|
|
47
68
|
end
|
|
48
69
|
|
|
70
|
+
it "doesn't match a parent directory with a new SimpleCov::ArrayFilter" do
|
|
71
|
+
parent_dir_name = File.basename(File.expand_path("..", File.dirname(__FILE__)))
|
|
72
|
+
expect(SimpleCov::ArrayFilter.new([parent_dir_name])).not_to be_matches subject
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "matches a new SimpleCov::ArrayFilter when /sample.rb/ is passed as array" do
|
|
76
|
+
expect(SimpleCov::ArrayFilter.new([/sample.rb/])).to be_matches subject
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "doesn't match a new SimpleCov::ArrayFilter when a file path different than /sample.rb/ is passed as array" do
|
|
80
|
+
expect(SimpleCov::ArrayFilter.new([/other_file.rb/])).not_to be_matches subject
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "matches a new SimpleCov::ArrayFilter when a block is passed as array and returns true" do
|
|
84
|
+
expect(SimpleCov::ArrayFilter.new([proc { true }])).to be_matches subject
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "doesn't match a new SimpleCov::ArrayFilter when a block that returns false is passed as array" do
|
|
88
|
+
expect(SimpleCov::ArrayFilter.new([proc { false }])).not_to be_matches subject
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "matches a new SimpleCov::ArrayFilter when a custom class that returns true is passed as array" do
|
|
92
|
+
filter = Class.new(SimpleCov::Filter) do
|
|
93
|
+
def matches?(_)
|
|
94
|
+
true
|
|
95
|
+
end
|
|
96
|
+
end.new(nil)
|
|
97
|
+
expect(SimpleCov::ArrayFilter.new([filter])).to be_matches subject
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "doesn't match a new SimpleCov::ArrayFilter when a custom class that returns false is passed as array" do
|
|
101
|
+
filter = Class.new(SimpleCov::Filter) do
|
|
102
|
+
def matches?(_)
|
|
103
|
+
false
|
|
104
|
+
end
|
|
105
|
+
end.new(nil)
|
|
106
|
+
expect(SimpleCov::ArrayFilter.new([filter])).not_to be_matches subject
|
|
107
|
+
end
|
|
108
|
+
|
|
49
109
|
context "with no filters set up and a basic source file in an array" do
|
|
50
110
|
before do
|
|
51
111
|
@prev_filters = SimpleCov.filters
|
|
@@ -94,5 +154,49 @@ if SimpleCov.usable?
|
|
|
94
154
|
expect(SimpleCov.filtered(subject)).to be_a SimpleCov::FileList
|
|
95
155
|
end
|
|
96
156
|
end
|
|
157
|
+
|
|
158
|
+
context "with the default configuration" do
|
|
159
|
+
skip "requires the default configuration" if ENV["SIMPLECOV_NO_DEFAULTS"]
|
|
160
|
+
|
|
161
|
+
def a_file(path)
|
|
162
|
+
path = File.join(SimpleCov.root, path) unless path.start_with?("/")
|
|
163
|
+
SimpleCov::SourceFile.new(path, [nil, 1, 1, 1, nil, nil, 1, 0, nil, nil])
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
context "inside the project" do
|
|
167
|
+
it "doesn't filter" do
|
|
168
|
+
expect(SimpleCov.filtered([a_file("foo.rb")]).count).to eq(1)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "filters vendor/bundle" do
|
|
172
|
+
expect(SimpleCov.filtered([a_file("vendor/bundle/foo.rb")]).count).to eq(0)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
context "outside the project" do
|
|
177
|
+
it "filters" do
|
|
178
|
+
expect(SimpleCov.filtered([a_file("/other/path/foo.rb")]).count).to eq(0)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it "filters even if the sibling directory has SimpleCov.root as a prefix" do
|
|
182
|
+
sibling_dir = SimpleCov.root + "_cache"
|
|
183
|
+
expect(SimpleCov.filtered([a_file(sibling_dir + "/foo.rb")]).count).to eq(0)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
describe ".class_for_argument" do
|
|
189
|
+
it "returns SimpleCov::StringFilter for a string" do
|
|
190
|
+
expect(SimpleCov::Filter.class_for_argument("filestring")).to eq(SimpleCov::StringFilter)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it "returns SimpleCov::RegexFilter for a string" do
|
|
194
|
+
expect(SimpleCov::Filter.class_for_argument(/regex/)).to eq(SimpleCov::RegexFilter)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it "returns SimpleCov::RegexFilter for a string" do
|
|
198
|
+
expect(SimpleCov::Filter.class_for_argument(%w[file1 file2])).to eq(SimpleCov::ArrayFilter)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
97
201
|
end
|
|
98
202
|
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require "helper"
|
|
2
|
+
require "simplecov/lines_classifier"
|
|
3
|
+
|
|
4
|
+
describe SimpleCov::LinesClassifier do
|
|
5
|
+
describe "#classify" do
|
|
6
|
+
describe "relevant lines" do
|
|
7
|
+
it "determines code as relevant" do
|
|
8
|
+
classified_lines = subject.classify [
|
|
9
|
+
"module Foo",
|
|
10
|
+
" class Baz",
|
|
11
|
+
" def Bar",
|
|
12
|
+
" puts 'hi'",
|
|
13
|
+
" end",
|
|
14
|
+
" end",
|
|
15
|
+
"end",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
expect(classified_lines.length).to eq 7
|
|
19
|
+
expect(classified_lines).to all be_relevant
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "not-relevant lines" do
|
|
24
|
+
it "determines whitespace is not-relevant" do
|
|
25
|
+
classified_lines = subject.classify [
|
|
26
|
+
"",
|
|
27
|
+
" ",
|
|
28
|
+
"\t\t",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
expect(classified_lines.length).to eq 3
|
|
32
|
+
expect(classified_lines).to all be_irrelevant
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe "comments" do
|
|
36
|
+
it "determines comments are not-relevant" do
|
|
37
|
+
classified_lines = subject.classify [
|
|
38
|
+
"#Comment",
|
|
39
|
+
" # Leading space comment",
|
|
40
|
+
"\t# Leading tab comment",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
expect(classified_lines.length).to eq 3
|
|
44
|
+
expect(classified_lines).to all be_irrelevant
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "doesn't mistake interpolation as a comment" do
|
|
48
|
+
classified_lines = subject.classify [
|
|
49
|
+
'puts "#{var}"',
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
expect(classified_lines.length).to eq 1
|
|
53
|
+
expect(classified_lines).to all be_relevant
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe ":nocov: blocks" do
|
|
58
|
+
it "determines :nocov: blocks are not-relevant" do
|
|
59
|
+
classified_lines = subject.classify [
|
|
60
|
+
"# :nocov:",
|
|
61
|
+
"def hi",
|
|
62
|
+
"end",
|
|
63
|
+
"# :nocov:",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
expect(classified_lines.length).to eq 4
|
|
67
|
+
expect(classified_lines).to all be_irrelevant
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "determines all lines after a non-closing :nocov: as not-relevant" do
|
|
71
|
+
classified_lines = subject.classify [
|
|
72
|
+
"# :nocov:",
|
|
73
|
+
"puts 'Not relevant'",
|
|
74
|
+
"# :nocov:",
|
|
75
|
+
"puts 'Relevant again'",
|
|
76
|
+
"puts 'Still relevant'",
|
|
77
|
+
"# :nocov:",
|
|
78
|
+
"puts 'Not relevant till the end'",
|
|
79
|
+
"puts 'Ditto'",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
expect(classified_lines.length).to eq 8
|
|
83
|
+
|
|
84
|
+
expect(classified_lines[0..2]).to all be_irrelevant
|
|
85
|
+
expect(classified_lines[3..4]).to all be_relevant
|
|
86
|
+
expect(classified_lines[5..7]).to all be_irrelevant
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
RSpec::Matchers.define :be_relevant do
|
|
93
|
+
match do |actual|
|
|
94
|
+
actual == SimpleCov::LinesClassifier::RELEVANT
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
RSpec::Matchers.define :be_irrelevant do
|
|
99
|
+
match do |actual|
|
|
100
|
+
actual == SimpleCov::LinesClassifier::NOT_RELEVANT
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
data/spec/result_merger_spec.rb
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
require "helper"
|
|
2
|
+
require "tempfile"
|
|
3
|
+
require "timeout"
|
|
2
4
|
|
|
3
5
|
if SimpleCov.usable?
|
|
4
6
|
describe SimpleCov::ResultMerger do
|
|
7
|
+
before do
|
|
8
|
+
SimpleCov::ResultMerger.clear_resultset
|
|
9
|
+
File.delete(SimpleCov::ResultMerger.resultset_path) if File.exist?(SimpleCov::ResultMerger.resultset_path)
|
|
10
|
+
end
|
|
11
|
+
|
|
5
12
|
describe "with two faked coverage resultsets" do
|
|
6
13
|
before do
|
|
7
|
-
SimpleCov.use_merging true
|
|
8
14
|
@resultset1 = {
|
|
9
15
|
source_fixture("sample.rb") => [nil, 1, 1, 1, nil, nil, 1, 1, nil, nil],
|
|
10
16
|
source_fixture("app/models/user.rb") => [nil, 1, 1, 1, nil, nil, 1, 0, nil, nil],
|
|
@@ -56,7 +62,7 @@ if SimpleCov.usable?
|
|
|
56
62
|
end
|
|
57
63
|
|
|
58
64
|
it "returns a hash containing keys ['result1' and 'result2'] for resultset" do
|
|
59
|
-
expect(SimpleCov::ResultMerger.resultset.keys.sort).to eq %w
|
|
65
|
+
expect(SimpleCov::ResultMerger.resultset.keys.sort).to eq %w[result1 result2]
|
|
60
66
|
end
|
|
61
67
|
|
|
62
68
|
it "returns proper values for merged_result" do
|
|
@@ -73,15 +79,92 @@ if SimpleCov.usable?
|
|
|
73
79
|
expect(SimpleCov::ResultMerger.results.length).to eq(1)
|
|
74
80
|
end
|
|
75
81
|
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe ".store_result" do
|
|
87
|
+
it "refreshes the resultset" do
|
|
88
|
+
set = SimpleCov::ResultMerger.resultset
|
|
89
|
+
SimpleCov::ResultMerger.store_result({})
|
|
90
|
+
new_set = SimpleCov::ResultMerger.resultset
|
|
91
|
+
expect(new_set).not_to be(set)
|
|
92
|
+
end
|
|
76
93
|
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
it "persists to disk" do
|
|
95
|
+
SimpleCov::ResultMerger.store_result("a" => [1])
|
|
96
|
+
SimpleCov::ResultMerger.clear_resultset
|
|
97
|
+
new_set = SimpleCov::ResultMerger.resultset
|
|
98
|
+
expect(new_set).to eq("a" => [1])
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it "synchronizes writes" do
|
|
102
|
+
expect(SimpleCov::ResultMerger).to receive(:synchronize_resultset)
|
|
103
|
+
SimpleCov::ResultMerger.store_result({})
|
|
104
|
+
end
|
|
105
|
+
end
|
|
79
106
|
|
|
80
|
-
|
|
81
|
-
|
|
107
|
+
describe ".resultset" do
|
|
108
|
+
it "caches" do
|
|
109
|
+
set = SimpleCov::ResultMerger.resultset
|
|
110
|
+
new_set = SimpleCov::ResultMerger.resultset
|
|
111
|
+
expect(new_set).to be(set)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "synchronizes reads" do
|
|
115
|
+
expect(SimpleCov::ResultMerger).to receive(:synchronize_resultset)
|
|
116
|
+
SimpleCov::ResultMerger.resultset
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
describe ".synchronize_resultset" do
|
|
121
|
+
it "is reentrant (i.e. doesn't block its own process)" do
|
|
122
|
+
# without @resultset_locked, this spec would fail and
|
|
123
|
+
# `.store_result` wouldn't work
|
|
124
|
+
expect do
|
|
125
|
+
Timeout.timeout(1) do
|
|
126
|
+
SimpleCov::ResultMerger.synchronize_resultset do
|
|
127
|
+
SimpleCov::ResultMerger.synchronize_resultset do
|
|
128
|
+
end
|
|
82
129
|
end
|
|
83
130
|
end
|
|
131
|
+
end.not_to raise_error
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it "blocks other processes" do
|
|
135
|
+
file = Tempfile.new("foo")
|
|
136
|
+
|
|
137
|
+
other_process = open("|ruby -e " + Shellwords.escape(<<-CODE) + " 2>/dev/null")
|
|
138
|
+
require "simplecov"
|
|
139
|
+
SimpleCov.coverage_dir(#{SimpleCov.coverage_dir.inspect})
|
|
140
|
+
|
|
141
|
+
# ensure the parent process has enough time to get a
|
|
142
|
+
# lock before we do
|
|
143
|
+
sleep 0.5
|
|
144
|
+
|
|
145
|
+
$stdout.sync = true
|
|
146
|
+
puts "running" # see `sleep`s in parent process
|
|
147
|
+
|
|
148
|
+
SimpleCov::ResultMerger.synchronize_resultset do
|
|
149
|
+
File.open(#{file.path.inspect}, "a") { |f| f.write("process 2\n") }
|
|
150
|
+
end
|
|
151
|
+
CODE
|
|
152
|
+
|
|
153
|
+
SimpleCov::ResultMerger.synchronize_resultset do
|
|
154
|
+
# wait until the child process is going, and then wait some more
|
|
155
|
+
# so we can be sure it blocks on the lock we already have.
|
|
156
|
+
sleep 0.1 until other_process.gets == "running\n"
|
|
157
|
+
sleep 1
|
|
158
|
+
|
|
159
|
+
# despite the sleeps, this will be written first since we got
|
|
160
|
+
# the first lock
|
|
161
|
+
File.open(file.path, "a") { |f| f.write("process 1\n") }
|
|
84
162
|
end
|
|
163
|
+
|
|
164
|
+
# wait for it to finish
|
|
165
|
+
other_process.gets
|
|
166
|
+
|
|
167
|
+
expect(file.read).to eq("process 1\nprocess 2\n")
|
|
85
168
|
end
|
|
86
169
|
end
|
|
87
170
|
end
|