diff options
Diffstat (limited to 'activesupport/lib/active_support/testing')
10 files changed, 202 insertions, 184 deletions
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 175f7ffe5a..8b649c193f 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -9,7 +9,7 @@ module ActiveSupport # # assert_not nil # => true # assert_not false # => true - # assert_not 'foo' # => 'foo' is not nil or false + # assert_not 'foo' # => Expected "foo" to be nil or false # # An error message can be specified. # @@ -66,7 +66,7 @@ module ActiveSupport exps = expressions.map { |e| e.respond_to?(:call) ? e : lambda { eval(e, block.binding) } } - before = exps.map { |e| e.call } + before = exps.map(&:call) yield @@ -92,36 +92,6 @@ module ActiveSupport def assert_no_difference(expression, message = nil, &block) assert_difference expression, 0, message, &block end - - # Test if an expression is blank. Passes if <tt>object.blank?</tt> - # is +true+. - # - # assert_blank [] # => true - # assert_blank [[]] # => [[]] is not blank - # - # An error message can be specified. - # - # assert_blank [], 'this should be blank' - def assert_blank(object, message=nil) - ActiveSupport::Deprecation.warn('"assert_blank" is deprecated. Please use "assert object.blank?" instead') - message ||= "#{object.inspect} is not blank" - assert object.blank?, message - end - - # Test if an expression is not blank. Passes if <tt>object.present?</tt> - # is +true+. - # - # assert_present({ data: 'x' }) # => true - # assert_present({}) # => {} is blank - # - # An error message can be specified. - # - # assert_present({ data: 'x' }, 'this should not be blank') - def assert_present(object, message=nil) - ActiveSupport::Deprecation.warn('"assert_present" is deprecated. Please use "assert object.present?" instead') - message ||= "#{object.inspect} is blank" - assert object.present?, message - end end end end diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb index c446adc16d..5aa5f46310 100644 --- a/activesupport/lib/active_support/testing/autorun.rb +++ b/activesupport/lib/active_support/testing/autorun.rb @@ -1,5 +1,5 @@ gem 'minitest' -require 'minitest/unit' +require 'minitest' -MiniTest::Unit.autorun +Minitest.autorun diff --git a/activesupport/lib/active_support/testing/constant_lookup.rb b/activesupport/lib/active_support/testing/constant_lookup.rb index 52bfeb7179..07d477c0db 100644 --- a/activesupport/lib/active_support/testing/constant_lookup.rb +++ b/activesupport/lib/active_support/testing/constant_lookup.rb @@ -36,10 +36,8 @@ module ActiveSupport while names.size > 0 do names.last.sub!(/Test$/, "") begin - constant = names.join("::").constantize + constant = names.join("::").safe_constantize break(constant) if yield(constant) - rescue NameError - # Constant wasn't found, move on ensure names.pop end diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb index 508e37254a..0bf3643a56 100644 --- a/activesupport/lib/active_support/testing/declarative.rb +++ b/activesupport/lib/active_support/testing/declarative.rb @@ -1,30 +1,16 @@ module ActiveSupport module Testing module Declarative - - def self.extended(klass) #:nodoc: - klass.class_eval do - - unless method_defined?(:describe) - def self.describe(text) - class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def self.name - "#{text}" - end - RUBY_EVAL - end - end - - end - end - unless defined?(Spec) - # test "verify something" do - # ... - # end + # Helper to define a test method using a String. Under the hood, it replaces + # spaces with underscores and defines the test method. + # + # test "verify something" do + # ... + # end def test(name, &block) test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym - defined = instance_method(test_name) rescue false + defined = method_defined? test_name raise "#{test_name} is already defined in #{self}" if defined if block_given? define_method(test_name, &block) diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb index a8342904dc..6c94c611b6 100644 --- a/activesupport/lib/active_support/testing/deprecation.rb +++ b/activesupport/lib/active_support/testing/deprecation.rb @@ -19,18 +19,17 @@ module ActiveSupport result end - private - def collect_deprecations - old_behavior = ActiveSupport::Deprecation.behavior - deprecations = [] - ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack| - deprecations << message - end - result = yield - [result, deprecations] - ensure - ActiveSupport::Deprecation.behavior = old_behavior + def collect_deprecations + old_behavior = ActiveSupport::Deprecation.behavior + deprecations = [] + ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack| + deprecations << message end + result = yield + [result, deprecations] + ensure + ActiveSupport::Deprecation.behavior = old_behavior + end end end end diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index e4f8959e7a..68bda35980 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -1,87 +1,14 @@ require 'rbconfig' + module ActiveSupport module Testing - class RemoteError < StandardError - - attr_reader :message, :backtrace - - def initialize(exception) - @message = "caught #{exception.class.name}: #{exception.message}" - @backtrace = exception.backtrace - end - end - - class ProxyTestResult - def initialize(calls = []) - @calls = calls - end - - def add_error(e) - e = Test::Unit::Error.new(e.test_name, RemoteError.new(e.exception)) - @calls << [:add_error, e] - end - - def __replay__(result) - @calls.each do |name, args| - result.send(name, *args) - end - end - - def marshal_dump - @calls - end - - def marshal_load(calls) - initialize(calls) - end - - def method_missing(name, *args) - @calls << [name, args] - end - end - module Isolation require 'thread' - # Recent versions of MiniTest (such as the one shipped with Ruby 2.0) already define - # a ParallelEach class. - unless defined? ParallelEach - class ParallelEach - include Enumerable - - # default to 2 cores - CORES = (ENV['TEST_CORES'] || 2).to_i - - def initialize list - @list = list - @queue = SizedQueue.new CORES - end - - def grep pattern - self.class.new super - end - - def each - threads = CORES.times.map { - Thread.new { - while job = @queue.pop - yield job - end - } - } - @list.each { |i| @queue << i } - CORES.times { @queue << nil } - threads.each(&:join) - end - end - end - def self.included(klass) #:nodoc: - klass.extend(Module.new { - def test_methods - ParallelEach.new super - end - }) + klass.class_eval do + parallelize_me! + end end def self.forking_env? @@ -99,27 +26,24 @@ module ActiveSupport end end - def run(runner) - _run_class_setup - - serialized = run_in_isolation do |isolated_runner| - super(isolated_runner) + def run + serialized = run_in_isolation do + super end - retval, proxy = Marshal.load(serialized) - proxy.__replay__(runner) - retval + Marshal.load(serialized) end module Forking def run_in_isolation(&blk) read, write = IO.pipe + read.binmode + write.binmode pid = fork do read.close - proxy = ProxyTestResult.new - retval = yield proxy - write.puts [Marshal.dump([retval, proxy])].pack("m") + yield + write.puts [Marshal.dump(self.dup)].pack("m") exit! end @@ -139,22 +63,31 @@ module ActiveSupport require "tempfile" if ENV["ISOLATION_TEST"] - proxy = ProxyTestResult.new - retval = yield proxy + yield File.open(ENV["ISOLATION_OUTPUT"], "w") do |file| - file.puts [Marshal.dump([retval, proxy])].pack("m") + file.puts [Marshal.dump(self.dup)].pack("m") end exit! else Tempfile.open("isolation") do |tmpfile| - ENV["ISOLATION_TEST"] = @method_name - ENV["ISOLATION_OUTPUT"] = tmpfile.path + env = { + ISOLATION_TEST: self.class.name, + ISOLATION_OUTPUT: tmpfile.path + } load_paths = $-I.map {|p| "-I\"#{File.expand_path(p)}\"" }.join(" ") - `#{Gem.ruby} #{load_paths} #{$0} #{ORIG_ARGV.join(" ")} -t\"#{self.class}\"` - - ENV.delete("ISOLATION_TEST") - ENV.delete("ISOLATION_OUTPUT") + orig_args = ORIG_ARGV.join(" ") + test_opts = "-n#{self.class.name}##{self.name}" + command = "#{Gem.ruby} #{load_paths} #{$0} #{orig_args} #{test_opts}" + + # IO.popen lets us pass env in a cross-platform way + child = IO.popen([env, command]) + + begin + Process.wait(child.pid) + rescue Errno::ECHILD # The child process may exit before we wait + nil + end return tmpfile.read.unpack("m")[0] end diff --git a/activesupport/lib/active_support/testing/pending.rb b/activesupport/lib/active_support/testing/pending.rb deleted file mode 100644 index b04bbbbaea..0000000000 --- a/activesupport/lib/active_support/testing/pending.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'active_support/deprecation' - -module ActiveSupport - module Testing - module Pending # :nodoc: - unless defined?(Spec) - def pending(description = "", &block) - ActiveSupport::Deprecation.warn("#pending is deprecated and will be removed in Rails 4.1, please use #skip instead.") - skip(description.blank? ? nil : description) - end - end - end - end -end diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index a65148cf1f..33f2b8dc9b 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -3,6 +3,19 @@ require 'active_support/callbacks' module ActiveSupport module Testing + # Adds support for +setup+ and +teardown+ callbacks. + # These callbacks serve as a replacement to overwriting the + # <tt>#setup</tt> and <tt>#teardown</tt> methods of your TestCase. + # + # class ExampleTest < ActiveSupport::TestCase + # setup do + # # ... + # end + # + # teardown do + # # ... + # end + # end module SetupAndTeardown extend ActiveSupport::Concern @@ -12,21 +25,23 @@ module ActiveSupport end module ClassMethods + # Add a callback, which runs before <tt>TestCase#setup</tt>. def setup(*args, &block) set_callback(:setup, :before, *args, &block) end + # Add a callback, which runs after <tt>TestCase#teardown</tt>. def teardown(*args, &block) set_callback(:teardown, :after, *args, &block) end end - def before_setup + def before_setup # :nodoc: super run_callbacks :setup end - def after_teardown + def after_teardown # :nodoc: run_callbacks :teardown super end diff --git a/activesupport/lib/active_support/testing/tagged_logging.rb b/activesupport/lib/active_support/testing/tagged_logging.rb index 9d43eb179f..843ce4a867 100644 --- a/activesupport/lib/active_support/testing/tagged_logging.rb +++ b/activesupport/lib/active_support/testing/tagged_logging.rb @@ -6,8 +6,8 @@ module ActiveSupport attr_writer :tagged_logger def before_setup - if tagged_logger - heading = "#{self.class}: #{__name__}" + if tagged_logger && tagged_logger.info? + heading = "#{self.class}: #{name}" divider = '-' * heading.size tagged_logger.info divider tagged_logger.info heading diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb new file mode 100644 index 0000000000..8c63815660 --- /dev/null +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -0,0 +1,131 @@ +module ActiveSupport + module Testing + class SimpleStubs # :nodoc: + Stub = Struct.new(:object, :method_name, :original_method) + + def initialize + @stubs = {} + end + + def stub_object(object, method_name, return_value) + key = [object.object_id, method_name] + + if stub = @stubs[key] + unstub_object(stub) + end + + new_name = "__simple_stub__#{method_name}" + + @stubs[key] = Stub.new(object, method_name, new_name) + + object.singleton_class.send :alias_method, new_name, method_name + object.define_singleton_method(method_name) { return_value } + end + + def unstub_all! + @stubs.each_value do |stub| + unstub_object(stub) + end + @stubs = {} + end + + private + + def unstub_object(stub) + singleton_class = stub.object.singleton_class + singleton_class.send :undef_method, stub.method_name + singleton_class.send :alias_method, stub.method_name, stub.original_method + singleton_class.send :undef_method, stub.original_method + end + end + + # Containing helpers that helps you test passage of time. + module TimeHelpers + # Changes current time to the time in the future or in the past by a given time difference by + # stubbing +Time.now+ and +Date.today+. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel 1.day + # Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00 + # Date.current # => Sun, 10 Nov 2013 + # + # This method also accepts a block, which will return the current time back to its original + # state at the end of the block: + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel 1.day do + # User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00 + # end + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + def travel(duration, &block) + travel_to Time.now + duration, &block + end + + # Changes current time to the given time by stubbing +Time.now+ and + # +Date.today+ to return the time or date passed into this method. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel_to Time.new(2004, 11, 24, 01, 04, 44) + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # Date.current # => Wed, 24 Nov 2004 + # + # Dates are taken as their timestamp at the beginning of the day in the + # application time zone. <tt>Time.current</tt> returns said timestamp, + # and <tt>Time.now</tt> its equivalent in the system time zone. Similarly, + # <tt>Date.current</tt> returns a date equal to the argument, and + # <tt>Date.today</tt> the date according to <tt>Time.now</tt>, which may + # be different. (Note that you rarely want to deal with <tt>Time.now</tt>, + # or <tt>Date.today</tt>, in order to honor the application time zone + # please always use <tt>Time.current</tt> and <tt>Date.current</tt>.) + # + # Note that the usec for the time passed will be set to 0 to prevent rounding + # errors with external services, like MySQL (which will round instead of floor, + # leading to off-by-one-second errors). + # + # This method also accepts a block, which will return the current time back to its original + # state at the end of the block: + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel_to Time.new(2004, 11, 24, 01, 04, 44) do + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # end + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + def travel_to(date_or_time) + if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime) + now = date_or_time.midnight.to_time + else + now = date_or_time.to_time.change(usec: 0) + end + + simple_stubs.stub_object(Time, :now, now) + simple_stubs.stub_object(Date, :today, now.to_date) + + if block_given? + begin + yield + ensure + travel_back + end + end + end + + # Returns the current time back to its original state, by removing the stubs added by + # `travel` and `travel_to`. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel_to Time.new(2004, 11, 24, 01, 04, 44) + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # travel_back + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + def travel_back + simple_stubs.unstub_all! + end + + private + + def simple_stubs + @simple_stubs ||= SimpleStubs.new + end + end + end +end |