forking_test_runner 1.4.0 → 1.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 24a22080e7feec720e83c73e48241d6984b0c7d769cce74f7687f563f1b1e75d
4
- data.tar.gz: 2a0b5a4bb46cad79e4f7de3cb9b37bdd1307d7b9754b27397686349278be9ae6
3
+ metadata.gz: fc4844e1ccea576d0e7992efedab19587cb803ec04df7db7f82061253bada8fe
4
+ data.tar.gz: 2a8a12c8446e35c410417ac5271cd401d525d508a9b0db2c1c0674bd81d9275f
5
5
  SHA512:
6
- metadata.gz: ca2e09cb43119ea4c62260fa8487d7c93e1b3a935a2dbbc1c5a219166c7891cdec55f8b95cf30610f83319334efb17739e505f9132b4dbdc6e7b78e59346e3fb
7
- data.tar.gz: 3385e2157a115e09810ff55cb50d5bb24e07be53effc1bf4b7362b434215ed6c4111ce26a379955dfbb540bf27a7e2ab954fff97080e8c6d9cc782e0ea8a222e
6
+ metadata.gz: 67e48c4c7e3ac01664bfb0b21bf8339875a1fed6b8a40a75f546cd849ad165df3346afaf641035317ff0be8d553ab745f2cc7e14fb3d747c5849db01d5bab1d9
7
+ data.tar.gz: 59c63fcf19c1a472804504987d31bfe01e85805453db87104068279227a7b83391e9cc74323f412033d1fbe83a5a9b4304805b249f275539ba2367a3478e2460
@@ -1,130 +1,74 @@
1
1
  require 'benchmark'
2
2
  require 'optparse'
3
3
  require 'forking_test_runner/version'
4
+ require 'forking_test_runner/coverage_capture'
5
+ require 'forking_test_runner/cli'
6
+ require 'parallel'
7
+ require 'tempfile'
4
8
 
5
9
  module ForkingTestRunner
6
10
  CLEAR = "------"
7
11
 
8
- module CoverageCapture
9
- def capture_coverage!
10
- @capture_coverage = peek_result.dup
11
- end
12
-
13
- # override to add pre-fork captured coverage when someone asks for the results
14
- def result
15
- original = super
16
- return original unless @capture_coverage
17
- CoverageCapture.merge_coverage(original, @capture_coverage)
18
- end
19
-
20
- class << self
21
- def merge_coverage(a, b)
22
- merged = a.dup
23
- b.each do |file, coverage|
24
- orig = merged[file]
25
- merged[file] = if orig
26
- if coverage.is_a?(Array)
27
- merge_lines_coverage(orig, coverage)
28
- else
29
- {
30
- lines: merge_lines_coverage(orig.fetch(:lines), coverage.fetch(:lines)),
31
- branches: merge_branches_coverage(orig.fetch(:branches), coverage.fetch(:branches))
32
- }
33
- end
34
- else
35
- coverage
36
- end
37
- end
38
- merged
39
- end
40
-
41
- private
42
-
43
- # assuming b has same or more keys since it comes from a fork
44
- # [nil,1,0] + [nil,nil,2] -> [nil,1,2]
45
- def merge_lines_coverage(a, b)
46
- b.each_with_index.map do |b_count, i|
47
- a_count = a[i]
48
- (a_count.nil? && b_count.nil?) ? nil : a_count.to_i + b_count.to_i
49
- end
50
- end
51
-
52
- # assuming b has same or more keys since it comes from a fork
53
- # {foo: {bar: 0, baz: 1}} + {foo: {bar: 1, baz: 0}} -> {foo: {bar: 1, baz: 1}}
54
- def merge_branches_coverage(a, b)
55
- b.each_with_object({}) do |(branch, v), all|
56
- vb = v.dup
57
- if part = a[branch]
58
- part.each do |nested, a_count|
59
- vb[nested] = a_count + vb[nested].to_i
60
- end
61
- end
62
- all[branch] = vb
63
- end
64
- end
65
- end
66
- end
67
-
68
12
  class << self
69
13
  def cli(argv)
70
- @options, tests = parse_options(argv)
71
-
72
- disable_test_autorun
73
-
74
- load_test_env(@options.fetch(:helper))
14
+ @options, tests = CLI.parse_options(argv)
75
15
 
76
16
  # figure out what we need to run
77
17
  runtime_log = @options.fetch(:runtime_log)
78
- group, group_count = find_group_args
79
- tests = find_tests_for_group(group, group_count, tests, runtime_log)
18
+ groups, group_count = find_group_args
19
+ parallel = @options.fetch(:parallel)
20
+ test_groups =
21
+ if parallel && !@options.fetch(:group)
22
+ Array.new(parallel) { |i| find_tests_for_group(i + 1, parallel, tests, runtime_log) }
23
+ else
24
+ groups.map { |group| find_tests_for_group(group, group_count, tests, runtime_log) }
25
+ end
80
26
 
27
+ # say what we are running
28
+ all_tests = test_groups.flatten(1)
81
29
  if @options.fetch(:quiet)
82
- puts "Running #{tests.size} test files"
30
+ puts "Running #{all_tests.size} test files"
83
31
  else
84
- puts "Running tests #{tests.map(&:first).join(" ")}"
32
+ puts "Running tests #{all_tests.map(&:first).join(" ")}"
85
33
  end
86
34
 
87
- if ar?
88
- preload_fixtures
89
- ActiveRecord::Base.connection.disconnect!
90
- end
91
-
92
- Coverage.capture_coverage! if @options.fetch(:merge_coverage)
93
-
94
35
  # run all the tests
95
- results = tests.map do |file, expected|
96
- puts "#{CLEAR} >>> #{file} "
97
- time, success, output = benchmark { run_test(file) }
98
-
99
- puts output if !success && @options.fetch(:quiet)
100
-
101
- if runtime_log && !@options.fetch(:quiet)
102
- puts "Time: expected #{expected.round(2)}, actual #{time.round(2)}"
103
- end
36
+ results = with_lock do |lock|
37
+ Parallel.map_with_index(test_groups, in_processes: parallel || 0) do |tests, env_index|
38
+ if parallel
39
+ ENV["TEST_ENV_NUMBER"] = (env_index == 0 ? '' : (env_index + 1).to_s) # NOTE: does not support first_is_1 option
40
+ end
104
41
 
105
- if !success || !@options.fetch(:quiet)
106
- puts "#{CLEAR} <<< #{file} ---- #{success ? "OK" : "Failed"}"
107
- end
42
+ reraise_clean_ar_error { load_test_env }
108
43
 
109
- [file, time, expected, output, success]
44
+ tests.map do |file, expected|
45
+ print_started file unless parallel
46
+ result = [file, expected, *benchmark { run_test(file) }]
47
+ sync_stdout lock do
48
+ print_started file if parallel
49
+ print_finished *result
50
+ end
51
+ result
52
+ end
53
+ end.flatten(1)
110
54
  end
111
55
 
112
56
  unless @options.fetch(:quiet)
113
57
  # pretty print the results
114
58
  puts "\nResults:"
115
59
  puts results.
116
- sort_by { |_,_,_,_,r| r ? 0 : 1 }. # failures should be last so they are easy to find
117
- map { |f,_,_,_,r| "#{f}: #{r ? "OK" : "Fail"}"}
60
+ sort_by { |_,_,_,r,_| r ? 0 : 1 }. # failures should be last so they are easy to find
61
+ map { |f,_,_,r,_| "#{f}: #{r ? "OK" : "Fail"}"}
118
62
  puts
119
63
  end
120
64
 
121
- success = results.map(&:last).all?
65
+ success = results.map { |r| r[3] }.all?
122
66
 
123
- puts colorize(success, summarize_results(results.map { |r| r[3] }))
67
+ puts colorize(success, summarize_results(results.map { |r| r[4] }))
124
68
 
125
69
  if runtime_log
126
70
  # show how long they ran vs expected
127
- diff = results.map { |_,time,expected| time - expected }.inject(:+).to_f
71
+ diff = results.map { |_, expected, time| time - expected }.inject(:+).to_f
128
72
  puts "Time: #{diff.round(2)} diff to expected"
129
73
  end
130
74
 
@@ -140,6 +84,38 @@ module ForkingTestRunner
140
84
 
141
85
  private
142
86
 
87
+ def with_lock(&block)
88
+ return yield unless @options.fetch(:parallel)
89
+ Tempfile.open"forking-test-runner-lock", &block
90
+ end
91
+
92
+ def sync_stdout(lock)
93
+ return yield unless @options.fetch(:parallel)
94
+ begin
95
+ lock.flock(File::LOCK_EX)
96
+ yield
97
+ ensure
98
+ lock.flock(File::LOCK_UN)
99
+ end
100
+ end
101
+
102
+ def print_started(file)
103
+ puts "#{CLEAR} >>> #{file}"
104
+ end
105
+
106
+ def print_finished(file, expected, time, success, stdout)
107
+ # print stdout if it was not shown before, but needs to be shown
108
+ puts stdout if (!success && @options.fetch(:quiet)) || (@options.fetch(:parallel) && !@options.fetch(:quiet))
109
+
110
+ if @options.fetch(:runtime_log) && !@options.fetch(:quiet)
111
+ puts "Time: expected #{expected.round(2)}, actual #{time.round(2)}"
112
+ end
113
+
114
+ if !success || !@options.fetch(:quiet)
115
+ puts "#{CLEAR} <<< #{file} ---- #{success ? "OK" : "Failed"}"
116
+ end
117
+ end
118
+
143
119
  def colorize(green, string)
144
120
  if $stdout.tty?
145
121
  "\e[#{green ? 32 : 31}m#{string}\e[0m"
@@ -162,15 +138,13 @@ module ForkingTestRunner
162
138
 
163
139
  def benchmark
164
140
  result = false
165
- time = Benchmark.realtime do
166
- result = yield
167
- end
168
- return [time, result].flatten
141
+ time = Benchmark.realtime { result = yield }
142
+ [time, *result]
169
143
  end
170
144
 
171
145
  # log runtime via dumping or curling it into the runtime log location
172
146
  def record_test_runtime(mode, results, log)
173
- data = results.map { |test, time| "#{test}:#{time.round(2)}" }.join("\n") << "\n"
147
+ data = results.map { |test, _, time| "#{test}:#{time.round(2)}" }.join("\n") << "\n"
174
148
 
175
149
  case mode
176
150
  when 'simple'
@@ -198,21 +172,47 @@ module ForkingTestRunner
198
172
  end
199
173
 
200
174
  def find_group_args
201
- if @options.fetch(:group) && @options.fetch(:groups)
175
+ group = @options.fetch(:group)
176
+ groups = @options.fetch(:groups)
177
+ if group && groups
202
178
  # delete options we want while leaving others as they are (-v / --seed etc)
203
- group = @options.fetch(:group)
204
- group_count = @options.fetch(:groups)
179
+ [group.split(",").map { |g| Integer(g) }, groups]
205
180
  else
206
- group = 1
207
- group_count = 1
181
+ [[1], 1]
182
+ end
183
+ end
184
+
185
+ def load_test_env
186
+ CoverageCapture.activate! if @options.fetch(:merge_coverage)
187
+
188
+ load_test_helper
189
+
190
+ if active_record?
191
+ preload_fixtures
192
+ ActiveRecord::Base.connection.disconnect!
208
193
  end
209
194
 
210
- [group, group_count]
195
+ CoverageCapture.capture! if @options.fetch(:merge_coverage)
211
196
  end
212
197
 
213
- def load_test_env(helper=nil)
198
+ def reraise_clean_ar_error
199
+ return yield unless @options.fetch(:parallel)
200
+
201
+ e = begin
202
+ yield
203
+ nil
204
+ rescue
205
+ $!
206
+ end
207
+
208
+ # needs to be done outside of the rescue block to avoid inheriting the cause
209
+ raise RuntimeError, "Re-raised error from test helper: #{e.message}", e.backtrace if e
210
+ end
211
+
212
+ def load_test_helper
213
+ disable_test_autorun
214
214
  require 'rspec/core' if @options.fetch(:rspec)
215
- helper = helper || (@options.fetch(:rspec) ? "spec/spec_helper" : "test/test_helper")
215
+ helper = @options.fetch(:helper) || (@options.fetch(:rspec) ? "spec/spec_helper" : "test/test_helper")
216
216
  require "./#{helper}"
217
217
  end
218
218
 
@@ -221,9 +221,8 @@ module ForkingTestRunner
221
221
  def preload_fixtures
222
222
  return if @options.fetch(:no_fixtures)
223
223
 
224
- fixtures = (ActiveSupport::VERSION::MAJOR == 3 ? ActiveRecord::Fixtures : ActiveRecord::FixtureSet)
225
-
226
224
  # reuse our pre-loaded fixtures even if we have a different connection
225
+ fixtures = ActiveRecord::FixtureSet
227
226
  fixtures_eigenclass = class << fixtures; self; end
228
227
  fixtures_eigenclass.send(:define_method, :cache_for_connection) do |_connection|
229
228
  fixtures.class_variable_get(:@@all_cached_fixtures)[:unique]
@@ -247,7 +246,7 @@ module ForkingTestRunner
247
246
  toggle_test_autorun true, file
248
247
  end
249
248
 
250
- def fork_with_captured_output(tee_to_stdout)
249
+ def fork_with_captured_stdout
251
250
  rpipe, wpipe = IO.pipe
252
251
 
253
252
  child = fork do
@@ -263,7 +262,7 @@ module ForkingTestRunner
263
262
 
264
263
  while ch = rpipe.read(1)
265
264
  buffer << ch
266
- $stdout.write(ch) if tee_to_stdout
265
+ $stdout.write(ch) if !@options.fetch(:quiet) && !@options.fetch(:parallel) # tee
267
266
  end
268
267
 
269
268
  Process.wait(child)
@@ -271,10 +270,10 @@ module ForkingTestRunner
271
270
  end
272
271
 
273
272
  def run_test(file)
274
- output = change_program_name_to file do
275
- fork_with_captured_output(!@options.fetch(:quiet)) do
273
+ stdout = change_program_name_to file do
274
+ fork_with_captured_stdout do
276
275
  SimpleCov.pid = Process.pid if defined?(SimpleCov) && SimpleCov.respond_to?(:pid=) # trick simplecov into reporting in this fork
277
- if ar?
276
+ if active_record?
278
277
  key = (ActiveRecord::VERSION::STRING >= "4.1.0" ? :test : "test")
279
278
  ActiveRecord::Base.establish_connection key
280
279
  end
@@ -282,14 +281,17 @@ module ForkingTestRunner
282
281
  end
283
282
  end
284
283
 
285
- [$?.success?, output]
284
+ [$?.success?, stdout]
286
285
  end
287
286
 
288
287
  def change_program_name_to(name)
289
- old, $0 = $0, name
290
- yield
291
- ensure
292
- $0 = old
288
+ return yield if @options.fetch(:parallel)
289
+ begin
290
+ old, $0 = $0, name
291
+ yield
292
+ ensure
293
+ $0 = old
294
+ end
293
295
  end
294
296
 
295
297
  def find_tests_for_group(group, group_count, tests, runtime_log)
@@ -310,21 +312,15 @@ module ForkingTestRunner
310
312
  group.map { |test| [test, (tests[test] if group_by == :runtime)] }
311
313
  end
312
314
 
313
- def ar?
315
+ def active_record?
314
316
  !@options.fetch(:no_ar) && defined?(ActiveRecord::Base)
315
317
  end
316
318
 
317
319
  def minitest_class
318
320
  @minitest_class ||= begin
319
321
  require 'bundler/setup'
320
- gem 'minitest'
321
- if Gem.loaded_specs["minitest"].version.segments.first == 4 # 4.x
322
- require 'minitest/unit'
323
- MiniTest::Unit
324
- else
325
- require 'minitest'
326
- Minitest
327
- end
322
+ require 'minitest'
323
+ Minitest
328
324
  end
329
325
  end
330
326
 
@@ -348,85 +344,5 @@ module ForkingTestRunner
348
344
  end
349
345
  end
350
346
  end
351
-
352
- # we remove the args we understand and leave the rest alone
353
- # so minitest / rspec can read their own options (--seed / -v ...)
354
- # - keep our options clear / unambiguous to avoid overriding
355
- # - read all serial non-flag arguments as tests and leave only unknown options behind
356
- # - use .fetch everywhere to make sure nothing is misspelled
357
- # GOOD: test --ours --theirs
358
- # OK: --ours test --theirs
359
- # BAD: --theirs test --ours
360
- def parse_options(argv)
361
- arguments = [
362
- [:rspec, "--rspec", "RSpec mode"],
363
- [:helper, "--helper", "Helper file to load before tests start", String],
364
- [:quiet, "--quiet", "Quiet"],
365
- [:no_fixtures, "--no-fixtures", "Do not load fixtures"],
366
- [:no_ar, "--no-ar", "Disable ActiveRecord logic"],
367
- [:merge_coverage, "--merge-coverage", "Merge base code coverage into indvidual files coverage, great for SingleCov"],
368
- [
369
- :record_runtime,
370
- "--record-runtime=MODE",
371
- "\n Record test runtime:\n" <<
372
- " simple = write to disk at --runtime-log)\n" <<
373
- " amend = write from multiple remote workers via http://github.com/grosser/amend, needs TRAVIS_REPO_SLUG & TRAVIS_BUILD_NUMBER",
374
- String
375
- ],
376
- [:runtime_log, "--runtime-log=FILE", "File to store runtime log in or runtime.log", String],
377
- [:group, "--group=NUM", "What group this is (use with --groups / starts at 1)", Integer],
378
- [:groups, "--groups=NUM", "How many groups there are in total (use with --group)", Integer],
379
- [:version, "--version", "Show version"],
380
- [:help, "--help", "Show help"]
381
- ]
382
-
383
- options = arguments.each_with_object({}) do |(setting, flag, _, type), all|
384
- all[setting] = delete_argv(flag.split('=', 2)[0], argv, type: type)
385
- end
386
-
387
- # show version
388
- if options.fetch(:version)
389
- puts VERSION
390
- exit 0
391
- end
392
-
393
- # # show help
394
- if options[:help]
395
- parser = OptionParser.new("forking-test-runner folder [options]", 32, '') do |opts|
396
- arguments.each do |_, flag, desc, type|
397
- opts.on(flag, desc, type)
398
- end
399
- end
400
- puts parser
401
- exit 0
402
- end
403
-
404
- # check if we can use merge_coverage
405
- if options.fetch(:merge_coverage)
406
- abort "merge_coverage does not work on ruby prior to 2.3" if RUBY_VERSION < "2.3.0"
407
- require 'coverage'
408
- klass = (class << Coverage; self; end)
409
- klass.prepend CoverageCapture
410
- end
411
-
412
- # all remaining non-flag options until the next flag must be tests
413
- next_flag = argv.index { |arg| arg.start_with?("-") } || argv.size
414
- tests = argv.slice!(0, next_flag)
415
- abort "No tests or folders found in arguments" if tests.empty?
416
- tests.each { |t| abort "Unable to find #{t}" unless File.exist?(t) }
417
-
418
- [options, tests]
419
- end
420
-
421
- def delete_argv(name, argv, type: nil)
422
- return unless index = argv.index(name)
423
- argv.delete_at(index)
424
- if type
425
- found = argv.delete_at(index) || raise("Missing argument for #{name}")
426
- send(type.name, found) # case found
427
- else
428
- true
429
- end
430
- end
431
347
  end
432
348
  end
@@ -0,0 +1,94 @@
1
+ module ForkingTestRunner
2
+ # read and delete options we support and pass the rest through to the underlying test runner (-v / --seed etc)
3
+ module CLI
4
+ OPTIONS = [
5
+ [:rspec, "--rspec", "RSpec mode"],
6
+ [:helper, "--helper", "Helper file to load before tests start", String],
7
+ [:quiet, "--quiet", "Quiet"],
8
+ [:no_fixtures, "--no-fixtures", "Do not load fixtures"],
9
+ [:no_ar, "--no-ar", "Disable ActiveRecord logic"],
10
+ [:merge_coverage, "--merge-coverage", "Merge base code coverage into indvidual files coverage, great for SingleCov"],
11
+ [
12
+ :record_runtime,
13
+ "--record-runtime=MODE",
14
+ "\n Record test runtime:\n" <<
15
+ " simple = write to disk at --runtime-log)\n" <<
16
+ " amend = write from multiple remote workers via http://github.com/grosser/amend, needs TRAVIS_REPO_SLUG & TRAVIS_BUILD_NUMBER",
17
+ String
18
+ ],
19
+ [:runtime_log, "--runtime-log=FILE", "File to store runtime log in or runtime.log", String],
20
+ [:parallel, "--parallel=NUM", "Number of parallel groups to run at once", Integer],
21
+ [:group, "--group=NUM[,NUM]", "What group this is (use with --groups / starts at 1)", String],
22
+ [:groups, "--groups=NUM", "How many groups there are in total (use with --group)", Integer],
23
+ [:version, "--version", "Show version"],
24
+ [:help, "--help", "Show help"]
25
+ ]
26
+
27
+ class << self
28
+ def parse_options(argv)
29
+ options = OPTIONS.each_with_object({}) do |(setting, flag, _, type), all|
30
+ all[setting] = delete_argv(flag.split('=', 2)[0], argv, type: type)
31
+ end
32
+
33
+ # show version
34
+ if options.fetch(:version)
35
+ puts VERSION
36
+ exit 0
37
+ end
38
+
39
+ # show help
40
+ if options[:help]
41
+ puts help
42
+ exit 0
43
+ end
44
+
45
+ # check if we can use merge_coverage
46
+ if options.fetch(:merge_coverage)
47
+ abort "merge_coverage does not work on ruby prior to 2.3" if RUBY_VERSION < "2.3.0"
48
+ end
49
+
50
+ if !!options.fetch(:group) ^ !!options.fetch(:groups)
51
+ abort "use --group and --groups together"
52
+ end
53
+
54
+ # all remaining non-flag options until the next flag must be tests
55
+ next_flag = argv.index { |arg| arg.start_with?("-") } || argv.size
56
+ tests = argv.slice!(0, next_flag)
57
+ abort "No tests or folders found in arguments" if tests.empty?
58
+ tests.each { |t| abort "Unable to find #{t}" unless File.exist?(t) }
59
+
60
+ [options, tests]
61
+ end
62
+
63
+ private
64
+
65
+ # fake parser that will print nicely
66
+ def help
67
+ OptionParser.new("forking-test-runner folder [options]", 32, '') do |opts|
68
+ OPTIONS.each do |_, flag, desc, type|
69
+ opts.on(flag, desc, type)
70
+ end
71
+ end
72
+ end
73
+
74
+ # we remove the args we understand and leave the rest alone
75
+ # so minitest / rspec can read their own options (--seed / -v ...)
76
+ # - keep our options clear / unambiguous to avoid overriding
77
+ # - read all serial non-flag arguments as tests and leave only unknown options behind
78
+ # - use .fetch everywhere to make sure nothing is misspelled
79
+ # GOOD: test --ours --theirs
80
+ # OK: --ours test --theirs
81
+ # BAD: --theirs test --ours
82
+ def delete_argv(name, argv, type: nil)
83
+ return unless index = argv.index(name)
84
+ argv.delete_at(index)
85
+ if type
86
+ found = argv.delete_at(index) || raise("Missing argument for #{name}")
87
+ send(type.name, found) # case found
88
+ else
89
+ true
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,71 @@
1
+ module ForkingTestRunner
2
+ module CoverageCapture
3
+ # override Coverage.result to add pre-fork captured coverage
4
+ def result
5
+ return super unless captured = CoverageCapture.coverage
6
+ CoverageCapture.merge_coverage(super, captured)
7
+ end
8
+
9
+ # deprecated, single_cov checks for this, so leave it here
10
+ def capture_coverage!
11
+ end
12
+
13
+ class << self
14
+ attr_accessor :coverage
15
+
16
+ def activate!
17
+ require 'coverage'
18
+ (class << Coverage; self; end).prepend self
19
+ end
20
+
21
+ def capture!
22
+ self.coverage = Coverage.peek_result.dup
23
+ end
24
+
25
+ def merge_coverage(a, b)
26
+ merged = a.dup
27
+ b.each do |file, coverage|
28
+ orig = merged[file]
29
+ merged[file] = if orig
30
+ if coverage.is_a?(Array)
31
+ merge_lines_coverage(orig, coverage)
32
+ else
33
+ {
34
+ lines: merge_lines_coverage(orig.fetch(:lines), coverage.fetch(:lines)),
35
+ branches: merge_branches_coverage(orig.fetch(:branches), coverage.fetch(:branches))
36
+ }
37
+ end
38
+ else
39
+ coverage
40
+ end
41
+ end
42
+ merged
43
+ end
44
+
45
+ private
46
+
47
+ # assuming b has same or more keys since it comes from a fork
48
+ # [nil,1,0] + [nil,nil,2] -> [nil,1,2]
49
+ def merge_lines_coverage(a, b)
50
+ b.each_with_index.map do |b_count, i|
51
+ a_count = a[i]
52
+ (a_count.nil? && b_count.nil?) ? nil : a_count.to_i + b_count.to_i
53
+ end
54
+ end
55
+
56
+ # assuming b has same or more keys since it comes from a fork
57
+ # {foo: {bar: 0, baz: 1}} + {foo: {bar: 1, baz: 0}} -> {foo: {bar: 1, baz: 1}}
58
+ def merge_branches_coverage(a, b)
59
+ b.each_with_object({}) do |(branch, v), all|
60
+ vb = v.dup
61
+ if part = a[branch]
62
+ part.each do |nested, a_count|
63
+ vb[nested] = a_count + vb[nested].to_i
64
+ end
65
+ end
66
+ all[branch] = vb
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,3 +1,3 @@
1
1
  module ForkingTestRunner
2
- VERSION = "1.4.0"
2
+ VERSION = "1.5.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forking_test_runner
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-25 00:00:00.000000000 Z
11
+ date: 2019-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parallel_tests
@@ -118,6 +118,8 @@ files:
118
118
  - MIT-LICENSE
119
119
  - bin/forking-test-runner
120
120
  - lib/forking_test_runner.rb
121
+ - lib/forking_test_runner/cli.rb
122
+ - lib/forking_test_runner/coverage_capture.rb
121
123
  - lib/forking_test_runner/version.rb
122
124
  homepage: https://github.com/grosser/forking_test_runner
123
125
  licenses:
@@ -131,15 +133,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
131
133
  requirements:
132
134
  - - ">="
133
135
  - !ruby/object:Gem::Version
134
- version: 2.0.0
136
+ version: 2.3.0
135
137
  required_rubygems_version: !ruby/object:Gem::Requirement
136
138
  requirements:
137
139
  - - ">="
138
140
  - !ruby/object:Gem::Version
139
141
  version: '0'
140
142
  requirements: []
141
- rubyforge_project:
142
- rubygems_version: 2.7.6
143
+ rubygems_version: 3.0.3
143
144
  signing_key:
144
145
  specification_version: 4
145
146
  summary: Run every test in a fork to avoid pollution and get clean output per test