serverside 0.2.9 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG +56 -0
  2. data/Rakefile +12 -52
  3. data/bin/serverside +1 -1
  4. data/lib/serverside/application.rb +2 -1
  5. data/lib/serverside/caching.rb +62 -50
  6. data/lib/serverside/controllers.rb +91 -0
  7. data/lib/serverside/core_ext.rb +6 -0
  8. data/lib/serverside/daemon.rb +25 -5
  9. data/lib/serverside/request.rb +17 -11
  10. data/lib/serverside/routing.rb +11 -10
  11. data/lib/serverside/server.rb +14 -6
  12. data/lib/serverside/static.rb +7 -18
  13. data/lib/serverside/template.rb +20 -12
  14. data/spec/caching_spec.rb +318 -0
  15. data/spec/cluster_spec.rb +140 -0
  16. data/{test/spec → spec}/connection_spec.rb +4 -4
  17. data/{test/spec/controller_spec.rb → spec/controllers_spec.rb} +15 -12
  18. data/{test/spec → spec}/core_ext_spec.rb +23 -18
  19. data/spec/daemon_spec.rb +99 -0
  20. data/{test/spec → spec}/request_spec.rb +45 -45
  21. data/spec/routing_spec.rb +240 -0
  22. data/spec/server_spec.rb +40 -0
  23. data/spec/static_spec.rb +279 -0
  24. data/spec/template_spec.rb +129 -0
  25. metadata +21 -35
  26. data/lib/serverside/controller.rb +0 -67
  27. data/test/functional/primitive_static_server_test.rb +0 -61
  28. data/test/functional/request_body_test.rb +0 -93
  29. data/test/functional/routing_server.rb +0 -14
  30. data/test/functional/routing_server_test.rb +0 -41
  31. data/test/functional/static_profile.rb +0 -17
  32. data/test/functional/static_rfuzz.rb +0 -31
  33. data/test/functional/static_server.rb +0 -7
  34. data/test/functional/static_server_test.rb +0 -31
  35. data/test/spec/caching_spec.rb +0 -139
  36. data/test/test_helper.rb +0 -2
  37. data/test/unit/cluster_test.rb +0 -129
  38. data/test/unit/connection_test.rb +0 -48
  39. data/test/unit/core_ext_test.rb +0 -46
  40. data/test/unit/daemon_test.rb +0 -75
  41. data/test/unit/request_test.rb +0 -278
  42. data/test/unit/routing_test.rb +0 -171
  43. data/test/unit/server_test.rb +0 -28
  44. data/test/unit/static_test.rb +0 -171
  45. data/test/unit/template_test.rb +0 -78
@@ -6,10 +6,11 @@ module ServerSide
6
6
  module Static
7
7
  include HTTP::Caching
8
8
 
9
- ETAG_FORMAT = '%x:%x:%x'.inspect.freeze
9
+ ETAG_FORMAT = '%x:%x:%x'.freeze
10
10
  TEXT_PLAIN = 'text/plain'.freeze
11
11
  TEXT_HTML = 'text/html'.freeze
12
12
  MAX_CACHE_FILE_SIZE = 100000.freeze # 100KB for the moment
13
+ MAX_AGE = 86400 # one day
13
14
 
14
15
  DIR_LISTING_START = '<html><head><title>Directory Listing for %s</title></head><body><h2>Directory listing for %s:</h2>'.freeze
15
16
  DIR_LISTING = '<a href="%s">%s</a><br/>'.freeze
@@ -29,8 +30,6 @@ module ServerSide
29
30
  '.png'.freeze => 'image/png'.freeze
30
31
  })
31
32
 
32
- @@static_files = {}
33
-
34
33
  # Serves a file over HTTP. The file is cached in memory for later retrieval.
35
34
  # If the If-None-Match header is included with an ETag, it is checked
36
35
  # against the file's current ETag. If there's a match, a 304 response is
@@ -38,17 +37,11 @@ module ServerSide
38
37
  def serve_file(fn)
39
38
  stat = File.stat(fn)
40
39
  etag = (ETAG_FORMAT % [stat.mtime.to_i, stat.size, stat.ino]).freeze
41
- validate_cache(etag, stat.mtime) do
42
- if @@static_files[fn] && (@@static_files[fn][0] == etag)
43
- content = @@static_files[fn][1]
44
- else
45
- content = IO.read(fn).freeze
46
- @@static_files[fn] = [etag.freeze, content]
47
- end
48
- send_response(200, @@mime_types[File.extname(fn)], content, stat.size)
40
+ validate_cache(stat.mtime, MAX_AGE, etag) do
41
+ send_response(200, @@mime_types[File.extname(fn)], IO.read(fn),
42
+ stat.size)
49
43
  end
50
44
  rescue => e
51
- puts e.message
52
45
  send_response(404, TEXT_PLAIN, 'Error reading file.')
53
46
  end
54
47
 
@@ -64,17 +57,13 @@ module ServerSide
64
57
  end
65
58
 
66
59
  def serve_template(fn, b = nil)
67
- if (fn =~ RHTML) || (File.file?(fn = fn + '.rhtml'))
68
- send_response(200, TEXT_HTML, Template.render(fn, b || binding))
69
- end
60
+ send_response(200, TEXT_HTML, Template.render(fn, b || binding))
70
61
  end
71
62
 
72
63
  # Serves static files and directory listings.
73
64
  def serve_static(path)
74
65
  if File.file?(path)
75
- serve_file(path)
76
- elsif serve_template(path)
77
- return
66
+ path =~ RHTML ? serve_template(path) : serve_file(path)
78
67
  elsif File.directory?(path)
79
68
  if File.file?(path/'index.html')
80
69
  serve_file(path/'index.html')
@@ -4,33 +4,41 @@ module ServerSide
4
4
  # The Template module implements an ERB template rendering system. Templates
5
5
  # are cached and automatically reloaded if the file changes.
6
6
  class Template
7
- # The @@templates variable is a hash keyed by template name. The values are
7
+ # The @@templates variable caches templates in use. The values are
8
8
  # arrays containing 2 objects: a file stamp (if the template comes from a
9
- # file,) and the template object itself.
9
+ # file,) and the template object itself.
10
10
  @@templates = {}
11
11
 
12
- # Stores a template for later use. The stamp parameter is used only when
12
+ # Caches a template for later use. The stamp parameter is used only when
13
13
  # the content of a template file is stored.
14
14
  def self.set(name, body, stamp = nil)
15
15
  @@templates[name] = [stamp, ERB.new(body)]
16
16
  end
17
17
 
18
- # Renders a template. If the template name is not found, attemps to load
19
- # the template from file. If the template has a non-nil stamp, the render
20
- # method compares it to the file stamp, and reloads the template content
21
- # if necessary.
22
- def self.render(name, binding)
18
+ # Validates the referenced template by checking its stamp. If the name
19
+ # refers to a file, its stamp is checked against the cache stamp, and it
20
+ # is reloaded if necessary. The function returns an ERB instance or nil if
21
+ # the template is not found.
22
+ def self.validate(name)
23
23
  t = @@templates[name]
24
- return t[1].result(binding) if t && t[0].nil?
25
-
24
+ return t[1] if t && t[0].nil?
26
25
  if File.file?(name)
27
26
  stamp = File.mtime(name)
28
27
  t = set(name, IO.read(name), stamp) if (!t || (stamp != t[0]))
29
- t[1].result(binding)
28
+ t[1]
30
29
  else
31
30
  @@templates[name] = nil
31
+ end
32
+ end
33
+
34
+ # Renders a template by first validating it, and by invoking it with the
35
+ # supplied binding.
36
+ def self.render(name, binding)
37
+ if template = validate(name)
38
+ template.result(binding)
39
+ else
32
40
  raise RuntimeError, 'Template not found.'
33
41
  end
34
42
  end
35
- end
43
+ end
36
44
  end
@@ -0,0 +1,318 @@
1
+ require File.join(File.dirname(__FILE__), '../lib/serverside')
2
+ require 'stringio'
3
+ include ServerSide::HTTP
4
+
5
+ class DummyRequest < Request
6
+ attr_accessor :socket, :persistent
7
+ include Caching
8
+
9
+ def initialize
10
+ super(StringIO.new)
11
+ @headers = {}
12
+ end
13
+ end
14
+
15
+ context "Caching#disable_caching" do
16
+ specify "should set the Cache-Control header to no-cache" do
17
+ r = DummyRequest.new
18
+ r.response_headers['Cache-Control'].should_be_nil
19
+ r.disable_caching
20
+ r.response_headers['Cache-Control'].should == 'no-cache'
21
+ end
22
+
23
+ specify "should remove all other cache-related headers" do
24
+ r = DummyRequest.new
25
+ r.response_headers['ETag'] = 'something'
26
+ r.response_headers['Vary'] = 'something'
27
+ r.response_headers['Expires'] = 'something'
28
+ r.response_headers['Last-Modified'] = 'something'
29
+ r.disable_caching
30
+ r.response_headers['ETag'].should_be_nil
31
+ r.response_headers['Vary'].should_be_nil
32
+ r.response_headers['Expires'].should_be_nil
33
+ r.response_headers['Last-Modified'].should_be_nil
34
+ end
35
+ end
36
+
37
+ context "Caching#etag_validators" do
38
+ specify "should return an empty array if no validators are present" do
39
+ r = DummyRequest.new
40
+ r.etag_validators.should == []
41
+ end
42
+
43
+ specify "should return an array containing all etag validators" do
44
+ r = DummyRequest.new
45
+ r.headers['If-None-Match'] = '"aaa-bbb"'
46
+ r.etag_validators.should == ['aaa-bbb']
47
+
48
+ r.headers['If-None-Match'] = '"aaa-bbb", "ccc-ddd"'
49
+ r.etag_validators.should == ['aaa-bbb', 'ccc-ddd']
50
+ end
51
+
52
+ specify "should handle etags with and without quotes" do
53
+ r = DummyRequest.new
54
+ r.headers['If-None-Match'] = 'aaa-bbb'
55
+ r.etag_validators.should == ['aaa-bbb']
56
+
57
+ r.headers['If-None-Match'] = 'aaa-bbb, "ccc-ddd"'
58
+ r.etag_validators.should == ['aaa-bbb', 'ccc-ddd']
59
+ end
60
+
61
+ specify "should handle a wildcard validator" do
62
+ r = DummyRequest.new
63
+ r.headers['If-None-Match'] = '*'
64
+ r.etag_validators.should == ['*']
65
+ end
66
+ end
67
+
68
+ context "Caching#valid_etag?" do
69
+ specify "should return nil if no validator matches the specified etag" do
70
+ r = DummyRequest.new
71
+ r.valid_etag?('xxx-yyy').should_be_nil
72
+
73
+ r.headers['If-None-Match'] = 'xx-yy, aaa-bbb'
74
+ r.valid_etag?('xxx-yyy').should_be_nil
75
+ end
76
+
77
+ specify "should return true if a validator matches the specifed etag" do
78
+ r = DummyRequest.new
79
+
80
+ r.headers['If-None-Match'] = 'xxx-yyy'
81
+ r.valid_etag?('xxx-yyy').should_be true
82
+
83
+ r.headers['If-None-Match'] = '"xxx-yyy"'
84
+ r.valid_etag?('xxx-yyy').should_be true
85
+
86
+ r.headers['If-None-Match'] = 'aaa-bbb, xxx-yyy'
87
+ r.valid_etag?('xxx-yyy').should_be true
88
+
89
+ r.headers['If-None-Match'] = 'xxx-yyy, aaa-bbb'
90
+ r.valid_etag?('xxx-yyy').should_be true
91
+ end
92
+
93
+ specify "should return true if a wildcard is included in If-None-Match" do
94
+ r = DummyRequest.new
95
+
96
+ r.headers['If-None-Match'] = '*'
97
+ r.valid_etag?('xxx-yyy').should_be true
98
+
99
+ r.headers['If-None-Match'] = 'aaa-bbb, *'
100
+ r.valid_etag?('xxx-yyy').should_be true
101
+ end
102
+ end
103
+
104
+ context "Caching#valid_etag? in expiry etag mode (no etag specified)" do
105
+ specify "should return nil if no etag validator is included" do
106
+ r = DummyRequest.new
107
+ r.valid_etag?.should_be_nil
108
+ end
109
+
110
+ specify "should return true if If-None-Match includes a wildcard" do
111
+ r = DummyRequest.new
112
+
113
+ r.headers['If-None-Match'] = '*'
114
+ r.valid_etag?.should_be true
115
+ end
116
+
117
+ specify "should ignore validators not formatted as expiry etags" do
118
+ r = DummyRequest.new
119
+
120
+ r.headers['If-None-Match'] = 'abcd'
121
+ r.valid_etag?.should_be_nil
122
+
123
+ r.headers['If-None-Match'] = 'xxx-yyy, zzz-zzz'
124
+ r.valid_etag?.should_be_nil
125
+ end
126
+
127
+ specify "should parse expiry etags and check the expiration stamp" do
128
+ r = DummyRequest.new
129
+ t = Time.now
130
+ fmt = Caching::EXPIRY_ETAG_FORMAT
131
+
132
+ r.headers['If-None-Match'] = fmt % [t.to_i, (t - 20).to_i]
133
+ r.valid_etag?.should_be_nil
134
+
135
+ r.headers['If-None-Match'] = fmt % [t.to_i, (t + 20).to_i]
136
+ r.valid_etag?.should_be true
137
+
138
+ r.headers['If-None-Match'] = "xxx-yyy, #{fmt % [t.to_i, (t + 20).to_i]}, #{fmt % [t.to_i, (t - 20).to_i]}"
139
+ r.valid_etag?.should_be true
140
+ end
141
+ end
142
+
143
+ context "Caching#expiry_etag" do
144
+ specify "should return an expiry etag with the stamp and expiration time" do
145
+ r = DummyRequest.new
146
+
147
+ t = Time.now
148
+ fmt = Caching::EXPIRY_ETAG_FORMAT
149
+ max_age = 54321
150
+
151
+ r.expiry_etag(t, max_age).should == (fmt % [t.to_i, (t + max_age).to_i])
152
+ end
153
+ end
154
+
155
+ context "Caching#valid_stamp?" do
156
+ specify "should return nil if no If-Modified-Since header is included" do
157
+ r = DummyRequest.new
158
+ r.valid_stamp?(Time.now).should_be_nil
159
+ end
160
+
161
+ specify "should return nil if the If-Modified-Since header is different than the specified stamp" do
162
+ t = Time.now
163
+ r = DummyRequest.new
164
+ r.headers['If-Modified-Since'] = t.httpdate
165
+ r.valid_stamp?(t + 1).should_be_nil
166
+ r.valid_stamp?(t - 1).should_be_nil
167
+ end
168
+
169
+ specify "should return true if the If-Modified-Since header matches the specified stamp" do
170
+ t = Time.now
171
+ r = DummyRequest.new
172
+ r.headers['If-Modified-Since'] = t.httpdate
173
+ r.valid_stamp?(t).should_be true
174
+ end
175
+ end
176
+
177
+ context "Caching#validate_cache" do
178
+ specify "should return nil if no validators are present" do
179
+ r = DummyRequest.new
180
+ r.validate_cache(Time.now, 360).should_be_nil
181
+ end
182
+
183
+ specify "should check for a stamp validator" do
184
+ r = DummyRequest.new
185
+ t = Time.now
186
+
187
+ r.headers['If-Modified-Since'] = t.httpdate
188
+ r.validate_cache(t + 1, 360).should_be_nil
189
+ r.validate_cache(t - 1, 360).should_be_nil
190
+ r.validate_cache(t, 360).should_be true
191
+ end
192
+
193
+ specify "should check for an etag validator" do
194
+ r = DummyRequest.new
195
+ t = Time.now
196
+ etag = 'abcdef'
197
+
198
+ r.validate_cache(t, 360, etag).should_be_nil
199
+ r.headers['If-None-Match'] = 'aaa-bbb'
200
+ r.validate_cache(t, 360, etag).should_be_nil
201
+ r.headers['If-None-Match'] = "aaa-bbb, #{etag}"
202
+ r.validate_cache(t, 360, etag).should_be true
203
+ r.headers['If-None-Match'] = '*'
204
+ r.validate_cache(t, 360, etag).should_be true
205
+ end
206
+
207
+ specify "should check for an expiry etag validator if etag is unspecified" do
208
+ r = DummyRequest.new
209
+ t = Time.now
210
+ fmt = Caching::EXPIRY_ETAG_FORMAT
211
+
212
+ r.headers['If-None-Match'] = 'aaa-bbb'
213
+ r.validate_cache(t, 360).should_be_nil
214
+ r.headers['If-None-Match'] = "aaa-bbb, #{fmt % [t.to_i, (t + 20).to_i]}"
215
+ r.validate_cache(t, 360).should_be true
216
+ r.headers['If-None-Match'] = '*'
217
+ r.validate_cache(t, 360).should_be true
218
+ end
219
+
220
+ specify "should set the response headers with caching info if request did not validate" do
221
+ r = DummyRequest.new
222
+ t = Time.now
223
+ r.validate_cache(t, 360, 'aaa-bbb', :public, 'Cookie')
224
+ r.response_headers['ETag'].should == '"aaa-bbb"'
225
+ r.response_headers['Last-Modified'].should == t.httpdate
226
+ r.response_headers['Expires'].should == ((t + 360).httpdate)
227
+ r.response_headers['Cache-Control'].should == :public
228
+ r.response_headers['Vary'].should == 'Cookie'
229
+ end
230
+
231
+ specify "should set an expiry etag if no etag is specified" do
232
+ r = DummyRequest.new
233
+ t = Time.now
234
+ fmt = Caching::EXPIRY_ETAG_FORMAT
235
+ r.validate_cache(t, 360)
236
+ r.response_headers['ETag'].should == (
237
+ "\"#{fmt % [t.to_i, (t + 360).to_i]}\"")
238
+ end
239
+
240
+ specify "should send a 304 response if the cache validates" do
241
+ r = DummyRequest.new
242
+ t = Time.now
243
+ fmt = Caching::EXPIRY_ETAG_FORMAT
244
+
245
+ r.headers['If-None-Match'] = "aaa-bbb, #{fmt % [t.to_i, (t + 20).to_i]}"
246
+ r.validate_cache(t, 360).should_be true
247
+ r.socket.rewind
248
+ resp = r.socket.read
249
+ resp.should_match /^HTTP\/1.1 304 Not Modified\r\n/
250
+
251
+ r = DummyRequest.new
252
+ t = Time.now
253
+
254
+ r.headers['If-Modified-Since'] = t.httpdate
255
+ r.validate_cache(t, 360).should_be true
256
+ r.socket.rewind
257
+ resp = r.socket.read
258
+ resp.should_match /^HTTP\/1.1 304 Not Modified\r\n/
259
+ end
260
+
261
+ specify "should not send anything if the cache doesn't validate" do
262
+ r = DummyRequest.new
263
+ t = Time.now
264
+
265
+ r.validate_cache(t, 360).should_be_nil
266
+ r.socket.rewind
267
+ resp = r.socket.read
268
+ resp.should_be_empty
269
+ end
270
+
271
+ specify "should not execute the given block if the cache validates" do
272
+ r = DummyRequest.new
273
+ t = Time.now
274
+ r.headers['If-Modified-Since'] = t.httpdate
275
+ proc {r.validate_cache(t, 360) {raise}}.should_not_raise
276
+ end
277
+
278
+ specify "should return the result of the given block if the cache doesn't validate" do
279
+ x = nil
280
+ l = proc {x = :executed}
281
+
282
+ r = DummyRequest.new
283
+ t = Time.now
284
+ r.validate_cache(t, 360, &l).should == :executed
285
+ x.should == :executed
286
+ end
287
+ end
288
+
289
+ context "Caching#send_not_modified_response" do
290
+ specify "should render a 304 response" do
291
+ r = DummyRequest.new
292
+ r.send_not_modified_response
293
+ r.socket.rewind
294
+ resp = r.socket.read
295
+ resp.should_match /^HTTP\/1.1 304 Not Modified\r\n/
296
+ resp.should_match /Content-Length: 0\r\n/
297
+ resp.should_match /\r\n\r\n$/ # empty response body
298
+ end
299
+
300
+ specify "should exclude Connection header if persistent" do
301
+ r = DummyRequest.new
302
+ r.persistent = true
303
+ r.send_not_modified_response
304
+ r.socket.rewind
305
+ resp = r.socket.read
306
+ resp.should_not_match /Connection: close\r\n/
307
+ end
308
+
309
+ specify "should include Connection header if persistent" do
310
+ r = DummyRequest.new
311
+ r.persistent = false
312
+ r.send_not_modified_response
313
+ r.socket.rewind
314
+ resp = r.socket.read
315
+ resp.should_match /Connection: close\r\n/
316
+ end
317
+ end
318
+
@@ -0,0 +1,140 @@
1
+ require File.join(File.dirname(__FILE__), '../lib/serverside')
2
+
3
+ __END__
4
+
5
+ context "Daemon::Cluster::PidFile" do
6
+ setup do
7
+ @fn = Daemon::Cluster::PidFile::FN
8
+ end
9
+
10
+ specify "::FN should be the cluster's pid file" do
11
+ Daemon::Cluster::PidFile::FN.should == 'serverside_cluster.pid'
12
+ end
13
+
14
+ specify "should delete the cluster's pid file" do
15
+ FileUtils.touch(@fn)
16
+ File.file?(@fn).should == true
17
+ Daemon::Cluster::PidFile.delete
18
+ File.file?(@fn).should == false
19
+ end
20
+
21
+ specify "should store multiple pids" do
22
+ Daemon::Cluster::PidFile.delete
23
+ Daemon::Cluster::PidFile.store_pid(1111)
24
+ IO.read(@fn).should == "1111\n"
25
+ Daemon::Cluster::PidFile.store_pid(2222)
26
+ IO.read(@fn).should == "1111\n2222\n"
27
+ end
28
+
29
+ def test_pid_recall_pids
30
+ Daemon::Cluster::PidFile.delete
31
+ proc {Daemon::Cluster::PidFile.recall_pids}.should_raise Errno::ENOENT
32
+ File.open(@fn, 'w') {|f| f.puts 3333; f.puts 4444}
33
+ Daemon::Cluster::PidFile.recall_pids.should == [3333, 4444]
34
+
35
+ FileUtils.rm(@fn)
36
+ Daemon::Cluster::PidFile.store_pid(6666)
37
+ Daemon::Cluster::PidFile.store_pid(7777)
38
+ Daemon::Cluster::PidFile.recall_pids.should == [6666, 7777]
39
+ end
40
+ end
41
+
42
+ class DummyCluster < Daemon::Cluster
43
+ FN = 'result'
44
+
45
+ def self.server_loop(port)
46
+ at_exit {File.open(FN, 'a') {|f| f.puts port}}
47
+ loop {sleep 10}
48
+ end
49
+
50
+ def self.ports
51
+ 5555..5556
52
+ end
53
+ end
54
+
55
+ context "Cluster.fork_server" do
56
+ specify "should fork a server on the specified port" do
57
+ FileUtils.rm(DummyCluster::FN) rescue nil
58
+ port = rand(5_000)
59
+ pid = DummyCluster.fork_server(port)
60
+ sleep 1
61
+ Process.kill('TERM', pid)
62
+ sleep 0.1
63
+ File.file?(DummyCluster::FN).should == true
64
+ File.open(DummyCluster::FN, 'r') do |f|
65
+ f.gets.to_i.should == port
66
+ f.eof?.should == true
67
+ end
68
+ FileUtils.rm(DummyCluster::FN) rescue nil
69
+ end
70
+ end
71
+
72
+ context "Cluster.start_servers" do
73
+ specify "should start a cluster of servers" do
74
+ FileUtils.rm(DummyCluster::FN) rescue nil
75
+ DummyCluster.start_servers
76
+ sleep 0.5
77
+ pids = Daemon::Cluster::PidFile.recall_pids
78
+ pids.length.should == 2
79
+ pids.each {|pid| Process.kill('TERM', pid)}
80
+ sleep 0.5
81
+ File.open(DummyCluster::FN, 'r') do |f|
82
+ p1, p2 = f.gets.to_i, f.gets.to_i
83
+ DummyCluster.ports.include?(p1).should == true
84
+ DummyCluster.ports.include?(p2).should == true
85
+ p1.should_not == p2
86
+ f.eof?.should == true
87
+ end
88
+ FileUtils.rm(DummyCluster::FN) rescue nil
89
+ end
90
+ end
91
+
92
+ context "Cluster.stop_servers" do
93
+ specify "should stop the cluster of servers" do
94
+ DummyCluster.start_servers
95
+ sleep 0.5
96
+ pids = Daemon::Cluster::PidFile.recall_pids
97
+ DummyCluster.stop_servers
98
+ sleep 0.5
99
+ File.file?(Daemon::Cluster::PidFile::FN).should == false
100
+ File.open(DummyCluster::FN, 'r') do |f|
101
+ p1, p2 = f.gets.to_i, f.gets.to_i
102
+ DummyCluster.ports.include?(p1).should == true
103
+ DummyCluster.ports.include?(p2).should == true
104
+ p1.should_not == p2
105
+ f.eof?.should == true
106
+ end
107
+ FileUtils.rm(DummyCluster::FN) rescue nil
108
+ end
109
+ end
110
+
111
+ class DummyCluster2 < Daemon::Cluster
112
+ def self.daemon_loop
113
+ @@a = true
114
+ end
115
+
116
+ def self.start_servers
117
+ @@b = true
118
+ end
119
+
120
+ def self.stop_servers
121
+ @@c = true
122
+ end
123
+
124
+ def self.a; @@a; end
125
+ def self.b; @@b; end
126
+ def self.c; @@c; end
127
+ end
128
+
129
+ context "Cluster.start and stop" do
130
+ specify "should start and stop the cluster daemon" do
131
+ DummyCluster2.start
132
+ DummyCluster2.a.should == true
133
+ DummyCluster2.b.should == true
134
+
135
+ proc {DummyCluster2.c}.should_raise
136
+ DummyCluster2.stop
137
+ DummyCluster2.c.should == true
138
+ end
139
+ end
140
+
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), '../../lib/serverside')
1
+ require File.join(File.dirname(__FILE__), '../lib/serverside')
2
2
 
3
3
  class ServerSide::HTTP::Connection
4
4
  attr_reader :socket, :request_class, :thread
@@ -49,11 +49,11 @@ context "Connection.initialize" do
49
49
  $pause_request = true
50
50
  c = Connection.new(DummySocket.new, DummyRequest1)
51
51
  c.thread.should_be_an_instance_of Thread
52
- c.thread.alive?.should_equal true
53
- DummyRequest1.instance_count.should_equal 1
52
+ c.thread.alive?.should == true
53
+ DummyRequest1.instance_count.should == 1
54
54
  $pause_request = false
55
55
  sleep 0.1 while c.thread.alive?
56
- DummyRequest1.instance_count.should_equal 1000
56
+ DummyRequest1.instance_count.should == 1000
57
57
  end
58
58
  end
59
59
 
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), '../../lib/serverside')
1
+ require File.join(File.dirname(__FILE__), '../lib/serverside')
2
2
  require 'stringio'
3
3
 
4
4
  class ServerSide::Router
@@ -28,14 +28,15 @@ context "ServerSide::Controller.mount" do
28
28
  ServerSide::Router.reset_rules
29
29
  rule = {:path => '/test'}
30
30
  c = ServerSide::Controller.mount(rule)
31
+ sub_class = Class.new(c)
31
32
  r = ServerSide::Router.rules.first
32
- r.first.should_equal rule
33
+ r.first.should == rule
33
34
  r.last.should_be_a_kind_of Proc
34
35
  c.module_eval do
35
36
  define_method(:initialize) {|req| $req = req}
36
37
  end
37
38
  res = r.last.call
38
- res.should_be_a_kind_of c
39
+ res.should_be_a_kind_of sub_class
39
40
 
40
41
  r = ServerSide::Router.new(StringIO.new)
41
42
  r.path = '/test'
@@ -46,13 +47,13 @@ context "ServerSide::Controller.mount" do
46
47
  specify "should accept either an argument or block as the rule" do
47
48
  ServerSide::Router.reset_rules
48
49
  rule = {:path => '/test'}
49
- c = ServerSide::Controller.mount(rule)
50
+ c = Class.new(ServerSide::Controller.mount(rule))
50
51
  r = ServerSide::Router.rules.first
51
52
  r.first.should_be rule
52
53
 
53
54
  ServerSide::Router.reset_rules
54
55
  rule = proc {true}
55
- c = ServerSide::Controller.mount(&rule)
56
+ c = Class.new(ServerSide::Controller.mount(&rule))
56
57
  r = ServerSide::Router.rules.first
57
58
  r.first.should_be rule
58
59
  end
@@ -69,10 +70,10 @@ end
69
70
  require 'metaid'
70
71
 
71
72
  class DummyController < ServerSide::Controller
72
- attr_reader :process_called
73
+ attr_reader :response_called
73
74
 
74
- def process
75
- @process_called = true
75
+ def response
76
+ @response_called = true
76
77
  end
77
78
 
78
79
  def render_default
@@ -91,10 +92,10 @@ context "ServerSide::Controller new instance" do
91
92
  c.parameters.should_be req.parameters
92
93
  end
93
94
 
94
- specify "should invoke the process method" do
95
+ specify "should invoke the response method" do
95
96
  req = ServerSide::HTTP::Request.new(StringIO.new)
96
97
  c = DummyController.new(req)
97
- c.process_called.should_be true
98
+ c.response_called.should_be true
98
99
  end
99
100
 
100
101
  specify "should invoke render_default unless @rendered" do
@@ -103,7 +104,7 @@ context "ServerSide::Controller new instance" do
103
104
  c.rendered.should_be :default
104
105
 
105
106
  c_class = Class.new(DummyController) do
106
- define_method(:process) {@rendered = true}
107
+ define_method(:response) {@rendered = true}
107
108
  end
108
109
  c = c_class.new(req)
109
110
  c.rendered.should_be true
@@ -134,6 +135,8 @@ context "ServerSide::Controller.render" do
134
135
  req = ServerSide::HTTP::Request.new(StringIO.new)
135
136
  c = ServerSide::Controller.new(req)
136
137
  c.render('hello world', 'text/plain')
137
- c.rendered.should_equal true
138
+ c.rendered.should == true
138
139
  end
139
140
  end
141
+
142
+