Skip to content

Commit 794252d

Browse files
committed
Merge branch 'json-gem-tests'
2 parents 14354f1 + a6b2ace commit 794252d

File tree

3 files changed

+186
-102
lines changed

3 files changed

+186
-102
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
require 'abstract_unit'
2+
require 'json'
3+
require 'json/encoding_test_cases'
4+
5+
# These test cases were added to test that we do not interfere with json gem's
6+
# output when the AS encoder is loaded, primarily for problems reported in
7+
# #20775. They need to be executed in isolation to reproduce the scenario
8+
# correctly, because other test cases might have already loaded additional
9+
# dependencies.
10+
11+
# The AS::JSON encoder requires the BigDecimal core_ext, which, unfortunately,
12+
# changes the BigDecimal#to_s output, and consequently the JSON gem output. So
13+
# we need to require this unfront to ensure we don't get a false failure, but
14+
# ideally we should just fix the BigDecimal core_ext to not change to_s without
15+
# arguments.
16+
require 'active_support/core_ext/big_decimal'
17+
18+
class JsonGemEncodingTest < ActiveSupport::TestCase
19+
include ActiveSupport::Testing::Isolation
20+
21+
JSONTest::EncodingTestCases.constants.each_with_index do |name|
22+
JSONTest::EncodingTestCases.const_get(name).each_with_index do |(subject, _), i|
23+
test("#{name[0..-6].underscore} #{i}") do
24+
assert_same_with_or_without_active_support(subject)
25+
end
26+
end
27+
end
28+
29+
class CustomToJson
30+
def to_json(*)
31+
'"custom"'
32+
end
33+
end
34+
35+
test "custom to_json" do
36+
assert_same_with_or_without_active_support(CustomToJson.new)
37+
end
38+
39+
private
40+
def require_or_skip(file)
41+
require(file) || skip("'#{file}' was already loaded")
42+
end
43+
44+
def assert_same_with_or_without_active_support(subject)
45+
begin
46+
expected = JSON.generate(subject, quirks_mode: true)
47+
rescue JSON::GeneratorError => e
48+
exception = e
49+
end
50+
51+
require_or_skip 'active_support/core_ext/object/json'
52+
53+
if exception
54+
assert_raises_with_message JSON::GeneratorError, e.message do
55+
JSON.generate(subject, quirks_mode: true)
56+
end
57+
else
58+
assert_equal expected, JSON.generate(subject, quirks_mode: true)
59+
end
60+
end
61+
62+
def assert_raises_with_message(exception_class, message, &block)
63+
err = assert_raises(exception_class) { block.call }
64+
assert_match message, err.message
65+
end
66+
end

activesupport/test/json/encoding_test.rb

Lines changed: 32 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -4,122 +4,24 @@
44
require 'active_support/json'
55
require 'active_support/time'
66
require 'time_zone_test_helpers'
7+
require 'json/encoding_test_cases'
78

89
class TestJSONEncoding < ActiveSupport::TestCase
910
include TimeZoneTestHelpers
1011

11-
class Foo
12-
def initialize(a, b)
13-
@a, @b = a, b
14-
end
15-
end
16-
17-
class Hashlike
18-
def to_hash
19-
{ :foo => "hello", :bar => "world" }
20-
end
21-
end
22-
23-
class Custom
24-
def initialize(serialized)
25-
@serialized = serialized
26-
end
27-
28-
def as_json(options = nil)
29-
@serialized
30-
end
31-
end
32-
33-
class CustomWithOptions
34-
attr_accessor :foo, :bar
35-
36-
def as_json(options={})
37-
options[:only] = %w(foo bar)
38-
super(options)
39-
end
40-
end
41-
42-
class OptionsTest
43-
def as_json(options = :default)
44-
options
45-
end
46-
end
47-
48-
class HashWithAsJson < Hash
49-
attr_accessor :as_json_called
50-
51-
def initialize(*)
52-
super
53-
end
54-
55-
def as_json(options={})
56-
@as_json_called = true
57-
super
58-
end
59-
end
60-
61-
TrueTests = [[ true, %(true) ]]
62-
FalseTests = [[ false, %(false) ]]
63-
NilTests = [[ nil, %(null) ]]
64-
NumericTests = [[ 1, %(1) ],
65-
[ 2.5, %(2.5) ],
66-
[ 0.0/0.0, %(null) ],
67-
[ 1.0/0.0, %(null) ],
68-
[ -1.0/0.0, %(null) ],
69-
[ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
70-
[ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
71-
72-
StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
73-
[ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
74-
[ 'http://test.host/posts/1', %("http://test.host/posts/1")],
75-
[ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
76-
%("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
77-
78-
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
79-
[ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
80-
81-
RangeTests = [[ 1..2, %("1..2")],
82-
[ 1...2, %("1...2")],
83-
[ 1.5..2.5, %("1.5..2.5")]]
84-
85-
SymbolTests = [[ :a, %("a") ],
86-
[ :this, %("this") ],
87-
[ :"a b", %("a b") ]]
88-
89-
ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
90-
HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
91-
CustomTests = [[ Custom.new("custom"), '"custom"' ],
92-
[ Custom.new(nil), 'null' ],
93-
[ Custom.new(:a), '"a"' ],
94-
[ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
95-
[ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
96-
[ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
97-
[ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
98-
99-
RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
100-
101-
DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
102-
TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
103-
DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
104-
105-
StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
106-
StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
107-
StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
108-
StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
109-
11012
def sorted_json(json)
11113
return json unless json =~ /^\{.*\}$/
11214
'{' + json[1..-2].split(',').sort.join(',') + '}'
11315
end
11416

115-
constants.grep(/Tests$/).each do |class_tests|
17+
JSONTest::EncodingTestCases.constants.each do |class_tests|
11618
define_method("test_#{class_tests[0..-6].underscore}") do
11719
begin
11820
prev = ActiveSupport.use_standard_json_time_format
11921

12022
ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/
12123
ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/
122-
self.class.const_get(class_tests).each do |pair|
24+
JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
12325
assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
12426
end
12527
ensure
@@ -224,7 +126,7 @@ def test_nested_hash_with_float
224126
end
225127

226128
def test_hash_like_with_options
227-
h = Hashlike.new
129+
h = JSONTest::Hashlike.new
228130
json = h.to_json :only => [:foo]
229131

230132
assert_equal({"foo"=>"hello"}, JSON.parse(json))
@@ -345,6 +247,15 @@ def test_enumerable_should_pass_encoding_options_to_children_in_to_json
345247
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
346248
end
347249

250+
class CustomWithOptions
251+
attr_accessor :foo, :bar
252+
253+
def as_json(options={})
254+
options[:only] = %w(foo bar)
255+
super(options)
256+
end
257+
end
258+
348259
def test_hash_to_json_should_not_keep_options_around
349260
f = CustomWithOptions.new
350261
f.foo = "hello"
@@ -365,6 +276,12 @@ def test_array_to_json_should_not_keep_options_around
365276
{"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
366277
end
367278

279+
class OptionsTest
280+
def as_json(options = :default)
281+
options
282+
end
283+
end
284+
368285
def test_hash_as_json_without_options
369286
json = { foo: OptionsTest.new }.as_json
370287
assert_equal({"foo" => :default}, json)
@@ -412,6 +329,19 @@ def test_nil_true_and_false_represented_as_themselves
412329
assert_equal false, false.as_json
413330
end
414331

332+
class HashWithAsJson < Hash
333+
attr_accessor :as_json_called
334+
335+
def initialize(*)
336+
super
337+
end
338+
339+
def as_json(options={})
340+
@as_json_called = true
341+
super
342+
end
343+
end
344+
415345
def test_json_gem_dump_by_passing_active_support_encoder
416346
h = HashWithAsJson.new
417347
h[:foo] = "hello"
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
require 'bigdecimal'
2+
3+
module JSONTest
4+
class Foo
5+
def initialize(a, b)
6+
@a, @b = a, b
7+
end
8+
end
9+
10+
class Hashlike
11+
def to_hash
12+
{ :foo => "hello", :bar => "world" }
13+
end
14+
end
15+
16+
class Custom
17+
def initialize(serialized)
18+
@serialized = serialized
19+
end
20+
21+
def as_json(options = nil)
22+
@serialized
23+
end
24+
end
25+
26+
class MyStruct < Struct.new(:name, :value)
27+
def initialize(*)
28+
@unused = "unused instance variable"
29+
super
30+
end
31+
end
32+
33+
module EncodingTestCases
34+
TrueTests = [[ true, %(true) ]]
35+
FalseTests = [[ false, %(false) ]]
36+
NilTests = [[ nil, %(null) ]]
37+
NumericTests = [[ 1, %(1) ],
38+
[ 2.5, %(2.5) ],
39+
[ 0.0/0.0, %(null) ],
40+
[ 1.0/0.0, %(null) ],
41+
[ -1.0/0.0, %(null) ],
42+
[ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
43+
[ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
44+
45+
StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
46+
[ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
47+
[ 'http://test.host/posts/1', %("http://test.host/posts/1")],
48+
[ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
49+
%("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
50+
51+
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
52+
[ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
53+
54+
HashTests = [[ {foo: "bar"}, %({\"foo\":\"bar\"}) ],
55+
[ {1 => 1, 2 => 'a', 3 => :b, 4 => nil, 5 => false}, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]]
56+
57+
RangeTests = [[ 1..2, %("1..2")],
58+
[ 1...2, %("1...2")],
59+
[ 1.5..2.5, %("1.5..2.5")]]
60+
61+
SymbolTests = [[ :a, %("a") ],
62+
[ :this, %("this") ],
63+
[ :"a b", %("a b") ]]
64+
65+
ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
66+
HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
67+
StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ],
68+
[ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]]
69+
CustomTests = [[ Custom.new("custom"), '"custom"' ],
70+
[ Custom.new(nil), 'null' ],
71+
[ Custom.new(:a), '"a"' ],
72+
[ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
73+
[ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
74+
[ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
75+
[ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
76+
77+
RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
78+
79+
DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
80+
TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
81+
DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
82+
83+
StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
84+
StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
85+
StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
86+
StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
87+
end
88+
end

0 commit comments

Comments
 (0)