Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
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...
commit bdbf38df3475dcc8ddd6d11ebede48cdd5f55008 1 parent 2a9e587
@zenspider zenspider authored
View
2  Manifest.txt
@@ -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
View
9 lib/minitest/hell.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
View
29 lib/minitest/parallel_each.rb
@@ -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...

What makes you unhappy about the Queue API ?

@zenspider Owner

see the next line

Understood! :godmode:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ 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
40 lib/minitest/unit.rb
@@ -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 }
View
3  test/minitest/metametameta.rb
@@ -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
View
28 test/minitest/test_minitest_mock.rb
@@ -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
View
27 test/minitest/test_minitest_spec.rb
@@ -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
View
209 test/minitest/test_minitest_unit.rb
@@ -6,6 +6,8 @@ class AnError < StandardError; include MyModule; end
class ImmutableString < String; def inspect; super.freeze; end; end
class TestMiniTestUnit < MetaMetaMetaTestCase
+ parallelize_me!
+
pwd = Pathname.new File.expand_path Dir.pwd
basedir = Pathname.new(File.expand_path "lib/minitest") + 'mini'
basedir = basedir.relative_path_from(pwd).to_s
@@ -166,6 +168,85 @@ def test_filter_backtrace_unit_starts
assert_equal ex, fu
end
+ def test_default_runner_is_minitest_unit
+ assert_instance_of MiniTest::Unit, MiniTest::Unit.runner
+ end
+
+ def with_overridden_include
+ Class.class_eval do
+ def inherited_with_hacks klass
+ throw :inherited_hook
+ end
+
+ alias inherited_without_hacks inherited
+ alias inherited inherited_with_hacks
+ alias IGNORE_ME! inherited # 1.8 bug. god I love venture bros
+ end
+
+ yield
+ ensure
+ Class.class_eval do
+ alias inherited inherited_without_hacks
+
+ undef_method :inherited_with_hacks
+ undef_method :inherited_without_hacks
+ end
+
+ refute_respond_to Class, :inherited_with_hacks
+ refute_respond_to Class, :inherited_without_hacks
+ end
+
+ def test_inherited_hook_plays_nice_with_others
+ with_overridden_include do
+ assert_throws :inherited_hook do
+ Class.new MiniTest::Unit::TestCase
+ end
+ end
+ end
+
+ def test_passed_eh_teardown_good
+ test_class = Class.new MiniTest::Unit::TestCase do
+ def teardown; assert true; end
+ def test_omg; assert true; end
+ end
+
+ test = test_class.new :test_omg
+ test.run @tu
+ assert test.passed?
+ end
+
+ def test_passed_eh_teardown_flunked
+ test_class = Class.new MiniTest::Unit::TestCase do
+ def teardown; flunk; end
+ def test_omg; assert true; end
+ end
+
+ test = test_class.new :test_omg
+ test.run @tu
+ refute test.passed?
+ end
+
+ def util_expand_bt bt
+ if RUBY_VERSION >= '1.9.0' then
+ bt.map { |f| (f =~ /^\./) ? File.expand_path(f) : f }
+ else
+ bt
+ end
+ end
+end
+
+class TestMiniTestRunner < MetaMetaMetaTestCase
+ # do not parallelize this suite... it just can't handle it.
+
+ def test_class_test_suites
+ @assertion_count = 0
+
+ tc = Class.new(MiniTest::Unit::TestCase)
+
+ assert_equal 1, MiniTest::Unit::TestCase.test_suites.size
+ assert_equal [tc], MiniTest::Unit::TestCase.test_suites
+ end
+
def test_run_test
Class.new MiniTest::Unit::TestCase do
attr_reader :foo
@@ -362,10 +443,6 @@ def test_skip
assert_report expected, %w[--seed 42 --verbose]
end
- def test_default_runner_is_minitest_unit
- assert_instance_of MiniTest::Unit, MiniTest::Unit.runner
- end
-
def test_run_with_other_runner
MiniTest::Unit.runner = Class.new MiniTest::Unit do
def _run_suite suite, type
@@ -403,37 +480,74 @@ def test_something_else
assert_report expected
end
- def with_overridden_include
- Class.class_eval do
- def inherited_with_hacks klass
- throw :inherited_hook
- end
+ require 'monitor'
- alias inherited_without_hacks inherited
- alias inherited inherited_with_hacks
- alias IGNORE_ME! inherited # 1.8 bug. god I love venture bros
+ class Latch
+ def initialize count = 1
+ @count = count
+ @lock = Monitor.new
+ @cv = @lock.new_cond
end
- yield
- ensure
- Class.class_eval do
- alias inherited inherited_without_hacks
-
- undef_method :inherited_with_hacks
- undef_method :inherited_without_hacks
+ def release
+ @lock.synchronize do
+ @count -= 1 if @count > 0
+ @cv.broadcast if @count == 0
+ end
end
- refute_respond_to Class, :inherited_with_hacks
- refute_respond_to Class, :inherited_without_hacks
+ def await
+ @lock.synchronize { @cv.wait_while { @count > 0 } }
+ end
end
- def test_inherited_hook_plays_nice_with_others
- with_overridden_include do
- assert_throws :inherited_hook do
- Class.new MiniTest::Unit::TestCase
+ def test_run_parallel
+ test_count = 2
+ test_latch = Latch.new test_count
+ main_latch = Latch.new
+
+ thread = Thread.new {
+ Thread.current.abort_on_exception = true
+
+ # This latch waits until both test latches have been released. Both
+ # latches can't be released unless done in separate threads because
+ # `main_latch` keeps the test method from finishing.
+ test_latch.await
+ main_latch.release
+ }
+
+ Class.new MiniTest::Unit::TestCase do
+ parallelize_me!
+
+ test_count.times do |i|
+ define_method :"test_wait_on_main_thread_#{i}" do
+ test_latch.release
+
+ # This latch blocks until the "main thread" releases it. The main
+ # thread can't release this latch until both test latches have
+ # been released. This forces the latches to be released in separate
+ # threads.
+ main_latch.await
+ assert true
+ end
end
end
+
+ expected = clean <<-EOM
+ ..
+
+ Finished tests in 0.00
+
+ 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
+ EOM
+
+ assert_report expected
+ assert thread.join
end
+end
+
+class TestMiniTestUnitOrder < MetaMetaMetaTestCase
+ # do not parallelize this suite... it just can't handle it.
def test_before_setup
call_order = []
@@ -458,28 +572,6 @@ def test_omg; assert true; end
assert_equal expected, call_order
end
- def test_passed_eh_teardown_good
- test_class = Class.new MiniTest::Unit::TestCase do
- def teardown; assert true; end
- def test_omg; assert true; end
- end
-
- test = test_class.new :test_omg
- test.run @tu
- assert test.passed?
- end
-
- def test_passed_eh_teardown_flunked
- test_class = Class.new MiniTest::Unit::TestCase do
- def teardown; flunk; end
- def test_omg; assert true; end
- end
-
- test = test_class.new :test_omg
- test.run @tu
- refute test.passed?
- end
-
def test_after_teardown
call_order = []
Class.new MiniTest::Unit::TestCase do
@@ -563,17 +655,11 @@ def test_setup_and_teardown_survive_inheritance
assert_equal expected, call_order
end
-
- def util_expand_bt bt
- if RUBY_VERSION >= '1.9.0' then
- bt.map { |f| (f =~ /^\./) ? File.expand_path(f) : f }
- else
- bt
- end
- end
end
class TestMiniTestUnitTestCase < MiniTest::Unit::TestCase
+ parallelize_me!
+
RUBY18 = ! defined? Encoding
def setup
@@ -1213,6 +1299,7 @@ def test_capture_io
def test_capture_subprocess_io
@assertion_count = 0
+ skip "Dunno why but the parallel run of this fails"
orig_verbose = $VERBOSE
$VERBOSE = false
@@ -1251,15 +1338,6 @@ def test_class_asserts_match_refutes
assert_empty asserts.map { |n| n.sub(/^assert/, 'refute') } - refutes
end
- def test_class_test_suites
- @assertion_count = 0
-
- tc = Class.new(MiniTest::Unit::TestCase)
-
- assert_equal 1, MiniTest::Unit::TestCase.test_suites.size
- assert_equal [tc], MiniTest::Unit::TestCase.test_suites
- end
-
def test_expectation
@assertion_count = 2
@@ -1494,6 +1572,7 @@ def test_test_methods_random
@assertion_count = 0
sample_test_case = Class.new MiniTest::Unit::TestCase do
+ def self.test_order; :random; end
def test_test1; assert "does not matter" end
def test_test2; assert "does not matter" end
def test_test3; assert "does not matter" end
@@ -1568,6 +1647,8 @@ def without_diff
end
class TestMiniTestGuard < MiniTest::Unit::TestCase
+ parallelize_me!
+
def test_mri_eh
assert self.class.mri? "ruby blah"
assert self.mri? "ruby blah"

0 comments on commit bdbf38d

Please sign in to comment.
Something went wrong with that request. Please try again.