From 575b95ea0bf3d3fff6f47dddad23c754fb294604 Mon Sep 17 00:00:00 2001 From: Yehuda Katz + Carl Lerche Date: Tue, 30 Jun 2009 12:00:50 -0700 Subject: 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. --- activesupport/lib/active_support/test_case.rb | 1 + .../lib/active_support/testing/isolation.rb | 39 ++++++ activesupport/test/isolation_test.rb | 141 +++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 activesupport/lib/active_support/testing/isolation.rb create mode 100644 activesupport/test/isolation_test.rb diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index bab2a401eb..e99a4854ce 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -13,6 +13,7 @@ require 'active_support/testing/assertions' 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 diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb new file mode 100644 index 0000000000..8b2957fbe1 --- /dev/null +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -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 \ No newline at end of file diff --git a/activesupport/test/isolation_test.rb b/activesupport/test/isolation_test.rb new file mode 100644 index 0000000000..4adf49ff62 --- /dev/null +++ b/activesupport/test/isolation_test.rb @@ -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 \ No newline at end of file -- cgit v1.2.3