rspec-tracer 0.9.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecTracer
4
+ class ReportGenerator
5
+ def initialize(reporter, cache)
6
+ @reporter = reporter
7
+ @cache = cache
8
+ end
9
+
10
+ def reverse_dependency_report
11
+ reverse_dependency = Hash.new do |examples, file_name|
12
+ examples[file_name] = {
13
+ example_count: 0,
14
+ examples: Hash.new(0)
15
+ }
16
+ end
17
+
18
+ @reporter.dependency.each_pair do |example_id, files|
19
+ next if @reporter.interrupted_examples.include?(example_id)
20
+
21
+ example_file = @reporter.all_examples[example_id][:rerun_file_name]
22
+
23
+ files.each do |file_name|
24
+ reverse_dependency[file_name][:example_count] += 1
25
+ reverse_dependency[file_name][:examples][example_file] += 1
26
+ end
27
+ end
28
+
29
+ reverse_dependency.transform_values! do |data|
30
+ {
31
+ example_count: data[:example_count],
32
+ examples: data[:examples].sort_by { |file_name, count| [-count, file_name] }.to_h
33
+ }
34
+ end
35
+
36
+ reverse_dependency.sort_by { |file_name, data| [-data[:example_count], file_name] }.to_h
37
+ end
38
+
39
+ def generate_report
40
+ generate_last_run_report
41
+ generate_examples_status_report
42
+
43
+ %i[all_files all_examples dependency examples_coverage reverse_dependency].each do |report_type|
44
+ starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
45
+
46
+ send("generate_#{report_type}_report")
47
+
48
+ ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
49
+ elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
50
+
51
+ puts "RSpec tracer generated #{report_type.to_s.tr('_', ' ')} report (took #{elapsed})" if RSpecTracer.verbose?
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def generate_last_run_report
58
+ @reporter.last_run = {
59
+ pid: RSpecTracer.pid,
60
+ actual_count: RSpec.world.example_count + @reporter.skipped_examples.count,
61
+ example_count: RSpec.world.example_count,
62
+ duplicate_examples: @reporter.duplicate_examples.sum { |_, examples| examples.count },
63
+ interrupted_examples: @reporter.interrupted_examples.count,
64
+ failed_examples: @reporter.failed_examples.count,
65
+ skipped_examples: @reporter.skipped_examples.count,
66
+ pending_examples: @reporter.pending_examples.count,
67
+ flaky_examples: @reporter.flaky_examples.count
68
+ }
69
+ end
70
+
71
+ def generate_examples_status_report
72
+ starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
73
+
74
+ generate_flaky_examples_report
75
+ generate_failed_examples_report
76
+ generate_pending_examples_report
77
+
78
+ ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
79
+ elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
80
+
81
+ puts "RSpec tracer generated flaky, failed, and pending examples report (took #{elapsed})" if RSpecTracer.verbose?
82
+ end
83
+
84
+ def generate_flaky_examples_report
85
+ @reporter.possibly_flaky_examples.each do |example_id|
86
+ next if @reporter.example_deleted?(example_id)
87
+ next unless @cache.flaky_examples.include?(example_id) ||
88
+ @reporter.example_passed?(example_id)
89
+
90
+ @reporter.register_flaky_example(example_id)
91
+ end
92
+ end
93
+
94
+ def generate_failed_examples_report
95
+ @cache.failed_examples.each do |example_id|
96
+ next if @reporter.example_deleted?(example_id) ||
97
+ @reporter.all_examples.key?(example_id)
98
+
99
+ @reporter.register_failed_example(example_id)
100
+ end
101
+ end
102
+
103
+ def generate_pending_examples_report
104
+ @cache.pending_examples.each do |example_id|
105
+ next if @reporter.example_deleted?(example_id) ||
106
+ @reporter.all_examples.key?(example_id)
107
+
108
+ @reporter.register_pending_example(example_id)
109
+ end
110
+ end
111
+
112
+ def generate_all_files_report
113
+ @cache.all_files.each_pair do |file_name, data|
114
+ next if @reporter.all_files.key?(file_name) ||
115
+ @reporter.file_deleted?(file_name)
116
+
117
+ @reporter.all_files[file_name] = data
118
+ end
119
+ end
120
+
121
+ def generate_all_examples_report
122
+ @cache.all_examples.each_pair do |example_id, data|
123
+ next if @reporter.all_examples.key?(example_id) ||
124
+ @reporter.example_deleted?(example_id)
125
+
126
+ @reporter.all_examples[example_id] = data
127
+ end
128
+ end
129
+
130
+ def generate_dependency_report
131
+ @cache.dependency.each_pair do |example_id, data|
132
+ next if @reporter.dependency.key?(example_id) ||
133
+ @reporter.example_deleted?(example_id)
134
+
135
+ @reporter.dependency[example_id] = data.reject do |file_name|
136
+ @reporter.file_deleted?(file_name)
137
+ end
138
+ end
139
+
140
+ @reporter.dependency.transform_values!(&:to_a)
141
+ end
142
+
143
+ def generate_examples_coverage_report
144
+ @cache.cached_examples_coverage.each_pair do |example_id, data|
145
+ next if @reporter.examples_coverage.key?(example_id) ||
146
+ @reporter.example_deleted?(example_id)
147
+
148
+ @reporter.examples_coverage[example_id] = data.reject do |file_name|
149
+ @reporter.file_deleted?(file_name)
150
+ end
151
+ end
152
+ end
153
+
154
+ def generate_reverse_dependency_report
155
+ @reporter.reverse_dependency = reverse_dependency_report
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecTracer
4
+ class ReportMerger
5
+ attr_reader :all_examples, :duplicate_examples, :interrupted_examples,
6
+ :flaky_examples, :failed_examples, :pending_examples, :skipped_examples,
7
+ :all_files, :dependency, :reverse_dependency, :examples_coverage, :last_run
8
+
9
+ def initialize
10
+ @last_run = {}
11
+ @all_examples = {}
12
+ @duplicate_examples = {}
13
+ @interrupted_examples = Set.new
14
+ @flaky_examples = Set.new
15
+ @failed_examples = Set.new
16
+ @pending_examples = Set.new
17
+ @skipped_examples = Set.new
18
+ @all_files = {}
19
+ @dependency = Hash.new { |hash, key| hash[key] = Set.new }
20
+ @reverse_dependency = {}
21
+ @examples_coverage = {}
22
+ end
23
+
24
+ def merge(reports_dir)
25
+ reports_dir.each do |report_dir|
26
+ next unless File.directory?(report_dir)
27
+
28
+ merge_cache(load_cache(report_dir))
29
+ merge_last_run_report(File.dirname(report_dir))
30
+ end
31
+
32
+ @dependency.transform_values!(&:to_a)
33
+
34
+ @reverse_dependency = RSpecTracer::ReportGenerator.new(self, nil).reverse_dependency_report
35
+ end
36
+
37
+ private
38
+
39
+ def load_cache(cache_dir)
40
+ cache = RSpecTracer::Cache.new
41
+
42
+ cache.send(:load_all_examples_cache, cache_dir, discard_run_reason: false)
43
+ cache.send(:load_duplicate_examples_cache, cache_dir)
44
+ cache.send(:load_interrupted_examples_cache, cache_dir)
45
+ cache.send(:load_flaky_examples_cache, cache_dir)
46
+ cache.send(:load_failed_examples_cache, cache_dir)
47
+ cache.send(:load_pending_examples_cache, cache_dir)
48
+ cache.send(:load_skipped_examples_cache, cache_dir)
49
+ cache.send(:load_all_files_cache, cache_dir)
50
+ cache.send(:load_dependency_cache, cache_dir)
51
+ cache.send(:load_examples_coverage_cache, cache_dir)
52
+
53
+ cache
54
+ end
55
+
56
+ def merge_cache(cache)
57
+ @all_examples.merge!(cache.all_examples) { |_, v1, v2| v1[:run_reason] ? v1 : v2 }
58
+ @duplicate_examples.merge!(cache.duplicate_examples) { |_, v1, v2| v1 + v2 }
59
+ @interrupted_examples.merge(cache.interrupted_examples)
60
+ @flaky_examples.merge(cache.flaky_examples)
61
+ @failed_examples.merge(cache.failed_examples)
62
+ @pending_examples.merge(cache.pending_examples)
63
+ @skipped_examples.merge(cache.skipped_examples)
64
+ @all_files.merge!(cache.all_files)
65
+ @dependency.merge!(cache.dependency) { |_, v1, v2| v1.merge(v2) }
66
+ @examples_coverage.merge!(cache.examples_coverage) do |_, v1, v2|
67
+ v1.merge(v2) { |_, v3, v4| v3.merge(v4) { |_, v5, v6| v5 + v6 } }
68
+ end
69
+ end
70
+
71
+ def merge_last_run_report(cache_dir)
72
+ file_name = File.join(cache_dir, 'last_run.json')
73
+ cached_last_run = JSON.parse(File.read(file_name), symbolize_names: true)
74
+ cached_last_run[:pid] = [cached_last_run[:pid]]
75
+
76
+ cached_last_run.delete_if { |key, _| %i[run_id timestamp].include?(key) }
77
+
78
+ @last_run.merge!(cached_last_run) { |_, v1, v2| v1 + v2 }
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecTracer
4
+ class ReportWriter
5
+ def initialize(report_dir, reporter)
6
+ @report_dir = report_dir
7
+ @reporter = reporter
8
+ end
9
+
10
+ def write_report
11
+ starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
12
+
13
+ @run_id = Digest::MD5.hexdigest(@reporter.all_examples.keys.sort.to_json)
14
+ @cache_dir = File.join(@report_dir, @run_id)
15
+
16
+ FileUtils.mkdir_p(@cache_dir)
17
+
18
+ write_all_examples_report
19
+ write_duplicate_examples_report
20
+ write_interrupted_examples_report
21
+ write_flaky_examples_report
22
+ write_failed_examples_report
23
+ write_pending_examples_report
24
+ write_skipped_examples_report
25
+ write_all_files_report
26
+ write_dependency_report
27
+ write_reverse_dependency_report
28
+ write_examples_coverage_report
29
+ write_last_run_report
30
+
31
+ ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
32
+ elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
33
+
34
+ puts "RSpec tracer reports written to #{@cache_dir} (took #{elapsed})"
35
+ end
36
+
37
+ def print_duplicate_examples
38
+ return if @reporter.duplicate_examples.empty?
39
+
40
+ total = @reporter.duplicate_examples.sum { |_, examples| examples.count }
41
+
42
+ puts '=' * 80
43
+ puts ' IMPORTANT NOTICE -- RSPEC TRACER COULD NOT IDENTIFY SOME EXAMPLES UNIQUELY'
44
+ puts '=' * 80
45
+ puts "RSpec tracer could not uniquely identify the following #{total} examples:"
46
+
47
+ justify = ' ' * 2
48
+ nested_justify = justify * 3
49
+
50
+ @reporter.duplicate_examples.each_pair do |example_id, examples|
51
+ puts "#{justify}- Example ID: #{example_id} (#{examples.count} examples)"
52
+
53
+ examples.each do |example|
54
+ description = example[:full_description].strip
55
+ file_name = example[:rerun_file_name].sub(%r{^/}, '')
56
+ line_number = example[:rerun_line_number]
57
+ location = "#{file_name}:#{line_number}"
58
+
59
+ puts "#{nested_justify}* #{description} (#{location})"
60
+ end
61
+ end
62
+
63
+ puts
64
+ end
65
+
66
+ private
67
+
68
+ def write_all_examples_report
69
+ file_name = File.join(@cache_dir, 'all_examples.json')
70
+
71
+ File.write(file_name, JSON.pretty_generate(@reporter.all_examples))
72
+ end
73
+
74
+ def write_duplicate_examples_report
75
+ file_name = File.join(@cache_dir, 'duplicate_examples.json')
76
+
77
+ File.write(file_name, JSON.pretty_generate(@reporter.duplicate_examples))
78
+ end
79
+
80
+ def write_interrupted_examples_report
81
+ file_name = File.join(@cache_dir, 'interrupted_examples.json')
82
+
83
+ File.write(file_name, JSON.pretty_generate(@reporter.interrupted_examples.sort.to_a))
84
+ end
85
+
86
+ def write_flaky_examples_report
87
+ file_name = File.join(@cache_dir, 'flaky_examples.json')
88
+
89
+ File.write(file_name, JSON.pretty_generate(@reporter.flaky_examples.sort.to_a))
90
+ end
91
+
92
+ def write_failed_examples_report
93
+ file_name = File.join(@cache_dir, 'failed_examples.json')
94
+
95
+ File.write(file_name, JSON.pretty_generate(@reporter.failed_examples.sort.to_a))
96
+ end
97
+
98
+ def write_pending_examples_report
99
+ file_name = File.join(@cache_dir, 'pending_examples.json')
100
+
101
+ File.write(file_name, JSON.pretty_generate(@reporter.pending_examples.sort.to_a))
102
+ end
103
+
104
+ def write_skipped_examples_report
105
+ file_name = File.join(@cache_dir, 'skipped_examples.json')
106
+
107
+ File.write(file_name, JSON.pretty_generate(@reporter.skipped_examples.sort.to_a))
108
+ end
109
+
110
+ def write_all_files_report
111
+ file_name = File.join(@cache_dir, 'all_files.json')
112
+
113
+ File.write(file_name, JSON.pretty_generate(@reporter.all_files))
114
+ end
115
+
116
+ def write_dependency_report
117
+ file_name = File.join(@cache_dir, 'dependency.json')
118
+
119
+ File.write(file_name, JSON.pretty_generate(@reporter.dependency))
120
+ end
121
+
122
+ def write_reverse_dependency_report
123
+ file_name = File.join(@cache_dir, 'reverse_dependency.json')
124
+
125
+ File.write(file_name, JSON.pretty_generate(@reporter.reverse_dependency))
126
+ end
127
+
128
+ def write_examples_coverage_report
129
+ file_name = File.join(@cache_dir, 'examples_coverage.json')
130
+
131
+ File.write(file_name, JSON.pretty_generate(@reporter.examples_coverage))
132
+ end
133
+
134
+ def write_last_run_report
135
+ file_name = File.join(@report_dir, 'last_run.json')
136
+ last_run_data = @reporter.last_run.merge(run_id: @run_id, timestamp: Time.now.utc)
137
+
138
+ File.write(file_name, JSON.pretty_generate(last_run_data))
139
+ end
140
+ end
141
+ end
@@ -4,8 +4,10 @@ module RSpecTracer
4
4
  class Reporter
5
5
  attr_reader :all_examples, :interrupted_examples, :duplicate_examples,
6
6
  :possibly_flaky_examples, :flaky_examples, :pending_examples,
7
- :all_files, :modified_files, :deleted_files, :dependency,
8
- :reverse_dependency, :examples_coverage, :last_run
7
+ :skipped_examples, :failed_examples, :all_files, :modified_files,
8
+ :deleted_files, :dependency, :examples_coverage
9
+
10
+ attr_accessor :reverse_dependency, :last_run
9
11
 
10
12
  def initialize
11
13
  initialize_examples
@@ -151,92 +153,6 @@ module RSpecTracer
151
153
  @examples_coverage = examples_coverage
152
154
  end
153
155
 
154
- def generate_reverse_dependency_report
155
- @dependency.each_pair do |example_id, files|
156
- next if @interrupted_examples.include?(example_id)
157
-
158
- example_file = @all_examples[example_id][:rerun_file_name]
159
-
160
- files.each do |file_name|
161
- @reverse_dependency[file_name][:example_count] += 1
162
- @reverse_dependency[file_name][:examples][example_file] += 1
163
- end
164
- end
165
-
166
- format_reverse_dependency_report
167
- end
168
-
169
- def generate_last_run_report
170
- @last_run = {
171
- pid: RSpecTracer.pid,
172
- actual_count: RSpec.world.example_count + @skipped_examples.count,
173
- example_count: RSpec.world.example_count,
174
- duplicate_examples: @duplicate_examples.sum { |_, examples| examples.count },
175
- interrupted_examples: @interrupted_examples.count,
176
- failed_examples: @failed_examples.count,
177
- skipped_examples: @skipped_examples.count,
178
- pending_examples: @pending_examples.count,
179
- flaky_examples: @flaky_examples.count
180
- }
181
- end
182
-
183
- def write_reports
184
- starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
185
-
186
- @run_id = Digest::MD5.hexdigest(@all_examples.keys.sort.to_json)
187
- @cache_dir = File.join(RSpecTracer.cache_path, @run_id)
188
-
189
- FileUtils.mkdir_p(@cache_dir)
190
-
191
- %i[
192
- all_examples
193
- flaky_examples
194
- failed_examples
195
- pending_examples
196
- all_files
197
- dependency
198
- reverse_dependency
199
- examples_coverage
200
- last_run
201
- ].each { |report_type| send("write_#{report_type}_report") }
202
-
203
- ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
204
- elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
205
-
206
- puts "RSpec tracer reports written to #{@cache_dir} (took #{elpased})"
207
- end
208
-
209
- # rubocop:disable Metrics/AbcSize
210
- def print_duplicate_examples
211
- return if @duplicate_examples.empty?
212
-
213
- total = @duplicate_examples.sum { |_, examples| examples.count }
214
-
215
- puts '=' * 80
216
- puts ' IMPORTANT NOTICE -- RSPEC TRACER COULD NOT IDENTIFY SOME EXAMPLES UNIQUELY'
217
- puts '=' * 80
218
- puts "RSpec tracer could not uniquely identify the following #{total} examples:"
219
-
220
- justify = ' ' * 2
221
- nested_justify = justify * 3
222
-
223
- @duplicate_examples.each_pair do |example_id, examples|
224
- puts "#{justify}- Example ID: #{example_id} (#{examples.count} examples)"
225
-
226
- examples.each do |example|
227
- description = example[:full_description].strip
228
- file_name = example[:rerun_file_name].sub(%r{^/}, '')
229
- line_number = example[:rerun_line_number]
230
- location = "#{file_name}:#{line_number}"
231
-
232
- puts "#{nested_justify}* #{description} (#{location})"
233
- end
234
- end
235
-
236
- puts
237
- end
238
- # rubocop:enable Metrics/AbcSize
239
-
240
156
  private
241
157
 
242
158
  def initialize_examples
@@ -284,75 +200,5 @@ module RSpecTracer
284
200
  status: result.status.to_s
285
201
  }
286
202
  end
287
-
288
- def format_reverse_dependency_report
289
- @reverse_dependency.transform_values! do |data|
290
- {
291
- example_count: data[:example_count],
292
- examples: data[:examples].sort_by { |file_name, count| [-count, file_name] }.to_h
293
- }
294
- end
295
-
296
- report = @reverse_dependency.sort_by do |file_name, data|
297
- [-data[:example_count], file_name]
298
- end
299
-
300
- @reverse_dependency = report.to_h
301
- end
302
-
303
- def write_all_examples_report
304
- file_name = File.join(@cache_dir, 'all_examples.json')
305
-
306
- File.write(file_name, JSON.pretty_generate(@all_examples))
307
- end
308
-
309
- def write_flaky_examples_report
310
- file_name = File.join(@cache_dir, 'flaky_examples.json')
311
-
312
- File.write(file_name, JSON.pretty_generate(@flaky_examples.to_a))
313
- end
314
-
315
- def write_failed_examples_report
316
- file_name = File.join(@cache_dir, 'failed_examples.json')
317
-
318
- File.write(file_name, JSON.pretty_generate(@failed_examples.to_a))
319
- end
320
-
321
- def write_pending_examples_report
322
- file_name = File.join(@cache_dir, 'pending_examples.json')
323
-
324
- File.write(file_name, JSON.pretty_generate(@pending_examples.to_a))
325
- end
326
-
327
- def write_all_files_report
328
- file_name = File.join(@cache_dir, 'all_files.json')
329
-
330
- File.write(file_name, JSON.pretty_generate(@all_files))
331
- end
332
-
333
- def write_dependency_report
334
- file_name = File.join(@cache_dir, 'dependency.json')
335
-
336
- File.write(file_name, JSON.pretty_generate(@dependency))
337
- end
338
-
339
- def write_reverse_dependency_report
340
- file_name = File.join(@cache_dir, 'reverse_dependency.json')
341
-
342
- File.write(file_name, JSON.pretty_generate(@reverse_dependency))
343
- end
344
-
345
- def write_examples_coverage_report
346
- file_name = File.join(@cache_dir, 'examples_coverage.json')
347
-
348
- File.write(file_name, JSON.pretty_generate(@examples_coverage))
349
- end
350
-
351
- def write_last_run_report
352
- file_name = File.join(RSpecTracer.cache_path, 'last_run.json')
353
- last_run_data = @last_run.merge(run_id: @run_id, timestamp: Time.now.utc)
354
-
355
- File.write(file_name, JSON.pretty_generate(last_run_data))
356
- end
357
203
  end
358
204
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module RSpecTracer
4
4
  module RSpecRunner
5
- # rubocop:disable Metrics/AbcSize
6
5
  def run_specs(example_groups)
7
6
  actual_count = RSpec.world.example_count
8
7
  RSpecTracer.no_examples = actual_count.zero?
@@ -23,18 +22,17 @@ module RSpecTracer
23
22
 
24
23
  current_count = RSpec.world.example_count
25
24
  ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
26
- elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
25
+ elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
27
26
 
28
27
  puts
29
28
  puts <<-EXAMPLES.strip.gsub(/\s+/, ' ')
30
29
  RSpec tracer is running #{current_count} examples (actual: #{actual_count},
31
- skipped: #{actual_count - current_count}) (took #{elpased})
30
+ skipped: #{actual_count - current_count}) (took #{elapsed})
32
31
  EXAMPLES
33
32
 
34
33
  RSpecTracer.running = true
35
34
 
36
35
  super(filtered_example_groups)
37
36
  end
38
- # rubocop:enable Metrics/AbcSize
39
37
  end
40
38
  end