mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00

Fix: https://github.com/ruby/json/issues/807
Since https://github.com/ruby/json/pull/800, `fpconv_dtoa` can actually
generate up to 28 chars.
d73ae93d3c
819 lines
25 KiB
Ruby
Executable file
819 lines
25 KiB
Ruby
Executable file
#!/usr/bin/env ruby
|
|
# frozen_string_literal: true
|
|
|
|
require_relative 'test_helper'
|
|
|
|
class JSONGeneratorTest < Test::Unit::TestCase
|
|
include JSON
|
|
|
|
def setup
|
|
@hash = {
|
|
'a' => 2,
|
|
'b' => 3.141,
|
|
'c' => 'c',
|
|
'd' => [ 1, "b", 3.14 ],
|
|
'e' => { 'foo' => 'bar' },
|
|
'g' => "\"\0\037",
|
|
'h' => 1000.0,
|
|
'i' => 0.001
|
|
}
|
|
@json2 = '{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' +
|
|
'"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}'
|
|
@json3 = <<~'JSON'.chomp
|
|
{
|
|
"a": 2,
|
|
"b": 3.141,
|
|
"c": "c",
|
|
"d": [
|
|
1,
|
|
"b",
|
|
3.14
|
|
],
|
|
"e": {
|
|
"foo": "bar"
|
|
},
|
|
"g": "\"\u0000\u001f",
|
|
"h": 1000.0,
|
|
"i": 0.001
|
|
}
|
|
JSON
|
|
end
|
|
|
|
def silence
|
|
v = $VERBOSE
|
|
$VERBOSE = nil
|
|
yield
|
|
ensure
|
|
$VERBOSE = v
|
|
end
|
|
|
|
def test_generate
|
|
json = generate(@hash)
|
|
assert_equal(parse(@json2), parse(json))
|
|
json = JSON[@hash]
|
|
assert_equal(parse(@json2), parse(json))
|
|
parsed_json = parse(json)
|
|
assert_equal(@hash, parsed_json)
|
|
json = generate({1=>2})
|
|
assert_equal('{"1":2}', json)
|
|
parsed_json = parse(json)
|
|
assert_equal({"1"=>2}, parsed_json)
|
|
assert_equal '666', generate(666)
|
|
end
|
|
|
|
def test_dump_unenclosed_hash
|
|
assert_equal '{"a":1,"b":2}', dump(a: 1, b: 2)
|
|
end
|
|
|
|
def test_dump_strict
|
|
assert_equal '{}', dump({}, strict: true)
|
|
|
|
assert_equal '{"array":[42,4.2,"forty-two",true,false,null]}', dump({
|
|
"array" => [42, 4.2, "forty-two", true, false, nil]
|
|
}, strict: true)
|
|
|
|
assert_equal '{"int":42,"float":4.2,"string":"forty-two","true":true,"false":false,"nil":null,"hash":{}}', dump({
|
|
"int" => 42,
|
|
"float" => 4.2,
|
|
"string" => "forty-two",
|
|
"true" => true,
|
|
"false" => false,
|
|
"nil" => nil,
|
|
"hash" => {},
|
|
}, strict: true)
|
|
|
|
assert_equal '[]', dump([], strict: true)
|
|
|
|
assert_equal '42', dump(42, strict: true)
|
|
assert_equal 'true', dump(true, strict: true)
|
|
|
|
assert_equal '"hello"', dump(:hello, strict: true)
|
|
assert_equal '"hello"', :hello.to_json(strict: true)
|
|
assert_equal '"World"', "World".to_json(strict: true)
|
|
end
|
|
|
|
def test_generate_pretty
|
|
json = pretty_generate({})
|
|
assert_equal('{}', json)
|
|
|
|
json = pretty_generate({1=>{}, 2=>[], 3=>4})
|
|
assert_equal(<<~'JSON'.chomp, json)
|
|
{
|
|
"1": {},
|
|
"2": [],
|
|
"3": 4
|
|
}
|
|
JSON
|
|
|
|
json = pretty_generate(@hash)
|
|
# hashes aren't (insertion) ordered on every ruby implementation
|
|
# assert_equal(@json3, json)
|
|
assert_equal(parse(@json3), parse(json))
|
|
parsed_json = parse(json)
|
|
assert_equal(@hash, parsed_json)
|
|
json = pretty_generate({1=>2})
|
|
assert_equal(<<~'JSON'.chomp, json)
|
|
{
|
|
"1": 2
|
|
}
|
|
JSON
|
|
parsed_json = parse(json)
|
|
assert_equal({"1"=>2}, parsed_json)
|
|
assert_equal '666', pretty_generate(666)
|
|
end
|
|
|
|
def test_generate_pretty_custom
|
|
state = State.new(:space_before => "<psb>", :space => "<ps>", :indent => "<pi>", :object_nl => "\n<po_nl>\n", :array_nl => "<pa_nl>")
|
|
json = pretty_generate({1=>{}, 2=>['a','b'], 3=>4}, state)
|
|
assert_equal(<<~'JSON'.chomp, json)
|
|
{
|
|
<po_nl>
|
|
<pi>"1"<psb>:<ps>{},
|
|
<po_nl>
|
|
<pi>"2"<psb>:<ps>[<pa_nl><pi><pi>"a",<pa_nl><pi><pi>"b"<pa_nl><pi>],
|
|
<po_nl>
|
|
<pi>"3"<psb>:<ps>4
|
|
<po_nl>
|
|
}
|
|
JSON
|
|
end
|
|
|
|
def test_generate_custom
|
|
state = State.new(:space_before => " ", :space => " ", :indent => "<i>", :object_nl => "\n", :array_nl => "<a_nl>")
|
|
json = generate({1=>{2=>3,4=>[5,6]}}, state)
|
|
assert_equal(<<~'JSON'.chomp, json)
|
|
{
|
|
<i>"1" : {
|
|
<i><i>"2" : 3,
|
|
<i><i>"4" : [<a_nl><i><i><i>5,<a_nl><i><i><i>6<a_nl><i><i>]
|
|
<i>}
|
|
}
|
|
JSON
|
|
end
|
|
|
|
def test_fast_generate
|
|
assert_deprecated_warning(/fast_generate/) do
|
|
json = fast_generate(@hash)
|
|
assert_equal(parse(@json2), parse(json))
|
|
parsed_json = parse(json)
|
|
assert_equal(@hash, parsed_json)
|
|
json = fast_generate({1=>2})
|
|
assert_equal('{"1":2}', json)
|
|
parsed_json = parse(json)
|
|
assert_equal({"1"=>2}, parsed_json)
|
|
assert_equal '666', fast_generate(666)
|
|
end
|
|
end
|
|
|
|
def test_own_state
|
|
state = State.new
|
|
json = generate(@hash, state)
|
|
assert_equal(parse(@json2), parse(json))
|
|
parsed_json = parse(json)
|
|
assert_equal(@hash, parsed_json)
|
|
json = generate({1=>2}, state)
|
|
assert_equal('{"1":2}', json)
|
|
parsed_json = parse(json)
|
|
assert_equal({"1"=>2}, parsed_json)
|
|
assert_equal '666', generate(666, state)
|
|
end
|
|
|
|
def test_states
|
|
json = generate({1=>2}, nil)
|
|
assert_equal('{"1":2}', json)
|
|
s = JSON.state.new
|
|
assert s.check_circular?
|
|
assert s[:check_circular?]
|
|
h = { 1=>2 }
|
|
h[3] = h
|
|
assert_raise(JSON::NestingError) { generate(h) }
|
|
assert_raise(JSON::NestingError) { generate(h, s) }
|
|
s = JSON.state.new
|
|
a = [ 1, 2 ]
|
|
a << a
|
|
assert_raise(JSON::NestingError) { generate(a, s) }
|
|
assert s.check_circular?
|
|
assert s[:check_circular?]
|
|
end
|
|
|
|
def test_falsy_state
|
|
object = { foo: [1, 2], bar: { egg: :spam }}
|
|
expected_json = JSON.generate(
|
|
object,
|
|
array_nl: "",
|
|
indent: "",
|
|
object_nl: "",
|
|
space: "",
|
|
space_before: "",
|
|
)
|
|
|
|
assert_equal expected_json, JSON.generate(
|
|
object,
|
|
array_nl: nil,
|
|
indent: nil,
|
|
object_nl: nil,
|
|
space: nil,
|
|
space_before: nil,
|
|
)
|
|
end
|
|
|
|
def test_state_defaults
|
|
state = JSON::State.new
|
|
assert_equal({
|
|
:allow_nan => false,
|
|
:array_nl => "",
|
|
:as_json => false,
|
|
:ascii_only => false,
|
|
:buffer_initial_length => 1024,
|
|
:depth => 0,
|
|
:script_safe => false,
|
|
:strict => false,
|
|
:indent => "",
|
|
:max_nesting => 100,
|
|
:object_nl => "",
|
|
:space => "",
|
|
:space_before => "",
|
|
}.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s })
|
|
end
|
|
|
|
def test_allow_nan
|
|
assert_deprecated_warning(/fast_generate/) do
|
|
error = assert_raise(GeneratorError) { generate([JSON::NaN]) }
|
|
assert_same JSON::NaN, error.invalid_object
|
|
assert_equal '[NaN]', generate([JSON::NaN], :allow_nan => true)
|
|
assert_raise(GeneratorError) { fast_generate([JSON::NaN]) }
|
|
assert_raise(GeneratorError) { pretty_generate([JSON::NaN]) }
|
|
assert_equal "[\n NaN\n]", pretty_generate([JSON::NaN], :allow_nan => true)
|
|
error = assert_raise(GeneratorError) { generate([JSON::Infinity]) }
|
|
assert_same JSON::Infinity, error.invalid_object
|
|
assert_equal '[Infinity]', generate([JSON::Infinity], :allow_nan => true)
|
|
assert_raise(GeneratorError) { fast_generate([JSON::Infinity]) }
|
|
assert_raise(GeneratorError) { pretty_generate([JSON::Infinity]) }
|
|
assert_equal "[\n Infinity\n]", pretty_generate([JSON::Infinity], :allow_nan => true)
|
|
error = assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) }
|
|
assert_same JSON::MinusInfinity, error.invalid_object
|
|
assert_equal '[-Infinity]', generate([JSON::MinusInfinity], :allow_nan => true)
|
|
assert_raise(GeneratorError) { fast_generate([JSON::MinusInfinity]) }
|
|
assert_raise(GeneratorError) { pretty_generate([JSON::MinusInfinity]) }
|
|
assert_equal "[\n -Infinity\n]", pretty_generate([JSON::MinusInfinity], :allow_nan => true)
|
|
end
|
|
end
|
|
|
|
def test_depth
|
|
ary = []; ary << ary
|
|
assert_raise(JSON::NestingError) { generate(ary) }
|
|
assert_raise(JSON::NestingError) { JSON.pretty_generate(ary) }
|
|
s = JSON.state.new
|
|
assert_equal 0, s.depth
|
|
assert_raise(JSON::NestingError) { ary.to_json(s) }
|
|
assert_equal 100, s.depth
|
|
end
|
|
|
|
def test_buffer_initial_length
|
|
s = JSON.state.new
|
|
assert_equal 1024, s.buffer_initial_length
|
|
s.buffer_initial_length = 0
|
|
assert_equal 1024, s.buffer_initial_length
|
|
s.buffer_initial_length = -1
|
|
assert_equal 1024, s.buffer_initial_length
|
|
s.buffer_initial_length = 128
|
|
assert_equal 128, s.buffer_initial_length
|
|
end
|
|
|
|
def test_gc
|
|
pid = fork do
|
|
bignum_too_long_to_embed_as_string = 1234567890123456789012345
|
|
expect = bignum_too_long_to_embed_as_string.to_s
|
|
GC.stress = true
|
|
|
|
10.times do |i|
|
|
tmp = bignum_too_long_to_embed_as_string.to_json
|
|
raise "#{expect}' is expected, but '#{tmp}'" unless tmp == expect
|
|
end
|
|
end
|
|
_, status = Process.waitpid2(pid)
|
|
assert_predicate status, :success?
|
|
end if GC.respond_to?(:stress=) && Process.respond_to?(:fork)
|
|
|
|
def test_configure_using_configure_and_merge
|
|
numbered_state = {
|
|
:indent => "1",
|
|
:space => '2',
|
|
:space_before => '3',
|
|
:object_nl => '4',
|
|
:array_nl => '5'
|
|
}
|
|
state1 = JSON.state.new
|
|
state1.merge(numbered_state)
|
|
assert_equal '1', state1.indent
|
|
assert_equal '2', state1.space
|
|
assert_equal '3', state1.space_before
|
|
assert_equal '4', state1.object_nl
|
|
assert_equal '5', state1.array_nl
|
|
state2 = JSON.state.new
|
|
state2.configure(numbered_state)
|
|
assert_equal '1', state2.indent
|
|
assert_equal '2', state2.space
|
|
assert_equal '3', state2.space_before
|
|
assert_equal '4', state2.object_nl
|
|
assert_equal '5', state2.array_nl
|
|
end
|
|
|
|
def test_configure_hash_conversion
|
|
state = JSON.state.new
|
|
state.configure(:indent => '1')
|
|
assert_equal '1', state.indent
|
|
state = JSON.state.new
|
|
foo = 'foo'.dup
|
|
assert_raise(TypeError) do
|
|
state.configure(foo)
|
|
end
|
|
def foo.to_h
|
|
{ indent: '2' }
|
|
end
|
|
state.configure(foo)
|
|
assert_equal '2', state.indent
|
|
end
|
|
|
|
def test_broken_bignum # [ruby-core:38867]
|
|
pid = fork do
|
|
x = 1 << 64
|
|
x.class.class_eval do
|
|
def to_s
|
|
end
|
|
end
|
|
begin
|
|
JSON::Ext::Generator::State.new.generate(x)
|
|
exit 1
|
|
rescue TypeError
|
|
exit 0
|
|
end
|
|
end
|
|
_, status = Process.waitpid2(pid)
|
|
assert status.success?
|
|
rescue NotImplementedError
|
|
# forking to avoid modifying core class of a parent process and
|
|
# introducing race conditions of tests are run in parallel
|
|
end
|
|
|
|
def test_hash_likeness_set_symbol
|
|
state = JSON.state.new
|
|
assert_equal nil, state[:foo]
|
|
assert_equal nil.class, state[:foo].class
|
|
assert_equal nil, state['foo']
|
|
state[:foo] = :bar
|
|
assert_equal :bar, state[:foo]
|
|
assert_equal :bar, state['foo']
|
|
state_hash = state.to_hash
|
|
assert_kind_of Hash, state_hash
|
|
assert_equal :bar, state_hash[:foo]
|
|
end
|
|
|
|
def test_hash_likeness_set_string
|
|
state = JSON.state.new
|
|
assert_equal nil, state[:foo]
|
|
assert_equal nil, state['foo']
|
|
state['foo'] = :bar
|
|
assert_equal :bar, state[:foo]
|
|
assert_equal :bar, state['foo']
|
|
state_hash = state.to_hash
|
|
assert_kind_of Hash, state_hash
|
|
assert_equal :bar, state_hash[:foo]
|
|
end
|
|
|
|
def test_json_state_to_h_roundtrip
|
|
state = JSON.state.new
|
|
assert_equal state.to_h, JSON.state.new(state.to_h).to_h
|
|
end
|
|
|
|
def test_json_generate
|
|
assert_raise JSON::GeneratorError do
|
|
generate(["\xea"])
|
|
end
|
|
end
|
|
|
|
def test_json_generate_error_detailed_message
|
|
error = assert_raise JSON::GeneratorError do
|
|
generate(["\xea"])
|
|
end
|
|
|
|
assert_not_nil(error.detailed_message)
|
|
end
|
|
|
|
def test_json_generate_unsupported_types
|
|
assert_raise JSON::GeneratorError do
|
|
generate(Object.new, strict: true)
|
|
end
|
|
end
|
|
|
|
def test_nesting
|
|
too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'
|
|
too_deep_ary = eval too_deep
|
|
assert_raise(JSON::NestingError) { generate too_deep_ary }
|
|
assert_raise(JSON::NestingError) { generate too_deep_ary, :max_nesting => 100 }
|
|
ok = generate too_deep_ary, :max_nesting => 101
|
|
assert_equal too_deep, ok
|
|
ok = generate too_deep_ary, :max_nesting => nil
|
|
assert_equal too_deep, ok
|
|
ok = generate too_deep_ary, :max_nesting => false
|
|
assert_equal too_deep, ok
|
|
ok = generate too_deep_ary, :max_nesting => 0
|
|
assert_equal too_deep, ok
|
|
end
|
|
|
|
def test_backslash
|
|
data = [ '\\.(?i:gif|jpe?g|png)$' ]
|
|
json = '["\\\\.(?i:gif|jpe?g|png)$"]'
|
|
assert_equal json, generate(data)
|
|
#
|
|
data = [ '\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$' ]
|
|
json = '["\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$"]'
|
|
assert_equal json, generate(data)
|
|
#
|
|
data = [ '\\"\\"\\"\\"\\"\\"\\"\\"\\"\\"\\"' ]
|
|
json = '["\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\""]'
|
|
assert_equal json, generate(data)
|
|
#
|
|
data = [ '/' ]
|
|
json = '["/"]'
|
|
assert_equal json, generate(data)
|
|
#
|
|
data = [ '////////////////////////////////////////////////////////////////////////////////////' ]
|
|
json = '["////////////////////////////////////////////////////////////////////////////////////"]'
|
|
assert_equal json, generate(data)
|
|
#
|
|
data = [ '/' ]
|
|
json = '["\/"]'
|
|
assert_equal json, generate(data, :script_safe => true)
|
|
#
|
|
data = [ '///////////' ]
|
|
json = '["\/\/\/\/\/\/\/\/\/\/\/"]'
|
|
assert_equal json, generate(data, :script_safe => true)
|
|
#
|
|
data = [ '///////////////////////////////////////////////////////' ]
|
|
json = '["\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/"]'
|
|
assert_equal json, generate(data, :script_safe => true)
|
|
#
|
|
data = [ "\u2028\u2029" ]
|
|
json = '["\u2028\u2029"]'
|
|
assert_equal json, generate(data, :script_safe => true)
|
|
#
|
|
data = [ "ABC \u2028 DEF \u2029 GHI" ]
|
|
json = '["ABC \u2028 DEF \u2029 GHI"]'
|
|
assert_equal json, generate(data, :script_safe => true)
|
|
#
|
|
data = [ "/\u2028\u2029" ]
|
|
json = '["\/\u2028\u2029"]'
|
|
assert_equal json, generate(data, :escape_slash => true)
|
|
#
|
|
data = ['"']
|
|
json = '["\""]'
|
|
assert_equal json, generate(data)
|
|
#
|
|
data = ['"""""""""""""""""""""""""']
|
|
json = '["\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\""]'
|
|
assert_equal json, generate(data)
|
|
#
|
|
data = ["'"]
|
|
json = '["\\\'"]'
|
|
assert_equal '["\'"]', generate(data)
|
|
#
|
|
data = ["倩", "瀨"]
|
|
json = '["倩","瀨"]'
|
|
assert_equal json, generate(data, script_safe: true)
|
|
#
|
|
data = '["This is a "test" of the emergency broadcast system."]'
|
|
json = "\"[\\\"This is a \\\"test\\\" of the emergency broadcast system.\\\"]\""
|
|
assert_equal json, generate(data)
|
|
#
|
|
data = '\tThis is a test of the emergency broadcast system.'
|
|
json = "\"\\\\tThis is a test of the emergency broadcast system.\""
|
|
assert_equal json, generate(data)
|
|
#
|
|
data = 'This\tis a test of the emergency broadcast system.'
|
|
json = "\"This\\\\tis a test of the emergency broadcast system.\""
|
|
assert_equal json, generate(data)
|
|
#
|
|
data = 'This is\ta test of the emergency broadcast system.'
|
|
json = "\"This is\\\\ta test of the emergency broadcast system.\""
|
|
assert_equal json, generate(data)
|
|
#
|
|
data = 'This is a test of the emergency broadcast\tsystem.'
|
|
json = "\"This is a test of the emergency broadcast\\\\tsystem.\""
|
|
assert_equal json, generate(data)
|
|
#
|
|
data = 'This is a test of the emergency broadcast\tsystem.\n'
|
|
json = "\"This is a test of the emergency broadcast\\\\tsystem.\\\\n\""
|
|
assert_equal json, generate(data)
|
|
data = '"' * 15
|
|
json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\""
|
|
assert_equal json, generate(data)
|
|
data = "\"\"\"\"\"\"\"\"\"\"\"\"\"\"a"
|
|
json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"a\""
|
|
assert_equal json, generate(data)
|
|
data = "\u0001\u0001\u0001\u0001"
|
|
json = "\"\\u0001\\u0001\\u0001\\u0001\""
|
|
assert_equal json, generate(data)
|
|
data = "\u0001a\u0001a\u0001a\u0001a"
|
|
json = "\"\\u0001a\\u0001a\\u0001a\\u0001a\""
|
|
assert_equal json, generate(data)
|
|
data = "\u0001aa\u0001aa"
|
|
json = "\"\\u0001aa\\u0001aa\""
|
|
assert_equal json, generate(data)
|
|
data = "\u0001aa\u0001aa\u0001aa"
|
|
json = "\"\\u0001aa\\u0001aa\\u0001aa\""
|
|
assert_equal json, generate(data)
|
|
data = "\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa"
|
|
json = "\"\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\""
|
|
assert_equal json, generate(data)
|
|
data = "\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002"
|
|
json = "\"\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\""
|
|
assert_equal json, generate(data)
|
|
data = "ab\u0002c"
|
|
json = "\"ab\\u0002c\""
|
|
assert_equal json, generate(data)
|
|
data = "ab\u0002cab\u0002cab\u0002cab\u0002c"
|
|
json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002c\""
|
|
assert_equal json, generate(data)
|
|
data = "ab\u0002cab\u0002cab\u0002cab\u0002cab\u0002cab\u0002c"
|
|
json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002c\""
|
|
assert_equal json, generate(data)
|
|
data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f"
|
|
json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\""
|
|
assert_equal json, generate(data)
|
|
data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f\b"
|
|
json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\""
|
|
assert_equal json, generate(data)
|
|
data = "a\n\t\f\b\n\t\f\b\n\t\f\b\n\t"
|
|
json = "\"a\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\""
|
|
assert_equal json, generate(data)
|
|
end
|
|
|
|
def test_string_subclass
|
|
s = Class.new(String) do
|
|
def to_s; self; end
|
|
undef to_json
|
|
end
|
|
assert_nothing_raised(SystemStackError) do
|
|
assert_equal '["foo"]', JSON.generate([s.new('foo')])
|
|
end
|
|
end
|
|
|
|
def test_invalid_encoding_string
|
|
error = assert_raise(JSON::GeneratorError) do
|
|
"\x82\xAC\xEF".to_json
|
|
end
|
|
assert_includes error.message, "source sequence is illegal/malformed utf-8"
|
|
|
|
error = assert_raise(JSON::GeneratorError) do
|
|
JSON.dump("\x82\xAC\xEF")
|
|
end
|
|
assert_includes error.message, "source sequence is illegal/malformed utf-8"
|
|
|
|
assert_raise(JSON::GeneratorError) do
|
|
JSON.dump("\x82\xAC\xEF".b)
|
|
end
|
|
|
|
assert_raise(JSON::GeneratorError) do
|
|
"\x82\xAC\xEF".b.to_json
|
|
end
|
|
|
|
assert_raise(JSON::GeneratorError) do
|
|
["\x82\xAC\xEF".b].to_json
|
|
end
|
|
|
|
badly_encoded = "\x82\xAC\xEF".b
|
|
exception = assert_raise(JSON::GeneratorError) do
|
|
{ foo: badly_encoded }.to_json
|
|
end
|
|
|
|
assert_kind_of EncodingError, exception.cause
|
|
assert_same badly_encoded, exception.invalid_object
|
|
end
|
|
|
|
class MyCustomString < String
|
|
def to_json(_state = nil)
|
|
'"my_custom_key"'
|
|
end
|
|
|
|
def to_s
|
|
self
|
|
end
|
|
end
|
|
|
|
def test_string_subclass_as_keys
|
|
# Ref: https://github.com/ruby/json/issues/667
|
|
# if key.to_s doesn't return a bare string, we call `to_json` on it.
|
|
key = MyCustomString.new("won't be used")
|
|
assert_equal '{"my_custom_key":1}', JSON.generate(key => 1)
|
|
end
|
|
|
|
class FakeString
|
|
def to_json(_state = nil)
|
|
raise "Shouldn't be called"
|
|
end
|
|
|
|
def to_s
|
|
self
|
|
end
|
|
end
|
|
|
|
def test_custom_object_as_keys
|
|
key = FakeString.new
|
|
error = assert_raise(TypeError) do
|
|
JSON.generate(key => 1)
|
|
end
|
|
assert_match "FakeString", error.message
|
|
end
|
|
|
|
def test_to_json_called_with_state_object
|
|
object = Object.new
|
|
called = false
|
|
argument = nil
|
|
object.singleton_class.define_method(:to_json) do |state|
|
|
called = true
|
|
argument = state
|
|
"<hello>"
|
|
end
|
|
|
|
assert_equal "<hello>", JSON.dump(object)
|
|
assert called, "#to_json wasn't called"
|
|
assert_instance_of JSON::State, argument
|
|
end
|
|
|
|
module CustomToJSON
|
|
def to_json(*)
|
|
%{"#{self.class.name}#to_json"}
|
|
end
|
|
end
|
|
|
|
module CustomToS
|
|
def to_s
|
|
"#{self.class.name}#to_s"
|
|
end
|
|
end
|
|
|
|
class ArrayWithToJSON < Array
|
|
include CustomToJSON
|
|
end
|
|
|
|
def test_array_subclass_with_to_json
|
|
assert_equal '["JSONGeneratorTest::ArrayWithToJSON#to_json"]', JSON.generate([ArrayWithToJSON.new])
|
|
assert_equal '{"[]":1}', JSON.generate(ArrayWithToJSON.new => 1)
|
|
end
|
|
|
|
class ArrayWithToS < Array
|
|
include CustomToS
|
|
end
|
|
|
|
def test_array_subclass_with_to_s
|
|
assert_equal '[[]]', JSON.generate([ArrayWithToS.new])
|
|
assert_equal '{"JSONGeneratorTest::ArrayWithToS#to_s":1}', JSON.generate(ArrayWithToS.new => 1)
|
|
end
|
|
|
|
class HashWithToJSON < Hash
|
|
include CustomToJSON
|
|
end
|
|
|
|
def test_hash_subclass_with_to_json
|
|
assert_equal '["JSONGeneratorTest::HashWithToJSON#to_json"]', JSON.generate([HashWithToJSON.new])
|
|
assert_equal '{"{}":1}', JSON.generate(HashWithToJSON.new => 1)
|
|
end
|
|
|
|
class HashWithToS < Hash
|
|
include CustomToS
|
|
end
|
|
|
|
def test_hash_subclass_with_to_s
|
|
assert_equal '[{}]', JSON.generate([HashWithToS.new])
|
|
assert_equal '{"JSONGeneratorTest::HashWithToS#to_s":1}', JSON.generate(HashWithToS.new => 1)
|
|
end
|
|
|
|
class StringWithToJSON < String
|
|
include CustomToJSON
|
|
end
|
|
|
|
def test_string_subclass_with_to_json
|
|
assert_equal '["JSONGeneratorTest::StringWithToJSON#to_json"]', JSON.generate([StringWithToJSON.new])
|
|
assert_equal '{"":1}', JSON.generate(StringWithToJSON.new => 1)
|
|
end
|
|
|
|
class StringWithToS < String
|
|
include CustomToS
|
|
end
|
|
|
|
def test_string_subclass_with_to_s
|
|
assert_equal '[""]', JSON.generate([StringWithToS.new])
|
|
assert_equal '{"JSONGeneratorTest::StringWithToS#to_s":1}', JSON.generate(StringWithToS.new => 1)
|
|
end
|
|
|
|
def test_string_subclass_with_broken_to_s
|
|
klass = Class.new(String) do
|
|
def to_s
|
|
false
|
|
end
|
|
end
|
|
s = klass.new("test")
|
|
assert_equal '["test"]', JSON.generate([s])
|
|
|
|
omit("Can't figure out how to match behavior in java code") if RUBY_PLATFORM == "java"
|
|
|
|
assert_raise TypeError do
|
|
JSON.generate(s => 1)
|
|
end
|
|
end
|
|
|
|
if defined?(JSON::Ext::Generator) and RUBY_PLATFORM != "java"
|
|
def test_valid_utf8_in_different_encoding
|
|
utf8_string = "€™"
|
|
wrong_encoding_string = utf8_string.b
|
|
# This behavior is historical. Not necessary desirable. We should deprecated it.
|
|
# The pure and java version of the gem already don't behave this way.
|
|
assert_warning(/UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0/) do
|
|
assert_equal utf8_string.to_json, wrong_encoding_string.to_json
|
|
end
|
|
|
|
assert_warning(/UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0/) do
|
|
assert_equal JSON.dump(utf8_string), JSON.dump(wrong_encoding_string)
|
|
end
|
|
end
|
|
|
|
def test_string_ext_included_calls_super
|
|
included = false
|
|
|
|
Module.send(:alias_method, :included_orig, :included)
|
|
Module.send(:remove_method, :included)
|
|
Module.send(:define_method, :included) do |base|
|
|
included_orig(base)
|
|
included = true
|
|
end
|
|
|
|
Class.new(String) do
|
|
include JSON::Ext::Generator::GeneratorMethods::String
|
|
end
|
|
|
|
assert included
|
|
ensure
|
|
if Module.private_method_defined?(:included_orig)
|
|
Module.send(:remove_method, :included) if Module.method_defined?(:included)
|
|
Module.send(:alias_method, :included, :included_orig)
|
|
Module.send(:remove_method, :included_orig)
|
|
end
|
|
end
|
|
end
|
|
|
|
def test_nonutf8_encoding
|
|
assert_equal("\"5\u{b0}\"", "5\xb0".dup.force_encoding(Encoding::ISO_8859_1).to_json)
|
|
end
|
|
|
|
def test_utf8_multibyte
|
|
assert_equal('["foßbar"]', JSON.generate(["foßbar"]))
|
|
assert_equal('"n€ßt€ð2"', JSON.generate("n€ßt€ð2"))
|
|
assert_equal('"\"\u0000\u001f"', JSON.generate("\"\u0000\u001f"))
|
|
end
|
|
|
|
def test_fragment
|
|
fragment = JSON::Fragment.new(" 42")
|
|
assert_equal '{"number": 42}', JSON.generate({ number: fragment })
|
|
assert_equal '{"number": 42}', JSON.generate({ number: fragment }, strict: true)
|
|
end
|
|
|
|
def test_json_generate_as_json_convert_to_proc
|
|
object = Object.new
|
|
assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: :object_id)
|
|
end
|
|
|
|
def test_json_generate_float
|
|
values = [-1.0, 1.0, 0.0, 12.2, 7.5 / 3.2, 12.0, 100.0, 1000.0]
|
|
expecteds = ["-1.0", "1.0", "0.0", "12.2", "2.34375", "12.0", "100.0", "1000.0"]
|
|
|
|
if RUBY_ENGINE == "jruby"
|
|
values << 1746861937.7842371
|
|
expecteds << "1.7468619377842371E9"
|
|
else
|
|
values << 1746861937.7842371
|
|
expecteds << "1746861937.7842371"
|
|
end
|
|
|
|
if RUBY_ENGINE == "ruby"
|
|
values << -2.2471348024634545e-08 << -2.2471348024634545e-09 << -2.2471348024634545e-10
|
|
expecteds << "-0.000000022471348024634545" << "-0.0000000022471348024634545" << "-2.2471348024634546e-10"
|
|
end
|
|
|
|
values.zip(expecteds).each do |value, expected|
|
|
assert_equal expected, value.to_json
|
|
end
|
|
end
|
|
|
|
def test_numbers_of_various_sizes
|
|
numbers = [
|
|
0, 1, -1, 9, -9, 13, -13, 91, -91, 513, -513, 7513, -7513,
|
|
17591, -17591, -4611686018427387904, 4611686018427387903,
|
|
2**62, 2**63, 2**64, -(2**62), -(2**63), -(2**64)
|
|
]
|
|
|
|
numbers.each do |number|
|
|
assert_equal "[#{number}]", JSON.generate([number])
|
|
end
|
|
end
|
|
end
|