json-stream 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module JSON
4
4
  module Stream
5
- VERSION = '0.1.3'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
@@ -0,0 +1,103 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'json/stream'
4
+ require 'minitest/autorun'
5
+
6
+ describe JSON::Stream::Buffer do
7
+ subject { JSON::Stream::Buffer.new }
8
+
9
+ it 'accepts single byte characters' do
10
+ assert_equal "", subject << ""
11
+ assert_equal "abc", subject << "abc"
12
+ assert_equal "\u0000abc", subject << "\u0000abc"
13
+ end
14
+
15
+ # The é character can be a single codepoint \u00e9 or two codepoints
16
+ # \u0065\u0301. The first is encoded in 2 bytes, the second in 3 bytes.
17
+ # The json and yajl-ruby gems and CouchDB do not normalize unicode text
18
+ # so neither will we. Although, a good way to normalize is by calling
19
+ # ActiveSupport::Multibyte::Chars.new("é").normalize(:c).
20
+ it 'accepts combined characters' do
21
+ assert_equal "\u0065\u0301", subject << "\u0065\u0301"
22
+ assert_equal 3, (subject << "\u0065\u0301").bytesize
23
+ assert_equal 2, (subject << "\u0065\u0301").size
24
+
25
+ assert_equal "\u00e9", subject << "\u00e9"
26
+ assert_equal 2, (subject << "\u00e9").bytesize
27
+ assert_equal 1, (subject << "\u00e9").size
28
+ end
29
+
30
+ it 'accepts valid two byte characters' do
31
+ assert_equal "abcé", subject << "abcé"
32
+ assert_equal "a", subject << "a\xC3"
33
+ assert_equal "é", subject << "\xA9"
34
+ assert_equal "", subject << "\xC3"
35
+ assert_equal "é", subject << "\xA9"
36
+ assert_equal "é", subject << "\xC3\xA9"
37
+ end
38
+
39
+ it 'accepts valid three byte characters' do
40
+ assert_equal "abcé\u2603", subject << "abcé\u2603"
41
+ assert_equal "a", subject << "a\xE2"
42
+ assert_equal "", subject << "\x98"
43
+ assert_equal "\u2603", subject << "\x83"
44
+ end
45
+
46
+ it 'accepts valid four byte characters' do
47
+ assert_equal "abcé\u2603\u{10102}é", subject << "abcé\u2603\u{10102}é"
48
+ assert_equal "a", subject << "a\xF0"
49
+ assert_equal "", subject << "\x90"
50
+ assert_equal "", subject << "\x84"
51
+ assert_equal "\u{10102}", subject << "\x82"
52
+ end
53
+
54
+ it 'rejects invalid two byte start characters' do
55
+ -> { subject << "\xC3\xC3" }.must_raise JSON::Stream::ParserError
56
+ end
57
+
58
+ it 'rejects invalid three byte start characters' do
59
+ -> { subject << "\xE2\xE2" }.must_raise JSON::Stream::ParserError
60
+ end
61
+
62
+ it 'rejects invalid four byte start characters' do
63
+ -> { subject << "\xF0\xF0" }.must_raise JSON::Stream::ParserError
64
+ end
65
+
66
+ it 'rejects a two byte start with single byte continuation character' do
67
+ -> { subject << "\xC3\u0000" }.must_raise JSON::Stream::ParserError
68
+ end
69
+
70
+ it 'rejects a three byte start with single byte continuation character' do
71
+ -> { subject << "\xE2\u0010" }.must_raise JSON::Stream::ParserError
72
+ end
73
+
74
+ it 'rejects a four byte start with single byte continuation character' do
75
+ -> { subject << "\xF0a" }.must_raise JSON::Stream::ParserError
76
+ end
77
+
78
+ it 'rejects an invalid continuation character' do
79
+ -> { subject << "\xA9" }.must_raise JSON::Stream::ParserError
80
+ end
81
+
82
+ it 'rejects an overlong form' do
83
+ -> { subject << "\xC0\x80" }.must_raise JSON::Stream::ParserError
84
+ end
85
+
86
+ describe 'checking for empty buffers' do
87
+ it 'is initially empty' do
88
+ assert subject.empty?
89
+ end
90
+
91
+ it 'is empty after processing complete characters' do
92
+ subject << 'test'
93
+ assert subject.empty?
94
+ end
95
+
96
+ it 'is not empty after processing partial multi-byte characters' do
97
+ subject << "\xC3"
98
+ refute subject.empty?
99
+ subject << "\xA9"
100
+ assert subject.empty?
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,157 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'json/stream'
4
+ require 'minitest/autorun'
5
+
6
+ describe JSON::Stream::Builder do
7
+ let(:parser) { JSON::Stream::Parser.new }
8
+ subject { JSON::Stream::Builder.new(parser) }
9
+
10
+ it 'builds a false value' do
11
+ assert_nil subject.result
12
+ subject.start_document
13
+ subject.value(false)
14
+ assert_nil subject.result
15
+ subject.end_document
16
+ assert_equal false, subject.result
17
+ end
18
+
19
+ it 'builds a string value' do
20
+ assert_nil subject.result
21
+ subject.start_document
22
+ subject.value("test")
23
+ assert_nil subject.result
24
+ subject.end_document
25
+ assert_equal "test", subject.result
26
+ end
27
+
28
+ it 'builds an empty array' do
29
+ assert_nil subject.result
30
+ subject.start_document
31
+ subject.start_array
32
+ subject.end_array
33
+ assert_nil subject.result
34
+ subject.end_document
35
+ assert_equal [], subject.result
36
+ end
37
+
38
+ it 'builds an array of numbers' do
39
+ subject.start_document
40
+ subject.start_array
41
+ subject.value(1)
42
+ subject.value(2)
43
+ subject.value(3)
44
+ subject.end_array
45
+ subject.end_document
46
+ assert_equal [1, 2, 3], subject.result
47
+ end
48
+
49
+ it 'builds nested empty arrays' do
50
+ subject.start_document
51
+ subject.start_array
52
+ subject.start_array
53
+ subject.end_array
54
+ subject.end_array
55
+ subject.end_document
56
+ assert_equal [[]], subject.result
57
+ end
58
+
59
+ it 'builds nested arrays of numbers' do
60
+ subject.start_document
61
+ subject.start_array
62
+ subject.value(1)
63
+ subject.start_array
64
+ subject.value(2)
65
+ subject.end_array
66
+ subject.value(3)
67
+ subject.end_array
68
+ subject.end_document
69
+ assert_equal [1, [2], 3], subject.result
70
+ end
71
+
72
+ it 'builds an empty object' do
73
+ subject.start_document
74
+ subject.start_object
75
+ subject.end_object
76
+ subject.end_document
77
+ assert_equal({}, subject.result)
78
+ end
79
+
80
+ it 'builds a complex object' do
81
+ subject.start_document
82
+ subject.start_object
83
+ subject.key("k1")
84
+ subject.value(1)
85
+ subject.key("k2")
86
+ subject.value(nil)
87
+ subject.key("k3")
88
+ subject.value(true)
89
+ subject.key("k4")
90
+ subject.value(false)
91
+ subject.key("k5")
92
+ subject.value("string value")
93
+ subject.end_object
94
+ subject.end_document
95
+ expected = {
96
+ "k1" => 1,
97
+ "k2" => nil,
98
+ "k3" => true,
99
+ "k4" => false,
100
+ "k5" => "string value"
101
+ }
102
+ assert_equal expected, subject.result
103
+ end
104
+
105
+ it 'builds a nested object' do
106
+ subject.start_document
107
+ subject.start_object
108
+ subject.key("k1")
109
+ subject.value(1)
110
+
111
+ subject.key("k2")
112
+ subject.start_object
113
+ subject.end_object
114
+
115
+ subject.key("k3")
116
+ subject.start_object
117
+ subject.key("sub1")
118
+ subject.start_array
119
+ subject.value(12)
120
+ subject.end_array
121
+ subject.end_object
122
+
123
+ subject.key("k4")
124
+ subject.start_array
125
+ subject.value(1)
126
+ subject.start_object
127
+ subject.key("sub2")
128
+ subject.start_array
129
+ subject.value(nil)
130
+ subject.end_array
131
+ subject.end_object
132
+ subject.end_array
133
+
134
+ subject.key("k5")
135
+ subject.value("string value")
136
+ subject.end_object
137
+ subject.end_document
138
+ expected = {
139
+ "k1" => 1,
140
+ "k2" => {},
141
+ "k3" => {"sub1" => [12]},
142
+ "k4" => [1, {"sub2" => [nil]}],
143
+ "k5" => "string value"
144
+ }
145
+ assert_equal expected, subject.result
146
+ end
147
+
148
+ it 'builds a real document' do
149
+ refute_nil subject
150
+ parser << File.read('spec/fixtures/repository.json')
151
+ refute_nil subject.result
152
+ assert_equal 'rails', subject.result['name']
153
+ assert_equal 4223, subject.result['owner']['id']
154
+ assert_equal false, subject.result['fork']
155
+ assert_equal nil, subject.result['mirror_url']
156
+ end
157
+ end
@@ -0,0 +1,107 @@
1
+ {
2
+ "id": 8514,
3
+ "name": "rails",
4
+ "full_name": "rails/rails",
5
+ "owner": {
6
+ "login": "rails",
7
+ "id": 4223,
8
+ "avatar_url": "https://avatars.githubusercontent.com/u/4223?",
9
+ "gravatar_id": "30f39a09e233e8369dddf6feb4be0308",
10
+ "url": "https://api.github.com/users/rails",
11
+ "html_url": "https://github.com/rails",
12
+ "followers_url": "https://api.github.com/users/rails/followers",
13
+ "following_url": "https://api.github.com/users/rails/following{/other_user}",
14
+ "gists_url": "https://api.github.com/users/rails/gists{/gist_id}",
15
+ "starred_url": "https://api.github.com/users/rails/starred{/owner}{/repo}",
16
+ "subscriptions_url": "https://api.github.com/users/rails/subscriptions",
17
+ "organizations_url": "https://api.github.com/users/rails/orgs",
18
+ "repos_url": "https://api.github.com/users/rails/repos",
19
+ "events_url": "https://api.github.com/users/rails/events{/privacy}",
20
+ "received_events_url": "https://api.github.com/users/rails/received_events",
21
+ "type": "Organization",
22
+ "site_admin": false
23
+ },
24
+ "private": false,
25
+ "html_url": "https://github.com/rails/rails",
26
+ "description": "Ruby on Rails",
27
+ "fork": false,
28
+ "url": "https://api.github.com/repos/rails/rails",
29
+ "forks_url": "https://api.github.com/repos/rails/rails/forks",
30
+ "keys_url": "https://api.github.com/repos/rails/rails/keys{/key_id}",
31
+ "collaborators_url": "https://api.github.com/repos/rails/rails/collaborators{/collaborator}",
32
+ "teams_url": "https://api.github.com/repos/rails/rails/teams",
33
+ "hooks_url": "https://api.github.com/repos/rails/rails/hooks",
34
+ "issue_events_url": "https://api.github.com/repos/rails/rails/issues/events{/number}",
35
+ "events_url": "https://api.github.com/repos/rails/rails/events",
36
+ "assignees_url": "https://api.github.com/repos/rails/rails/assignees{/user}",
37
+ "branches_url": "https://api.github.com/repos/rails/rails/branches{/branch}",
38
+ "tags_url": "https://api.github.com/repos/rails/rails/tags",
39
+ "blobs_url": "https://api.github.com/repos/rails/rails/git/blobs{/sha}",
40
+ "git_tags_url": "https://api.github.com/repos/rails/rails/git/tags{/sha}",
41
+ "git_refs_url": "https://api.github.com/repos/rails/rails/git/refs{/sha}",
42
+ "trees_url": "https://api.github.com/repos/rails/rails/git/trees{/sha}",
43
+ "statuses_url": "https://api.github.com/repos/rails/rails/statuses/{sha}",
44
+ "languages_url": "https://api.github.com/repos/rails/rails/languages",
45
+ "stargazers_url": "https://api.github.com/repos/rails/rails/stargazers",
46
+ "contributors_url": "https://api.github.com/repos/rails/rails/contributors",
47
+ "subscribers_url": "https://api.github.com/repos/rails/rails/subscribers",
48
+ "subscription_url": "https://api.github.com/repos/rails/rails/subscription",
49
+ "commits_url": "https://api.github.com/repos/rails/rails/commits{/sha}",
50
+ "git_commits_url": "https://api.github.com/repos/rails/rails/git/commits{/sha}",
51
+ "comments_url": "https://api.github.com/repos/rails/rails/comments{/number}",
52
+ "issue_comment_url": "https://api.github.com/repos/rails/rails/issues/comments/{number}",
53
+ "contents_url": "https://api.github.com/repos/rails/rails/contents/{+path}",
54
+ "compare_url": "https://api.github.com/repos/rails/rails/compare/{base}...{head}",
55
+ "merges_url": "https://api.github.com/repos/rails/rails/merges",
56
+ "archive_url": "https://api.github.com/repos/rails/rails/{archive_format}{/ref}",
57
+ "downloads_url": "https://api.github.com/repos/rails/rails/downloads",
58
+ "issues_url": "https://api.github.com/repos/rails/rails/issues{/number}",
59
+ "pulls_url": "https://api.github.com/repos/rails/rails/pulls{/number}",
60
+ "milestones_url": "https://api.github.com/repos/rails/rails/milestones{/number}",
61
+ "notifications_url": "https://api.github.com/repos/rails/rails/notifications{?since,all,participating}",
62
+ "labels_url": "https://api.github.com/repos/rails/rails/labels{/name}",
63
+ "releases_url": "https://api.github.com/repos/rails/rails/releases{/id}",
64
+ "created_at": "2008-04-11T02:19:47Z",
65
+ "updated_at": "2014-06-25T21:08:45Z",
66
+ "pushed_at": "2014-06-25T17:47:52Z",
67
+ "git_url": "git://github.com/rails/rails.git",
68
+ "ssh_url": "git@github.com:rails/rails.git",
69
+ "clone_url": "https://github.com/rails/rails.git",
70
+ "svn_url": "https://github.com/rails/rails",
71
+ "homepage": "http://rubyonrails.org",
72
+ "size": 331047,
73
+ "stargazers_count": 22248,
74
+ "watchers_count": 22248,
75
+ "language": "Ruby",
76
+ "has_issues": true,
77
+ "has_downloads": true,
78
+ "has_wiki": false,
79
+ "forks_count": 8278,
80
+ "mirror_url": null,
81
+ "open_issues_count": 625,
82
+ "forks": 8278,
83
+ "open_issues": 625,
84
+ "watchers": 22248,
85
+ "default_branch": "master",
86
+ "organization": {
87
+ "login": "rails",
88
+ "id": 4223,
89
+ "avatar_url": "https://avatars.githubusercontent.com/u/4223?",
90
+ "gravatar_id": "30f39a09e233e8369dddf6feb4be0308",
91
+ "url": "https://api.github.com/users/rails",
92
+ "html_url": "https://github.com/rails",
93
+ "followers_url": "https://api.github.com/users/rails/followers",
94
+ "following_url": "https://api.github.com/users/rails/following{/other_user}",
95
+ "gists_url": "https://api.github.com/users/rails/gists{/gist_id}",
96
+ "starred_url": "https://api.github.com/users/rails/starred{/owner}{/repo}",
97
+ "subscriptions_url": "https://api.github.com/users/rails/subscriptions",
98
+ "organizations_url": "https://api.github.com/users/rails/orgs",
99
+ "repos_url": "https://api.github.com/users/rails/repos",
100
+ "events_url": "https://api.github.com/users/rails/events{/privacy}",
101
+ "received_events_url": "https://api.github.com/users/rails/received_events",
102
+ "type": "Organization",
103
+ "site_admin": false
104
+ },
105
+ "network_count": 8278,
106
+ "subscribers_count": 1521
107
+ }
@@ -0,0 +1,913 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'json/stream'
4
+ require 'minitest/autorun'
5
+
6
+ describe JSON::Stream::Parser do
7
+ subject { JSON::Stream::Parser.new }
8
+
9
+ describe 'parsing a document' do
10
+ it 'rejects documents containing bad start character' do
11
+ expected = [:error]
12
+ assert_equal expected, events('a')
13
+ end
14
+
15
+ it 'rejects documents starting with period' do
16
+ expected = [:error]
17
+ assert_equal expected, events('.')
18
+ end
19
+
20
+ it 'parses a null value document' do
21
+ expected = [:start_document, [:value, nil], :end_document]
22
+ assert_equal expected, events('null')
23
+ end
24
+
25
+ it 'parses a false value document' do
26
+ expected = [:start_document, [:value, false], :end_document]
27
+ assert_equal expected, events('false')
28
+ end
29
+
30
+ it 'parses a true value document' do
31
+ expected = [:start_document, [:value, true], :end_document]
32
+ assert_equal expected, events('true')
33
+ end
34
+
35
+ it 'parses a string document' do
36
+ expected = [:start_document, [:value, "test"], :end_document]
37
+ assert_equal expected, events('"test"')
38
+ end
39
+
40
+ it 'parses a single digit integer value document' do
41
+ expected = [:start_document, [:value, 2], :end_document]
42
+ events = events('2', subject)
43
+ assert events.empty?
44
+ subject.finish
45
+ assert_equal expected, events
46
+ end
47
+
48
+ it 'parses a multiple digit integer value document' do
49
+ expected = [:start_document, [:value, 12], :end_document]
50
+ events = events('12', subject)
51
+ assert events.empty?
52
+ subject.finish
53
+ assert_equal expected, events
54
+ end
55
+
56
+ it 'parses a zero literal document' do
57
+ expected = [:start_document, [:value, 0], :end_document]
58
+ events = events('0', subject)
59
+ assert events.empty?
60
+ subject.finish
61
+ assert_equal expected, events
62
+ end
63
+
64
+ it 'parses a negative integer document' do
65
+ expected = [:start_document, [:value, -1], :end_document]
66
+ events = events('-1', subject)
67
+ assert events.empty?
68
+ subject.finish
69
+ assert_equal expected, events
70
+ end
71
+
72
+ it 'parses an exponent literal document' do
73
+ expected = [:start_document, [:value, 200.0], :end_document]
74
+ events = events('2e2', subject)
75
+ assert events.empty?
76
+ subject.finish
77
+ assert_equal expected, events
78
+ end
79
+
80
+ it 'parses a float value document' do
81
+ expected = [:start_document, [:value, 12.1], :end_document]
82
+ events = events('12.1', subject)
83
+ assert events.empty?
84
+ subject.finish
85
+ assert_equal expected, events
86
+ end
87
+
88
+ it 'parses a value document with leading whitespace' do
89
+ expected = [:start_document, [:value, false], :end_document]
90
+ assert_equal expected, events(' false ')
91
+ end
92
+
93
+ it 'parses array documents' do
94
+ expected = [:start_document, :start_array, :end_array, :end_document]
95
+ assert_equal expected, events('[]')
96
+ assert_equal expected, events('[ ]')
97
+ assert_equal expected, events(' [] ')
98
+ assert_equal expected, events(' [ ] ')
99
+ end
100
+
101
+ it 'parses object documents' do
102
+ expected = [:start_document, :start_object, :end_object, :end_document]
103
+ assert_equal expected, events('{}')
104
+ assert_equal expected, events('{ }')
105
+ assert_equal expected, events(' {} ')
106
+ assert_equal expected, events(' { } ')
107
+ end
108
+
109
+ it 'rejects documents with trailing characters' do
110
+ expected = [:start_document, :start_object, :end_object, :end_document, :error]
111
+ assert_equal expected, events('{}a')
112
+ assert_equal expected, events('{ } 12')
113
+ assert_equal expected, events(' {} false')
114
+ assert_equal expected, events(' { }, {}')
115
+ end
116
+
117
+ it 'ignores whitespace around tokens, preserves it within strings' do
118
+ json = %Q{
119
+ { " key 1 " : \t [
120
+ 1, 2, " my string ",\r
121
+ false, true, null ]
122
+ }
123
+ }
124
+ expected = [
125
+ :start_document,
126
+ :start_object,
127
+ [:key, " key 1 "],
128
+ :start_array,
129
+ [:value, 1],
130
+ [:value, 2],
131
+ [:value, " my string "],
132
+ [:value, false],
133
+ [:value, true],
134
+ [:value, nil],
135
+ :end_array,
136
+ :end_object,
137
+ :end_document
138
+ ]
139
+ assert_equal expected, events(json)
140
+ end
141
+
142
+ it 'rejects form feed whitespace' do
143
+ json = "[1,\f 2]"
144
+ expected = [:start_document, :start_array, [:value, 1], :error]
145
+ assert_equal expected, events(json)
146
+ end
147
+
148
+ it 'rejects vertical tab whitespace' do
149
+ json = "[1,\v 2]"
150
+ expected = [:start_document, :start_array, [:value, 1], :error]
151
+ assert_equal expected, events(json)
152
+ end
153
+
154
+ it 'rejects partial keyword tokens' do
155
+ expected = [:start_document, :start_array, :error]
156
+ assert_equal expected, events('[tru]')
157
+ assert_equal expected, events('[fal]')
158
+ assert_equal expected, events('[nul,true]')
159
+ assert_equal expected, events('[fals1]')
160
+ end
161
+
162
+ it 'rejects scrambled keyword tokens' do
163
+ expected = [:start_document, :start_array, :error]
164
+ assert_equal expected, events('[ture]')
165
+ assert_equal expected, events('[fales]')
166
+ assert_equal expected, events('[nlul]')
167
+ end
168
+
169
+ it 'parses single keyword tokens' do
170
+ expected = [:start_document, :start_array, [:value, true], :end_array, :end_document]
171
+ assert_equal expected, events('[true]')
172
+ end
173
+
174
+ it 'parses keywords in series' do
175
+ expected = [:start_document, :start_array, [:value, true], [:value, nil], :end_array, :end_document]
176
+ assert_equal expected, events('[true, null]')
177
+ end
178
+ end
179
+
180
+ describe 'finishing the parse' do
181
+ it 'rejects finish with no json data provided' do
182
+ -> { subject.finish }.must_raise JSON::Stream::ParserError
183
+ end
184
+
185
+ it 'rejects partial null keyword' do
186
+ subject << 'nul'
187
+ -> { subject.finish }.must_raise JSON::Stream::ParserError
188
+ end
189
+
190
+ it 'rejects partial true keyword' do
191
+ subject << 'tru'
192
+ -> { subject.finish }.must_raise JSON::Stream::ParserError
193
+ end
194
+
195
+ it 'rejects partial false keyword' do
196
+ subject << 'fals'
197
+ -> { subject.finish }.must_raise JSON::Stream::ParserError
198
+ end
199
+
200
+ it 'rejects partial float literal' do
201
+ subject << '42.'
202
+ -> { subject.finish }.must_raise JSON::Stream::ParserError
203
+ end
204
+
205
+ it 'rejects partial exponent' do
206
+ subject << '42e'
207
+ -> { subject.finish }.must_raise JSON::Stream::ParserError
208
+ end
209
+
210
+ it 'rejects malformed exponent' do
211
+ subject << '42e+'
212
+ -> { subject.finish }.must_raise JSON::Stream::ParserError
213
+ end
214
+
215
+ it 'rejects partial negative number' do
216
+ subject << '-'
217
+ -> { subject.finish }.must_raise JSON::Stream::ParserError
218
+ end
219
+
220
+ it 'rejects partial string literal' do
221
+ subject << '"test'
222
+ -> { subject.finish }.must_raise JSON::Stream::ParserError
223
+ end
224
+
225
+ it 'rejects partial object ending in literal value' do
226
+ subject << '{"test": 42'
227
+ -> { subject.finish }.must_raise JSON::Stream::ParserError
228
+ end
229
+
230
+ it 'rejects partial array ending in literal value' do
231
+ subject << '[42'
232
+ -> { subject.finish }.must_raise JSON::Stream::ParserError
233
+ end
234
+
235
+ it 'does nothing on subsequent finish' do
236
+ begin
237
+ subject << 'false'
238
+ subject.finish
239
+ subject.finish
240
+ rescue
241
+ fail 'raised unexpected error'
242
+ end
243
+ end
244
+ end
245
+
246
+ describe 'parsing number tokens' do
247
+ it 'rejects invalid negative numbers' do
248
+ expected = [:start_document, :start_array, :error]
249
+ assert_equal expected, events('[-]')
250
+
251
+ expected = [:start_document, :start_array, [:value, 1], :error]
252
+ assert_equal expected, events('[1-0]')
253
+ end
254
+
255
+ it 'parses integer zero' do
256
+ expected = [:start_document, :start_array, [:value, 0], :end_array, :end_document]
257
+ assert_equal expected, events('[0]')
258
+ assert_equal expected, events('[-0]')
259
+ end
260
+
261
+ it 'parses float zero' do
262
+ expected = [:start_document, :start_array, [:value, 0.0], :end_array, :end_document]
263
+ assert_equal expected, events('[0.0]')
264
+ assert_equal expected, events('[-0.0]')
265
+ end
266
+
267
+ it 'rejects multi zero' do
268
+ expected = [:start_document, :start_array, [:value, 0], :error]
269
+ assert_equal expected, events('[00]')
270
+ assert_equal expected, events('[-00]')
271
+ end
272
+
273
+ it 'rejects integers that start with zero' do
274
+ expected = [:start_document, :start_array, [:value, 0], :error]
275
+ assert_equal expected, events('[01]')
276
+ assert_equal expected, events('[-01]')
277
+ end
278
+
279
+ it 'parses integer tokens' do
280
+ expected = [:start_document, :start_array, [:value, 1], :end_array, :end_document]
281
+ assert_equal expected, events('[1]')
282
+
283
+ expected = [:start_document, :start_array, [:value, -1], :end_array, :end_document]
284
+ assert_equal expected, events('[-1]')
285
+
286
+ expected = [:start_document, :start_array, [:value, 123], :end_array, :end_document]
287
+ assert_equal expected, events('[123]')
288
+
289
+ expected = [:start_document, :start_array, [:value, -123], :end_array, :end_document]
290
+ assert_equal expected, events('[-123]')
291
+ end
292
+
293
+ it 'parses float tokens' do
294
+ expected = [:start_document, :start_array, [:value, 1.0], :end_array, :end_document]
295
+ assert_equal expected, events('[1.0]')
296
+ assert_equal expected, events('[1.00]')
297
+ end
298
+
299
+ it 'parses negative floats' do
300
+ expected = [:start_document, :start_array, [:value, -1.0], :end_array, :end_document]
301
+ assert_equal expected, events('[-1.0]')
302
+ assert_equal expected, events('[-1.00]')
303
+ end
304
+
305
+ it 'parses multi-digit floats' do
306
+ expected = [:start_document, :start_array, [:value, 123.012], :end_array, :end_document]
307
+ assert_equal expected, events('[123.012]')
308
+ assert_equal expected, events('[123.0120]')
309
+ end
310
+
311
+ it 'parses negative multi-digit floats' do
312
+ expected = [:start_document, :start_array, [:value, -123.012], :end_array, :end_document]
313
+ assert_equal expected, events('[-123.012]')
314
+ assert_equal expected, events('[-123.0120]')
315
+ end
316
+
317
+ it 'rejects floats missing leading zero' do
318
+ expected = [:start_document, :start_array, :error]
319
+ assert_equal expected, events('[.1]')
320
+ assert_equal expected, events('[-.1]')
321
+ assert_equal expected, events('[.01]')
322
+ assert_equal expected, events('[-.01]')
323
+ end
324
+
325
+ it 'rejects float missing fraction' do
326
+ expected = [:start_document, :start_array, :error]
327
+ assert_equal expected, events('[.]')
328
+ assert_equal expected, events('[..]')
329
+ assert_equal expected, events('[0.]')
330
+ assert_equal expected, events('[12.]')
331
+ end
332
+
333
+ it 'parses zero with implicit positive exponent as float' do
334
+ expected = [:start_document, :start_array, [:value, 0.0], :end_array, :end_document]
335
+ events = events('[0e2]')
336
+ assert_equal expected, events
337
+ assert_kind_of Float, events[2][1]
338
+ end
339
+
340
+ it 'parses zero with explicit positive exponent as float' do
341
+ expected = [:start_document, :start_array, [:value, 0.0], :end_array, :end_document]
342
+ events = events('[0e+2]')
343
+ assert_equal expected, events
344
+ assert_kind_of Float, events[2][1]
345
+ end
346
+
347
+ it 'parses zero with negative exponent as float' do
348
+ expected = [:start_document, :start_array, [:value, 0.0], :end_array, :end_document]
349
+ events = events('[0e-2]')
350
+ assert_equal expected, events
351
+ assert_kind_of Float, events[2][1]
352
+ end
353
+
354
+ it 'parses positive exponent integers as floats' do
355
+ expected = [:start_document, :start_array, [:value, 212.0], :end_array, :end_document]
356
+
357
+ events = events('[2.12e2]')
358
+ assert_equal expected, events('[2.12e2]')
359
+ assert_kind_of Float, events[2][1]
360
+
361
+ assert_equal expected, events('[2.12e02]')
362
+ assert_equal expected, events('[2.12e+2]')
363
+ assert_equal expected, events('[2.12e+02]')
364
+ end
365
+
366
+ it 'parses positive exponent floats' do
367
+ expected = [:start_document, :start_array, [:value, 21.2], :end_array, :end_document]
368
+ assert_equal expected, events('[2.12e1]')
369
+ assert_equal expected, events('[2.12e01]')
370
+ assert_equal expected, events('[2.12e+1]')
371
+ assert_equal expected, events('[2.12e+01]')
372
+ end
373
+
374
+ it 'parses negative exponent' do
375
+ expected = [:start_document, :start_array, [:value, 0.0212], :end_array, :end_document]
376
+ assert_equal expected, events('[2.12e-2]')
377
+ assert_equal expected, events('[2.12e-02]')
378
+ assert_equal expected, events('[2.12e-2]')
379
+ assert_equal expected, events('[2.12e-02]')
380
+ end
381
+
382
+ it 'parses zero exponent floats' do
383
+ expected = [:start_document, :start_array, [:value, 2.12], :end_array, :end_document]
384
+ assert_equal expected, events('[2.12e0]')
385
+ assert_equal expected, events('[2.12e00]')
386
+ assert_equal expected, events('[2.12e-0]')
387
+ assert_equal expected, events('[2.12e-00]')
388
+ end
389
+
390
+ it 'parses zero exponent integers' do
391
+ expected = [:start_document, :start_array, [:value, 2.0], :end_array, :end_document]
392
+ assert_equal expected, events('[2e0]')
393
+ assert_equal expected, events('[2e00]')
394
+ assert_equal expected, events('[2e-0]')
395
+ assert_equal expected, events('[2e-00]')
396
+ end
397
+
398
+ it 'rejects missing exponent' do
399
+ expected = [:start_document, :start_array, :error]
400
+ assert_equal expected, events('[e]')
401
+ assert_equal expected, events('[1e]')
402
+ assert_equal expected, events('[1e-]')
403
+ assert_equal expected, events('[1e--]')
404
+ assert_equal expected, events('[1e+]')
405
+ assert_equal expected, events('[1e++]')
406
+ assert_equal expected, events('[0.e]')
407
+ assert_equal expected, events('[10.e]')
408
+ end
409
+
410
+ it 'rejects float with trailing character' do
411
+ expected = [:start_document, :start_array, [:value, 0.0], :error]
412
+ assert_equal expected, events('[0.0q]')
413
+ end
414
+
415
+ it 'rejects integer with trailing character' do
416
+ expected = [:start_document, :start_array, [:value, 1], :error]
417
+ assert_equal expected, events('[1q]')
418
+ end
419
+ end
420
+
421
+ describe 'parsing string tokens' do
422
+ describe 'parsing two-character escapes' do
423
+ it 'rejects invalid escape characters' do
424
+ expected = [:start_document, :start_array, :error]
425
+ assert_equal expected, events('["\\a"]')
426
+ end
427
+
428
+ it 'parses quotation mark' do
429
+ expected = [:start_document, :start_array, [:value, "\""], :end_array, :end_document]
430
+ assert_equal expected, events('["\""]')
431
+ end
432
+
433
+ it 'parses reverse solidus' do
434
+ expected = [:start_document, :start_array, [:value, "\\"], :end_array, :end_document]
435
+ assert_equal expected, events('["\\\"]')
436
+ end
437
+
438
+ it 'parses solidus' do
439
+ expected = [:start_document, :start_array, [:value, "/"], :end_array, :end_document]
440
+ assert_equal expected, events('["\/"]')
441
+ end
442
+
443
+ it 'parses backspace' do
444
+ expected = [:start_document, :start_array, [:value, "\b"], :end_array, :end_document]
445
+ assert_equal expected, events('["\b"]')
446
+ end
447
+
448
+ it 'parses form feed' do
449
+ expected = [:start_document, :start_array, [:value, "\f"], :end_array, :end_document]
450
+ assert_equal expected, events('["\f"]')
451
+ end
452
+
453
+ it 'parses line feed' do
454
+ expected = [:start_document, :start_array, [:value, "\n"], :end_array, :end_document]
455
+ assert_equal expected, events('["\n"]')
456
+ end
457
+
458
+ it 'parses carriage return' do
459
+ expected = [:start_document, :start_array, [:value, "\r"], :end_array, :end_document]
460
+ assert_equal expected, events('["\r"]')
461
+ end
462
+
463
+ it 'parses tab' do
464
+ expected = [:start_document, :start_array, [:value, "\t"], :end_array, :end_document]
465
+ assert_equal expected, events('["\t"]')
466
+ end
467
+
468
+ it 'parses a series of escapes with whitespace' do
469
+ expected = [:start_document, :start_array, [:value, "\" \\ / \b \f \n \r \t"], :end_array, :end_document]
470
+ assert_equal expected, events('["\" \\\ \/ \b \f \n \r \t"]')
471
+ end
472
+
473
+ it 'parses a series of escapes without whitespace' do
474
+ expected = [:start_document, :start_array, [:value, "\"\\/\b\f\n\r\t"], :end_array, :end_document]
475
+ assert_equal expected, events('["\"\\\\/\b\f\n\r\t"]')
476
+ end
477
+
478
+ it 'parses a series of escapes with duplicate characters between them' do
479
+ expected = [:start_document, :start_array, [:value, "\"t\\b/f\bn\f/\nn\rr\t"], :end_array, :end_document]
480
+ assert_equal expected, events('["\"t\\\b\/f\bn\f/\nn\rr\t"]')
481
+ end
482
+ end
483
+
484
+ describe 'parsing control characters' do
485
+ it 'rejects control character in array' do
486
+ expected = [:start_document, :start_array, :error]
487
+ assert_equal expected, events("[\" \u0000 \"]")
488
+ end
489
+
490
+ it 'rejects control character in object' do
491
+ expected = [:start_document, :start_object, :error]
492
+ assert_equal expected, events("{\" \u0000 \":12}")
493
+ end
494
+
495
+ it 'parses escaped control character' do
496
+ expected = [:start_document, :start_array, [:value, "\u0000"], :end_array, :end_document]
497
+ assert_equal expected, events('["\\u0000"]')
498
+ end
499
+
500
+ it 'parses escaped control character in object key' do
501
+ expected = [:start_document, :start_object, [:key, "\u0000"], [:value, 12], :end_object, :end_document]
502
+ assert_equal expected, events('{"\\u0000": 12}')
503
+ end
504
+
505
+ it 'parses non-control character' do
506
+ # del ascii 127 is allowed unescaped in json
507
+ expected = [:start_document, :start_array, [:value, " \u007F "], :end_array, :end_document]
508
+ assert_equal expected, events("[\" \u007f \"]")
509
+ end
510
+ end
511
+
512
+ describe 'parsing unicode escape sequences' do
513
+ it 'parses escaped ascii character' do
514
+ a = "\x61"
515
+ escaped = '\u0061'
516
+ expected = [:start_document, :start_array, [:value, a], :end_array, :end_document]
517
+ assert_equal expected, events('["' + escaped + '"]')
518
+ end
519
+
520
+ it 'parses un-escaped raw unicode' do
521
+ # U+1F602 face with tears of joy
522
+ face = "\xf0\x9f\x98\x82"
523
+ expected = [:start_document, :start_array, [:value, face], :end_array, :end_document]
524
+ assert_equal expected, events('["' + face + '"]')
525
+ end
526
+
527
+ it 'parses escaped unicode surrogate pairs' do
528
+ # U+1F602 face with tears of joy
529
+ face = "\xf0\x9f\x98\x82"
530
+ escaped = '\uD83D\uDE02'
531
+ expected = [:start_document, :start_array, [:value, face], :end_array, :end_document]
532
+ assert_equal expected, events('["' + escaped + '"]')
533
+ end
534
+
535
+ it 'rejects partial unicode escapes' do
536
+ expected = [:start_document, :start_array, :error]
537
+ assert_equal expected, events('[" \\u "]')
538
+ assert_equal expected, events('[" \\u2 "]')
539
+ assert_equal expected, events('[" \\u26 "]')
540
+ assert_equal expected, events('[" \\u260 "]')
541
+ end
542
+
543
+ it 'parses unicode escapes' do
544
+ # U+2603 snowman
545
+ snowman = "\xe2\x98\x83"
546
+ escaped = '\u2603'
547
+
548
+ expected = [:start_document, :start_array, [:value, snowman], :end_array, :end_document]
549
+ assert_equal expected, events('["' + escaped + '"]')
550
+
551
+ expected = [:start_document, :start_array, [:value, 'snow' + snowman + ' man'], :end_array, :end_document]
552
+ assert_equal expected, events('["snow' + escaped + ' man"]')
553
+
554
+ expected = [:start_document, :start_array, [:value, 'snow' + snowman + '3 man'], :end_array, :end_document]
555
+ assert_equal expected, events('["snow' + escaped + '3 man"]')
556
+
557
+ expected = [:start_document, :start_object, [:key, 'snow' + snowman + '3 man'], [:value, 1], :end_object, :end_document]
558
+ assert_equal expected, events('{"snow\\u26033 man": 1}')
559
+ end
560
+ end
561
+
562
+ describe 'parsing unicode escapes with surrogate pairs' do
563
+ it 'rejects missing second pair' do
564
+ expected = [:start_document, :start_array, :error]
565
+ assert_equal expected, events('["\uD834"]')
566
+ end
567
+
568
+ it 'rejects missing first pair' do
569
+ expected = [:start_document, :start_array, :error]
570
+ assert_equal expected, events('["\uDD1E"]')
571
+ end
572
+
573
+ it 'rejects double first pair' do
574
+ expected = [:start_document, :start_array, :error]
575
+ assert_equal expected, events('["\uD834\uD834"]')
576
+ end
577
+
578
+ it 'rejects double second pair' do
579
+ expected = [:start_document, :start_array, :error]
580
+ assert_equal expected, events('["\uDD1E\uDD1E"]')
581
+ end
582
+
583
+ it 'rejects reversed pair' do
584
+ expected = [:start_document, :start_array, :error]
585
+ assert_equal expected, events('["\uDD1E\uD834"]')
586
+ end
587
+
588
+ it 'parses correct pairs in object keys and values' do
589
+ # U+1D11E G-Clef
590
+ clef = "\xf0\x9d\x84\x9e"
591
+ expected = [
592
+ :start_document,
593
+ :start_object,
594
+ [:key, clef],
595
+ [:value, "g\u{1D11E}clef"],
596
+ :end_object,
597
+ :end_document
598
+ ]
599
+ assert_equal expected, events(%q{ {"\uD834\uDD1E": "g\uD834\uDD1Eclef"} })
600
+ end
601
+ end
602
+ end
603
+
604
+ describe 'parsing arrays' do
605
+ it 'rejects trailing comma' do
606
+ expected = [:start_document, :start_array, [:value, 12], :error]
607
+ assert_equal expected, events('[12, ]')
608
+ end
609
+
610
+ it 'parses nested empty array' do
611
+ expected = [:start_document, :start_array, :start_array, :end_array, :end_array, :end_document]
612
+ assert_equal expected, events('[[]]')
613
+ end
614
+
615
+ it 'parses nested array with value' do
616
+ expected = [:start_document, :start_array, :start_array, [:value, 2.1], :end_array, :end_array, :end_document]
617
+ assert_equal expected, events('[[ 2.10 ]]')
618
+ end
619
+
620
+ it 'rejects malformed arrays' do
621
+ expected = [:start_document, :start_array, :error]
622
+ assert_equal expected, events('[}')
623
+ assert_equal expected, events('[,]')
624
+ assert_equal expected, events('[, 12]')
625
+ end
626
+
627
+ it 'rejects malformed nested arrays' do
628
+ expected = [:start_document, :start_array, :start_array, :error]
629
+ assert_equal(expected, events('[[}]'))
630
+ assert_equal expected, events('[[}]')
631
+ assert_equal expected, events('[[,]]')
632
+ end
633
+
634
+ it 'rejects malformed array value lists' do
635
+ expected = [:start_document, :start_array, [:value, "test"], :error]
636
+ assert_equal expected, events('["test"}')
637
+ assert_equal expected, events('["test",]')
638
+ assert_equal expected, events('["test" "test"]')
639
+ assert_equal expected, events('["test" 12]')
640
+ end
641
+
642
+ it 'parses array with value' do
643
+ expected = [:start_document, :start_array, [:value, "test"], :end_array, :end_document]
644
+ assert_equal expected, events('["test"]')
645
+ end
646
+
647
+ it 'parses array with value list' do
648
+ expected = [
649
+ :start_document,
650
+ :start_array,
651
+ [:value, 1],
652
+ [:value, 2],
653
+ [:value, nil],
654
+ [:value, 12.1],
655
+ [:value, "test"],
656
+ :end_array,
657
+ :end_document
658
+ ]
659
+ assert_equal expected, events('[1,2, null, 12.1,"test"]')
660
+ end
661
+ end
662
+
663
+ describe 'parsing objects' do
664
+ it 'rejects malformed objects' do
665
+ expected = [:start_document, :start_object, :error]
666
+ assert_equal expected, events('{]')
667
+ assert_equal expected, events('{:}')
668
+ end
669
+
670
+ it 'parses single key object' do
671
+ expected = [:start_document, :start_object, [:key, "key 1"], [:value, 12], :end_object, :end_document]
672
+ assert_equal expected, events('{"key 1" : 12}')
673
+ end
674
+
675
+ it 'parses object key value list' do
676
+ expected = [
677
+ :start_document,
678
+ :start_object,
679
+ [:key, "key 1"], [:value, 12],
680
+ [:key, "key 2"], [:value, "two"],
681
+ :end_object,
682
+ :end_document
683
+ ]
684
+ assert_equal expected, events('{"key 1" : 12, "key 2":"two"}')
685
+ end
686
+
687
+ it 'rejects object key with no value' do
688
+ expected = [
689
+ :start_document,
690
+ :start_object,
691
+ [:key, "key"],
692
+ :start_array,
693
+ [:value, nil],
694
+ [:value, false],
695
+ [:value, true],
696
+ :end_array,
697
+ [:key, "key 2"],
698
+ :error
699
+ ]
700
+ assert_equal expected, events('{"key": [ null , false , true ] ,"key 2"}')
701
+ end
702
+
703
+ it 'rejects object with trailing comma' do
704
+ expected = [:start_document, :start_object, [:key, "key 1"], [:value, 12], :error]
705
+ assert_equal expected, events('{"key 1" : 12,}')
706
+ end
707
+ end
708
+
709
+ describe 'parsing unicode bytes' do
710
+ it 'parses single byte utf-8' do
711
+ expected = [:start_document, :start_array, [:value, "test"], :end_array, :end_document]
712
+ assert_equal expected, events('["test"]')
713
+ end
714
+
715
+ it 'parses full two byte utf-8' do
716
+ expected = [
717
+ :start_document,
718
+ :start_array,
719
+ [:value, "résumé"],
720
+ [:value, "éé"],
721
+ :end_array,
722
+ :end_document
723
+ ]
724
+ assert_equal expected, events("[\"résumé\", \"é\xC3\xA9\"]")
725
+ end
726
+
727
+ # Parser should throw an error when only one byte of a two byte character
728
+ # is available. The \xC3 byte is the first byte of the é character.
729
+ it 'rejects a partial two byte utf-8 string' do
730
+ expected = [:start_document, :start_array, :error]
731
+ assert_equal expected, events("[\"\xC3\"]")
732
+ end
733
+
734
+ it 'parses valid two byte utf-8 string' do
735
+ expected = [:start_document, :start_array, [:value, 'é'], :end_array, :end_document]
736
+ assert_equal expected, events("[\"\xC3\xA9\"]")
737
+ end
738
+
739
+ it 'parses full three byte utf-8 string' do
740
+ expected = [
741
+ :start_document,
742
+ :start_array,
743
+ [:value, "snow\u2603man"],
744
+ [:value, "\u2603\u2603"],
745
+ :end_array,
746
+ :end_document
747
+ ]
748
+ assert_equal expected, events("[\"snow\u2603man\", \"\u2603\u2603\"]")
749
+ end
750
+
751
+ it 'rejects one byte of three byte utf-8 string' do
752
+ expected = [:start_document, :start_array, :error]
753
+ assert_equal expected, events("[\"\xE2\"]")
754
+ end
755
+
756
+ it 'rejects two bytes of three byte utf-8 string' do
757
+ expected = [:start_document, :start_array, :error]
758
+ assert_equal expected, events("[\"\xE2\x98\"]")
759
+ end
760
+
761
+ it 'parses full three byte utf-8 string' do
762
+ expected = [:start_document, :start_array, [:value, "\u2603"], :end_array, :end_document]
763
+ assert_equal expected, events("[\"\xE2\x98\x83\"]")
764
+ end
765
+
766
+ it 'parses full four byte utf-8 string' do
767
+ expected = [
768
+ :start_document,
769
+ :start_array,
770
+ [:value, "\u{10102} check mark"],
771
+ :end_array,
772
+ :end_document
773
+ ]
774
+ assert_equal expected, events("[\"\u{10102} check mark\"]")
775
+ end
776
+
777
+ it 'rejects one byte of four byte utf-8 string' do
778
+ expected = [:start_document, :start_array, :error]
779
+ assert_equal expected, events("[\"\xF0\"]")
780
+ end
781
+
782
+ it 'rejects two bytes of four byte utf-8 string' do
783
+ expected = [:start_document, :start_array, :error]
784
+ assert_equal expected, events("[\"\xF0\x90\"]")
785
+ end
786
+
787
+ it 'rejects three bytes of four byte utf-8 string' do
788
+ expected = [:start_document, :start_array, :error]
789
+ assert_equal expected, events("[\"\xF0\x90\x84\"]")
790
+ end
791
+
792
+ it 'parses full four byte utf-8 string' do
793
+ expected = [:start_document, :start_array, [:value, "\u{10102}"], :end_array, :end_document]
794
+ assert_equal expected, events("[\"\xF0\x90\x84\x82\"]")
795
+ end
796
+ end
797
+
798
+ describe 'parsing json text from the module' do
799
+ it 'parses an array document' do
800
+ result = JSON::Stream::Parser.parse('[1,2,3]')
801
+ assert_equal [1, 2, 3], result
802
+ end
803
+
804
+ it 'parses a true keyword literal document' do
805
+ result = JSON::Stream::Parser.parse('true')
806
+ assert_equal true, result
807
+ end
808
+
809
+ it 'parses a false keyword literal document' do
810
+ result = JSON::Stream::Parser.parse('false')
811
+ assert_equal false, result
812
+ end
813
+
814
+ it 'parses a null keyword literal document' do
815
+ result = JSON::Stream::Parser.parse('null')
816
+ assert_equal nil, result
817
+ end
818
+
819
+ it 'parses a string literal document' do
820
+ result = JSON::Stream::Parser.parse('"hello"')
821
+ assert_equal 'hello', result
822
+ end
823
+
824
+ it 'parses an integer literal document' do
825
+ result = JSON::Stream::Parser.parse('42')
826
+ assert_equal 42, result
827
+ end
828
+
829
+ it 'parses a float literal document' do
830
+ result = JSON::Stream::Parser.parse('42.12')
831
+ assert_equal 42.12, result
832
+ end
833
+
834
+ it 'rejects a partial float literal document' do
835
+ assert_raises(JSON::Stream::ParserError) do
836
+ JSON::Stream::Parser.parse('42.')
837
+ end
838
+ end
839
+
840
+ it 'rejects a partial document' do
841
+ assert_raises(JSON::Stream::ParserError) do
842
+ JSON::Stream::Parser.parse('{')
843
+ end
844
+ end
845
+
846
+ it 'rejects an empty document' do
847
+ assert_raises(JSON::Stream::ParserError) do
848
+ JSON::Stream::Parser.parse('')
849
+ end
850
+ end
851
+ end
852
+
853
+ it 'registers observers in initializer block' do
854
+ events = []
855
+ parser = JSON::Stream::Parser.new do
856
+ start_document { events << :start_document }
857
+ end_document { events << :end_document }
858
+ start_object { events << :start_object }
859
+ end_object { events << :end_object }
860
+ key {|k| events << [:key, k] }
861
+ value {|v| events << [:value, v] }
862
+ end
863
+ parser << '{"key":12}'
864
+ expected = [:start_document, :start_object, [:key, "key"], [:value, 12], :end_object, :end_document]
865
+ assert_equal expected, events
866
+ end
867
+
868
+ private
869
+
870
+ # Run a worst case, one byte at a time, parse against the JSON string and
871
+ # return a list of events generated by the parser. A special :error event is
872
+ # included if the parser threw an exception.
873
+ #
874
+ # json - The String to parse.
875
+ # parser - The optional Parser instance to use.
876
+ #
877
+ # Returns an Events instance.
878
+ def events(json, parser = nil)
879
+ parser ||= JSON::Stream::Parser.new
880
+ collector = Events.new(parser)
881
+ begin
882
+ json.each_byte {|byte| parser << [byte].pack('C') }
883
+ rescue JSON::Stream::ParserError
884
+ collector.error
885
+ end
886
+ collector.events
887
+ end
888
+
889
+ # Dynamically map methods in this class to parser callback methods
890
+ # so we can collect parser events for inspection by test cases.
891
+ class Events
892
+ METHODS = %w[start_document end_document start_object end_object start_array end_array key value]
893
+
894
+ attr_reader :events
895
+
896
+ def initialize(parser)
897
+ @events = []
898
+ METHODS.each do |name|
899
+ parser.send(name, &method(name))
900
+ end
901
+ end
902
+
903
+ METHODS.each do |name|
904
+ define_method(name) do |*args|
905
+ @events << (args.empty? ? name.to_sym : [name.to_sym, *args])
906
+ end
907
+ end
908
+
909
+ def error
910
+ @events << :error
911
+ end
912
+ end
913
+ end