Skip to content

Commit bdbf38d

Browse files
committed
! _run_suites now runs suites in parallel if they opt-in.
! Added support for :parallel test_order to run test cases in parallel. ! Added minitest/hell - run all your tests through the ringer! + Added TestCase#synchronize + Added TestCase.parallelize_me! Wrapped up or rearranged minitest tests to handle being run in parallel as much as possible. [git-p4: depot-paths = "//src/minitest/dev/": change = 7861]
1 parent 2a9e587 commit bdbf38d

File tree

8 files changed

+262
-85
lines changed

8 files changed

+262
-85
lines changed

Manifest.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ design_rationale.rb
77
lib/hoe/minitest.rb
88
lib/minitest/autorun.rb
99
lib/minitest/benchmark.rb
10+
lib/minitest/hell.rb
1011
lib/minitest/mock.rb
12+
lib/minitest/parallel_each.rb
1113
lib/minitest/pride.rb
1214
lib/minitest/spec.rb
1315
lib/minitest/unit.rb

lib/minitest/hell.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class Minitest::Unit::TestCase
2+
class << self
3+
alias :old_test_order :test_order
4+
5+
def test_order # :nodoc:
6+
:parallel
7+
end
8+
end
9+
end

lib/minitest/parallel_each.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
class ParallelEach
2+
require 'thread'
3+
include Enumerable
4+
5+
N = (ENV['N'] || 2).to_i
6+
7+
def initialize list
8+
@queue = Queue.new # *sigh*... the Queue api sucks sooo much...
9+
10+
list.each { |i| @queue << i }
11+
N.times { @queue << nil }
12+
end
13+
14+
def grep pattern
15+
self.class.new super
16+
end
17+
18+
def each
19+
threads = N.times.map {
20+
Thread.new do
21+
Thread.current.abort_on_exception = true
22+
while job = @queue.pop
23+
yield job
24+
end
25+
end
26+
}
27+
threads.map(&:join)
28+
end
29+
end

lib/minitest/unit.rb

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'optparse'
22
require 'rbconfig'
33
require 'thread' # required for 1.8
4+
require 'minitest/parallel_each'
45

56
##
67
# Minimal (mostly drop-in) replacement for test-unit.
@@ -437,6 +438,8 @@ def assert_throws sym, msg = nil
437438
catch(sym) do
438439
begin
439440
yield
441+
rescue ThreadError => e # wtf?!? 1.8 + threads == suck
442+
default += ", not :#{e.message[/uncaught throw \`(\w+?)\'/, 1]}"
440443
rescue ArgumentError => e # 1.9 exception
441444
default += ", not #{e.message.split(/ /).last}"
442445
rescue NameError => e # 1.8 exception
@@ -468,7 +471,7 @@ def capture_io
468471

469472
captured_stdout, captured_stderr = StringIO.new, StringIO.new
470473

471-
MiniTest::Unit.runner.synchronize do
474+
synchronize do
472475
orig_stdout, orig_stderr = $stdout, $stderr
473476
$stdout, $stderr = captured_stdout, captured_stderr
474477

@@ -503,7 +506,7 @@ def capture_subprocess_io
503506

504507
captured_stdout, captured_stderr = Tempfile.new("out"), Tempfile.new("err")
505508

506-
MiniTest::Unit.runner.synchronize do
509+
synchronize do
507510
orig_stdout, orig_stderr = $stdout.dup, $stderr.dup
508511
$stdout.reopen captured_stdout
509512
$stderr.reopen captured_stderr
@@ -714,6 +717,15 @@ def skip msg = nil, bt = caller
714717
msg ||= "Skipped, no message given"
715718
raise MiniTest::Skip, msg, bt
716719
end
720+
721+
##
722+
# Takes a block and wraps it with the runner's shared mutex.
723+
724+
def synchronize
725+
Minitest::Unit.runner.synchronize do
726+
yield
727+
end
728+
end
717729
end
718730

719731
class Unit # :nodoc:
@@ -867,10 +879,15 @@ def _run_anything type
867879
end
868880

869881
##
870-
# Runs all the +suites+ for a given +type+.
882+
# Runs all the +suites+ for a given +type+. Runs suites declaring
883+
# a test_order of +:parallel+ in parallel, and everything else
884+
# serial.
871885

872886
def _run_suites suites, type
873-
suites.map { |suite| _run_suite suite, type }
887+
parallel, serial = suites.partition { |s| s.test_order == :parallel }
888+
889+
ParallelEach.new(parallel).map { |suite| _run_suite suite, type } +
890+
serial.map { |suite| _run_suite suite, type }
874891
end
875892

876893
##
@@ -1346,6 +1363,18 @@ class << self
13461363
end
13471364
end
13481365

1366+
##
1367+
# Call this at the top of your tests when you want to run your
1368+
# tests in parallel. In doing so, you're admitting that you rule
1369+
# and your tests are awesome.
1370+
1371+
def self.parallelize_me!
1372+
class << self
1373+
undef_method :test_order if method_defined? :test_order
1374+
define_method :test_order do :parallel end
1375+
end
1376+
end
1377+
13491378
def self.inherited klass # :nodoc:
13501379
@@test_suites[klass] = true
13511380
klass.reset_setup_teardown_hooks
@@ -1364,6 +1393,9 @@ def self.test_methods # :nodoc:
13641393
methods = public_instance_methods(true).grep(/^test/).map { |m| m.to_s }
13651394

13661395
case self.test_order
1396+
when :parallel
1397+
max = methods.size
1398+
ParallelEach.new methods.sort.sort_by { rand max }
13671399
when :random then
13681400
max = methods.size
13691401
methods.sort.sort_by { rand max }

test/minitest/metametameta.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def setup
4444
srand 42
4545
MiniTest::Unit::TestCase.reset
4646
@tu = MiniTest::Unit.new
47+
4748
MiniTest::Unit.runner = nil # protect the outer runner from the inner tests
4849
end
4950

@@ -52,7 +53,7 @@ def teardown
5253
end
5354

5455
def with_output
55-
Minitest::Unit.runner.synchronize do
56+
synchronize do
5657
begin
5758
@output = StringIO.new("")
5859
MiniTest::Unit.output = @output

test/minitest/test_minitest_mock.rb

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
MiniTest::Unit.autorun
55

66
class TestMiniTestMock < MiniTest::Unit::TestCase
7+
parallelize_me!
8+
79
def setup
810
@mock = MiniTest::Mock.new.expect(:foo, nil)
911
@mock.expect(:meaning_of_life, 42)
@@ -208,6 +210,8 @@ def util_verify_bad exp
208210
require "minitest/metametameta"
209211

210212
class TestMiniTestStub < MiniTest::Unit::TestCase
213+
parallelize_me!
214+
211215
def setup
212216
super
213217
MiniTest::Unit::TestCase.reset
@@ -224,13 +228,15 @@ def teardown
224228
def assert_stub val_or_callable
225229
@assertion_count += 1
226230

227-
t = Time.now.to_i
231+
synchronize do
232+
t = Time.now.to_i
228233

229-
Time.stub :now, val_or_callable do
230-
@tc.assert_equal 42, Time.now
231-
end
234+
Time.stub :now, val_or_callable do
235+
@tc.assert_equal 42, Time.now
236+
end
232237

233-
@tc.assert_operator Time.now.to_i, :>=, t
238+
@tc.assert_operator Time.now.to_i, :>=, t
239+
end
234240
end
235241

236242
def test_stub_value
@@ -244,13 +250,15 @@ def test_stub_block
244250
def test_stub_block_args
245251
@assertion_count += 1
246252

247-
t = Time.now.to_i
253+
synchronize do
254+
t = Time.now.to_i
248255

249-
Time.stub :now, lambda { |n| n * 2 } do
250-
@tc.assert_equal 42, Time.now(21)
251-
end
256+
Time.stub :now, lambda { |n| n * 2 } do
257+
@tc.assert_equal 42, Time.now(21)
258+
end
252259

253-
@tc.assert_operator Time.now.to_i, :>=, t
260+
@tc.assert_operator Time.now.to_i, :>=, t
261+
end
254262
end
255263

256264
def test_stub_callable

test/minitest/test_minitest_spec.rb

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ class ExampleA; end
88
class ExampleB < ExampleA; end
99

1010
describe MiniTest::Spec do
11+
# do not parallelize this suite... it just can't handle it.
12+
1113
def assert_triggered expected = "blah", klass = MiniTest::Assertion
1214
@assertion_count += 2
1315

@@ -561,6 +563,8 @@ def _count
561563
end
562564

563565
class TestMeta < MiniTest::Unit::TestCase
566+
parallelize_me!
567+
564568
def test_setup
565569
srand 42
566570
MiniTest::Unit::TestCase.reset
@@ -652,17 +656,15 @@ def test_setup_teardown_behavior
652656
_, _, z, before_list, after_list = util_structure
653657

654658
@tu = MiniTest::Unit.new
655-
@output = StringIO.new("")
656659
MiniTest::Unit.runner = nil # protect the outer runner from the inner tests
657-
MiniTest::Unit.output = @output
658660

659-
tc = z.new :test_0002_anonymous
660-
tc.run @tu
661+
with_output do
662+
tc = z.new :test_0002_anonymous
663+
tc.run @tu
664+
end
661665

662666
assert_equal [1, 2, 3], before_list
663667
assert_equal [3, 2, 1], after_list
664-
ensure
665-
MiniTest::Unit.output = $stdout
666668
end
667669

668670
def test_children
@@ -716,4 +718,17 @@ def xyz; end
716718
assert_respond_to y.new(nil), "xyz"
717719
assert_respond_to z.new(nil), "xyz"
718720
end
721+
722+
def with_output # REFACTOR: dupe from metametameta
723+
synchronize do
724+
begin
725+
@output = StringIO.new("")
726+
MiniTest::Unit.output = @output
727+
728+
yield
729+
ensure
730+
MiniTest::Unit.output = STDOUT
731+
end
732+
end
733+
end
719734
end

0 commit comments

Comments
 (0)