forking_test_runner 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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