stomp_parser 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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