1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Fix ruby-master test suite (Psych 4.0.0)

Ruby master ships with Psych 4.0.0 which makes `YAML.load`
defaults to safe mode (https://github.com/ruby/psych/pull/487).

However since these YAML files are trustworthy sources
we can parse them with `unsafe_load`.
This commit is contained in:
Jean Boussier 2021-05-19 11:22:13 +02:00
parent 811f7471d1
commit 1e56b1d115
18 changed files with 95 additions and 41 deletions

View file

@ -23,30 +23,33 @@ class ParametersSerializationTest < ActiveSupport::TestCase
test "yaml deserialization" do
params = ActionController::Parameters.new(key: :value)
roundtripped = YAML.load(YAML.dump(params))
payload = YAML.dump(params)
roundtripped = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(payload) : YAML.load(payload)
assert_equal params, roundtripped
assert_not_predicate roundtripped, :permitted?
end
test "yaml backwardscompatible with psych 2.0.8 format" do
params = YAML.load <<~end_of_yaml
payload = <<~end_of_yaml
--- !ruby/hash:ActionController::Parameters
key: :value
end_of_yaml
params = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(payload) : YAML.load(payload)
assert_equal :value, params[:key]
assert_not_predicate params, :permitted?
end
test "yaml backwardscompatible with psych 2.0.9+ format" do
params = YAML.load(<<~end_of_yaml)
payload = <<~end_of_yaml
--- !ruby/hash-with-ivars:ActionController::Parameters
elements:
key: :value
ivars:
:@permitted: false
end_of_yaml
params = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(payload) : YAML.load(payload)
assert_equal :value, params[:key]
assert_not_predicate params, :permitted?

View file

@ -821,7 +821,7 @@ class ErrorsTest < ActiveModel::TestCase
messages: {}
CODE
errors = YAML.load(yaml)
errors = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(yaml) : YAML.load(yaml)
errors.add(:name, :invalid)
assert_equal({ name: ["is invalid"] }, errors.messages)
assert_equal({ name: [{ error: :invalid }] }, errors.details)
@ -847,7 +847,7 @@ class ErrorsTest < ActiveModel::TestCase
- :error: :invalid
CODE
errors = YAML.load(yaml)
errors = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(yaml) : YAML.load(yaml)
assert_equal({ name: ["is invalid"] }, errors.messages)
assert_equal({ name: [{ error: :invalid }] }, errors.details)
@ -872,7 +872,7 @@ class ErrorsTest < ActiveModel::TestCase
options: {}
CODE
errors = YAML.load(yaml)
errors = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(yaml) : YAML.load(yaml)
assert_equal({ name: ["is invalid"] }, errors.messages)
assert_equal({ name: [{ error: :invalid }] }, errors.details)

View file

@ -1,3 +1,10 @@
* Fix compatibility with `psych >= 4`.
Starting in Psych 4.0.0 `YAML.load` behaves like `YAML.safe_load`. To preserve compatibility
Active Record's schema cache loader and `YAMLColumn` now uses `YAML.unsafe_load` if available.
*Jean Boussier*
* `ActiveRecord::Base.logger` is now a `class_attribute`.
This means it can no longer be accessed directly through `@@logger`, and that setting `logger =`

View file

@ -23,7 +23,7 @@ module ActiveRecord
def load(yaml)
return object_class.new if object_class != Object && yaml.nil?
return yaml unless yaml.is_a?(String) && yaml.start_with?("---")
obj = YAML.load(yaml)
obj = yaml_load(yaml)
assert_valid_value(obj, action: "load")
obj ||= object_class.new if object_class != Object
@ -44,6 +44,16 @@ module ActiveRecord
rescue ArgumentError
raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
end
if YAML.respond_to?(:unsafe_load)
def yaml_load(payload)
YAML.unsafe_load(payload)
end
else
def yaml_load(payload)
YAML.load(payload)
end
end
end
end
end

View file

@ -9,7 +9,15 @@ module ActiveRecord
return unless File.file?(filename)
read(filename) do |file|
filename.include?(".dump") ? Marshal.load(file) : YAML.load(file)
if filename.include?(".dump")
Marshal.load(file)
else
if YAML.respond_to?(:unsafe_load)
YAML.unsafe_load(file)
else
YAML.load(file)
end
end
end
end

View file

@ -148,7 +148,8 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
assert_equal "fr", x.language
assert_equal "GMT", x.timezone
y = YAML.load(YAML.dump(x))
payload = YAML.dump(x)
y = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(payload) : YAML.load(payload)
assert_equal "fr", y.language
assert_equal "GMT", y.timezone
end

View file

@ -789,7 +789,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase
in_time_zone "Pacific Time (US & Canada)" do
record = Topic.new(id: 1)
record.written_on = "Jan 01 00:00:00 2014"
assert_equal record, YAML.load(YAML.dump(record))
payload = YAML.dump(record)
assert_equal record, YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(payload) : YAML.load(payload)
end
ensure
# NOTE: Reset column info because global topics

View file

@ -52,7 +52,9 @@ module ActiveRecord
cache.dump_to(tempfile.path)
# Unzip and load manually.
cache = Zlib::GzipReader.open(tempfile.path) { |gz| YAML.load(gz.read) }
cache = Zlib::GzipReader.open(tempfile.path) do |gz|
YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(gz.read) : YAML.load(gz.read)
end
# Give it a connection. Usually the connection
# would get set on the cache when it's retrieved

View file

@ -147,7 +147,8 @@ module JSONSharedTestCases
x = klass.new(resolution: "320×480")
assert_equal "320×480", x.resolution
y = YAML.load(YAML.dump(x))
payload = YAML.dump(x)
y = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(payload) : YAML.load(payload)
assert_equal "320×480", y.resolution
end

View file

@ -550,7 +550,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase
def test_yaml_dumping_with_lock_column
t1 = LockWithoutDefault.new
t2 = YAML.load(YAML.dump(t1))
payload = YAML.dump(t1)
t2 = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(payload) : YAML.load(payload)
assert_equal t1.attributes, t2.attributes
end

View file

@ -276,11 +276,12 @@ class StoreTest < ActiveRecord::TestCase
test "dump, load and dump again a model" do
dumped = YAML.dump(@john)
loaded = YAML.load(dumped)
loaded = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(dumped) : YAML.load(dumped)
assert_equal @john, loaded
second_dump = YAML.dump(loaded)
assert_equal @john, YAML.load(second_dump)
second_loaded = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(second_dump) : YAML.load(second_dump)
assert_equal @john, second_loaded
end
test "read store attributes through accessors with default suffix" do

View file

@ -19,26 +19,26 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_roundtrip
topic = Topic.first
assert topic
t = YAML.load YAML.dump topic
t = yaml_load YAML.dump topic
assert_equal topic, t
end
def test_roundtrip_serialized_column
topic = Topic.new(content: { omg: :lol })
assert_equal({ omg: :lol }, YAML.load(YAML.dump(topic)).content)
assert_equal({ omg: :lol }, yaml_load(YAML.dump(topic)).content)
end
def test_psych_roundtrip
topic = Topic.first
assert topic
t = Psych.load Psych.dump topic
t = yaml_load Psych.dump topic
assert_equal topic, t
end
def test_psych_roundtrip_new_object
topic = Topic.new
assert topic
t = Psych.load Psych.dump topic
t = yaml_load Psych.dump topic
assert_equal topic.attributes, t.attributes
end
@ -49,30 +49,30 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_raw_types_are_not_changed_on_round_trip
topic = Topic.new(parent_id: "123")
assert_equal "123", topic.parent_id_before_type_cast
assert_equal "123", YAML.load(YAML.dump(topic)).parent_id_before_type_cast
assert_equal "123", yaml_load(YAML.dump(topic)).parent_id_before_type_cast
end
def test_cast_types_are_not_changed_on_round_trip
topic = Topic.new(parent_id: "123")
assert_equal 123, topic.parent_id
assert_equal 123, YAML.load(YAML.dump(topic)).parent_id
assert_equal 123, yaml_load(YAML.dump(topic)).parent_id
end
def test_new_records_remain_new_after_round_trip
topic = Topic.new
assert topic.new_record?, "Sanity check that new records are new"
assert YAML.load(YAML.dump(topic)).new_record?, "Record should be new after deserialization"
assert yaml_load(YAML.dump(topic)).new_record?, "Record should be new after deserialization"
topic.save!
assert_not topic.new_record?, "Saved records are not new"
assert_not YAML.load(YAML.dump(topic)).new_record?, "Saved record should not be new after deserialization"
assert_not yaml_load(YAML.dump(topic)).new_record?, "Saved record should not be new after deserialization"
topic = Topic.select("title").last
assert_not topic.new_record?, "Loaded records without ID are not new"
assert_not YAML.load(YAML.dump(topic)).new_record?, "Record should not be new after deserialization"
assert_not yaml_load(YAML.dump(topic)).new_record?, "Record should not be new after deserialization"
end
def test_types_of_virtual_columns_are_not_changed_on_round_trip
@ -80,7 +80,7 @@ class YamlSerializationTest < ActiveRecord::TestCase
.joins(:posts)
.group("authors.id")
.first
dumped = YAML.load(YAML.dump(author))
dumped = yaml_load(YAML.dump(author))
assert_equal 5, author.posts_count
assert_equal 5, dumped.posts_count
@ -94,7 +94,7 @@ class YamlSerializationTest < ActiveRecord::TestCase
end
def test_deserializing_rails_v2_yaml
topic = YAML.load(yaml_fixture("rails_v2"))
topic = yaml_load(yaml_fixture("rails_v2"))
assert_not_predicate topic, :new_record?
assert_equal 1, topic.id
@ -103,7 +103,7 @@ class YamlSerializationTest < ActiveRecord::TestCase
end
def test_deserializing_rails_v1_mysql_yaml
topic = YAML.load(yaml_fixture("rails_v1_mysql"))
topic = yaml_load(yaml_fixture("rails_v1_mysql"))
assert_not_predicate topic, :new_record?
assert_equal 1, topic.id
@ -113,7 +113,7 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_deserializing_rails_41_yaml
topic = assert_deprecated do
YAML.load(yaml_fixture("rails_4_1"))
yaml_load(yaml_fixture("rails_4_1"))
end
assert_predicate topic, :new_record?
@ -124,7 +124,7 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_deserializing_rails_4_2_0_yaml
topic = assert_deprecated do
YAML.load(yaml_fixture("rails_4_2_0"))
yaml_load(yaml_fixture("rails_4_2_0"))
end
assert_not_predicate topic, :new_record?
@ -136,7 +136,7 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_yaml_encoding_keeps_mutations
author = Author.first
author.name = "Sean"
dumped = YAML.load(YAML.dump(author))
dumped = yaml_load(YAML.dump(author))
assert_equal "Sean", dumped.name
assert_equal author.name_was, dumped.name_was
@ -146,12 +146,16 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_yaml_encoding_keeps_false_values
topic = Topic.first
topic.approved = false
dumped = YAML.load(YAML.dump(topic))
dumped = yaml_load(YAML.dump(topic))
assert_equal false, dumped.approved
end
private
def yaml_load(payload)
YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(payload) : YAML.load(payload)
end
def yaml_fixture(file_name)
path = File.expand_path(
"support/yaml_compatibility_fixtures/#{file_name}.yml",

View file

@ -20,9 +20,9 @@ module ActiveSupport
def parse(context: nil, **options)
source = render(context)
begin
YAML.load(source, aliases: true, **options) || {}
rescue ArgumentError
if YAML.respond_to?(:unsafe_load)
YAML.unsafe_load(source, **options) || {}
else
YAML.load(source, **options) || {}
end
rescue Psych::SyntaxError => error

View file

@ -713,7 +713,8 @@ class DurationTest < ActiveSupport::TestCase
end
def test_durations_survive_yaml_serialization
d1 = YAML.load(YAML.dump(10.minutes))
payload = YAML.dump(10.minutes)
d1 = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(payload) : YAML.load(payload)
assert_equal 600, d1.to_i
assert_equal 660, (d1 + 60).to_i
end

View file

@ -217,7 +217,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase
time: 1999-12-31 19:00:00.000000000 Z
EOF
assert_equal(@twz, YAML.load(yaml))
loaded = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(yaml) : YAML.load(yaml)
assert_equal(@twz, loaded)
end
def test_ruby_yaml_load
@ -230,7 +231,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase
time: 1999-12-31 19:00:00.000000000 Z
EOF
assert_equal({ "twz" => @twz }, YAML.load(yaml))
loaded = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(yaml) : YAML.load(yaml)
assert_equal({ "twz" => @twz }, loaded)
end
def test_httpdate

View file

@ -854,6 +854,8 @@ class TimeZoneTest < ActiveSupport::TestCase
end
def test_yaml_load
assert_equal(ActiveSupport::TimeZone["Pacific/Honolulu"], YAML.load("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n"))
payload = "--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n"
loaded = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(payload) : YAML.load(payload)
assert_equal(ActiveSupport::TimeZone["Pacific/Honolulu"], loaded)
end
end

View file

@ -1,3 +1,10 @@
* Fix compatibility with `psych >= 4`.
Starting in Psych 4.0.0 `YAML.load` behaves like `YAML.safe_load`. To preserve compatibility
`Rails.application.config_for` now uses `YAML.unsafe_load` if available.
*Jean Boussier*
* Allow loading nested locales in engines.
*Gannon McGibbon*

View file

@ -275,10 +275,13 @@ module Rails
if path = paths["config/database"].existent.first
require "rails/application/dummy_erb_compiler"
yaml = Pathname.new(path)
erb = DummyERB.new(yaml.read)
yaml = DummyERB.new(Pathname.new(path).read).result
YAML.load(erb.result) || {}
if YAML.respond_to?(:unsafe_load)
YAML.unsafe_load(yaml) || {}
else
YAML.load(yaml) || {}
end
else
{}
end