oj 3.7.4 → 3.13.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1352 -0
  3. data/README.md +29 -8
  4. data/RELEASE_NOTES.md +61 -0
  5. data/ext/oj/buf.h +53 -72
  6. data/ext/oj/cache.c +326 -0
  7. data/ext/oj/cache.h +21 -0
  8. data/ext/oj/cache8.c +61 -64
  9. data/ext/oj/cache8.h +12 -39
  10. data/ext/oj/circarray.c +37 -43
  11. data/ext/oj/circarray.h +16 -17
  12. data/ext/oj/code.c +165 -179
  13. data/ext/oj/code.h +27 -29
  14. data/ext/oj/compat.c +174 -194
  15. data/ext/oj/custom.c +809 -866
  16. data/ext/oj/debug.c +132 -0
  17. data/ext/oj/dump.c +848 -863
  18. data/ext/oj/dump.h +81 -67
  19. data/ext/oj/dump_compat.c +85 -123
  20. data/ext/oj/dump_leaf.c +100 -188
  21. data/ext/oj/dump_object.c +527 -656
  22. data/ext/oj/dump_strict.c +315 -338
  23. data/ext/oj/encode.h +7 -34
  24. data/ext/oj/encoder.c +43 -0
  25. data/ext/oj/err.c +40 -29
  26. data/ext/oj/err.h +48 -48
  27. data/ext/oj/extconf.rb +17 -4
  28. data/ext/oj/fast.c +1070 -1087
  29. data/ext/oj/intern.c +301 -0
  30. data/ext/oj/intern.h +26 -0
  31. data/ext/oj/mimic_json.c +469 -436
  32. data/ext/oj/object.c +525 -593
  33. data/ext/oj/odd.c +154 -138
  34. data/ext/oj/odd.h +37 -38
  35. data/ext/oj/oj.c +1325 -986
  36. data/ext/oj/oj.h +333 -316
  37. data/ext/oj/parse.c +1002 -846
  38. data/ext/oj/parse.h +92 -87
  39. data/ext/oj/parser.c +1557 -0
  40. data/ext/oj/parser.h +91 -0
  41. data/ext/oj/rails.c +888 -878
  42. data/ext/oj/rails.h +11 -14
  43. data/ext/oj/reader.c +141 -147
  44. data/ext/oj/reader.h +73 -89
  45. data/ext/oj/resolve.c +41 -62
  46. data/ext/oj/resolve.h +7 -9
  47. data/ext/oj/rxclass.c +71 -75
  48. data/ext/oj/rxclass.h +18 -19
  49. data/ext/oj/saj.c +443 -486
  50. data/ext/oj/saj2.c +602 -0
  51. data/ext/oj/scp.c +88 -113
  52. data/ext/oj/sparse.c +787 -709
  53. data/ext/oj/stream_writer.c +133 -159
  54. data/ext/oj/strict.c +127 -118
  55. data/ext/oj/string_writer.c +230 -249
  56. data/ext/oj/trace.c +34 -41
  57. data/ext/oj/trace.h +19 -19
  58. data/ext/oj/usual.c +1254 -0
  59. data/ext/oj/util.c +136 -0
  60. data/ext/oj/util.h +20 -0
  61. data/ext/oj/val_stack.c +59 -67
  62. data/ext/oj/val_stack.h +91 -129
  63. data/ext/oj/validate.c +46 -0
  64. data/ext/oj/wab.c +342 -353
  65. data/lib/oj/bag.rb +1 -0
  66. data/lib/oj/easy_hash.rb +5 -4
  67. data/lib/oj/error.rb +1 -1
  68. data/lib/oj/json.rb +1 -1
  69. data/lib/oj/mimic.rb +48 -14
  70. data/lib/oj/saj.rb +20 -6
  71. data/lib/oj/state.rb +8 -7
  72. data/lib/oj/version.rb +2 -2
  73. data/lib/oj.rb +0 -8
  74. data/pages/Compatibility.md +1 -1
  75. data/pages/JsonGem.md +15 -0
  76. data/pages/Modes.md +53 -46
  77. data/pages/Options.md +72 -11
  78. data/pages/Parser.md +309 -0
  79. data/pages/Rails.md +73 -22
  80. data/pages/Security.md +1 -1
  81. data/test/activerecord/result_test.rb +7 -2
  82. data/test/activesupport5/abstract_unit.rb +45 -0
  83. data/test/activesupport5/decoding_test.rb +68 -60
  84. data/test/activesupport5/encoding_test.rb +111 -96
  85. data/test/activesupport5/encoding_test_cases.rb +33 -25
  86. data/test/activesupport5/test_helper.rb +43 -21
  87. data/test/activesupport5/time_zone_test_helpers.rb +18 -3
  88. data/test/activesupport6/abstract_unit.rb +44 -0
  89. data/test/activesupport6/decoding_test.rb +133 -0
  90. data/test/activesupport6/encoding_test.rb +507 -0
  91. data/test/activesupport6/encoding_test_cases.rb +98 -0
  92. data/test/activesupport6/test_common.rb +17 -0
  93. data/test/activesupport6/test_helper.rb +163 -0
  94. data/test/activesupport6/time_zone_test_helpers.rb +39 -0
  95. data/test/activesupport7/abstract_unit.rb +49 -0
  96. data/test/activesupport7/decoding_test.rb +125 -0
  97. data/test/activesupport7/encoding_test.rb +486 -0
  98. data/test/activesupport7/encoding_test_cases.rb +104 -0
  99. data/test/activesupport7/time_zone_test_helpers.rb +47 -0
  100. data/test/bar.rb +6 -12
  101. data/test/baz.rb +16 -0
  102. data/test/bug.rb +16 -0
  103. data/test/foo.rb +69 -75
  104. data/test/helper.rb +16 -0
  105. data/test/json_gem/json_common_interface_test.rb +8 -3
  106. data/test/json_gem/json_generator_test.rb +18 -4
  107. data/test/json_gem/json_parser_test.rb +9 -0
  108. data/test/json_gem/test_helper.rb +12 -0
  109. data/test/mem.rb +33 -0
  110. data/test/perf.rb +1 -1
  111. data/test/perf_dump.rb +50 -0
  112. data/test/perf_once.rb +58 -0
  113. data/test/perf_parser.rb +189 -0
  114. data/test/perf_scp.rb +11 -10
  115. data/test/perf_strict.rb +17 -23
  116. data/test/prec.rb +23 -0
  117. data/test/sample_json.rb +1 -1
  118. data/test/test_compat.rb +46 -10
  119. data/test/test_custom.rb +147 -8
  120. data/test/test_fast.rb +62 -2
  121. data/test/test_file.rb +25 -2
  122. data/test/test_gc.rb +13 -0
  123. data/test/test_generate.rb +21 -0
  124. data/test/test_hash.rb +11 -1
  125. data/test/test_integer_range.rb +7 -2
  126. data/test/test_object.rb +85 -9
  127. data/test/test_parser.rb +27 -0
  128. data/test/test_parser_saj.rb +335 -0
  129. data/test/test_parser_usual.rb +217 -0
  130. data/test/test_rails.rb +35 -0
  131. data/test/test_saj.rb +1 -1
  132. data/test/test_scp.rb +5 -5
  133. data/test/test_strict.rb +26 -1
  134. data/test/test_various.rb +87 -65
  135. data/test/test_wab.rb +2 -0
  136. data/test/test_writer.rb +19 -2
  137. data/test/tests.rb +1 -1
  138. data/test/zoo.rb +13 -0
  139. metadata +60 -110
  140. data/ext/oj/hash.c +0 -163
  141. data/ext/oj/hash.h +0 -46
  142. data/ext/oj/hash_test.c +0 -512
@@ -0,0 +1,507 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require_relative "abstract_unit"
5
+ require "active_support/core_ext/string/inflections"
6
+ require "active_support/json"
7
+ require "active_support/time"
8
+ require_relative "time_zone_test_helpers"
9
+ require_relative "encoding_test_cases"
10
+
11
+ require 'oj'
12
+
13
+ # Sets the ActiveSupport encoder to be Oj and also wraps the setting of
14
+ # globals.
15
+ Oj::Rails.set_encoder()
16
+ #Oj::Rails.optimize(Hash, Array, BigDecimal, Time, Range, Regexp, ActiveSupport::TimeWithZone)
17
+ Oj::Rails.optimize()
18
+
19
+ class TestJSONEncoding < ActiveSupport::TestCase
20
+ include TimeZoneTestHelpers
21
+
22
+ # Added for testing if Oj is used.
23
+ test "oj is used as an encoder" do
24
+ assert_equal ActiveSupport.json_encoder, Oj::Rails::Encoder
25
+ end
26
+
27
+ def sorted_json(json)
28
+ if json.start_with?("{") && json.end_with?("}")
29
+ "{" + json[1..-2].split(",").sort.join(",") + "}"
30
+ else
31
+ json
32
+ end
33
+ end
34
+
35
+ JSONTest::EncodingTestCases.constants.each do |class_tests|
36
+ define_method("test_#{class_tests[0..-6].underscore}") do
37
+ prev = ActiveSupport.use_standard_json_time_format
38
+
39
+ standard_class_tests = /Standard/.match?(class_tests)
40
+
41
+ ActiveSupport.escape_html_entities_in_json = !standard_class_tests
42
+ ActiveSupport.use_standard_json_time_format = standard_class_tests
43
+ JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
44
+ assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
45
+ end
46
+ ensure
47
+ ActiveSupport.escape_html_entities_in_json = false
48
+ ActiveSupport.use_standard_json_time_format = prev
49
+ end
50
+ end
51
+
52
+ def test_process_status
53
+ rubinius_skip "https://github.com/rubinius/rubinius/issues/3334"
54
+
55
+ # There doesn't seem to be a good way to get a handle on a Process::Status object without actually
56
+ # creating a child process, hence this to populate $?
57
+ system("not_a_real_program_#{SecureRandom.hex}")
58
+ assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?)
59
+ end
60
+
61
+ def test_hash_encoding
62
+ assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(a: :b)
63
+ assert_equal %({\"a\":1}), ActiveSupport::JSON.encode("a" => 1)
64
+ assert_equal %({\"a\":[1,2]}), ActiveSupport::JSON.encode("a" => [1, 2])
65
+ assert_equal %({"1":2}), ActiveSupport::JSON.encode(1 => 2)
66
+
67
+ assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(a: :b, c: :d))
68
+ end
69
+
70
+ def test_hash_keys_encoding
71
+ ActiveSupport.escape_html_entities_in_json = true
72
+ assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>")
73
+ ensure
74
+ ActiveSupport.escape_html_entities_in_json = false
75
+ end
76
+
77
+ def test_utf8_string_encoded_properly
78
+ # The original test seems to expect that
79
+ # ActiveSupport.escape_html_entities_in_json reverts to true even after
80
+ # being set to false. I haven't been able to figure that out so the value is
81
+ # set to true, the default, before running the test. This might be wrong but
82
+ # for now it will have to do.
83
+ ActiveSupport.escape_html_entities_in_json = true
84
+ result = ActiveSupport::JSON.encode("€2.99")
85
+ assert_equal '"€2.99"', result
86
+ assert_equal(Encoding::UTF_8, result.encoding)
87
+
88
+ result = ActiveSupport::JSON.encode("✎☺")
89
+ assert_equal '"✎☺"', result
90
+ assert_equal(Encoding::UTF_8, result.encoding)
91
+ end
92
+
93
+ def test_non_utf8_string_transcodes
94
+ s = "二".encode("Shift_JIS")
95
+ result = ActiveSupport::JSON.encode(s)
96
+ assert_equal '"二"', result
97
+ assert_equal Encoding::UTF_8, result.encoding
98
+ end
99
+
100
+ def test_wide_utf8_chars
101
+ w = "𠜎"
102
+ result = ActiveSupport::JSON.encode(w)
103
+ assert_equal '"𠜎"', result
104
+ end
105
+
106
+ def test_wide_utf8_roundtrip
107
+ hash = { string: "𐒑" }
108
+ json = ActiveSupport::JSON.encode(hash)
109
+ decoded_hash = ActiveSupport::JSON.decode(json)
110
+ assert_equal "𐒑", decoded_hash["string"]
111
+ end
112
+
113
+ def test_hash_key_identifiers_are_always_quoted
114
+ values = { 0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B" }
115
+ assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values))
116
+ end
117
+
118
+ def test_hash_should_allow_key_filtering_with_only
119
+ assert_equal %({"a":1}), ActiveSupport::JSON.encode({ "a" => 1, :b => 2, :c => 3 }, { only: "a" })
120
+ end
121
+
122
+ def test_hash_should_allow_key_filtering_with_except
123
+ assert_equal %({"b":2}), ActiveSupport::JSON.encode({ "foo" => "bar", :b => 2, :c => 3 }, { except: ["foo", :c] })
124
+ end
125
+
126
+ def test_time_to_json_includes_local_offset
127
+ with_standard_json_time_format(true) do
128
+ with_env_tz "US/Eastern" do
129
+ assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005, 2, 1, 15, 15, 10))
130
+ end
131
+ end
132
+ end
133
+
134
+ def test_hash_with_time_to_json
135
+ with_standard_json_time_format(false) do
136
+ assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { time: Time.utc(2009) }.to_json
137
+ end
138
+ end
139
+
140
+ def test_nested_hash_with_float
141
+ assert_nothing_raised do
142
+ hash = {
143
+ "CHI" => {
144
+ display_name: "chicago",
145
+ latitude: 123.234
146
+ }
147
+ }
148
+ ActiveSupport::JSON.encode(hash)
149
+ end
150
+ end
151
+
152
+ def test_hash_like_with_options
153
+ h = JSONTest::Hashlike.new
154
+ json = h.to_json only: [:foo]
155
+
156
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
157
+ end
158
+
159
+ def test_object_to_json_with_options
160
+ obj = Object.new
161
+ obj.instance_variable_set :@foo, "hello"
162
+ obj.instance_variable_set :@bar, "world"
163
+ json = obj.to_json only: ["foo"]
164
+
165
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
166
+ end
167
+
168
+ def test_struct_to_json_with_options
169
+ struct = Struct.new(:foo, :bar).new
170
+ struct.foo = "hello"
171
+ struct.bar = "world"
172
+ json = struct.to_json only: [:foo]
173
+
174
+ assert_equal({ "foo" => "hello" }, JSON.parse(json))
175
+ end
176
+
177
+ def test_struct_to_json_with_options_nested
178
+ klass = Struct.new(:foo, :bar)
179
+ struct = klass.new "hello", "world"
180
+ parent_struct = klass.new struct, "world"
181
+ json = parent_struct.to_json only: [:foo]
182
+
183
+ assert_equal({ "foo" => { "foo" => "hello" } }, JSON.parse(json))
184
+ end
185
+
186
+
187
+ def test_hash_should_pass_encoding_options_to_children_in_as_json
188
+ person = {
189
+ name: "John",
190
+ address: {
191
+ city: "London",
192
+ country: "UK"
193
+ }
194
+ }
195
+ json = person.as_json only: [:address, :city]
196
+
197
+ assert_equal({ "address" => { "city" => "London" } }, json)
198
+ end
199
+
200
+ def test_hash_should_pass_encoding_options_to_children_in_to_json
201
+ person = {
202
+ name: "John",
203
+ address: {
204
+ city: "London",
205
+ country: "UK"
206
+ }
207
+ }
208
+ json = person.to_json only: [:address, :city]
209
+
210
+ assert_equal(%({"address":{"city":"London"}}), json)
211
+ end
212
+
213
+ def test_array_should_pass_encoding_options_to_children_in_as_json
214
+ people = [
215
+ { name: "John", address: { city: "London", country: "UK" } },
216
+ { name: "Jean", address: { city: "Paris", country: "France" } }
217
+ ]
218
+ json = people.as_json only: [:address, :city]
219
+ expected = [
220
+ { "address" => { "city" => "London" } },
221
+ { "address" => { "city" => "Paris" } }
222
+ ]
223
+
224
+ assert_equal(expected, json)
225
+ end
226
+
227
+ def test_array_should_pass_encoding_options_to_children_in_to_json
228
+ people = [
229
+ { name: "John", address: { city: "London", country: "UK" } },
230
+ { name: "Jean", address: { city: "Paris", country: "France" } }
231
+ ]
232
+ json = people.to_json only: [:address, :city]
233
+
234
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
235
+ end
236
+
237
+ People = Class.new(BasicObject) do
238
+ include Enumerable
239
+ def initialize
240
+ @people = [
241
+ { name: "John", address: { city: "London", country: "UK" } },
242
+ { name: "Jean", address: { city: "Paris", country: "France" } }
243
+ ]
244
+ end
245
+ def each(*, &blk)
246
+ @people.each do |p|
247
+ yield p if blk
248
+ p
249
+ end.each
250
+ end
251
+ end
252
+
253
+ def test_enumerable_should_generate_json_with_as_json
254
+ json = People.new.as_json only: [:address, :city]
255
+ expected = [
256
+ { "address" => { "city" => "London" } },
257
+ { "address" => { "city" => "Paris" } }
258
+ ]
259
+
260
+ assert_equal(expected, json)
261
+ end
262
+
263
+ def test_enumerable_should_generate_json_with_to_json
264
+ json = People.new.to_json only: [:address, :city]
265
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
266
+ end
267
+
268
+ def test_enumerable_should_pass_encoding_options_to_children_in_as_json
269
+ json = People.new.each.as_json only: [:address, :city]
270
+ expected = [
271
+ { "address" => { "city" => "London" } },
272
+ { "address" => { "city" => "Paris" } }
273
+ ]
274
+
275
+ assert_equal(expected, json)
276
+ end
277
+
278
+ def test_enumerable_should_pass_encoding_options_to_children_in_to_json
279
+ json = People.new.each.to_json only: [:address, :city]
280
+
281
+ assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
282
+ end
283
+
284
+ class CustomWithOptions
285
+ attr_accessor :foo, :bar
286
+
287
+ def as_json(options = {})
288
+ options[:only] = %w(foo bar)
289
+ super(options)
290
+ end
291
+ end
292
+
293
+ def test_hash_to_json_should_not_keep_options_around
294
+ f = CustomWithOptions.new
295
+ f.foo = "hello"
296
+ f.bar = "world"
297
+
298
+ hash = { "foo" => f, "other_hash" => { "foo" => "other_foo", "test" => "other_test" } }
299
+ assert_equal({ "foo" => { "foo" => "hello", "bar" => "world" },
300
+ "other_hash" => { "foo" => "other_foo", "test" => "other_test" } }, ActiveSupport::JSON.decode(hash.to_json))
301
+ end
302
+
303
+ def test_array_to_json_should_not_keep_options_around
304
+ f = CustomWithOptions.new
305
+ f.foo = "hello"
306
+ f.bar = "world"
307
+
308
+ array = [f, { "foo" => "other_foo", "test" => "other_test" }]
309
+ assert_equal([{ "foo" => "hello", "bar" => "world" },
310
+ { "foo" => "other_foo", "test" => "other_test" }], ActiveSupport::JSON.decode(array.to_json))
311
+ end
312
+
313
+ class OptionsTest
314
+ def as_json(options = :default)
315
+ options
316
+ end
317
+ end
318
+
319
+ def test_hash_as_json_without_options
320
+ json = { foo: OptionsTest.new }.as_json
321
+ assert_equal({ "foo" => :default }, json)
322
+ end
323
+
324
+ def test_array_as_json_without_options
325
+ json = [ OptionsTest.new ].as_json
326
+ assert_equal([:default], json)
327
+ end
328
+
329
+ def test_struct_encoding
330
+ Struct.new("UserNameAndEmail", :name, :email)
331
+ Struct.new("UserNameAndDate", :name, :date)
332
+ Struct.new("Custom", :name, :sub)
333
+ user_email = Struct::UserNameAndEmail.new "David", "sample@example.com"
334
+ user_birthday = Struct::UserNameAndDate.new "David", Date.new(2010, 01, 01)
335
+ custom = Struct::Custom.new "David", user_birthday
336
+
337
+ json_strings = ""
338
+ json_string_and_date = ""
339
+ json_custom = ""
340
+
341
+ assert_nothing_raised do
342
+ json_strings = user_email.to_json
343
+ json_string_and_date = user_birthday.to_json
344
+ json_custom = custom.to_json
345
+ end
346
+
347
+ assert_equal({ "name" => "David",
348
+ "sub" => {
349
+ "name" => "David",
350
+ "date" => "2010-01-01" } }, ActiveSupport::JSON.decode(json_custom))
351
+
352
+ assert_equal({ "name" => "David", "email" => "sample@example.com" },
353
+ ActiveSupport::JSON.decode(json_strings))
354
+
355
+ assert_equal({ "name" => "David", "date" => "2010-01-01" },
356
+ ActiveSupport::JSON.decode(json_string_and_date))
357
+ end
358
+
359
+ def test_nil_true_and_false_represented_as_themselves
360
+ assert_nil nil.as_json
361
+ assert_equal true, true.as_json
362
+ assert_equal false, false.as_json
363
+ end
364
+
365
+ class HashWithAsJson < Hash
366
+ attr_accessor :as_json_called
367
+
368
+ def initialize(*)
369
+ super
370
+ end
371
+
372
+ def as_json(options = {})
373
+ @as_json_called = true
374
+ super
375
+ end
376
+ end
377
+
378
+ def test_json_gem_dump_by_passing_active_support_encoder
379
+ h = HashWithAsJson.new
380
+ h[:foo] = "hello"
381
+ h[:bar] = "world"
382
+
383
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.dump(h)
384
+ assert_nil h.as_json_called
385
+ end
386
+
387
+ def test_json_gem_generate_by_passing_active_support_encoder
388
+ h = HashWithAsJson.new
389
+ h[:foo] = "hello"
390
+ h[:bar] = "world"
391
+
392
+ assert_equal %({"foo":"hello","bar":"world"}), JSON.generate(h)
393
+ assert_nil h.as_json_called
394
+ end
395
+
396
+ def test_json_gem_pretty_generate_by_passing_active_support_encoder
397
+ h = HashWithAsJson.new
398
+ h[:foo] = "hello"
399
+ h[:bar] = "world"
400
+
401
+ assert_equal <<EXPECTED.chomp, JSON.pretty_generate(h)
402
+ {
403
+ "foo": "hello",
404
+ "bar": "world"
405
+ }
406
+ EXPECTED
407
+ assert_nil h.as_json_called
408
+ end
409
+
410
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_false
411
+ with_standard_json_time_format(false) do
412
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
413
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
414
+ assert_equal "\"1999/12/31 19:00:00 -0500\"", ActiveSupport::JSON.encode(time)
415
+ end
416
+ end
417
+
418
+ def test_twz_to_json_with_use_standard_json_time_format_config_set_to_true
419
+ with_standard_json_time_format(true) do
420
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
421
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
422
+ assert_equal "\"1999-12-31T19:00:00.000-05:00\"", ActiveSupport::JSON.encode(time)
423
+ end
424
+ end
425
+
426
+ def test_twz_to_json_with_custom_time_precision
427
+ with_standard_json_time_format(true) do
428
+ with_time_precision(0) do
429
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
430
+ time = ActiveSupport::TimeWithZone.new(Time.utc(2000), zone)
431
+ assert_equal "\"1999-12-31T19:00:00-05:00\"", ActiveSupport::JSON.encode(time)
432
+ end
433
+ end
434
+ end
435
+
436
+ def test_time_to_json_with_custom_time_precision
437
+ with_standard_json_time_format(true) do
438
+ with_time_precision(0) do
439
+ assert_equal "\"2000-01-01T00:00:00Z\"", ActiveSupport::JSON.encode(Time.utc(2000))
440
+ end
441
+ end
442
+ end
443
+
444
+ def test_datetime_to_json_with_custom_time_precision
445
+ with_standard_json_time_format(true) do
446
+ with_time_precision(0) do
447
+ assert_equal "\"2000-01-01T00:00:00+00:00\"", ActiveSupport::JSON.encode(DateTime.new(2000))
448
+ end
449
+ end
450
+ end
451
+
452
+ def test_twz_to_json_when_wrapping_a_date_time
453
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
454
+ time = ActiveSupport::TimeWithZone.new(DateTime.new(2000), zone)
455
+ assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
456
+ end
457
+
458
+ def test_exception_to_json
459
+ exception = Exception.new("foo")
460
+ assert_equal '"foo"', ActiveSupport::JSON.encode(exception)
461
+ end
462
+
463
+ class InfiniteNumber
464
+ def as_json(options = nil)
465
+ { "number" => Float::INFINITY }
466
+ end
467
+ end
468
+
469
+ def test_to_json_works_when_as_json_returns_infinite_number
470
+ assert_equal '{"number":null}', InfiniteNumber.new.to_json
471
+ end
472
+
473
+ class NaNNumber
474
+ def as_json(options = nil)
475
+ { "number" => Float::NAN }
476
+ end
477
+ end
478
+
479
+ def test_to_json_works_when_as_json_returns_NaN_number
480
+ assert_equal '{"number":null}', NaNNumber.new.to_json
481
+ end
482
+
483
+ def test_to_json_works_on_io_objects
484
+ assert_equal STDOUT.to_s.to_json, STDOUT.to_json
485
+ end
486
+
487
+ private
488
+
489
+ def object_keys(json_object)
490
+ json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort
491
+ end
492
+
493
+ def with_standard_json_time_format(boolean = true)
494
+ old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, boolean
495
+ yield
496
+ ensure
497
+ ActiveSupport.use_standard_json_time_format = old
498
+ end
499
+
500
+ def with_time_precision(value)
501
+ old_value = ActiveSupport::JSON::Encoding.time_precision
502
+ ActiveSupport::JSON::Encoding.time_precision = value
503
+ yield
504
+ ensure
505
+ ActiveSupport::JSON::Encoding.time_precision = old_value
506
+ end
507
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+ require "date"
5
+ require "time"
6
+ require "pathname"
7
+ require "uri"
8
+
9
+ module JSONTest
10
+ class Foo
11
+ def initialize(a, b)
12
+ @a, @b = a, b
13
+ end
14
+ end
15
+
16
+ class Hashlike
17
+ def to_hash
18
+ { foo: "hello", bar: "world" }
19
+ end
20
+ end
21
+
22
+ class Custom
23
+ def initialize(serialized)
24
+ @serialized = serialized
25
+ end
26
+
27
+ def as_json(options = nil)
28
+ @serialized
29
+ end
30
+ end
31
+
32
+ MyStruct = Struct.new(:name, :value) do
33
+ def initialize(*)
34
+ @unused = "unused instance variable"
35
+ super
36
+ end
37
+ end
38
+
39
+ module EncodingTestCases
40
+ TrueTests = [[ true, %(true) ]]
41
+ FalseTests = [[ false, %(false) ]]
42
+ NilTests = [[ nil, %(null) ]]
43
+ NumericTests = [[ 1, %(1) ],
44
+ [ 2.5, %(2.5) ],
45
+ [ 0.0 / 0.0, %(null) ],
46
+ [ 1.0 / 0.0, %(null) ],
47
+ [ -1.0 / 0.0, %(null) ],
48
+ [ BigDecimal("0.0") / BigDecimal("0.0"), %(null) ],
49
+ [ BigDecimal("2.5"), %("#{BigDecimal('2.5')}") ]]
50
+
51
+ StringTests = [[ "this is the <string>", %("this is the \\u003cstring\\u003e")],
52
+ [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
53
+ [ "http://test.host/posts/1", %("http://test.host/posts/1")],
54
+ [ "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",
55
+ %("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") ]]
56
+
57
+ ArrayTests = [[ ["a", "b", "c"], %([\"a\",\"b\",\"c\"]) ],
58
+ [ [1, "a", :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
59
+
60
+ HashTests = [[ { foo: "bar" }, %({\"foo\":\"bar\"}) ],
61
+ [ { 1 => 1, 2 => "a", 3 => :b, 4 => nil, 5 => false }, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]]
62
+
63
+ RangeTests = [[ 1..2, %("1..2")],
64
+ [ 1...2, %("1...2")],
65
+ [ 1.5..2.5, %("1.5..2.5")]]
66
+
67
+ SymbolTests = [[ :a, %("a") ],
68
+ [ :this, %("this") ],
69
+ [ :"a b", %("a b") ]]
70
+
71
+ ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
72
+ HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
73
+ StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ],
74
+ [ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]]
75
+ CustomTests = [[ Custom.new("custom"), '"custom"' ],
76
+ [ Custom.new(nil), "null" ],
77
+ [ Custom.new(:a), '"a"' ],
78
+ [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
79
+ [ Custom.new(foo: "hello", bar: "world"), '{"bar":"world","foo":"hello"}' ],
80
+ [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
81
+ [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
82
+
83
+ RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
84
+
85
+ URITests = [[ URI.parse("http://example.com"), %("http://example.com") ]]
86
+
87
+ PathnameTests = [[ Pathname.new("lib/index.rb"), %("lib/index.rb") ]]
88
+
89
+ DateTests = [[ Date.new(2005, 2, 1), %("2005/02/01") ]]
90
+ TimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]]
91
+ DateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005/02/01 15:15:10 +0000") ]]
92
+
93
+ StandardDateTests = [[ Date.new(2005, 2, 1), %("2005-02-01") ]]
94
+ StandardTimeTests = [[ Time.utc(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000Z") ]]
95
+ StandardDateTimeTests = [[ DateTime.civil(2005, 2, 1, 15, 15, 10), %("2005-02-01T15:15:10.000+00:00") ]]
96
+ StandardStringTests = [[ "this is the <string>", %("this is the <string>")]]
97
+ end
98
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ if ENV["BUILDKITE"]
4
+ require "minitest/reporters"
5
+ require "fileutils"
6
+
7
+ module Minitest
8
+ def self.plugin_rails_ci_junit_format_test_report_for_buildkite_init(*)
9
+ dir = File.join(__dir__, "../test-reports/#{ENV['BUILDKITE_JOB_ID']}")
10
+ reporter << Minitest::Reporters::JUnitReporter.new(dir, false)
11
+ FileUtils.mkdir_p(dir)
12
+ end
13
+ end
14
+
15
+ Minitest.load_plugins
16
+ Minitest.extensions.unshift "rails_ci_junit_format_test_report_for_buildkite"
17
+ end