rspec-tracer 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rspec_tracer.rb CHANGED
@@ -13,11 +13,16 @@ require 'set'
13
13
  require_relative 'rspec_tracer/configuration'
14
14
  RSpecTracer.extend RSpecTracer::Configuration
15
15
 
16
+ require_relative 'rspec_tracer/coverage_merger'
16
17
  require_relative 'rspec_tracer/coverage_reporter'
18
+ require_relative 'rspec_tracer/coverage_writer'
17
19
  require_relative 'rspec_tracer/defaults'
18
20
  require_relative 'rspec_tracer/example'
19
21
  require_relative 'rspec_tracer/html_reporter/reporter'
20
22
  require_relative 'rspec_tracer/remote_cache/cache'
23
+ require_relative 'rspec_tracer/report_generator'
24
+ require_relative 'rspec_tracer/report_merger'
25
+ require_relative 'rspec_tracer/report_writer'
21
26
  require_relative 'rspec_tracer/rspec_reporter'
22
27
  require_relative 'rspec_tracer/rspec_runner'
23
28
  require_relative 'rspec_tracer/ruby_coverage'
@@ -34,10 +39,12 @@ module RSpecTracer
34
39
  RSpecTracer.running = false
35
40
  RSpecTracer.pid = Process.pid
36
41
 
37
- puts 'Started RSpec tracer'
42
+ return if RUBY_ENGINE == 'jruby' && !valid_jruby_opts?
38
43
 
39
- configure(&block) if block
44
+ puts "Started RSpec tracer (pid: #{RSpecTracer.pid})"
40
45
 
46
+ parallel_tests_setup
47
+ configure(&block) if block
41
48
  initial_setup
42
49
  end
43
50
 
@@ -67,6 +74,8 @@ module RSpecTracer
67
74
  end
68
75
  end
69
76
 
77
+ runner.deregister_duplicate_examples
78
+
70
79
  [to_run, groups.to_a]
71
80
  end
72
81
  # rubocop:enable Metrics/AbcSize
@@ -74,10 +83,12 @@ module RSpecTracer
74
83
  def at_exit_behavior
75
84
  return unless RSpecTracer.pid == Process.pid && RSpecTracer.running
76
85
 
77
- ::Kernel.exit(1) if runner.incorrect_analysis?
78
-
79
86
  run_exit_tasks
87
+
88
+ ::Kernel.exit(1) if runner.non_zero_exit_code?
80
89
  ensure
90
+ FileUtils.rm_f(RSpecTracer.parallel_tests_lock_file) if parallel_tests_last_process?
91
+
81
92
  RSpecTracer.running = false
82
93
  end
83
94
 
@@ -107,6 +118,14 @@ module RSpecTracer
107
118
  return @coverage_reporter if defined?(@coverage_reporter)
108
119
  end
109
120
 
121
+ def coverage_merger
122
+ return @coverage_merger if defined?(@coverage_merger)
123
+ end
124
+
125
+ def report_merger
126
+ return @report_merger if defined?(@report_merger)
127
+ end
128
+
110
129
  def trace_point
111
130
  return @trace_point if defined?(@trace_point)
112
131
  end
@@ -123,8 +142,28 @@ module RSpecTracer
123
142
  return @simplecov if defined?(@simplecov)
124
143
  end
125
144
 
145
+ def parallel_tests?
146
+ return @parallel_tests if defined?(@parallel_tests)
147
+ end
148
+
126
149
  private
127
150
 
151
+ def valid_jruby_opts?
152
+ require 'jruby'
153
+
154
+ return true if Java::OrgJruby::RubyInstanceConfig.FULL_TRACE_ENABLED &&
155
+ JRuby.runtime.object_space_enabled?
156
+
157
+ puts <<-WARN.strip.gsub(/\s+/, ' ')
158
+ RSpec Tracer is not running as it requires debug and object space enabled. Use
159
+ command line options "--debug" and "-X+O" or set the "debug.fullTrace=true" and
160
+ "objectspace.enabled=true" options in your .jrubyrc file. You can also use
161
+ JRUBY_OPTS="--debug -X+O".
162
+ WARN
163
+
164
+ false
165
+ end
166
+
128
167
  def initial_setup
129
168
  unless setup_rspec
130
169
  puts 'Could not find a running RSpec process'
@@ -139,6 +178,36 @@ module RSpecTracer
139
178
  @coverage_reporter = RSpecTracer::CoverageReporter.new
140
179
  end
141
180
 
181
+ def parallel_tests_setup
182
+ @parallel_tests = !(ENV['TEST_ENV_NUMBER'] && ENV['PARALLEL_TEST_GROUPS']).nil?
183
+
184
+ return unless parallel_tests?
185
+
186
+ require 'parallel_tests' unless defined?(ParallelTests)
187
+
188
+ @coverage_merger = RSpecTracer::CoverageMerger.new
189
+ @report_merger = RSpecTracer::ReportMerger.new
190
+ rescue LoadError => e
191
+ puts "Failed to load parallel tests (Error: #{e.message})"
192
+ ensure
193
+ track_parallel_tests_test_env_number
194
+ end
195
+
196
+ def track_parallel_tests_test_env_number
197
+ return unless parallel_tests?
198
+
199
+ File.open(RSpecTracer.parallel_tests_lock_file, File::RDWR | File::CREAT, 0o644) do |f|
200
+ f.flock(File::LOCK_EX)
201
+
202
+ test_num = [f.read.to_i, ENV['TEST_ENV_NUMBER'].to_i].max
203
+
204
+ f.rewind
205
+ f.write("#{test_num}\n")
206
+ f.flush
207
+ f.truncate(f.pos)
208
+ end
209
+ end
210
+
142
211
  def setup_rspec
143
212
  runners = ObjectSpace.each_object(::RSpec::Core::Runner) do |runner|
144
213
  runner_clazz = runner.singleton_class
@@ -182,28 +251,37 @@ module RSpecTracer
182
251
  end
183
252
 
184
253
  simplecov? ? run_simplecov_exit_task : run_coverage_exit_task
254
+
255
+ run_parallel_tests_exit_tasks
185
256
  end
186
257
 
187
258
  def generate_reports
188
- puts 'RSpec tracer is generating reports'
259
+ puts "RSpec tracer is generating reports (pid: #{RSpecTracer.pid})"
189
260
 
190
261
  process_dependency
191
262
  process_coverage
192
- runner.generate_report
193
- RSpecTracer::HTMLReporter::Reporter.new.generate_report
263
+
264
+ RSpecTracer::ReportGenerator.new(runner.reporter, runner.cache).generate_report
265
+
266
+ report_writer = RSpecTracer::ReportWriter.new(RSpecTracer.cache_path, runner.reporter)
267
+ report_writer.write_report
268
+ report_writer.print_duplicate_examples
269
+
270
+ RSpecTracer::HTMLReporter::Reporter.new(RSpecTracer.report_path, runner.reporter).generate_report
194
271
  end
195
272
 
196
273
  def process_dependency
197
274
  starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
198
275
 
276
+ runner.register_interrupted_examples
199
277
  runner.register_deleted_examples
200
278
  runner.register_dependency(coverage_reporter.examples_coverage)
201
279
  runner.register_untraced_dependency(@traced_files)
202
280
 
203
281
  ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
204
- elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
282
+ elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
205
283
 
206
- puts "RSpec tracer processed dependency (took #{elpased})" if RSpecTracer.verbose?
284
+ puts "RSpec tracer processed dependency (took #{elapsed})" if RSpecTracer.verbose?
207
285
  end
208
286
 
209
287
  def process_coverage
@@ -214,9 +292,9 @@ module RSpecTracer
214
292
  runner.register_examples_coverage(coverage_reporter.examples_coverage)
215
293
 
216
294
  ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
217
- elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
295
+ elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
218
296
 
219
- puts "RSpec tracer processed coverage (took #{elpased})" if RSpecTracer.verbose?
297
+ puts "RSpec tracer processed coverage (took #{elapsed})" if RSpecTracer.verbose?
220
298
  end
221
299
 
222
300
  def run_simplecov_exit_task
@@ -236,34 +314,129 @@ module RSpecTracer
236
314
  coverage_reporter.generate_final_coverage
237
315
 
238
316
  file_name = File.join(RSpecTracer.coverage_path, 'coverage.json')
317
+ coverage_writer = RSpecTracer::CoverageWriter.new(file_name, coverage_reporter)
239
318
 
240
- write_coverage_report(file_name)
319
+ coverage_writer.write_report
241
320
 
242
321
  ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
243
- elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
244
322
 
245
- print_coverage_stats(file_name, elpased)
323
+ coverage_writer.print_stats(ending - starting)
246
324
  end
247
325
 
248
- def write_coverage_report(file_name)
249
- report = {
250
- RSpecTracer: {
251
- coverage: coverage_reporter.coverage,
252
- timestamp: Time.now.utc.to_i
253
- }
254
- }
326
+ def run_parallel_tests_exit_tasks
327
+ return unless parallel_tests_executed?
255
328
 
256
- File.write(file_name, JSON.pretty_generate(report))
329
+ merge_parallel_tests_reports
330
+ write_parallel_tests_merged_report
331
+ merge_parallel_tests_coverage_reports
332
+ write_parallel_tests_coverage_report
333
+ purge_parallel_tests_reports
257
334
  end
258
335
 
259
- def print_coverage_stats(file_name, elpased)
260
- stat = coverage_reporter.coverage_stat
336
+ def merge_parallel_tests_reports
337
+ return unless parallel_tests_executed?
338
+
339
+ starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
340
+ reports_dir = []
341
+
342
+ 1.upto(ENV['PARALLEL_TEST_GROUPS'].to_i) do |test_num|
343
+ cache_path = File.dirname(RSpecTracer.cache_path)
344
+ cache_dir = File.join(cache_path, "parallel_tests_#{test_num}")
345
+
346
+ next unless File.directory?(cache_dir)
347
+
348
+ run_id = JSON.parse(File.read(File.join(cache_dir, 'last_run.json')))['run_id']
349
+
350
+ reports_dir << File.join(cache_dir, run_id)
351
+ end
352
+
353
+ report_merger.merge(reports_dir)
354
+
355
+ ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
356
+ elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
357
+
358
+ puts "\nRSpec tracer merged parallel tests reports (took #{elapsed})"
359
+ end
360
+
361
+ def write_parallel_tests_merged_report
362
+ return unless parallel_tests_executed?
363
+
364
+ report_dir = File.dirname(RSpecTracer.cache_path)
365
+
366
+ RSpecTracer::ReportWriter.new(report_dir, report_merger).write_report
367
+
368
+ report_dir = File.dirname(RSpecTracer.report_path)
369
+
370
+ RSpecTracer::HTMLReporter::Reporter.new(report_dir, report_merger).generate_report
371
+ end
372
+
373
+ def merge_parallel_tests_coverage_reports
374
+ return unless parallel_tests_executed? && !simplecov?
375
+
376
+ starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
377
+ reports_dir = []
378
+
379
+ 1.upto(ENV['PARALLEL_TEST_GROUPS'].to_i) do |test_num|
380
+ coverage_path = File.dirname(RSpecTracer.coverage_path)
381
+ coverage_dir = File.join(coverage_path, "parallel_tests_#{test_num}")
382
+
383
+ reports_dir << coverage_dir if File.directory?(coverage_dir)
384
+ end
385
+
386
+ coverage_merger.merge(reports_dir)
387
+
388
+ ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
389
+ elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
390
+
391
+ puts "RSpec tracer merged parallel tests coverage reports (took #{elapsed})"
392
+ end
393
+
394
+ def write_parallel_tests_coverage_report
395
+ return unless parallel_tests_executed? && !simplecov?
396
+
397
+ starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
398
+
399
+ coverage_path = File.dirname(RSpecTracer.coverage_path)
400
+ file_name = File.join(coverage_path, 'coverage.json')
401
+ coverage_writer = RSpecTracer::CoverageWriter.new(file_name, coverage_merger)
402
+
403
+ coverage_writer.write_report
404
+
405
+ ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
406
+
407
+ coverage_writer.print_stats(ending - starting)
408
+ end
409
+
410
+ def purge_parallel_tests_reports
411
+ return unless parallel_tests_executed?
412
+
413
+ 1.upto(ENV['PARALLEL_TEST_GROUPS'].to_i) do |test_num|
414
+ [RSpecTracer.cache_path, RSpecTracer.coverage_path, RSpecTracer.report_path].each do |path|
415
+ FileUtils.rm_rf(File.join(File.dirname(path), "parallel_tests_#{test_num}"))
416
+ end
417
+ end
418
+ end
419
+
420
+ def parallel_tests_executed?
421
+ return false unless parallel_tests? && parallel_tests_last_process?
422
+
423
+ ParallelTests.wait_for_other_processes_to_finish
424
+
425
+ true
426
+ end
427
+
428
+ def parallel_tests_last_process?
429
+ return false unless parallel_tests?
430
+
431
+ max_test_num = 0
432
+
433
+ File.open(RSpecTracer.parallel_tests_lock_file, 'r') do |f|
434
+ f.flock(File::LOCK_SH)
435
+
436
+ max_test_num = f.read.to_i
437
+ end
261
438
 
262
- puts <<-REPORT.strip.gsub(/\s+/, ' ')
263
- Coverage report generated for RSpecTracer to #{file_name}. #{stat[:covered_lines]}
264
- / #{stat[:total_lines]} LOC (#{stat[:covered_percent]}%) covered
265
- (took #{elpased})
266
- REPORT
439
+ ENV['TEST_ENV_NUMBER'].to_i == max_test_num
267
440
  end
268
441
  end
269
442
  end
metadata CHANGED
@@ -1,60 +1,60 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-tracer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 1.0.0
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-15 00:00:00.000000000 Z
11
+ date: 2021-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: docile
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.1'
20
17
  - - ">="
21
18
  - !ruby/object:Gem::Version
22
19
  version: 1.1.0
20
+ - - "~>"
21
+ - !ruby/object:Gem::Version
22
+ version: '1.1'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - "~>"
28
- - !ruby/object:Gem::Version
29
- version: '1.1'
30
27
  - - ">="
31
28
  - !ruby/object:Gem::Version
32
29
  version: 1.1.0
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.1'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: rspec-core
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
38
- - !ruby/object:Gem::Version
39
- version: '3.6'
40
37
  - - ">="
41
38
  - !ruby/object:Gem::Version
42
39
  version: 3.6.0
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '3.6'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
46
46
  requirements:
47
- - - "~>"
48
- - !ruby/object:Gem::Version
49
- version: '3.6'
50
47
  - - ">="
51
48
  - !ruby/object:Gem::Version
52
49
  version: 3.6.0
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '3.6'
53
53
  description: RSpec Tracer is a specs dependency analyzer, flaky tests detector, tests
54
54
  accelerator, and coverage reporter tool for RSpec. It maintains a list of files
55
55
  for each test, enabling itself to skip tests in the subsequent runs if none of the
56
56
  dependent files are changed. It uses Ruby's built-in coverage library to keep track
57
- of the coverage for each test
57
+ of the coverage for each test.
58
58
  email:
59
59
  - abhisinghabhimanyu@gmail.com
60
60
  executables: []
@@ -67,7 +67,9 @@ files:
67
67
  - lib/rspec_tracer.rb
68
68
  - lib/rspec_tracer/cache.rb
69
69
  - lib/rspec_tracer/configuration.rb
70
+ - lib/rspec_tracer/coverage_merger.rb
70
71
  - lib/rspec_tracer/coverage_reporter.rb
72
+ - lib/rspec_tracer/coverage_writer.rb
71
73
  - lib/rspec_tracer/defaults.rb
72
74
  - lib/rspec_tracer/example.rb
73
75
  - lib/rspec_tracer/filter.rb
@@ -90,6 +92,7 @@ files:
90
92
  - lib/rspec_tracer/html_reporter/public/favicon.png
91
93
  - lib/rspec_tracer/html_reporter/public/loading.gif
92
94
  - lib/rspec_tracer/html_reporter/reporter.rb
95
+ - lib/rspec_tracer/html_reporter/views/duplicate_examples.erb
93
96
  - lib/rspec_tracer/html_reporter/views/examples.erb
94
97
  - lib/rspec_tracer/html_reporter/views/examples_dependency.erb
95
98
  - lib/rspec_tracer/html_reporter/views/files_dependency.erb
@@ -100,6 +103,9 @@ files:
100
103
  - lib/rspec_tracer/remote_cache/cache.rb
101
104
  - lib/rspec_tracer/remote_cache/repo.rb
102
105
  - lib/rspec_tracer/remote_cache/validator.rb
106
+ - lib/rspec_tracer/report_generator.rb
107
+ - lib/rspec_tracer/report_merger.rb
108
+ - lib/rspec_tracer/report_writer.rb
103
109
  - lib/rspec_tracer/reporter.rb
104
110
  - lib/rspec_tracer/rspec_reporter.rb
105
111
  - lib/rspec_tracer/rspec_runner.rb
@@ -113,7 +119,7 @@ licenses:
113
119
  - MIT
114
120
  metadata:
115
121
  homepage_uri: https://github.com/avmnu-sng/rspec-tracer
116
- source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v0.9.0
122
+ source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v1.0.0
117
123
  changelog_uri: https://github.com/avmnu-sng/rspec-tracer/blob/main/CHANGELOG.md
118
124
  bug_tracker_uri: https://github.com/avmnu-sng/rspec-tracer/issues
119
125
  post_install_message:
@@ -131,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
137
  - !ruby/object:Gem::Version
132
138
  version: '0'
133
139
  requirements: []
134
- rubygems_version: 3.2.26
140
+ rubygems_version: 3.0.9
135
141
  signing_key:
136
142
  specification_version: 4
137
143
  summary: RSpec Tracer is a specs dependency analyzer, flaky tests detector, tests