Created AS::Testing::Isolation which runs each test case in a separate process.
This allows for testing rails bootup (files are required, correct constants are set, etc...). Currently, this is implemented via forking only, but we will add support for jruby and windows shortly.
This commit is contained in:
parent
9101941c12
commit
575b95ea0b
@ -13,6 +13,7 @@
|
||||
require 'active_support/testing/deprecation'
|
||||
require 'active_support/testing/declarative'
|
||||
require 'active_support/testing/pending'
|
||||
require 'active_support/testing/isolation'
|
||||
|
||||
module ActiveSupport
|
||||
class TestCase < ::Test::Unit::TestCase
|
||||
|
39
activesupport/lib/active_support/testing/isolation.rb
Normal file
39
activesupport/lib/active_support/testing/isolation.rb
Normal file
@ -0,0 +1,39 @@
|
||||
module ActiveSupport::Testing
|
||||
class ProxyTestResult
|
||||
def initialize
|
||||
@calls = []
|
||||
end
|
||||
|
||||
def __replay__(result)
|
||||
@calls.each do |name, args|
|
||||
result.send(name, *args)
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing(name, *args)
|
||||
@calls << [name, args]
|
||||
end
|
||||
end
|
||||
|
||||
module Isolation
|
||||
def run(result)
|
||||
yield(Test::Unit::TestCase::STARTED, name)
|
||||
|
||||
read, write = IO.pipe
|
||||
|
||||
pid = fork do
|
||||
# child
|
||||
read.close
|
||||
proxy = ProxyTestResult.new
|
||||
super(proxy) { }
|
||||
write.puts [Marshal.dump(proxy)].pack("m")
|
||||
exit!
|
||||
end
|
||||
|
||||
write.close
|
||||
Marshal.load(read.read.unpack("m")[0]).__replay__(result)
|
||||
Process.wait2(pid)
|
||||
yield(Test::Unit::TestCase::FINISHED, name)
|
||||
end
|
||||
end
|
||||
end
|
141
activesupport/test/isolation_test.rb
Normal file
141
activesupport/test/isolation_test.rb
Normal file
@ -0,0 +1,141 @@
|
||||
require 'abstract_unit'
|
||||
|
||||
# Does awesome
|
||||
if ENV['CHILD']
|
||||
class ChildIsolationTest < ActiveSupport::TestCase
|
||||
include ActiveSupport::Testing::Isolation
|
||||
|
||||
def setup
|
||||
@instance = "HELLO"
|
||||
end
|
||||
|
||||
def teardown
|
||||
raise if @boom
|
||||
end
|
||||
|
||||
test "runs the test" do
|
||||
assert true
|
||||
end
|
||||
|
||||
test "captures errors" do
|
||||
raise
|
||||
end
|
||||
|
||||
test "captures failures" do
|
||||
assert false
|
||||
end
|
||||
|
||||
test "first runs in isolation" do
|
||||
assert_nil $x
|
||||
$x = 1
|
||||
end
|
||||
|
||||
test "second runs in isolation" do
|
||||
assert_nil $x
|
||||
$x = 2
|
||||
end
|
||||
|
||||
test "runs with slow tests" do
|
||||
sleep 0.3
|
||||
assert true
|
||||
sleep 0.2
|
||||
end
|
||||
|
||||
test "runs setup" do
|
||||
assert "HELLO", @instance
|
||||
end
|
||||
|
||||
test "runs teardown" do
|
||||
@boom = true
|
||||
end
|
||||
|
||||
test "resets requires one" do
|
||||
assert !defined?(Racc)
|
||||
require 'racc/parser'
|
||||
end
|
||||
|
||||
test "resets requires two" do
|
||||
assert !defined?(Racc)
|
||||
require 'racc/parser'
|
||||
end
|
||||
end
|
||||
else
|
||||
class ParentIsolationTest < ActiveSupport::TestCase
|
||||
|
||||
ENV["CHILD"] = "1"
|
||||
OUTPUT = `#{Gem.ruby} -I#{File.dirname(__FILE__)} #{File.expand_path(__FILE__)} -v`
|
||||
ENV.delete("CHILD")
|
||||
|
||||
def setup
|
||||
# Extract the results
|
||||
@results = {}
|
||||
OUTPUT[/Started\n\s*(.*)\s*\nFinished/mi, 1].split(/\s*\n\s*/).each do |result|
|
||||
result =~ %r'^(\w+)\(\w+\):\s*(\.|E|F)$'
|
||||
@results[$1] = { 'E' => :error, '.' => :success, 'F' => :failure }[$2]
|
||||
end
|
||||
|
||||
# Extract the backtraces
|
||||
@backtraces = {}
|
||||
OUTPUT.scan(/^\s*\d+\).*?\n\n/m).each do |backtrace|
|
||||
# \n 1) Error:\ntest_captures_errors(ChildIsolationTest):
|
||||
backtrace =~ %r'\s*\d+\)\s*(Error|Failure):\n(\w+)'i
|
||||
@backtraces[$2] = { :type => $1, :output => backtrace }
|
||||
end
|
||||
end
|
||||
|
||||
def assert_failing(name)
|
||||
assert_equal :failure, @results[name.to_s], "Test #{name} did not fail"
|
||||
end
|
||||
|
||||
def assert_passing(name)
|
||||
assert_equal :success, @results[name.to_s], "Test #{name} did not pass"
|
||||
end
|
||||
|
||||
def assert_erroring(name)
|
||||
assert_equal :error, @results[name.to_s], "Test #{name} did not error"
|
||||
end
|
||||
|
||||
test "has all tests" do
|
||||
assert_equal 10, @results.length
|
||||
end
|
||||
|
||||
test "passing tests are still reported" do
|
||||
assert_passing :test_runs_the_test
|
||||
assert_passing :test_runs_with_slow_tests
|
||||
end
|
||||
|
||||
test "resets global variables" do
|
||||
assert_passing :test_first_runs_in_isolation
|
||||
assert_passing :test_second_runs_in_isolation
|
||||
end
|
||||
|
||||
test "resets requires" do
|
||||
assert_passing :test_resets_requires_one
|
||||
assert_passing :test_resets_requires_two
|
||||
end
|
||||
|
||||
test "erroring tests are still reported" do
|
||||
assert_erroring :test_captures_errors
|
||||
end
|
||||
|
||||
test "runs setup and teardown methods" do
|
||||
assert_passing :test_runs_setup
|
||||
assert_erroring :test_runs_teardown
|
||||
end
|
||||
|
||||
test "correct tests fail" do
|
||||
assert_failing :test_captures_failures
|
||||
end
|
||||
|
||||
test "backtrace is printed for errors" do
|
||||
assert_equal 'Error', @backtraces["test_captures_errors"][:type]
|
||||
assert_match %{isolation_test.rb:21:in `test_captures_errors'}, @backtraces["test_captures_errors"][:output]
|
||||
end
|
||||
|
||||
test "backtrace is printed for failures" do
|
||||
assert_equal 'Failure', @backtraces["test_captures_failures"][:type]
|
||||
assert_match %{isolation_test.rb:25:in `test_captures_failures'}, @backtraces["test_captures_failures"][:output]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user