Permalink
Browse files

! _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]
  • Loading branch information...
1 parent 2a9e587 commit bdbf38df3475dcc8ddd6d11ebede48cdd5f55008 @zenspider zenspider committed Oct 25, 2012
View
@@ -7,7 +7,9 @@ design_rationale.rb
lib/hoe/minitest.rb
lib/minitest/autorun.rb
lib/minitest/benchmark.rb
+lib/minitest/hell.rb
lib/minitest/mock.rb
+lib/minitest/parallel_each.rb
lib/minitest/pride.rb
lib/minitest/spec.rb
lib/minitest/unit.rb
@@ -0,0 +1,9 @@
+class Minitest::Unit::TestCase
+ class << self
+ alias :old_test_order :test_order
+
+ def test_order # :nodoc:
+ :parallel
+ end
+ end
+end
@@ -0,0 +1,29 @@
+class ParallelEach
+ require 'thread'
+ include Enumerable
+
+ N = (ENV['N'] || 2).to_i
+
+ def initialize list
+ @queue = Queue.new # *sigh*... the Queue api sucks sooo much...
@leehambley
leehambley Oct 29, 2012

What makes you unhappy about the Queue API ?

@zenspider
zenspider Nov 19, 2012 Seattle Ruby Brigade member

see the next line

+
+ list.each { |i| @queue << i }
+ N.times { @queue << nil }
+ end
+
+ def grep pattern
+ self.class.new super
+ end
+
+ def each
+ threads = N.times.map {
+ Thread.new do
+ Thread.current.abort_on_exception = true
+ while job = @queue.pop
+ yield job
+ end
+ end
+ }
+ threads.map(&:join)
+ end
+end
View
@@ -1,6 +1,7 @@
require 'optparse'
require 'rbconfig'
require 'thread' # required for 1.8
+require 'minitest/parallel_each'
##
# Minimal (mostly drop-in) replacement for test-unit.
@@ -437,6 +438,8 @@ def assert_throws sym, msg = nil
catch(sym) do
begin
yield
+ rescue ThreadError => e # wtf?!? 1.8 + threads == suck
+ default += ", not :#{e.message[/uncaught throw \`(\w+?)\'/, 1]}"
rescue ArgumentError => e # 1.9 exception
default += ", not #{e.message.split(/ /).last}"
rescue NameError => e # 1.8 exception
@@ -468,7 +471,7 @@ def capture_io
captured_stdout, captured_stderr = StringIO.new, StringIO.new
- MiniTest::Unit.runner.synchronize do
+ synchronize do
orig_stdout, orig_stderr = $stdout, $stderr
$stdout, $stderr = captured_stdout, captured_stderr
@@ -503,7 +506,7 @@ def capture_subprocess_io
captured_stdout, captured_stderr = Tempfile.new("out"), Tempfile.new("err")
- MiniTest::Unit.runner.synchronize do
+ synchronize do
orig_stdout, orig_stderr = $stdout.dup, $stderr.dup
$stdout.reopen captured_stdout
$stderr.reopen captured_stderr
@@ -714,6 +717,15 @@ def skip msg = nil, bt = caller
msg ||= "Skipped, no message given"
raise MiniTest::Skip, msg, bt
end
+
+ ##
+ # Takes a block and wraps it with the runner's shared mutex.
+
+ def synchronize
+ Minitest::Unit.runner.synchronize do
+ yield
+ end
+ end
end
class Unit # :nodoc:
@@ -867,10 +879,15 @@ def _run_anything type
end
##
- # Runs all the +suites+ for a given +type+.
+ # Runs all the +suites+ for a given +type+. Runs suites declaring
+ # a test_order of +:parallel+ in parallel, and everything else
+ # serial.
def _run_suites suites, type
- suites.map { |suite| _run_suite suite, type }
+ parallel, serial = suites.partition { |s| s.test_order == :parallel }
+
+ ParallelEach.new(parallel).map { |suite| _run_suite suite, type } +
+ serial.map { |suite| _run_suite suite, type }
end
##
@@ -1346,6 +1363,18 @@ class << self
end
end
+ ##
+ # Call this at the top of your tests when you want to run your
+ # tests in parallel. In doing so, you're admitting that you rule
+ # and your tests are awesome.
+
+ def self.parallelize_me!
+ class << self
+ undef_method :test_order if method_defined? :test_order
+ define_method :test_order do :parallel end
+ end
+ end
+
def self.inherited klass # :nodoc:
@@test_suites[klass] = true
klass.reset_setup_teardown_hooks
@@ -1364,6 +1393,9 @@ def self.test_methods # :nodoc:
methods = public_instance_methods(true).grep(/^test/).map { |m| m.to_s }
case self.test_order
+ when :parallel
+ max = methods.size
+ ParallelEach.new methods.sort.sort_by { rand max }
when :random then
max = methods.size
methods.sort.sort_by { rand max }
@@ -44,6 +44,7 @@ def setup
srand 42
MiniTest::Unit::TestCase.reset
@tu = MiniTest::Unit.new
+
MiniTest::Unit.runner = nil # protect the outer runner from the inner tests
end
@@ -52,7 +53,7 @@ def teardown
end
def with_output
- Minitest::Unit.runner.synchronize do
+ synchronize do
begin
@output = StringIO.new("")
MiniTest::Unit.output = @output
@@ -4,6 +4,8 @@
MiniTest::Unit.autorun
class TestMiniTestMock < MiniTest::Unit::TestCase
+ parallelize_me!
+
def setup
@mock = MiniTest::Mock.new.expect(:foo, nil)
@mock.expect(:meaning_of_life, 42)
@@ -208,6 +210,8 @@ def util_verify_bad exp
require "minitest/metametameta"
class TestMiniTestStub < MiniTest::Unit::TestCase
+ parallelize_me!
+
def setup
super
MiniTest::Unit::TestCase.reset
@@ -224,13 +228,15 @@ def teardown
def assert_stub val_or_callable
@assertion_count += 1
- t = Time.now.to_i
+ synchronize do
+ t = Time.now.to_i
- Time.stub :now, val_or_callable do
- @tc.assert_equal 42, Time.now
- end
+ Time.stub :now, val_or_callable do
+ @tc.assert_equal 42, Time.now
+ end
- @tc.assert_operator Time.now.to_i, :>=, t
+ @tc.assert_operator Time.now.to_i, :>=, t
+ end
end
def test_stub_value
@@ -244,13 +250,15 @@ def test_stub_block
def test_stub_block_args
@assertion_count += 1
- t = Time.now.to_i
+ synchronize do
+ t = Time.now.to_i
- Time.stub :now, lambda { |n| n * 2 } do
- @tc.assert_equal 42, Time.now(21)
- end
+ Time.stub :now, lambda { |n| n * 2 } do
+ @tc.assert_equal 42, Time.now(21)
+ end
- @tc.assert_operator Time.now.to_i, :>=, t
+ @tc.assert_operator Time.now.to_i, :>=, t
+ end
end
def test_stub_callable
@@ -8,6 +8,8 @@ class ExampleA; end
class ExampleB < ExampleA; end
describe MiniTest::Spec do
+ # do not parallelize this suite... it just can't handle it.
+
def assert_triggered expected = "blah", klass = MiniTest::Assertion
@assertion_count += 2
@@ -561,6 +563,8 @@ def _count
end
class TestMeta < MiniTest::Unit::TestCase
+ parallelize_me!
+
def test_setup
srand 42
MiniTest::Unit::TestCase.reset
@@ -652,17 +656,15 @@ def test_setup_teardown_behavior
_, _, z, before_list, after_list = util_structure
@tu = MiniTest::Unit.new
- @output = StringIO.new("")
MiniTest::Unit.runner = nil # protect the outer runner from the inner tests
- MiniTest::Unit.output = @output
- tc = z.new :test_0002_anonymous
- tc.run @tu
+ with_output do
+ tc = z.new :test_0002_anonymous
+ tc.run @tu
+ end
assert_equal [1, 2, 3], before_list
assert_equal [3, 2, 1], after_list
- ensure
- MiniTest::Unit.output = $stdout
end
def test_children
@@ -716,4 +718,17 @@ def xyz; end
assert_respond_to y.new(nil), "xyz"
assert_respond_to z.new(nil), "xyz"
end
+
+ def with_output # REFACTOR: dupe from metametameta
+ synchronize do
+ begin
+ @output = StringIO.new("")
+ MiniTest::Unit.output = @output
+
+ yield
+ ensure
+ MiniTest::Unit.output = STDOUT
+ end
+ end
+ end
end
Oops, something went wrong. Retry.

0 comments on commit bdbf38d

Please sign in to comment.