stomp_parser 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ require_relative "../bench_helper"
2
+
3
+ describe "Frame#to_str minimal" do |bench|
4
+ bench.setup do
5
+ @frame = StompParser::Frame.new("CONNECT", nil)
6
+ end
7
+
8
+ bench.code { @frame.to_str }
9
+
10
+ bench.assert do |frame_str|
11
+ frame_str == "CONNECT\ncontent-length:0\n\n\x00"
12
+ end
13
+ end
14
+
15
+ describe "Frame#to_str with header" do |bench|
16
+ bench.setup do
17
+ @frame = StompParser::Frame.new("CONNECT", { "heart-beat" => "0,0" }, nil)
18
+ end
19
+
20
+ bench.code { @frame.to_str }
21
+
22
+ bench.assert do |frame_str|
23
+ frame_str == "CONNECT\nheart-beat:0,0\ncontent-length:0\n\n\x00"
24
+ end
25
+ end
26
+
27
+ describe "Frame#to_str with headers and small body" do |bench|
28
+ bench.setup do
29
+ @frame = StompParser::Frame.new("CONNECT", { "some" => "header" }, "body")
30
+ end
31
+
32
+ bench.code { @frame.to_str }
33
+
34
+ bench.assert do |frame_str|
35
+ frame_str == "CONNECT\nsome:header\ncontent-length:4\n\nbody\x00"
36
+ end
37
+ end
38
+
39
+ describe "Frame#to_str with headers and large body" do |bench|
40
+ bench.setup do
41
+ large_binary = "b\x00" * 2 # make room for headers
42
+ @frame = StompParser::Frame.new("CONNECT", { "some" => "header" }, large_binary)
43
+ end
44
+
45
+ bench.code { @frame.to_str }
46
+
47
+ bench.assert do |frame_str|
48
+ frame_str == "CONNECT\nsome:header\ncontent-length:#{@frame.body.bytesize}\n\n#{@frame.body}\x00"
49
+ end
50
+ end
@@ -0,0 +1,43 @@
1
+ require_relative "../bench_helper"
2
+
3
+ def parse_one(parser, data)
4
+ frame = nil
5
+ parser.parse(data) { |m| frame = m }
6
+ frame
7
+ end
8
+
9
+ %w[CParser JavaParser RubyParser].each do |parser|
10
+ parser = begin
11
+ StompParser.const_get(parser)
12
+ rescue NameError
13
+ next
14
+ end
15
+
16
+ describe "#{parser}: minimal" do |bench|
17
+ bench.setup do
18
+ @parser = parser.new
19
+ @frame = "CONNECT\n\n\x00"
20
+ end
21
+
22
+ bench.code { parse_one(@parser, @frame) }
23
+ end
24
+
25
+ describe "#{parser}: headers and small body" do |bench|
26
+ bench.setup do
27
+ @parser = parser.new
28
+ @frame = "CONNECT\ncontent-length:4\n\nbody\x00"
29
+ end
30
+
31
+ bench.code { parse_one(@parser, @frame) }
32
+ end
33
+
34
+ describe "#{parser}: headers and large body" do |bench|
35
+ bench.setup do
36
+ @parser = parser.new
37
+ large_body = ("b" * (StompParser.max_frame_size - 50)) # make room for headers
38
+ @frame = "CONNECT\ncontent-length:#{large_body.bytesize}\n\n#{large_body}\x00"
39
+ end
40
+
41
+ bench.code { parse_one(@parser, @frame) }
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ require "bundler/setup"
2
+ require "stomp_parser"
3
+ require "perftools"
4
+
5
+ parser = StompParser::Parser.new
6
+ body_size = (1024 * 99) / 2
7
+ large_binary = "b\x00" * body_size # make room for headers
8
+ data = <<-MESSAGE
9
+ CONNECT
10
+ content-length:#{large_binary.bytesize}
11
+
12
+ #{large_binary}\x00
13
+ MESSAGE
14
+ stream = data + data + data + data
15
+
16
+ profile_output = File.expand_path("./profile/parser.profile", File.dirname(__FILE__))
17
+ PerfTools::CpuProfiler.start(profile_output) do
18
+ i = 100
19
+ loop do
20
+ parser.parse(data) do |frame|
21
+ # no op
22
+ end
23
+
24
+ i -= 1
25
+ break if i <= 0
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ require "bundler/setup"
2
+ require "stomp_parser"
3
+ require "pry"
4
+ require "timeout"
5
+
6
+ require "support/shared_parser_examples"
@@ -0,0 +1,5 @@
1
+ if defined?(StompParser::CParser)
2
+ describe StompParser::CParser do
3
+ it_behaves_like "a stomp_parser parser"
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ if defined?(StompParser::JavaParser)
2
+ describe StompParser::JavaParser do
3
+ it_behaves_like "a stomp_parser parser"
4
+ end
5
+ end
@@ -0,0 +1,50 @@
1
+ describe StompParser::Frame do
2
+ describe "#content_length" do
3
+ it "returns content length if available" do
4
+ frame = StompParser::Frame.new("CONNECT", { "content-length" => "1337" }, nil)
5
+ frame.content_length.should eq 1337
6
+ end
7
+
8
+ it "returns nil if no content length defined" do
9
+ frame = StompParser::Frame.new("CONNECT", nil)
10
+ frame.content_length.should be_nil
11
+ end
12
+
13
+ it "raises an error if invalid content length defined" do
14
+ frame = StompParser::Frame.new("CONNECT", { "content-length" => "LAWL" }, nil)
15
+ expect { frame.content_length }.to raise_error(StompParser::Error, /invalid content length "LAWL"/)
16
+ end
17
+ end
18
+
19
+ describe "#to_str" do
20
+ specify "frame with command only" do
21
+ frame = StompParser::Frame.new("CONNECT", nil)
22
+ frame.to_str.should eq "CONNECT\ncontent-length:0\n\n\x00"
23
+ end
24
+
25
+ specify "frame with with headers" do
26
+ frame = StompParser::Frame.new("CONNECT", { "moo" => "cow", "boo" => "hoo" }, nil)
27
+ frame.to_str.should eq "CONNECT\nmoo:cow\nboo:hoo\ncontent-length:0\n\n\x00"
28
+ end
29
+
30
+ specify "frame with with body" do
31
+ frame = StompParser::Frame.new("CONNECT", "this is a body")
32
+ frame.to_str.should eq "CONNECT\ncontent-length:14\n\nthis is a body\x00"
33
+ end
34
+
35
+ specify "frame with escapeable characters in headers" do
36
+ frame = StompParser::Frame.new("CONNECT", { "k\\\n\r:" => "v\\\n\r:" }, nil)
37
+ frame.to_str.should eq "CONNECT\nk\\\\\\n\\r\\c:v\\\\\\n\\r\\c\ncontent-length:0\n\n\x00"
38
+ end
39
+
40
+ specify "frame with binary body" do
41
+ frame = StompParser::Frame.new("CONNECT", "\x00ab\x00")
42
+ frame.to_str.should eq "CONNECT\ncontent-length:4\n\n\x00ab\x00\x00"
43
+ end
44
+
45
+ specify "overrides user-specified content-length" do
46
+ frame = StompParser::Frame.new("CONNECT", { "content-length" => "10" }, "\x00ab\x00")
47
+ frame.to_str.should eq "CONNECT\ncontent-length:4\n\n\x00ab\x00\x00"
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,3 @@
1
+ describe StompParser::RubyParser do
2
+ it_behaves_like "a stomp_parser parser"
3
+ end
@@ -0,0 +1,9 @@
1
+ describe StompParser do
2
+ specify { defined?(StompParser::VERSION).should be_true }
3
+
4
+ describe ".max_frame_size" do
5
+ it "has a sane default value" do
6
+ StompParser.max_frame_size.should be_a(Fixnum)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,268 @@
1
+ # encoding: UTF-8
2
+ RSpec.shared_examples_for "a stomp_parser parser" do
3
+ let(:parser) { described_class.new }
4
+
5
+ context "#parse" do
6
+ def parse_all(data)
7
+ frames = []
8
+ parser.parse(data) { |m| frames << m }
9
+ frames
10
+ end
11
+
12
+ it "parses frame as binary" do
13
+ frames = parse_all("CONNECT\n\n\x00")
14
+ frames.length.should eq(1)
15
+ frames[0].command.encoding.should eq Encoding::BINARY
16
+ end
17
+
18
+ context "command" do
19
+ it "can parse commands" do
20
+ frames = parse_all("CONNECT\n\n\x00")
21
+ frames.length.should eq(1)
22
+ frames[0].command.should eq("CONNECT")
23
+ end
24
+ end
25
+
26
+ context "headers" do
27
+ it "can parse simple headers" do
28
+ frames = parse_all("CONNECT\nmoo:cow\n\n\x00")
29
+ frames.length.should eq(1)
30
+ frames[0].headers.should eq("moo" => "cow")
31
+ end
32
+
33
+ it "can parse multiple headers" do
34
+ frames = parse_all("CONNECT\nmoo:cow\nbaah:sheep\n\n\x00")
35
+ frames.length.should eq(1)
36
+ frames[0].headers.should eq("moo" => "cow", "baah" => "sheep")
37
+ end
38
+
39
+ it "can parse headers with NULLs in them" do
40
+ frames = parse_all("CONNECT\nnull\x00:null\x00\n\n\x00")
41
+ frames.length.should eq(1)
42
+ frames[0].headers.should eq("null\x00" => "null\x00")
43
+ end
44
+
45
+ it "can parse headers with escape characters" do
46
+ frames = parse_all("CONNECT\nnull\\c:\\r\\n\\c\\\\\n\n\x00")
47
+ frames.length.should eq(1)
48
+ frames[0].headers.should eq("null:" => "\r\n:\\")
49
+ end
50
+
51
+ it "can parse headers with no value" do
52
+ frames = parse_all("CONNECT\nmoo:\n\n\x00")
53
+ frames.length.should eq(1)
54
+ frames[0].headers.should eq("moo" => nil)
55
+ end
56
+
57
+ it "nullifies previous headers" do
58
+ frames = parse_all("CONNECT\nmoo:\nmoo:hello\n\n\x00")
59
+ frames.length.should eq(1)
60
+ frames[0].headers.should eq("moo" => nil)
61
+ end
62
+
63
+ it "prioritises first header when given multiple of same key" do
64
+ frames = parse_all("CONNECT\nkey:first\nkey:second\n\n\x00")
65
+ frames.length.should eq(1)
66
+ frames[0].headers.should eq("key" => "first")
67
+ end
68
+
69
+ it "parses multibyte headers as UTF-8 even if content type specifies something else" do
70
+ frames = parse_all("MESSAGE\ncontent-type:text/plain;charset=ISO-8859-1\nwhät:üp\n\n\x00")
71
+ frames.length.should eq(1)
72
+
73
+ key, value = frames[0].headers.to_a[1]
74
+
75
+ key.should eq "wh\xC3\xA4t".force_encoding("UTF-8")
76
+ key.encoding.should eq Encoding::UTF_8
77
+
78
+ value.should eq "\xC3\xBCp".force_encoding("UTF-8")
79
+ value.encoding.should eq Encoding::UTF_8
80
+ end
81
+ end
82
+
83
+ context "body" do
84
+ it "can parse body" do
85
+ frames = parse_all("CONNECT\n\nbody\x00")
86
+ frames.length.should eq(1)
87
+ frames[0].body.should eq "body"
88
+ end
89
+
90
+ it "can parse binary body" do
91
+ frames = parse_all("CONNECT\ncontent-length:5\n\nbo\x00dy\x00")
92
+ frames.length.should eq(1)
93
+ frames[0].body.should eq "bo\x00dy"
94
+ end
95
+
96
+ it "parses body as binary string when no content-type given" do
97
+ frames = parse_all("MESSAGE\n\nWhat üp\x00")
98
+ frames.length.should eq(1)
99
+ frames[0].body.should eq "What \xC3\xBCp".force_encoding("BINARY")
100
+ frames[0].body.encoding.should eq Encoding::BINARY
101
+ end
102
+
103
+ it "parses body as encoded string when content-type is a text type and charset is given" do
104
+ frames = parse_all("MESSAGE\ncontent-type:text/plain;charset=ISO-8859-1\n\nWhat \xFCp\x00")
105
+ frames.length.should eq(1)
106
+ frames[0].body.should eq "What \xFCp".force_encoding("iso-8859-1")
107
+ frames[0].body.encoding.should eq Encoding::ISO_8859_1
108
+ end
109
+
110
+ it "parses body as encoded string when content-type is not a text type and charset is given" do
111
+ frames = parse_all("MESSAGE\ncontent-type:application/octet-stream;charset=ISO-8859-1\n\nWhat \xFCp\x00")
112
+ frames.length.should eq(1)
113
+ frames[0].body.should eq "What \xFCp".force_encoding("iso-8859-1")
114
+ frames[0].body.encoding.should eq Encoding::ISO_8859_1
115
+ end
116
+
117
+ it "parses body as utf-8 encoded string when content-type is a text type and charset is not given" do
118
+ frames = parse_all("MESSAGE\ncontent-type:text/plain\n\nWhat üp\x00")
119
+ frames.length.should eq(1)
120
+ frames[0].body.should eq "What \xC3\xBCp".force_encoding("UTF-8")
121
+ frames[0].body.encoding.should eq Encoding::UTF_8
122
+ end
123
+
124
+ it "parses body as binary string when content-type is not a text type and charset is not given" do
125
+ frames = parse_all("MESSAGE\ncontent-type:application/octet-stream\n\nWhat \xFCp\x00")
126
+ frames.length.should eq(1)
127
+ frames[0].body.should eq "What \xFCp".force_encoding("BINARY")
128
+ frames[0].body.encoding.should eq Encoding::BINARY
129
+ end
130
+ end
131
+
132
+ context "multiple frames" do
133
+ it "yields multiple frames in a single invocation" do
134
+ frames = parse_all("CONNECT\n\n\x00CONNECT\n\n\x00CONNECT\n\n\x00")
135
+ frames.length.should eq(3)
136
+ frames.map(&:command).should eq %w[CONNECT CONNECT CONNECT]
137
+ frames.uniq.length.should eq(3)
138
+ end
139
+
140
+ it "allows newlines between frames" do
141
+ frames = parse_all("\n\r\n\nCONNECT\n\n\x00\n\n\r\nCONNECT\n\n\x00\n\n")
142
+ frames.length.should eq(2)
143
+ frames.map(&:command).should eq %w[CONNECT CONNECT]
144
+ frames.uniq.length.should eq(2)
145
+ end
146
+ end
147
+
148
+ context "multiple invocations" do
149
+ it "parses simple split up frames" do
150
+ frames = parse_all("CONNECT\n")
151
+ frames.should be_empty
152
+
153
+ frames = parse_all("\n\x00")
154
+ frames.length.should eq(1)
155
+ frames[0].command.should eq "CONNECT"
156
+ end
157
+
158
+ it "parses frames split across buffer markings" do
159
+ frames = parse_all("\n\nCONN")
160
+ frames.should be_empty
161
+
162
+ frames = parse_all("ECT\n\n\x00")
163
+ frames.length.should eq(1)
164
+ frames[0].command.should eq "CONNECT"
165
+ end
166
+
167
+ it "parses frames split across header keys" do
168
+ frames = parse_all("CONNECT\nheader:")
169
+ frames.should be_empty
170
+
171
+ frames = parse_all("value\n\n\x00")
172
+ frames.length.should eq(1)
173
+ frames[0].command.should eq "CONNECT"
174
+ frames[0].headers.should eq("header" => "value")
175
+ end
176
+
177
+ it "parses binary frame split across body" do
178
+ frames = parse_all("CONNECT\ncontent-length:4\n\n\x00a")
179
+ frames.should be_empty
180
+
181
+ frames = parse_all("b\x00\x00")
182
+ frames.length.should eq(1)
183
+ frames[0].command.should eq "CONNECT"
184
+ frames[0].body.should eq("\x00ab\x00")
185
+ end
186
+
187
+ it "parses frames split across frames" do
188
+ frames = parse_all("CONNECT\n")
189
+ frames.should be_empty
190
+
191
+ frames = parse_all("\n\x00CONNEC")
192
+ frames.length.should eq(1)
193
+ frames[0].command.should eq "CONNECT"
194
+
195
+ frames = parse_all("T\n\n\x00")
196
+ frames.length.should eq(1)
197
+ frames[0].command.should eq "CONNECT"
198
+ end
199
+ end
200
+
201
+ context "fails on invalid frames" do
202
+ specify "no block given" do
203
+ expect { parser.parse("CONNECT\n\n\x00") }.to raise_error(LocalJumpError)
204
+ end
205
+
206
+ specify "invalid command" do
207
+ expect { parser.parse("CONNET\n\n\x00") }.to raise_error(StompParser::ParseError)
208
+ end
209
+
210
+ specify "unfinished command" do
211
+ expect { parser.parse("CONNECT\x00") }.to raise_error(StompParser::ParseError)
212
+ end
213
+
214
+ specify "header with colon" do
215
+ expect { parser.parse("CONNECT\nfoo: :bar\n\n\x00") }.to raise_error(StompParser::ParseError)
216
+ end
217
+
218
+ specify "header with invalid escape" do
219
+ expect { parser.parse("CONNECT\nfoo:\\t\n\n\x00") }.to raise_error(StompParser::ParseError)
220
+ end
221
+
222
+ specify "body longer than content length" do
223
+ expect { parser.parse("CONNECT\ncontent-length:0\n\nx\x00") }.to raise_error(StompParser::ParseError)
224
+ end
225
+
226
+ specify "invalid content length" do
227
+ expect { parser.parse("CONNECT\ncontent-length:LAWL\n\nx\x00") }.to raise_error(StompParser::Error, /invalid content length "LAWL"/)
228
+ end
229
+
230
+ specify "re-trying invocation after an error" do
231
+ first_error = begin
232
+ parser.parse("CONNET")
233
+ rescue StompParser::ParseError => ex
234
+ ex
235
+ end
236
+
237
+ first_error.should be_a(StompParser::ParseError)
238
+
239
+ second_error = begin
240
+ parser.parse("")
241
+ rescue StompParser::ParseError => ex
242
+ ex
243
+ end
244
+
245
+ second_error.should eql(first_error)
246
+ end
247
+
248
+ specify "total size bigger than global max frame size setting" do
249
+ StompParser.stub(:max_frame_size => 30)
250
+ parser = described_class.new
251
+ parser.parse("CONNECT\n") # 8
252
+ parser.parse("header:value\n") # 21
253
+ expect {
254
+ parser.parse("other:val\n") # 31
255
+ }.to raise_error(StompParser::FrameSizeExceeded)
256
+ end
257
+
258
+ specify "total size bigger than local max frame size setting" do
259
+ parser = described_class.new(max_frame_size = 30)
260
+ parser.parse("CONNECT\n") # 8
261
+ parser.parse("header:value\n") # 21
262
+ expect {
263
+ parser.parse("other:val\n") # 31
264
+ }.to raise_error(StompParser::FrameSizeExceeded)
265
+ end
266
+ end
267
+ end
268
+ end