aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md5
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb8
-rw-r--r--activesupport/lib/active_support/test_case.rb86
-rw-r--r--activesupport/lib/active_support/testing/parallelization.rb102
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb3
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb2
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb9
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb13
10 files changed, 222 insertions, 10 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index fc47c76e99..2976040a59 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,2 +1,7 @@
+* Adds parallel testing to Rails
+
+ Parallelize your test suite with forked processes or threads.
+
+ *Eileen M. Uchitelle*, *Aaron Patterson*
Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 5d4e4e2d3b..ee3228e677 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
s.summary = "A toolkit of support libraries and Ruby core extensions extracted from the Rails framework."
s.description = "A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing."
- s.required_ruby_version = ">= 2.2.2"
+ s.required_ruby_version = ">= 2.3.0"
s.license = "MIT"
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index 7928efb871..fa33ff945f 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -98,7 +98,7 @@ class Class
singleton_class.silence_redefinition_of_method("#{name}?")
define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
- ivar = "@#{name}"
+ ivar = "@#{name}".to_sym
singleton_class.silence_redefinition_of_method("#{name}=")
define_singleton_method("#{name}=") do |val|
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 2e2ed8a25d..eda7ec3940 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -311,6 +311,14 @@ module ActiveSupport
dup.tap { |hash| hash.transform_keys!(*args, &block) }
end
+ def transform_keys!
+ return enum_for(:transform_keys!) { size } unless block_given?
+ keys.each do |key|
+ self[yield(key)] = delete(key)
+ end
+ self
+ end
+
def slice(*keys)
keys.map! { |key| convert_key(key) }
self.class.new(super)
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index d1f7e6ea09..3588a5ac02 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -11,6 +11,7 @@ require "active_support/testing/isolation"
require "active_support/testing/constant_lookup"
require "active_support/testing/time_helpers"
require "active_support/testing/file_fixtures"
+require "active_support/testing/parallelization"
module ActiveSupport
class TestCase < ::Minitest::Test
@@ -39,6 +40,91 @@ module ActiveSupport
def test_order
ActiveSupport.test_order ||= :random
end
+
+ # Parallelizes the test suite.
+ #
+ # Takes a `workers` argument that controls how many times the process
+ # is forked. For each process a new database will be created suffixed
+ # with the worker number.
+ #
+ # test-database-0
+ # test-database-1
+ #
+ # If `ENV["PARALLEL_WORKERS"]` is set the workers argument will be ignored
+ # and the environment variable will be used instead. This is useful for CI
+ # environments, or other environments where you may need more workers than
+ # you do for local testing.
+ #
+ # If the number of workers is set to `1` or fewer, the tests will not be
+ # parallelized.
+ #
+ # The default parallelization method is to fork processes. If you'd like to
+ # use threads instead you can pass `with: :threads` to the `parallelize`
+ # method. Note the threaded parallelization does not create multiple
+ # database and will not work with system tests at this time.
+ #
+ # parallelize(workers: 2, with: :threads)
+ #
+ # The threaded parallelization uses Minitest's parallel exector directly.
+ # The processes paralleliztion uses a Ruby Drb server.
+ def parallelize(workers: 2, with: :processes)
+ workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"]
+
+ return if workers <= 1
+
+ executor = case with
+ when :processes
+ Testing::Parallelization.new(workers)
+ when :threads
+ Minitest::Parallel::Executor.new(workers)
+ else
+ raise ArgumentError, "#{with} is not a supported parallelization exectutor."
+ end
+
+ self.lock_threads = false if defined?(self.lock_threads) && with == :threads
+
+ Minitest.parallel_executor = executor
+
+ parallelize_me!
+ end
+
+ # Set up hook for parallel testing. This can be used if you have multiple
+ # databases or any behavior that needs to be run after the process is forked
+ # but before the tests run.
+ #
+ # Note: this feature is not available with the threaded parallelization.
+ #
+ # In your +test_helper.rb+ add the following:
+ #
+ # class ActiveSupport::TestCase
+ # parallelize_setup do
+ # # create databases
+ # end
+ # end
+ def parallelize_setup(&block)
+ ActiveSupport::Testing::Parallelization.after_fork_hook do |worker|
+ yield worker
+ end
+ end
+
+ # Clean up hook for parallel testing. This can be used to drop databases
+ # if your app uses multiple write/read databases or other clean up before
+ # the tests finish. This runs before the forked process is closed.
+ #
+ # Note: this feature is not available with the threaded parallelization.
+ #
+ # In your +test_helper.rb+ add the following:
+ #
+ # class ActiveSupport::TestCase
+ # parallelize_teardown do
+ # # drop databases
+ # end
+ # end
+ def parallelize_teardown(&block)
+ ActiveSupport::Testing::Parallelization.run_cleanup_hook do |worker|
+ yield worker
+ end
+ end
end
alias_method :method_name, :name
diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb
new file mode 100644
index 0000000000..59c8486f41
--- /dev/null
+++ b/activesupport/lib/active_support/testing/parallelization.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require "drb"
+require "drb/unix"
+
+module ActiveSupport
+ module Testing
+ class Parallelization # :nodoc:
+ class Server
+ include DRb::DRbUndumped
+
+ def initialize
+ @queue = Queue.new
+ end
+
+ def record(reporter, result)
+ reporter.synchronize do
+ reporter.record(result)
+ end
+ end
+
+ def <<(o)
+ @queue << o
+ end
+
+ def pop; @queue.pop; end
+ end
+
+ @after_fork_hooks = []
+
+ def self.after_fork_hook(&blk)
+ @after_fork_hooks << blk
+ end
+
+ def self.after_fork_hooks
+ @after_fork_hooks
+ end
+
+ @run_cleanup_hooks = []
+
+ def self.run_cleanup_hook(&blk)
+ @run_cleanup_hooks << blk
+ end
+
+ def self.run_cleanup_hooks
+ @run_cleanup_hooks
+ end
+
+ def initialize(queue_size)
+ @queue_size = queue_size
+ @queue = Server.new
+ @pool = []
+
+ @url = DRb.start_service("drbunix:", @queue).uri
+ end
+
+ def after_fork(worker)
+ self.class.after_fork_hooks.each do |cb|
+ cb.call(worker)
+ end
+ end
+
+ def run_cleanup(worker)
+ self.class.run_cleanup_hooks.each do |cb|
+ cb.call(worker)
+ end
+ end
+
+ def start
+ @pool = @queue_size.times.map do |worker|
+ fork do
+ DRb.stop_service
+
+ after_fork(worker)
+
+ queue = DRbObject.new_with_uri(@url)
+
+ while job = queue.pop
+ klass = job[0]
+ method = job[1]
+ reporter = job[2]
+ result = Minitest.run_one_method(klass, method)
+
+ queue.record(reporter, result)
+ end
+
+ run_cleanup(worker)
+ end
+ end
+ end
+
+ def <<(work)
+ @queue << work
+ end
+
+ def shutdown
+ @queue_size.times { @queue << nil }
+ @pool.each { |pid| Process.waitpid pid }
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index 998a51a34c..801ea2909b 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "active_support/core_ext/module/redefine_method"
-require "active_support/core_ext/string/strip" # for strip_heredoc
require "active_support/core_ext/time/calculations"
require "concurrent/map"
@@ -112,7 +111,7 @@ module ActiveSupport
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
def travel_to(date_or_time)
if block_given? && simple_stubs.stubbing(Time, :now)
- travel_to_nested_block_call = <<-MSG.strip_heredoc
+ travel_to_nested_block_call = <<~MSG
Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing.
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 472190277e..b8b6387ee5 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -9,9 +9,9 @@ require "constantize_test_cases"
require "active_support/inflector"
require "active_support/core_ext/string"
require "active_support/time"
-require "active_support/core_ext/string/strip"
require "active_support/core_ext/string/output_safety"
require "active_support/core_ext/string/indent"
+require "active_support/core_ext/string/strip"
require "time_zone_test_helpers"
require "yaml"
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index be3b8a49a9..2ea5f0921c 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -3,7 +3,6 @@
require "abstract_unit"
require "active_support/time"
require "time_zone_test_helpers"
-require "active_support/core_ext/string/strip"
require "yaml"
class TimeWithZoneTest < ActiveSupport::TestCase
@@ -163,7 +162,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_to_yaml
- yaml = <<-EOF.strip_heredoc
+ yaml = <<~EOF
--- !ruby/object:ActiveSupport::TimeWithZone
utc: 2000-01-01 00:00:00.000000000 Z
zone: !ruby/object:ActiveSupport::TimeZone
@@ -175,7 +174,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_ruby_to_yaml
- yaml = <<-EOF.strip_heredoc
+ yaml = <<~EOF
---
twz: !ruby/object:ActiveSupport::TimeWithZone
utc: 2000-01-01 00:00:00.000000000 Z
@@ -188,7 +187,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_yaml_load
- yaml = <<-EOF.strip_heredoc
+ yaml = <<~EOF
--- !ruby/object:ActiveSupport::TimeWithZone
utc: 2000-01-01 00:00:00.000000000 Z
zone: !ruby/object:ActiveSupport::TimeZone
@@ -200,7 +199,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_ruby_yaml_load
- yaml = <<-EOF.strip_heredoc
+ yaml = <<~EOF
---
twz: !ruby/object:ActiveSupport::TimeWithZone
utc: 2000-01-01 00:00:00.000000000 Z
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
index 41d653fa59..09f2598067 100644
--- a/activesupport/test/hash_with_indifferent_access_test.rb
+++ b/activesupport/test/hash_with_indifferent_access_test.rb
@@ -404,6 +404,12 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase
assert_equal({ "aa" => 1, "bb" => 2 }, hash)
assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).transform_keys { |k| k.to_sym }
+
+ assert_equal(1, hash[:a])
+ assert_equal(1, hash["a"])
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
end
def test_indifferent_transform_keys_bang
@@ -412,6 +418,13 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase
assert_equal({ "aa" => 1, "bb" => 2 }, indifferent_strings)
assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+
+ indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
+ indifferent_strings.transform_keys! { |k| k.to_sym }
+
+ assert_equal(1, indifferent_strings[:a])
+ assert_equal(1, indifferent_strings["a"])
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
end
def test_indifferent_transform_values