hatetepe 0.0.4 → 0.2.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,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