diff options
-rw-r--r-- | actioncable/Rakefile | 3 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/testing/integration.rb | 2 | ||||
-rw-r--r-- | actionpack/test/controller/integration_test.rb | 8 | ||||
-rw-r--r-- | actionpack/test/controller/parameters/serialization_test.rb | 10 | ||||
-rw-r--r-- | activejob/lib/active_job/test_helper.rb | 24 | ||||
-rw-r--r-- | activerecord/CHANGELOG.md | 11 | ||||
-rw-r--r-- | activerecord/lib/active_record/locking/optimistic.rb | 20 | ||||
-rw-r--r-- | activerecord/test/cases/dirty_test.rb | 2 | ||||
-rw-r--r-- | activerecord/test/cases/locking_test.rb | 142 | ||||
-rw-r--r-- | activerecord/test/schema/schema.rb | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/callbacks.rb | 12 | ||||
-rw-r--r-- | activesupport/lib/active_support/hash_with_indifferent_access.rb | 1 | ||||
-rw-r--r-- | activesupport/lib/active_support/test_case.rb | 2 | ||||
-rw-r--r-- | activesupport/test/executor_test.rb | 2 | ||||
-rw-r--r-- | railties/lib/rails/generators/rails/app/app_generator.rb | 1 | ||||
-rw-r--r-- | railties/lib/rails/test_unit/railtie.rb | 4 | ||||
-rw-r--r-- | railties/test/application/runner_test.rb | 9 | ||||
-rw-r--r-- | railties/test/generators/app_generator_test.rb | 1 |
18 files changed, 212 insertions, 44 deletions
diff --git a/actioncable/Rakefile b/actioncable/Rakefile index 648de57004..87d443919c 100644 --- a/actioncable/Rakefile +++ b/actioncable/Rakefile @@ -1,7 +1,6 @@ require "rake/testtask" require "pathname" require "action_cable" -require "blade" dir = File.dirname(__FILE__) @@ -25,6 +24,7 @@ namespace :test do end task :integration do + require "blade" if ENV["CI"] Blade.start(interface: :ci) else @@ -36,6 +36,7 @@ end namespace :assets do desc "Compile Action Cable assets" task :compile do + require "blade" Blade.build end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index e24b943026..e2ea96513c 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -599,8 +599,6 @@ module ActionDispatch class IntegrationTest < ActiveSupport::TestCase include TestProcess - undef :assigns - module UrlOptions extend ActiveSupport::Concern def url_options diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 652c0f0dd2..1f02e0e908 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -153,14 +153,6 @@ class IntegrationTestTest < ActiveSupport::TestCase mixin.__send__(:remove_method, :method_missing) end end - - def test_assigns_is_undefined_and_not_point_to_the_gem - e = assert_raises(NoMethodError) do - @test.assigns(:foo) - end - - assert_match(/undefined method/, e.message) - end end # Tests that integration tests don't call Controller test methods for processing. diff --git a/actionpack/test/controller/parameters/serialization_test.rb b/actionpack/test/controller/parameters/serialization_test.rb index 4fb1564c68..6fba2fde91 100644 --- a/actionpack/test/controller/parameters/serialization_test.rb +++ b/actionpack/test/controller/parameters/serialization_test.rb @@ -14,12 +14,10 @@ class ParametersSerializationTest < ActiveSupport::TestCase test "yaml serialization" do params = ActionController::Parameters.new(key: :value) - assert_equal <<-end_of_yaml.strip_heredoc, YAML.dump(params) - --- !ruby/object:ActionController::Parameters - parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess - key: :value - permitted: false - end_of_yaml + yaml_dump = YAML.dump(params) + assert_match("--- !ruby/object:ActionController::Parameters", yaml_dump) + assert_match(/parameters: !ruby\/hash:ActiveSupport::HashWithIndifferentAccess\n\s+key: :value/, yaml_dump) + assert_match("permitted: false", yaml_dump) end test "yaml deserialization" do diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index bbd2a0c06c..1a8b3375ae 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -269,6 +269,25 @@ module ActiveJob instantiate_job(matching_job) end + # Performs all enqueued jobs in the duration of the block. + # + # def test_perform_enqueued_jobs + # perform_enqueued_jobs do + # MyJob.perform_later(1, 2, 3) + # end + # assert_performed_jobs 1 + # end + # + # This method also supports filtering. If the +:only+ option is specified, + # then only the listed job(s) will be performed. + # + # def test_perform_enqueued_jobs_with_only + # perform_enqueued_jobs(only: MyJob) do + # MyJob.perform_later(1, 2, 3) # will be performed + # HelloJob.perform_later(1, 2, 3) # will not be perfomed + # end + # assert_performed_jobs 1 + # end def perform_enqueued_jobs(only: nil) old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs @@ -286,6 +305,11 @@ module ActiveJob end end + # Accesses the queue_adapter set by ActiveJob::Base. + # + # def test_assert_job_has_custom_queue_adapter_set + # assert_instance_of CustomQueueAdapter, HelloJob.queue_adapter + # end def queue_adapter ActiveJob::Base.queue_adapter end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 15b49e0a0b..f847c71420 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,14 @@ +* Optimistic locking: Added ability update locking_column value. + Ignore optimistic locking if update with new locking_column value. + + *bogdanvlviv* + +* Fixed: Optimistic locking does not work well with null in the database. + + Fixes #26024 + + *bogdanvlviv* + * Fixed support for case insensitive comparisons of `text` columns in PostgreSQL. diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 8e8a97990a..d39b7181f4 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -60,6 +60,7 @@ module ActiveRecord end private + def increment_lock lock_col = self.class.locking_column previous_lock_value = send(lock_col).to_i @@ -77,21 +78,24 @@ module ActiveRecord def _update_record(attribute_names = self.attribute_names) #:nodoc: return super unless locking_enabled? - return 0 if attribute_names.empty? lock_col = self.class.locking_column - previous_lock_value = send(lock_col).to_i - increment_lock - attribute_names += [lock_col] - attribute_names.uniq! + return super if attribute_names.include?(lock_col) + return 0 if attribute_names.empty? begin + previous_lock_value = read_attribute_before_type_cast(lock_col) + + increment_lock + + attribute_names.push(lock_col) + relation = self.class.unscoped affected_rows = relation.where( self.class.primary_key => id, - lock_col => previous_lock_value, + lock_col => previous_lock_value ).update_all( attributes_for_update(attribute_names).map do |name| [name, _read_attribute(name)] @@ -104,9 +108,9 @@ module ActiveRecord affected_rows - # If something went wrong, revert the version. + # If something went wrong, revert the locking_column value. rescue Exception - send(lock_col + "=", previous_lock_value) + send(lock_col + "=", previous_lock_value.to_i) raise end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 09bd00291d..3bd8475bb0 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -341,7 +341,7 @@ class DirtyTest < ActiveRecord::TestCase def test_partial_update_with_optimistic_locking person = Person.new(first_name: "foo") - old_lock_version = 1 + old_lock_version = person.lock_version with_partial_writes Person, false do assert_queries(2) { 2.times { person.save! } } diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 13b6f6daaf..0579df0a07 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -161,14 +161,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase assert_equal(error.record.object_id, p2.object_id) end - def test_lock_new_with_nil - p1 = Person.new(first_name: "anika") - p1.save! - p1.lock_version = nil # simulate bad fixture or column with no default - p1.save! - assert_equal 1, p1.lock_version - end - def test_lock_new_when_explicitly_passing_nil p1 = Person.new(first_name: "anika", lock_version: nil) p1.save! @@ -222,22 +214,149 @@ class OptimisticLockingTest < ActiveRecord::TestCase def test_lock_without_default_sets_version_to_zero t1 = LockWithoutDefault.new + + assert_equal 0, t1.lock_version + assert_nil t1.lock_version_before_type_cast + + t1.save! + t1.reload + assert_equal 0, t1.lock_version + assert_equal 0, t1.lock_version_before_type_cast + end + + def test_lock_without_default_should_work_with_null_in_the_database + ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults(title) VALUES('title1')") + t1 = LockWithoutDefault.last + t2 = LockWithoutDefault.last + + assert_equal 0, t1.lock_version + assert_nil t1.lock_version_before_type_cast + assert_equal 0, t2.lock_version + assert_nil t2.lock_version_before_type_cast + + t1.title = "new title1" + t2.title = "new title2" + + assert_nothing_raised { t1.save! } + assert_equal 1, t1.lock_version + assert_equal "new title1", t1.title + + assert_raise(ActiveRecord::StaleObjectError) { t2.save! } + assert_equal 0, t2.lock_version + assert_equal "new title2", t2.title + end + + def test_lock_without_default_should_update_with_lock_col + t1 = LockWithoutDefault.create(title: "title1", lock_version: 6) + + assert_equal 6, t1.lock_version + + t1.update(lock_version: 0) + t1.reload - t1.save - t1 = LockWithoutDefault.find(t1.id) assert_equal 0, t1.lock_version end + def test_lock_without_default_queries_count + t1 = LockWithoutDefault.create(title: "title1") + + assert_equal "title1", t1.title + assert_equal 0, t1.lock_version + + assert_queries(1) { t1.update(title: "title2") } + + t1.reload + assert_equal "title2", t1.title + assert_equal 1, t1.lock_version + + assert_queries(1) { t1.update(title: "title3", lock_version: 6) } + + t1.reload + assert_equal "title3", t1.title + assert_equal 6, t1.lock_version + + t2 = LockWithoutDefault.new(title: "title1") + + assert_queries(1) { t2.save! } + + t2.reload + assert_equal "title1", t2.title + assert_equal 0, t2.lock_version + end + def test_lock_with_custom_column_without_default_sets_version_to_zero t1 = LockWithCustomColumnWithoutDefault.new + assert_equal 0, t1.custom_lock_version assert_nil t1.custom_lock_version_before_type_cast t1.save! t1.reload + + assert_equal 0, t1.custom_lock_version + assert_equal 0, t1.custom_lock_version_before_type_cast + end + + def test_lock_with_custom_column_without_default_should_work_with_null_in_the_database + ActiveRecord::Base.connection.execute("INSERT INTO lock_without_defaults_cust(title) VALUES('title1')") + + t1 = LockWithCustomColumnWithoutDefault.last + t2 = LockWithCustomColumnWithoutDefault.last + assert_equal 0, t1.custom_lock_version - assert [0, "0"].include?(t1.custom_lock_version_before_type_cast) + assert_nil t1.custom_lock_version_before_type_cast + assert_equal 0, t2.custom_lock_version + assert_nil t2.custom_lock_version_before_type_cast + + t1.title = "new title1" + t2.title = "new title2" + + assert_nothing_raised { t1.save! } + assert_equal 1, t1.custom_lock_version + assert_equal "new title1", t1.title + + assert_raise(ActiveRecord::StaleObjectError) { t2.save! } + assert_equal 0, t2.custom_lock_version + assert_equal "new title2", t2.title + end + + def test_lock_with_custom_column_without_default_should_update_with_lock_col + t1 = LockWithCustomColumnWithoutDefault.create(title: "title1", custom_lock_version: 6) + + assert_equal 6, t1.custom_lock_version + + t1.update(custom_lock_version: 0) + t1.reload + + assert_equal 0, t1.custom_lock_version + end + + def test_lock_with_custom_column_without_default_queries_count + t1 = LockWithCustomColumnWithoutDefault.create(title: "title1") + + assert_equal "title1", t1.title + assert_equal 0, t1.custom_lock_version + + assert_queries(1) { t1.update(title: "title2") } + + t1.reload + assert_equal "title2", t1.title + assert_equal 1, t1.custom_lock_version + + assert_queries(1) { t1.update(title: "title3", custom_lock_version: 6) } + + t1.reload + assert_equal "title3", t1.title + assert_equal 6, t1.custom_lock_version + + t2 = LockWithCustomColumnWithoutDefault.new(title: "title1") + + assert_queries(1) { t2.save! } + + t2.reload + assert_equal "title1", t2.title + assert_equal 0, t2.custom_lock_version end def test_readonly_attributes @@ -461,6 +580,7 @@ unless in_memory_db? end protected + def duel(zzz = 5) t0, t1, t2, t3 = nil, nil, nil, nil diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index d2fb090118..983ac076a9 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -436,10 +436,12 @@ ActiveRecord::Schema.define do end create_table :lock_without_defaults, force: true do |t| + t.column :title, :string t.column :lock_version, :integer end create_table :lock_without_defaults_cust, force: true do |t| + t.column :title, :string t.column :custom_lock_version, :integer end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 890b1cd73b..2ad1145fae 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -117,8 +117,8 @@ module ActiveSupport (skipped ||= []) << current next else - expanded = current.expand_call_template(env, invoke_sequence) - expanded.shift.send(*expanded, &expanded.shift) + target, block, method, *arguments = current.expand_call_template(env, invoke_sequence) + target.send(method, *arguments, &block) end current.invoke_after(env) skipped.pop.invoke_after(env) while skipped && skipped.first @@ -410,8 +410,8 @@ module ActiveSupport # values. def make_lambda lambda do |target, value, &block| - c = expand(target, value, block) - c.shift.send(*c, &c.shift) + target, block, method, *arguments = expand(target, value, block) + target.send(method, *arguments, &block) end end @@ -419,8 +419,8 @@ module ActiveSupport # values, but then return the boolean inverse of that result. def inverted_lambda lambda do |target, value, &block| - c = expand(target, value, block) - ! c.shift.send(*c, &c.shift) + target, block, method, *arguments = expand(target, value, block) + ! target.send(method, *arguments, &block) end end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 74a603c05d..a56943d99d 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -1,5 +1,6 @@ require "active_support/core_ext/hash/keys" require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/hash/indifferent_access" module ActiveSupport # Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 1c599b8851..3de4ccc1da 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -65,5 +65,7 @@ module ActiveSupport alias :assert_not_predicate :refute_predicate alias :assert_not_respond_to :refute_respond_to alias :assert_not_same :refute_same + + ActiveSupport.run_load_hooks(:active_support_test_case, self) end end diff --git a/activesupport/test/executor_test.rb b/activesupport/test/executor_test.rb index d409216206..03ec6020c3 100644 --- a/activesupport/test/executor_test.rb +++ b/activesupport/test/executor_test.rb @@ -192,6 +192,8 @@ class ExecutorTest < ActiveSupport::TestCase end def test_class_serial_is_unaffected + skip if !defined?(RubyVM) + hook = Class.new do define_method(:run) do nil diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 9f9c50ca10..03573b274d 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -241,6 +241,7 @@ module Rails end def create_db_files + return if options[:skip_active_record] build(:db) end diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb index d0fc795515..746120e6a1 100644 --- a/railties/lib/rails/test_unit/railtie.rb +++ b/railties/lib/rails/test_unit/railtie.rb @@ -14,7 +14,9 @@ module Rails end initializer "test_unit.line_filtering" do - ActiveSupport::TestCase.extend Rails::LineFiltering + ActiveSupport.on_load(:active_support_test_case) { + ActiveSupport::TestCase.extend Rails::LineFiltering + } end rake_tasks do diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb index 77e7a2cca5..8769703f66 100644 --- a/railties/test/application/runner_test.rb +++ b/railties/test/application/runner_test.rb @@ -43,6 +43,15 @@ module ApplicationTests assert_match "42", Dir.chdir(app_path) { `bin/rails runner "bin/count_users.rb"` } end + def test_no_minitest_loaded_in_production_mode + app_file "bin/print_features.rb", <<-SCRIPT + p $LOADED_FEATURES.grep(/minitest/) + SCRIPT + assert_match "[]", Dir.chdir(app_path) { + `RAILS_ENV=production bin/rails runner "bin/print_features.rb"` + } + end + def test_should_set_dollar_0_to_file app_file "bin/dollar0.rb", <<-SCRIPT puts $0 diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 8f7fa1155f..830e49a1b5 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -356,6 +356,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] + assert_no_directory "db/" assert_no_file "config/database.yml" assert_no_file "app/models/application_record.rb" assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ |