rspec-tracer 0.7.0 → 0.9.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/README.md +331 -260
- data/lib/rspec_tracer/cache.rb +8 -3
- data/lib/rspec_tracer/configuration.rb +12 -23
- data/lib/rspec_tracer/html_reporter/reporter.rb +15 -3
- data/lib/rspec_tracer/remote_cache/aws.rb +178 -0
- data/lib/rspec_tracer/remote_cache/cache.rb +36 -144
- data/lib/rspec_tracer/remote_cache/repo.rb +175 -0
- data/lib/rspec_tracer/remote_cache/validator.rb +52 -0
- data/lib/rspec_tracer/reporter.rb +79 -4
- data/lib/rspec_tracer/rspec_runner.rb +14 -4
- data/lib/rspec_tracer/runner.rb +29 -0
- data/lib/rspec_tracer/version.rb +1 -1
- data/lib/rspec_tracer.rb +17 -13
- metadata +13 -8
- data/lib/rspec_tracer/remote_cache/git.rb +0 -113
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecTracer
|
4
|
+
module RemoteCache
|
5
|
+
class Repo
|
6
|
+
class RepoError < StandardError; end
|
7
|
+
|
8
|
+
attr_reader :branch_name, :branch_ref, :branch_refs, :ancestry_refs, :cache_refs
|
9
|
+
|
10
|
+
def initialize(aws)
|
11
|
+
@aws = aws
|
12
|
+
@branch_name = ENV['GIT_BRANCH'].chomp
|
13
|
+
|
14
|
+
raise RepoError, 'GIT_BRANCH environment variable is not set' if @branch_name.nil?
|
15
|
+
|
16
|
+
fetch_head_ref
|
17
|
+
fetch_branch_ref
|
18
|
+
fetch_ancestry_refs
|
19
|
+
fetch_branch_refs
|
20
|
+
generate_cache_refs
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def fetch_head_ref
|
26
|
+
@head_ref = `git rev-parse HEAD`.chomp
|
27
|
+
|
28
|
+
raise RepoError, 'Could not find HEAD commit sha' unless $CHILD_STATUS.success?
|
29
|
+
end
|
30
|
+
|
31
|
+
def fetch_branch_ref
|
32
|
+
@merged_parents = []
|
33
|
+
@ignored_refs = []
|
34
|
+
|
35
|
+
unless merged?
|
36
|
+
@branch_ref = @head_ref
|
37
|
+
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
@ignored_refs << @head_ref
|
42
|
+
|
43
|
+
fetch_merged_parents
|
44
|
+
fetch_merged_branch_ref
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch_ancestry_refs
|
48
|
+
ref_list = `git rev-list --max-count=25 #{@branch_ref}`.chomp.split
|
49
|
+
|
50
|
+
raise RepoError, 'Could not find ancestry refs' unless $CHILD_STATUS.success?
|
51
|
+
|
52
|
+
ref_list = ref_list.to_set - @ignored_refs
|
53
|
+
@ancestry_refs = refs_committer_timestamp(ref_list.to_a)
|
54
|
+
|
55
|
+
return if @ancestry_refs.empty?
|
56
|
+
|
57
|
+
print_refs(@ancestry_refs, 'ancestry')
|
58
|
+
end
|
59
|
+
|
60
|
+
def fetch_branch_refs
|
61
|
+
unless @aws.branch_refs?(@branch_name)
|
62
|
+
puts "No branch refs for #{@branch_name} branch found in S3"
|
63
|
+
|
64
|
+
@branch_refs = {}
|
65
|
+
|
66
|
+
return
|
67
|
+
end
|
68
|
+
|
69
|
+
download_branch_refs
|
70
|
+
end
|
71
|
+
|
72
|
+
def generate_cache_refs
|
73
|
+
ref_list = @ancestry_refs.merge(@branch_refs)
|
74
|
+
|
75
|
+
if ref_list.empty?
|
76
|
+
@cache_refs = {}
|
77
|
+
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
@cache_refs = ref_list.sort_by { |_, timestamp| -timestamp }.to_h
|
82
|
+
|
83
|
+
print_refs(@cache_refs, 'cache')
|
84
|
+
end
|
85
|
+
|
86
|
+
def merged?
|
87
|
+
system('git', 'rev-parse', 'HEAD^2', out: File::NULL, err: File::NULL)
|
88
|
+
end
|
89
|
+
|
90
|
+
def fetch_merged_parents
|
91
|
+
first_parent = `git rev-parse HEAD^1`.chomp
|
92
|
+
@merged_parents << first_parent if $CHILD_STATUS.success?
|
93
|
+
|
94
|
+
second_parent = `git rev-parse HEAD^2`.chomp
|
95
|
+
@merged_parents << second_parent if $CHILD_STATUS.success?
|
96
|
+
|
97
|
+
raise RepoError, 'Could not find merged commit parents' if @merged_parents.length != 2
|
98
|
+
end
|
99
|
+
|
100
|
+
def fetch_merged_branch_ref
|
101
|
+
@origin_head_ref = `git rev-parse origin/HEAD`.chomp
|
102
|
+
@branch_ref = nil
|
103
|
+
|
104
|
+
if @merged_parents.first != @origin_head_ref
|
105
|
+
@branch_ref = @head_ref
|
106
|
+
@ignored_refs = []
|
107
|
+
|
108
|
+
return
|
109
|
+
end
|
110
|
+
|
111
|
+
@branch_ref = @merged_parents.last
|
112
|
+
@ignored_refs = @ignored_refs.to_set | `git rev-list #{@branch_ref}..origin/HEAD`.chomp.split
|
113
|
+
|
114
|
+
raise RepoError, 'Could not find ignored refs' unless $CHILD_STATUS.success?
|
115
|
+
end
|
116
|
+
|
117
|
+
def refs_committer_timestamp(ref_list)
|
118
|
+
return {} if ref_list.empty?
|
119
|
+
|
120
|
+
command = <<-COMMAND.strip.gsub(/\s+/, ' ')
|
121
|
+
git show
|
122
|
+
--no-patch
|
123
|
+
--format="%H %ct"
|
124
|
+
#{ref_list.join(' ')}
|
125
|
+
COMMAND
|
126
|
+
|
127
|
+
ref_list = `#{command}`.chomp
|
128
|
+
|
129
|
+
raise RepoError, 'Could not find ancestry refs' unless $CHILD_STATUS.success?
|
130
|
+
|
131
|
+
ref_list.split("\n").map(&:split).to_h.transform_values(&:to_i)
|
132
|
+
end
|
133
|
+
|
134
|
+
def download_branch_refs
|
135
|
+
file_name = File.join(RSpecTracer.cache_path, 'branch_refs.json')
|
136
|
+
|
137
|
+
if @aws.download_branch_refs(branch_name, file_name)
|
138
|
+
@branch_refs = JSON.parse(File.read(file_name)).transform_values(&:to_i)
|
139
|
+
|
140
|
+
return if @branch_refs.empty?
|
141
|
+
|
142
|
+
filter_branch_refs
|
143
|
+
print_refs(@branch_refs, 'branch')
|
144
|
+
else
|
145
|
+
@branch_refs = {}
|
146
|
+
|
147
|
+
File.rm_f(file_name)
|
148
|
+
|
149
|
+
puts "Failed to fetch branch refs for #{@branch_name} branch"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def filter_branch_refs
|
154
|
+
if @ancestry_refs.empty?
|
155
|
+
@branch_refs = @branch_refs.sort_by { |_, timestamp| -timestamp }.first(25).to_h
|
156
|
+
|
157
|
+
return
|
158
|
+
end
|
159
|
+
|
160
|
+
oldest_ancestry_time = @ancestry_refs.values.min
|
161
|
+
|
162
|
+
@branch_refs = @branch_refs
|
163
|
+
.select { |_, timestamp| timestamp >= oldest_ancestry_time }
|
164
|
+
.sort_by { |_, timestamp| -timestamp }
|
165
|
+
.first(25)
|
166
|
+
.to_h
|
167
|
+
end
|
168
|
+
|
169
|
+
def print_refs(refs, type)
|
170
|
+
puts "Fetched the following #{type} refs for #{@branch_name} branch:"
|
171
|
+
puts refs.map { |ref, timestamp| " * #{ref} (commit timestamp: #{timestamp})" }.join("\n")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecTracer
|
4
|
+
module RemoteCache
|
5
|
+
class Validator
|
6
|
+
CACHE_FILES_PER_TEST_SUITE = 8
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@test_suite_id = ENV['TEST_SUITE_ID']
|
10
|
+
@test_suites = ENV['TEST_SUITES']
|
11
|
+
|
12
|
+
if @test_suite_id.nil? ^ @test_suites.nil?
|
13
|
+
raise(
|
14
|
+
ValidationError,
|
15
|
+
'Both the enviornment variables TEST_SUITE_ID and TEST_SUITES are not set'
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
setup
|
20
|
+
end
|
21
|
+
|
22
|
+
def valid?(ref, cache_files)
|
23
|
+
last_run_regex = Regexp.new(format(@last_run_files_regex, ref: ref))
|
24
|
+
|
25
|
+
return false if cache_files.count { |file| file.match?(last_run_regex) } != @last_run_files_count
|
26
|
+
|
27
|
+
cache_regex = Regexp.new(format(@cached_files_regex, ref: ref))
|
28
|
+
|
29
|
+
cache_files.count { |file| file.match?(cache_regex) } == @cached_files_count
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def setup
|
35
|
+
if @test_suites.nil?
|
36
|
+
@last_run_files_count = 1
|
37
|
+
@last_run_files_regex = '/%<ref>s/last_run.json$'
|
38
|
+
@cached_files_count = CACHE_FILES_PER_TEST_SUITE
|
39
|
+
@cached_files_regex = '/%<ref>s/[0-9a-f]{32}/.+.json'
|
40
|
+
else
|
41
|
+
@test_suites = @test_suites.to_i
|
42
|
+
@test_suites_regex = (1..@test_suites).to_a.join('|')
|
43
|
+
|
44
|
+
@last_run_files_count = @test_suites
|
45
|
+
@last_run_files_regex = "/%<ref>s/(#{@test_suites_regex})/last_run.json$"
|
46
|
+
@cached_files_count = CACHE_FILES_PER_TEST_SUITE * @test_suites
|
47
|
+
@cached_files_regex = "/%<ref>s/(#{@test_suites_regex})/[0-9a-f]{32}/.+.json$"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -2,9 +2,10 @@
|
|
2
2
|
|
3
3
|
module RSpecTracer
|
4
4
|
class Reporter
|
5
|
-
attr_reader :all_examples, :
|
6
|
-
:
|
7
|
-
:
|
5
|
+
attr_reader :all_examples, :interrupted_examples, :possibly_flaky_examples,
|
6
|
+
:flaky_examples, :pending_examples, :all_files, :modified_files,
|
7
|
+
:deleted_files, :dependency, :reverse_dependency, :examples_coverage,
|
8
|
+
:last_run
|
8
9
|
|
9
10
|
def initialize
|
10
11
|
initialize_examples
|
@@ -15,6 +16,7 @@ module RSpecTracer
|
|
15
16
|
|
16
17
|
def register_example(example)
|
17
18
|
@all_examples[example[:example_id]] = example
|
19
|
+
@duplicate_examples[example[:example_id]] << example
|
18
20
|
end
|
19
21
|
|
20
22
|
def on_example_skipped(example_id)
|
@@ -36,8 +38,21 @@ module RSpecTracer
|
|
36
38
|
@all_examples[example_id][:execution_result] = formatted_execution_result(result)
|
37
39
|
end
|
38
40
|
|
41
|
+
def register_interrupted_examples
|
42
|
+
@all_examples.each_pair do |example_id, example|
|
43
|
+
next if example.key?(:execution_result)
|
44
|
+
|
45
|
+
@interrupted_examples << example_id
|
46
|
+
end
|
47
|
+
|
48
|
+
return if @interrupted_examples.empty?
|
49
|
+
|
50
|
+
puts "RSpec tracer is not processing #{@interrupted_examples.count} interrupted examples"
|
51
|
+
end
|
52
|
+
|
39
53
|
def register_deleted_examples(seen_examples)
|
40
54
|
@deleted_examples = seen_examples.keys.to_set - (@skipped_examples | @all_examples.keys)
|
55
|
+
@deleted_examples -= @interrupted_examples
|
41
56
|
|
42
57
|
@deleted_examples.select! do |example_id|
|
43
58
|
example = seen_examples[example_id]
|
@@ -62,6 +77,10 @@ module RSpecTracer
|
|
62
77
|
@pending_examples << example_id
|
63
78
|
end
|
64
79
|
|
80
|
+
def example_interrupted?(example_id)
|
81
|
+
@interrupted_examples.include?(example_id)
|
82
|
+
end
|
83
|
+
|
65
84
|
def example_passed?(example_id)
|
66
85
|
@passed_examples.include?(example_id)
|
67
86
|
end
|
@@ -106,6 +125,17 @@ module RSpecTracer
|
|
106
125
|
file_deleted?(file_name) || file_modified?(file_name)
|
107
126
|
end
|
108
127
|
|
128
|
+
def incorrect_analysis?
|
129
|
+
@duplicate_examples.select! { |_, examples| examples.count > 1 }
|
130
|
+
|
131
|
+
return false if @duplicate_examples.empty?
|
132
|
+
|
133
|
+
print_not_use_notice
|
134
|
+
print_duplicate_examples
|
135
|
+
|
136
|
+
true
|
137
|
+
end
|
138
|
+
|
109
139
|
def register_dependency(example_id, file_name)
|
110
140
|
@dependency[example_id] << file_name
|
111
141
|
end
|
@@ -116,6 +146,8 @@ module RSpecTracer
|
|
116
146
|
|
117
147
|
def generate_reverse_dependency_report
|
118
148
|
@dependency.each_pair do |example_id, files|
|
149
|
+
next if @interrupted_examples.include?(example_id)
|
150
|
+
|
119
151
|
example_file = @all_examples[example_id][:rerun_file_name]
|
120
152
|
|
121
153
|
files.each do |file_name|
|
@@ -133,9 +165,11 @@ module RSpecTracer
|
|
133
165
|
pid: RSpecTracer.pid,
|
134
166
|
actual_count: RSpec.world.example_count + @skipped_examples.count,
|
135
167
|
example_count: RSpec.world.example_count,
|
168
|
+
interrupted_examples: @interrupted_examples.count,
|
136
169
|
failed_examples: @failed_examples.count,
|
137
170
|
skipped_examples: @skipped_examples.count,
|
138
|
-
pending_examples: @pending_examples.count
|
171
|
+
pending_examples: @pending_examples.count,
|
172
|
+
flaky_examples: @flaky_examples.count
|
139
173
|
}
|
140
174
|
end
|
141
175
|
|
@@ -169,6 +203,8 @@ module RSpecTracer
|
|
169
203
|
|
170
204
|
def initialize_examples
|
171
205
|
@all_examples = {}
|
206
|
+
@duplicate_examples = Hash.new { |examples, example_id| examples[example_id] = [] }
|
207
|
+
@interrupted_examples = Set.new
|
172
208
|
@passed_examples = Set.new
|
173
209
|
@possibly_flaky_examples = Set.new
|
174
210
|
@flaky_examples = Set.new
|
@@ -226,6 +262,45 @@ module RSpecTracer
|
|
226
262
|
@reverse_dependency = report.to_h
|
227
263
|
end
|
228
264
|
|
265
|
+
def print_not_use_notice
|
266
|
+
justify = ' ' * 4
|
267
|
+
four_justify = justify * 4
|
268
|
+
|
269
|
+
puts '=' * 80
|
270
|
+
puts "#{four_justify}IMPORTANT NOTICE -- DO NOT USE RSPEC TRACER"
|
271
|
+
puts '=' * 80
|
272
|
+
puts "#{justify}It would be best to make changes so that the RSpec tracer can uniquely"
|
273
|
+
puts "#{justify}identify all the examples, and then you can enable the RSpec tracer back."
|
274
|
+
puts '=' * 80
|
275
|
+
puts
|
276
|
+
end
|
277
|
+
|
278
|
+
# rubocop:disable Metrics/AbcSize
|
279
|
+
def print_duplicate_examples
|
280
|
+
total = @duplicate_examples.sum { |_, examples| examples.length }
|
281
|
+
|
282
|
+
puts "RSpec tracer could not uniquely identify the following #{total} examples:"
|
283
|
+
|
284
|
+
justify = ' ' * 2
|
285
|
+
nested_justify = justify * 3
|
286
|
+
|
287
|
+
@duplicate_examples.each_pair do |example_id, examples|
|
288
|
+
puts "#{justify}- Example ID: #{example_id} (#{examples.count} examples)"
|
289
|
+
|
290
|
+
examples.each do |example|
|
291
|
+
description = example[:full_description].strip
|
292
|
+
file_name = example[:rerun_file_name].sub(%r{^/}, '')
|
293
|
+
line_number = example[:rerun_line_number]
|
294
|
+
location = "#{file_name}:#{line_number}"
|
295
|
+
|
296
|
+
puts "#{nested_justify}* #{description} (#{location})"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
puts
|
301
|
+
end
|
302
|
+
# rubocop:enable Metrics/AbcSize
|
303
|
+
|
229
304
|
def write_all_examples_report
|
230
305
|
file_name = File.join(@cache_dir, 'all_examples.json')
|
231
306
|
|
@@ -3,13 +3,23 @@
|
|
3
3
|
module RSpecTracer
|
4
4
|
module RSpecRunner
|
5
5
|
# rubocop:disable Metrics/AbcSize
|
6
|
-
def run_specs(
|
6
|
+
def run_specs(example_groups)
|
7
7
|
actual_count = RSpec.world.example_count
|
8
|
+
RSpecTracer.no_examples = actual_count.zero?
|
9
|
+
|
10
|
+
if RSpecTracer.no_examples
|
11
|
+
RSpecTracer.running = true
|
12
|
+
|
13
|
+
super(example_groups)
|
14
|
+
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
8
18
|
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
9
|
-
filtered_examples,
|
19
|
+
filtered_examples, filtered_example_groups = RSpecTracer.filter_examples
|
10
20
|
|
11
21
|
RSpec.world.instance_variable_set(:@filtered_examples, filtered_examples)
|
12
|
-
RSpec.world.instance_variable_set(:@example_groups,
|
22
|
+
RSpec.world.instance_variable_set(:@example_groups, filtered_example_groups)
|
13
23
|
|
14
24
|
current_count = RSpec.world.example_count
|
15
25
|
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
@@ -23,7 +33,7 @@ module RSpecTracer
|
|
23
33
|
|
24
34
|
RSpecTracer.running = true
|
25
35
|
|
26
|
-
super(
|
36
|
+
super(filtered_example_groups)
|
27
37
|
end
|
28
38
|
# rubocop:enable Metrics/AbcSize
|
29
39
|
end
|
data/lib/rspec_tracer/runner.rb
CHANGED
@@ -8,6 +8,7 @@ module RSpecTracer
|
|
8
8
|
EXAMPLE_RUN_REASON = {
|
9
9
|
explicit_run: 'Explicit run',
|
10
10
|
no_cache: 'No cache',
|
11
|
+
interrupted: 'Interrupted previously',
|
11
12
|
flaky_example: 'Flaky example',
|
12
13
|
failed_example: 'Failed previously',
|
13
14
|
pending_example: 'Pending previously',
|
@@ -59,10 +60,18 @@ module RSpecTracer
|
|
59
60
|
@reporter.on_example_pending(example_id, execution_result)
|
60
61
|
end
|
61
62
|
|
63
|
+
def register_interrupted_examples
|
64
|
+
@reporter.register_interrupted_examples
|
65
|
+
end
|
66
|
+
|
62
67
|
def register_deleted_examples
|
63
68
|
@reporter.register_deleted_examples(@cache.all_examples)
|
64
69
|
end
|
65
70
|
|
71
|
+
def incorrect_analysis?
|
72
|
+
@reporter.incorrect_analysis?
|
73
|
+
end
|
74
|
+
|
66
75
|
# rubocop:disable Metrics/AbcSize
|
67
76
|
def generate_missed_coverage
|
68
77
|
missed_coverage = Hash.new do |files_coverage, file_path|
|
@@ -73,6 +82,7 @@ module RSpecTracer
|
|
73
82
|
|
74
83
|
@cache.cached_examples_coverage.each_pair do |example_id, example_coverage|
|
75
84
|
example_coverage.each_pair do |file_path, line_coverage|
|
85
|
+
next if @reporter.example_interrupted?(example_id)
|
76
86
|
next unless @reporter.example_skipped?(example_id)
|
77
87
|
|
78
88
|
file_name = RSpecTracer::SourceFile.file_name(file_path)
|
@@ -93,6 +103,8 @@ module RSpecTracer
|
|
93
103
|
filtered_files = Set.new
|
94
104
|
|
95
105
|
examples_coverage.each_pair do |example_id, example_coverage|
|
106
|
+
next if @reporter.example_interrupted?(example_id)
|
107
|
+
|
96
108
|
register_example_files_dependency(example_id)
|
97
109
|
|
98
110
|
example_coverage.each_key do |file_path|
|
@@ -118,6 +130,8 @@ module RSpecTracer
|
|
118
130
|
@reporter.register_source_file(source_file)
|
119
131
|
|
120
132
|
@reporter.all_examples.each_key do |example_id|
|
133
|
+
next if @reporter.example_interrupted?(example_id)
|
134
|
+
|
121
135
|
@reporter.register_dependency(example_id, source_file[:file_name])
|
122
136
|
end
|
123
137
|
end
|
@@ -165,6 +179,7 @@ module RSpecTracer
|
|
165
179
|
end
|
166
180
|
|
167
181
|
def filter_by_example_status
|
182
|
+
add_previously_interrupted_examples
|
168
183
|
add_previously_flaky_examples
|
169
184
|
add_previously_failed_examples
|
170
185
|
add_previously_pending_examples
|
@@ -179,10 +194,17 @@ module RSpecTracer
|
|
179
194
|
end
|
180
195
|
end
|
181
196
|
|
197
|
+
def add_previously_interrupted_examples
|
198
|
+
@cache.interrupted_examples.each do |example_id|
|
199
|
+
@filtered_examples[example_id] = EXAMPLE_RUN_REASON[:interrupted]
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
182
203
|
def add_previously_flaky_examples
|
183
204
|
@cache.flaky_examples.each do |example_id|
|
184
205
|
@filtered_examples[example_id] = EXAMPLE_RUN_REASON[:flaky_example]
|
185
206
|
|
207
|
+
next unless @cache.dependency.key?(example_id)
|
186
208
|
next unless (@changed_files & @cache.dependency[example_id]).empty?
|
187
209
|
|
188
210
|
@reporter.register_possibly_flaky_example(example_id)
|
@@ -195,6 +217,7 @@ module RSpecTracer
|
|
195
217
|
|
196
218
|
@filtered_examples[example_id] = EXAMPLE_RUN_REASON[:failed_example]
|
197
219
|
|
220
|
+
next unless @cache.dependency.key?(example_id)
|
198
221
|
next unless (@changed_files & @cache.dependency[example_id]).empty?
|
199
222
|
|
200
223
|
@reporter.register_possibly_flaky_example(example_id)
|
@@ -243,6 +266,8 @@ module RSpecTracer
|
|
243
266
|
end
|
244
267
|
|
245
268
|
def register_example_files_dependency(example_id)
|
269
|
+
return if @reporter.example_interrupted?(example_id)
|
270
|
+
|
246
271
|
example = @reporter.all_examples[example_id]
|
247
272
|
|
248
273
|
register_example_file_dependency(example_id, example[:file_name])
|
@@ -253,6 +278,8 @@ module RSpecTracer
|
|
253
278
|
end
|
254
279
|
|
255
280
|
def register_example_file_dependency(example_id, file_name)
|
281
|
+
return if @reporter.example_interrupted?(example_id)
|
282
|
+
|
256
283
|
source_file = RSpecTracer::SourceFile.from_name(file_name)
|
257
284
|
|
258
285
|
@reporter.register_source_file(source_file)
|
@@ -260,6 +287,8 @@ module RSpecTracer
|
|
260
287
|
end
|
261
288
|
|
262
289
|
def register_file_dependency(example_id, file_path)
|
290
|
+
return if @reporter.example_interrupted?(example_id)
|
291
|
+
|
263
292
|
source_file = RSpecTracer::SourceFile.from_path(file_path)
|
264
293
|
|
265
294
|
return false if RSpecTracer.filters.any? { |filter| filter.match?(source_file) }
|
data/lib/rspec_tracer/version.rb
CHANGED
data/lib/rspec_tracer.rb
CHANGED
@@ -28,7 +28,7 @@ require_relative 'rspec_tracer/version'
|
|
28
28
|
|
29
29
|
module RSpecTracer
|
30
30
|
class << self
|
31
|
-
attr_accessor :running, :pid
|
31
|
+
attr_accessor :running, :pid, :no_examples
|
32
32
|
|
33
33
|
def start(&block)
|
34
34
|
RSpecTracer.running = false
|
@@ -74,7 +74,11 @@ module RSpecTracer
|
|
74
74
|
def at_exit_behavior
|
75
75
|
return unless RSpecTracer.pid == Process.pid && RSpecTracer.running
|
76
76
|
|
77
|
+
::Kernel.exit(1) if runner.incorrect_analysis?
|
78
|
+
|
77
79
|
run_exit_tasks
|
80
|
+
ensure
|
81
|
+
RSpecTracer.running = false
|
78
82
|
end
|
79
83
|
|
80
84
|
def start_example_trace
|
@@ -154,15 +158,11 @@ module RSpecTracer
|
|
154
158
|
def setup_coverage
|
155
159
|
@simplecov = defined?(SimpleCov) && SimpleCov.running
|
156
160
|
|
157
|
-
if simplecov?
|
158
|
-
# rubocop:disable Lint/EmptyBlock
|
159
|
-
SimpleCov.at_exit {}
|
160
|
-
# rubocop:enable Lint/EmptyBlock
|
161
|
-
else
|
162
|
-
require 'coverage'
|
161
|
+
return if simplecov?
|
163
162
|
|
164
|
-
|
165
|
-
|
163
|
+
require 'coverage'
|
164
|
+
|
165
|
+
::Coverage.start
|
166
166
|
end
|
167
167
|
|
168
168
|
def setup_trace_point
|
@@ -175,11 +175,13 @@ module RSpecTracer
|
|
175
175
|
end
|
176
176
|
|
177
177
|
def run_exit_tasks
|
178
|
-
|
178
|
+
if RSpecTracer.no_examples
|
179
|
+
puts 'Skipped reports generation since all examples were filtered out'
|
180
|
+
else
|
181
|
+
generate_reports
|
182
|
+
end
|
179
183
|
|
180
184
|
simplecov? ? run_simplecov_exit_task : run_coverage_exit_task
|
181
|
-
ensure
|
182
|
-
RSpecTracer.running = false
|
183
185
|
end
|
184
186
|
|
185
187
|
def generate_reports
|
@@ -194,6 +196,7 @@ module RSpecTracer
|
|
194
196
|
def process_dependency
|
195
197
|
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
196
198
|
|
199
|
+
runner.register_interrupted_examples
|
197
200
|
runner.register_deleted_examples
|
198
201
|
runner.register_dependency(coverage_reporter.examples_coverage)
|
199
202
|
runner.register_untraced_dependency(@traced_files)
|
@@ -224,12 +227,13 @@ module RSpecTracer
|
|
224
227
|
|
225
228
|
puts 'SimpleCov will now generate coverage report (<3 RSpec tracer)'
|
226
229
|
|
227
|
-
|
230
|
+
coverage_reporter.record_coverage if RSpecTracer.no_examples
|
228
231
|
end
|
229
232
|
|
230
233
|
def run_coverage_exit_task
|
231
234
|
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
232
235
|
|
236
|
+
coverage_reporter.record_coverage if RSpecTracer.no_examples
|
233
237
|
coverage_reporter.generate_final_coverage
|
234
238
|
|
235
239
|
file_name = File.join(RSpecTracer.coverage_path, 'coverage.json')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-tracer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abhimanyu Singh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: docile
|
@@ -50,9 +50,11 @@ dependencies:
|
|
50
50
|
- - ">="
|
51
51
|
- !ruby/object:Gem::Version
|
52
52
|
version: 3.6.0
|
53
|
-
description: RSpec Tracer is a specs dependency
|
54
|
-
RSpec. It maintains a list of files
|
55
|
-
in the subsequent runs if none of the
|
53
|
+
description: RSpec Tracer is a specs dependency analyzer, flaky tests detector, tests
|
54
|
+
accelerator, and coverage reporter tool for RSpec. It maintains a list of files
|
55
|
+
for each test, enabling itself to skip tests in the subsequent runs if none of the
|
56
|
+
dependent files are changed. It uses Ruby's built-in coverage library to keep track
|
57
|
+
of the coverage for each test.
|
56
58
|
email:
|
57
59
|
- abhisinghabhimanyu@gmail.com
|
58
60
|
executables: []
|
@@ -94,8 +96,10 @@ files:
|
|
94
96
|
- lib/rspec_tracer/html_reporter/views/flaky_examples.erb
|
95
97
|
- lib/rspec_tracer/html_reporter/views/layout.erb
|
96
98
|
- lib/rspec_tracer/remote_cache/Rakefile
|
99
|
+
- lib/rspec_tracer/remote_cache/aws.rb
|
97
100
|
- lib/rspec_tracer/remote_cache/cache.rb
|
98
|
-
- lib/rspec_tracer/remote_cache/
|
101
|
+
- lib/rspec_tracer/remote_cache/repo.rb
|
102
|
+
- lib/rspec_tracer/remote_cache/validator.rb
|
99
103
|
- lib/rspec_tracer/reporter.rb
|
100
104
|
- lib/rspec_tracer/rspec_reporter.rb
|
101
105
|
- lib/rspec_tracer/rspec_runner.rb
|
@@ -109,7 +113,7 @@ licenses:
|
|
109
113
|
- MIT
|
110
114
|
metadata:
|
111
115
|
homepage_uri: https://github.com/avmnu-sng/rspec-tracer
|
112
|
-
source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v0.
|
116
|
+
source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v0.9.2
|
113
117
|
changelog_uri: https://github.com/avmnu-sng/rspec-tracer/blob/main/CHANGELOG.md
|
114
118
|
bug_tracker_uri: https://github.com/avmnu-sng/rspec-tracer/issues
|
115
119
|
post_install_message:
|
@@ -130,5 +134,6 @@ requirements: []
|
|
130
134
|
rubygems_version: 3.2.26
|
131
135
|
signing_key:
|
132
136
|
specification_version: 4
|
133
|
-
summary: RSpec Tracer is a specs dependency
|
137
|
+
summary: RSpec Tracer is a specs dependency analyzer, flaky tests detector, tests
|
138
|
+
accelerator, and coverage reporter tool.
|
134
139
|
test_files: []
|