summaryrefslogtreecommitdiff
path: root/test/json/test_json_generator.rb
diff options
context:
space:
mode:
Diffstat (limited to 'test/json/test_json_generator.rb')
-rw-r--r--test/json/test_json_generator.rb389
1 files changed, 389 insertions, 0 deletions
diff --git a/test/json/test_json_generator.rb b/test/json/test_json_generator.rb
new file mode 100644
index 00000000000..05235b39db6
--- /dev/null
+++ b/test/json/test_json_generator.rb
@@ -0,0 +1,389 @@
+#!/usr/bin/env ruby
+# encoding: utf-8
+# frozen_string_literal: false
+
+require 'test_helper'
+
+class TestJSONGenerator < 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 = <<'EOT'.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
+}
+EOT
+ 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_generate_pretty
+ 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(<<'EOT'.chomp, json)
+{
+ "1": 2
+}
+EOT
+ parsed_json = parse(json)
+ assert_equal({"1"=>2}, parsed_json)
+ assert_equal '666', pretty_generate(666)
+ 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(<<'EOT'.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>}
+}
+EOT
+ end
+
+ def test_fast_generate
+ 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
+
+ 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_pretty_state
+ state = PRETTY_STATE_PROTOTYPE.dup
+ assert_equal({
+ :allow_nan => false,
+ :array_nl => "\n",
+ :ascii_only => false,
+ :buffer_initial_length => 1024,
+ :depth => 0,
+ :indent => " ",
+ :max_nesting => 100,
+ :object_nl => "\n",
+ :space => " ",
+ :space_before => "",
+ }.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s })
+ end
+
+ def test_safe_state
+ state = SAFE_STATE_PROTOTYPE.dup
+ assert_equal({
+ :allow_nan => false,
+ :array_nl => "",
+ :ascii_only => false,
+ :buffer_initial_length => 1024,
+ :depth => 0,
+ :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_fast_state
+ state = FAST_STATE_PROTOTYPE.dup
+ assert_equal({
+ :allow_nan => false,
+ :array_nl => "",
+ :ascii_only => false,
+ :buffer_initial_length => 1024,
+ :depth => 0,
+ :indent => "",
+ :max_nesting => 0,
+ :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_raise(GeneratorError) { generate([JSON::NaN]) }
+ 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)
+ assert_raise(GeneratorError) { generate([JSON::Infinity]) }
+ 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)
+ assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) }
+ 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
+
+ def test_depth
+ ary = []; ary << ary
+ assert_equal 0, JSON::SAFE_STATE_PROTOTYPE.depth
+ assert_raise(JSON::NestingError) { generate(ary) }
+ assert_equal 0, JSON::SAFE_STATE_PROTOTYPE.depth
+ assert_equal 0, JSON::PRETTY_STATE_PROTOTYPE.depth
+ assert_raise(JSON::NestingError) { JSON.pretty_generate(ary) }
+ assert_equal 0, JSON::PRETTY_STATE_PROTOTYPE.depth
+ 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
+ if respond_to?(:assert_in_out_err)
+ assert_in_out_err(%w[-rjson --disable-gems], <<-EOS, [], [])
+ 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
+ EOS
+ end
+ end if GC.respond_to?(:stress=)
+
+ 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'
+ assert_raise(TypeError) do
+ state.configure(foo)
+ end
+ def foo.to_h
+ { :indent => '2' }
+ end
+ state.configure(foo)
+ assert_equal '2', state.indent
+ end
+
+ if defined?(JSON::Ext::Generator)
+ def test_broken_bignum # [ruby-core:38867]
+ pid = fork do
+ Bignum.class_eval do
+ def to_s
+ end
+ end
+ begin
+ JSON::Ext::Generator::State.new.generate(1<<64)
+ 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
+ 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_generate
+ assert_raise JSON::GeneratorError do
+ assert_equal true, generate(["\xea"])
+ 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 = [ '\\"' ]
+ json = '["\\\\\""]'
+ assert_equal json, generate(data)
+ #
+ data = [ '/' ]
+ json = '["/"]'
+ assert_equal json, generate(data)
+ #
+ data = ['"']
+ json = '["\""]'
+ assert_equal json, generate(data)
+ #
+ data = ["'"]
+ json = '["\\\'"]'
+ assert_equal '["\'"]', 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
+
+ if EnvUtil.gc_stress_to_class?
+ def assert_no_memory_leak(code, *rest, **opt)
+ code = "8.times {20_000.times {begin #{code}; rescue NoMemoryError; end}; GC.start}"
+ super(["-rjson/ext/generator"],
+ "GC.add_stress_to_class(JSON::Ext::Generator::State); "\
+ "#{code}", code, *rest, rss: true, limit: 1.1, **opt)
+ end
+
+ def test_no_memory_leak_allocate
+ assert_no_memory_leak("JSON::Ext::Generator::State.allocate")
+ end
+ end
+end