hatetepe 0.0.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,96 @@
1
+ require "spec_helper"
2
+ require "hatetepe/events"
3
+
4
+ describe Hatetepe::Events do
5
+ let(:klass) {
6
+ Class.new {
7
+ include Hatetepe::Events
8
+ event :foo
9
+ }
10
+ }
11
+ let(:obj) { klass.new }
12
+
13
+ context "#event(name, *args)" do
14
+ let(:args) { ["arg#1", "arg#2", "arg#3"] }
15
+ let(:called) { [] }
16
+
17
+ before {
18
+ obj.on_foo {|*args| called << [:bar, args] }
19
+ obj.on_foo {|*args| called << [:baz, args] }
20
+ }
21
+
22
+ it "calls the listeners" do
23
+ obj.event :foo, *args
24
+ called.should == [[:bar, args], [:baz, args]]
25
+ end
26
+ end
27
+
28
+ context "#event!(name, *args)" do
29
+ let(:args) { [:foo, "arg#1", "arg#2"] }
30
+
31
+ it "forwards to #event" do
32
+ obj.should_receive(:event).with(*args)
33
+ obj.event! *args
34
+ end
35
+
36
+ it "changes the state to specified name" do
37
+ obj.on_foo {
38
+ obj.foo?.should be_true
39
+ }
40
+ obj.event! :foo
41
+ end
42
+ end
43
+
44
+ context ".event(name, *more_names)" do
45
+ before { klass.event :bar, :baz }
46
+
47
+ it "adds #on_name method" do
48
+ obj.should respond_to(:on_bar)
49
+ end
50
+
51
+ it "adds #name? method" do
52
+ obj.should respond_to(:bar?)
53
+ end
54
+
55
+ it "calls itself for each additional name" do
56
+ obj.should respond_to(:on_baz)
57
+ obj.should respond_to(:baz?)
58
+ end
59
+ end
60
+
61
+ context "#on_name {|*args| ... }" do
62
+ let(:block) { proc {} }
63
+
64
+ it "adds the block to the listener stack" do
65
+ obj.on_foo &block
66
+ obj.on_foo.should include(block)
67
+ end
68
+ end
69
+
70
+ context "#on_name" do
71
+ let(:blocks) { [proc {}, proc {}] }
72
+
73
+ it "returns the listener stack" do
74
+ obj.on_foo &blocks[0]
75
+ obj.on_foo &blocks[1]
76
+
77
+ obj.on_foo.should == blocks
78
+ end
79
+
80
+ it "returns an empty stack if no listeners have been added yet" do
81
+ obj.on_foo.should be_empty
82
+ end
83
+ end
84
+
85
+ context "#name?" do
86
+ it "returns true if the state equals :name" do
87
+ obj.stub :state => :foo
88
+ obj.foo?.should be_true
89
+ end
90
+
91
+ it "returns false if the state doesn't equal :name" do
92
+ obj.stub :state => :bar
93
+ obj.foo?.should be_false
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,215 @@
1
+ require "spec_helper"
2
+ require "hatetepe/parser"
3
+
4
+ describe Hatetepe::Parser do
5
+ let(:parser) { Hatetepe::Parser.new }
6
+
7
+ context "#initialize" do
8
+ it "calls #reset" do
9
+ Hatetepe::Parser.allocate.tap {|ins|
10
+ ins.should_receive :reset
11
+ ins.__send__ :initialize
12
+ }
13
+ end
14
+ end
15
+
16
+ context "#initialize {|instance| ... }" do
17
+ it "yields the new instance" do
18
+ instance = nil
19
+ Hatetepe::Parser.new {|ins|
20
+ instance = ins
21
+ }.should equal(instance)
22
+ end
23
+
24
+ it "evals the block in its original context" do
25
+ expected, actual = nil, nil
26
+ Object.new.instance_eval {
27
+ expected = self
28
+ ::Hatetepe::Parser.new {|ins|
29
+ actual = self
30
+ }
31
+ }
32
+
33
+ actual.should equal(expected)
34
+ end
35
+ end
36
+
37
+ context "#initialize {|| ... }" do
38
+ it "evals the block in the new instance' context" do
39
+ actual = nil
40
+ expected = Hatetepe::Parser.new {
41
+ actual = self
42
+ }
43
+
44
+ actual.should equal(expected)
45
+ end
46
+ end
47
+
48
+ context "#reset" do
49
+ before { parser << "GET / HTTP/1.1\r\n\r\n" }
50
+
51
+ it "resets the message" do
52
+ parser.reset
53
+ parser.message.should be_nil
54
+ end
55
+
56
+ it "resets the state to :reset" do
57
+ parser.reset
58
+ parser.reset?.should be_true
59
+ end
60
+ end
61
+
62
+ context "#<<(data)" do
63
+ it "raises a ParserError if parsing fails" do
64
+ expect {
65
+ parser << "herp derp\r\n"
66
+ }.to raise_error(Hatetepe::ParserError)
67
+ end
68
+ end
69
+
70
+ let(:block) {
71
+ stub("block").tap {|blk|
72
+ blk.stub :to_proc => proc {|*args| blk.call *args }
73
+ }
74
+ }
75
+
76
+ let(:do_request) {
77
+ parser << "POST / HTTP/1.1\r\n"
78
+ parser << "Transfer-Encoding: chunked\r\n"
79
+ parser << "Bar: baz\r\n"
80
+ parser << "\r\n"
81
+ parser << "6\r\n"
82
+ parser << "Hello!\r\n"
83
+ parser << "0\r\n"
84
+ parser << "\r\n"
85
+ }
86
+
87
+ let(:do_response) {
88
+ parser << "HTTP/1.1 200 OK\r\n"
89
+ parser << "\r\n"
90
+ }
91
+
92
+ context "#on_request {|request| ... }" do
93
+ it "evals the block when a request line comes in" do
94
+ block.should_receive(:call) {|request|
95
+ request.should equal(parser.message)
96
+
97
+ request.verb.should == "POST"
98
+ request.uri.should == "/"
99
+ request.http_version.should == "1.1"
100
+ request.headers.should be_empty
101
+ }
102
+
103
+ parser.on_request &block
104
+ do_request
105
+ end
106
+
107
+ it "changes the state to :request" do
108
+ block.should_receive(:call) {
109
+ parser.request?.should be_true
110
+ }
111
+
112
+ parser.on_request &block
113
+ do_request
114
+ end
115
+ end
116
+
117
+ context "#on_response {|response| ... }" do
118
+ it "evals the block when a response line comes in" do
119
+ block.should_receive(:call) {|response|
120
+ response.should equal(parser.message)
121
+
122
+ response.status.should == 200
123
+ response.http_version.should == "1.1"
124
+ }
125
+
126
+ parser.on_response &block
127
+ do_response
128
+ end
129
+
130
+ it "changes the state to :response" do
131
+ block.should_receive(:call) {
132
+ parser.response?.should be_true
133
+ }
134
+
135
+ parser.on_response &block
136
+ do_response
137
+ end
138
+ end
139
+
140
+ context "#on_headers {|headers| ... }" do
141
+ it "evals the block when the headers are complete" do
142
+ block.should_receive(:call) {|headers|
143
+ headers.should equal(parser.message.headers)
144
+
145
+ headers["Transfer-Encoding"].should == "chunked"
146
+ headers["Bar"].should == "baz"
147
+ }
148
+
149
+ parser.on_headers &block
150
+ do_request
151
+ end
152
+
153
+ it "changes the state to :headers" do
154
+ block.should_receive(:call) {
155
+ parser.headers?.should be_true
156
+ }
157
+
158
+ parser.on_headers &block
159
+ do_request
160
+ end
161
+ end
162
+
163
+ context "#on_body {|body| ... }" do
164
+ it "evals the block when the body starts" do
165
+ block.should_receive(:call) {|body|
166
+ body.should equal(parser.message.body)
167
+
168
+ body.should be_empty
169
+ }
170
+
171
+ parser.on_body &block
172
+ do_request
173
+
174
+ parser.message.body.tap {|b|
175
+ b.rewind
176
+ b.length.should == 6
177
+ b.read.should == "Hello!"
178
+ }
179
+ end
180
+
181
+ it "changes the state to :body" do
182
+ block.should_receive(:call) {
183
+ parser.body?.should be_true
184
+ }
185
+
186
+ parser.on_body &block
187
+ do_request
188
+ end
189
+ end
190
+
191
+ context "#on_complete { ... }" do
192
+ it "evals the block when the message is completely parsed" do
193
+ block.should_receive(:call)
194
+
195
+ parser.on_complete &block
196
+ do_request
197
+ end
198
+
199
+ it "changes the state to :complete" do
200
+ block.should_receive(:call) {
201
+ parser.complete?.should be_true
202
+ }
203
+
204
+ parser.on_complete &block
205
+ do_request
206
+ end
207
+
208
+ it "finishes the body" do
209
+ parser.on_body {|body|
210
+ body.should_receive :close_write
211
+ }
212
+ do_request
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,70 @@
1
+ require "spec_helper"
2
+ require "rack/handler/hatetepe"
3
+
4
+ describe Rack::Handler::Hatetepe do
5
+ let(:app) { stub "app" }
6
+ let(:options) {
7
+ {
8
+ :Host => stub("host"),
9
+ :Port => stub("port")
10
+ }
11
+ }
12
+ let(:server) { stub "server" }
13
+
14
+ describe ".run(app, options) {|server| ... }" do
15
+ before {
16
+ EM.stub :epoll
17
+ Signal.stub :trap
18
+ Hatetepe::Server.stub :start
19
+ }
20
+
21
+ it "starts an Hatetepe server" do
22
+ EM.should_receive(:run) {|&block|
23
+ EM.should_receive(:epoll)
24
+ Hatetepe::Server.should_receive(:start) {|opts|
25
+ opts[:host].should equal(options[:Host])
26
+ opts[:port].should equal(options[:Port])
27
+ opts[:app].should equal(app)
28
+ }
29
+ block.call
30
+ }
31
+ Rack::Handler::Hatetepe.run app, options
32
+ end
33
+
34
+ it "yields the server" do
35
+ Hatetepe::Server.stub :start => server
36
+
37
+ srvr = nil
38
+ Rack::Handler::Hatetepe.run(app) {|s|
39
+ srvr = s
40
+ EM.stop
41
+ }
42
+ srvr.should equal(server)
43
+ end
44
+
45
+ it "can be stopped by sending SIGTERM or SIGINT" do
46
+ EM.should_receive(:run) {|&block| block.call }
47
+
48
+ trapped_signals = []
49
+ Signal.should_receive(:trap).twice {|sig, &block|
50
+ trapped_signals << sig
51
+ EM.should_receive :stop
52
+ block.call
53
+ }
54
+ Rack::Handler::Hatetepe.run app
55
+
56
+ trapped_signals.should include("TERM")
57
+ trapped_signals.should include("INT")
58
+ end
59
+ end
60
+
61
+ describe ".run(app) {|server| ... }" do
62
+ it "defaults Host to 0.0.0.0 and Port to 8080" do
63
+ Hatetepe::Server.should_receive(:start) {|opts|
64
+ opts[:host].should == "0.0.0.0"
65
+ opts[:port].should == 8080
66
+ }
67
+ Rack::Handler::Hatetepe.run(app) { EM.stop }
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,255 @@
1
+ require "spec_helper"
2
+ require "hatetepe/server"
3
+
4
+ describe Hatetepe::Server do
5
+ let(:server) {
6
+ Hatetepe::Server.allocate.tap {|s|
7
+ s.send :initialize, config
8
+ s.post_init
9
+ s.requests << request
10
+ }
11
+ }
12
+ let(:request) { stub "request", :to_hash => env }
13
+ let(:env) {
14
+ {
15
+ "rack.input" => Hatetepe::Body.new
16
+ }
17
+ }
18
+
19
+ let(:app) { stub "app" }
20
+ let(:host) { "127.0.4.1" }
21
+ let(:port) { 8081 }
22
+ let(:errors) { stub "errors", :<< => nil, :flush => nil }
23
+ let(:config) {
24
+ {
25
+ :app => app,
26
+ :host => host,
27
+ :port => port,
28
+ :errors => errors
29
+ }
30
+ }
31
+
32
+ context ".start(config)" do
33
+ it "starts an EventMachine server" do
34
+ args = [host, port, Hatetepe::Server, config]
35
+ EM.should_receive(:start_server).with(*args) { server }
36
+
37
+ Hatetepe::Server.start(config).should equal(server)
38
+ end
39
+ end
40
+
41
+ context "#initialize(config)" do
42
+ let(:server) { Hatetepe::Server.allocate }
43
+ let(:builder) { stub "app builder" }
44
+
45
+ it "builds the app" do
46
+ Rack::Builder.stub :new => builder
47
+ builder.should_receive(:use).with Hatetepe::App
48
+ builder.should_receive(:run).with app
49
+
50
+ server.send :initialize, config
51
+ server.app.should equal(builder)
52
+ end
53
+
54
+ it "sets up the error stream" do
55
+ server.send :initialize, config
56
+ server.errors.should equal(errors)
57
+ end
58
+
59
+ it "uses stderr as default error stream" do
60
+ config.delete :errors
61
+ server.send :initialize, config
62
+ server.errors.should equal($stderr)
63
+ end
64
+ end
65
+
66
+ context "#post_init" do
67
+ let(:server) {
68
+ Hatetepe::Server.allocate.tap {|s| s.post_init }
69
+ }
70
+
71
+ it "sets up the request queue" do
72
+ server.requests.should be_an(Array)
73
+ server.requests.should be_empty
74
+ end
75
+
76
+ it "sets up the parser" do
77
+ server.parser.should respond_to(:<<)
78
+ server.parser.on_request[0].should == server.requests.method(:<<)
79
+ end
80
+
81
+ it "sets up the builder" do
82
+ server.builder.on_write[0].should == server.method(:send_data)
83
+ end
84
+ end
85
+
86
+ context "#receive_data(data)" do
87
+ before { server.stub :close_connection_after_writing }
88
+
89
+ it "feeds data into the parser" do
90
+ data = stub("data")
91
+ server.parser.should_receive(:<<).with data
92
+ server.receive_data data
93
+ end
94
+
95
+ it "closes the connection if parsing fails" do
96
+ server.parser.should_receive(:<<).and_raise(Hatetepe::ParserError)
97
+ server.should_receive :close_connection
98
+
99
+ server.receive_data "irrelevant data"
100
+ end
101
+
102
+ it "closes the connection when catching an exception" do
103
+ server.parser.should_receive(:<<).and_raise
104
+ server.should_receive :close_connection_after_writing
105
+
106
+ server.receive_data ""
107
+ end
108
+
109
+ it "logs caught exceptions" do
110
+ server.parser.should_receive(:<<).and_raise "error message"
111
+ errors.should_receive(:<<) {|str|
112
+ str.should include("error message")
113
+ }
114
+ errors.should_receive :flush
115
+
116
+ server.receive_data ""
117
+ end
118
+ end
119
+
120
+ context "#process" do
121
+ it "puts useful stuff into env[]" do
122
+ app.should_receive(:call) {|e|
123
+ e.should equal(env)
124
+ e["rack.url_scheme"].should == "http"
125
+ e["hatetepe.connection"].should equal(server)
126
+ e["rack.input"].source.should equal(server)
127
+ e["rack.errors"].should equal(server.errors)
128
+ e["rack.multithread"].should be_false
129
+ e["rack.multiprocess"].should be_false
130
+ e["rack.run_once"].should be_false
131
+
132
+ e["SERVER_NAME"].should == host
133
+ e["SERVER_NAME"].should_not equal(host)
134
+ e["SERVER_PORT"].should == String(port)
135
+ e["HTTP_HOST"].should == "#{host}:#{port}"
136
+
137
+ [-1]
138
+ }
139
+ server.process
140
+ end
141
+
142
+ it "calls the app within a new Fiber" do
143
+ outer_fiber = Fiber.current
144
+ app.should_receive(:call) {
145
+ Fiber.current.should_not equal(outer_fiber)
146
+ [-1]
147
+ }
148
+ server.process
149
+ end
150
+ end
151
+
152
+ context "env[stream.start].call(response)" do
153
+ let(:previous) { EM::DefaultDeferrable.new }
154
+ let(:response) {
155
+ [200, {"Key" => "value"}, Rack::STREAMING]
156
+ }
157
+
158
+ before {
159
+ server.requests.unshift previous
160
+ app.stub(:call) {|e| response }
161
+ request.stub :succeed
162
+ server.builder.stub :response
163
+ }
164
+
165
+ it "deletes itself from env[] to prevent multiple calls" do
166
+ app.stub(:call) {|e|
167
+ e["stream.start"].call response
168
+ e.key?("stream.start").should be_false
169
+ [-1]
170
+ }
171
+ previous.succeed
172
+ server.process
173
+ end
174
+
175
+ it "waits for the previous request's response to finish" do
176
+ server.builder.should_not_receive :response
177
+ server.process
178
+ end
179
+
180
+ it "initiates the response" do
181
+ server.builder.should_receive(:response) {|res|
182
+ res[0].should equal(response[0])
183
+ res[1]["Key"].should equal(response[1]["Key"])
184
+ res[1]["Server"].should == "hatetepe/#{Hatetepe::VERSION}"
185
+ }
186
+ previous.succeed
187
+ server.process
188
+ end
189
+ end
190
+
191
+ context "env[stream.send].call(chunk)" do
192
+ it "passes data to the builder" do
193
+ app.stub(:call) {|e|
194
+ e["stream.send"].should == server.builder.method(:body)
195
+ [-1]
196
+ }
197
+ server.process
198
+ end
199
+ end
200
+
201
+ context "env[stream.close].call" do
202
+ before {
203
+ server.stub :close_connection
204
+ server.builder.stub :complete
205
+ request.stub :succeed
206
+ }
207
+
208
+ it "completes the response" do
209
+ server.builder.should_receive :complete
210
+ app.stub(:call) {|e|
211
+ e["stream.close"].call
212
+ [-1]
213
+ }
214
+ server.process
215
+ end
216
+
217
+ it "succeeds the request" do
218
+ request.should_receive :succeed
219
+ app.stub(:call) {|e|
220
+ e["stream.close"].call
221
+ [-1]
222
+ }
223
+ server.process
224
+ end
225
+
226
+ it "leaves the connection open" do
227
+ server.should_not_receive :close_connection
228
+ app.stub(:call) {|e|
229
+ server.requests << stub("another request")
230
+ e["stream.close"].call
231
+ [-1]
232
+ }
233
+ server.process
234
+ end
235
+
236
+ it "closes the connection if there are no more requests" do
237
+ server.should_receive(:close_connection).with true
238
+ app.stub(:call) {|e|
239
+ e["stream.close"].call
240
+ [-1]
241
+ }
242
+ server.process
243
+ end
244
+
245
+ it "deletes itself and stream.send from env[] to prevent multiple calls" do
246
+ app.stub(:call) {|e|
247
+ e["stream.close"].call
248
+ e.key?("stream.send").should be_false
249
+ e.key?("stream.close").should be_false
250
+ [-1]
251
+ }
252
+ server.process
253
+ end
254
+ end
255
+ end