juggernaut 0.5.8 → 2.0.0.beta1

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.
@@ -1,34 +0,0 @@
1
-
2
- $:.unshift "../lib"
3
- require "juggernaut"
4
- require "test/unit"
5
- require "shoulda"
6
- require "mocha"
7
-
8
- class TestJuggernaut < Test::Unit::TestCase
9
-
10
- context "Juggernaut" do
11
-
12
- setup do
13
- Juggernaut.options = { }
14
- end
15
-
16
- should "set options correctly" do
17
- options = {
18
- :host => "0.0.0.0",
19
- :port => 5001,
20
- :debug => false
21
- }
22
- Juggernaut.options = options
23
- assert_equal options, Juggernaut.options
24
- end
25
-
26
- should "have a debug logger by default" do
27
- log = Juggernaut.logger
28
- assert_not_nil log
29
- assert_equal Logger::DEBUG, log.level
30
- end
31
-
32
- end
33
-
34
- end
@@ -1,26 +0,0 @@
1
-
2
- $:.unshift "../lib"
3
- require "juggernaut"
4
- require "test/unit"
5
- require "shoulda"
6
- require "mocha"
7
-
8
- class TestMessage < Test::Unit::TestCase
9
-
10
- context "Message" do
11
-
12
- setup do
13
- @msg = Juggernaut::Message.new(1, "A pre-determined message body", "a81fef13919")
14
- assert_not_nil @msg
15
- end
16
-
17
- should "generate valid JSON" do
18
- obj = {"signature" => "a81fef13919", "body" => "A pre-determined message body", "id" => "1"}
19
- assert_nothing_raised do
20
- assert_equal obj, JSON.parse(@msg.to_s)
21
- end
22
- end
23
-
24
- end
25
-
26
- end
@@ -1,26 +0,0 @@
1
-
2
- $:.unshift "../lib"
3
- require "juggernaut"
4
- require "test/unit"
5
- require "shoulda"
6
- require "mocha"
7
- require "tempfile"
8
-
9
- class TestRunner < Test::Unit::TestCase
10
-
11
- CONFIG = File.join(File.dirname(__FILE__), "files", "juggernaut.yml")
12
-
13
- context "Runner" do
14
-
15
- should "always be true" do
16
- assert true
17
- end
18
-
19
- # should "run" do
20
- # EM.run { EM.stop }
21
- # Juggernaut::Runner.run(["-c", CONFIG])
22
- # end
23
-
24
- end
25
-
26
- end
@@ -1,573 +0,0 @@
1
-
2
- $:.unshift "../lib"
3
- require "juggernaut"
4
- require "test/unit"
5
- require "shoulda"
6
- require "mocha"
7
-
8
- class TestServer < Test::Unit::TestCase
9
-
10
- CONFIG = File.join(File.dirname(__FILE__), "files", "juggernaut.yml")
11
-
12
- DEFAULT_OPTIONS = {
13
- :host => "0.0.0.0",
14
- :port => 5001,
15
- :debug => false,
16
- :cleanup_timer => 2,
17
- :timeout => 10,
18
- :store_messages => false
19
- }
20
-
21
- OPTIONS = DEFAULT_OPTIONS.merge(YAML::load(ERB.new(IO.read(CONFIG)).result))
22
-
23
- class DirectClient
24
- attr_reader :channels
25
- def broadcast_to_channels(channels, body)
26
- self.transmit :command => :broadcast, :type => :to_channels, :channels => channels, :body => body
27
- self
28
- end
29
- def broadcast_to_clients(clients, body)
30
- self.transmit :command => :broadcast, :type => :to_clients, :client_ids => clients, :body => body
31
- end
32
- def close
33
- @socket.close if @socket
34
- end
35
- def initialize(options)
36
- @options = options
37
- @socket = nil
38
- @client_id = options[:client_id]
39
- @session_id = options[:session_id] || rand(1_000_000).to_s(16)
40
- @channels = [ ]
41
- @socket = TCPSocket.new(@options[:host], @options[:port])
42
- end
43
- def inspect
44
- {:channels => @channels, :client_id => @client_id, :session_id => @session_id}.inspect
45
- end
46
- def request_crossdomain_file
47
- @socket.print "<policy-file-request/>\0"
48
- self
49
- end
50
- def query_remove_channels_from_all_clients(channels)
51
- self.transmit :command => :query, :type => :remove_channels_from_all_clients, :channels => channels
52
- self
53
- end
54
- def query_remove_channels_from_client(channels, clients)
55
- self.transmit :command => :query, :type => :remove_channels_from_client, :client_ids => clients, :channels => channels
56
- self
57
- end
58
- def query_show_channels_for_client(client_id)
59
- self.transmit :command => :query, :type => :show_channels_for_client, :client_id => client_id
60
- self
61
- end
62
- def query_show_client(client_id)
63
- self.transmit :command => :query, :type => :show_client, :client_id => client_id
64
- self
65
- end
66
- def query_show_clients(client_ids = [])
67
- self.transmit :command => :query, :type => :show_clients, :client_ids => client_ids
68
- self
69
- end
70
- def query_show_clients_for_channels(channels)
71
- self.transmit :command => :query, :type => :show_clients_for_channels, :channels => channels
72
- self
73
- end
74
- def receive(as_json = true)
75
- return nil unless @socket
76
- begin
77
- # response = @socket.read.to_s
78
- # response = @socket.readline("\0").to_s
79
- response = ""
80
- begin
81
- response << @socket.read_nonblock(1024)
82
- rescue Errno::EAGAIN
83
- end
84
- response.chomp!("\0")
85
- Juggernaut.logger.info "DirectClient read: " + response.inspect
86
- as_json ? JSON.parse(response) : response
87
- rescue => e
88
- Juggernaut.logger.error "DirectClient #{e.class}: #{e.message}"
89
- raise
90
- end
91
- end
92
- def subscribe(channels)
93
- channels.each do |channel|
94
- @channels << channel.to_s unless @channels.include?(channel.to_s)
95
- end
96
- self.transmit :command => :subscribe, :channels => channels
97
- self
98
- end
99
- def send_raw(raw, wait_response = false)
100
- @socket.print(raw + "\0")
101
- @socket.flush
102
- if wait_response
103
- self.receive
104
- else
105
- nil
106
- end
107
- end
108
- def transmit(hash, wait_response = false)
109
- hash[:client_id] ||= @client_id
110
- hash[:session_id] ||= @session_id
111
- self.send_raw(hash.to_json, wait_response)
112
- end
113
- end
114
-
115
- # Assert that the DirectClient has an awaiting message with +body+.
116
- def assert_body(body, subscriber)
117
- assert_response subscriber do |result|
118
- assert_respond_to result, :[]
119
- assert_equal body, result["body"]
120
- end
121
- end
122
-
123
- # Assert that the DirectClient has no awaiting message.
124
- def assert_no_body(subscriber)
125
- assert_response subscriber do |result|
126
- assert_equal false, result
127
- end
128
- end
129
-
130
- def assert_no_response(subscriber)
131
- assert_not_nil subscriber
132
- assert_raise(EOFError) { subscriber.receive }
133
- ensure
134
- subscriber.close
135
- end
136
-
137
- def assert_raw_response(subscriber, response = nil)
138
- assert_not_nil subscriber
139
- result = nil
140
- assert_nothing_raised { result = subscriber.receive(false) }
141
- assert_not_nil result
142
- if block_given?
143
- yield result
144
- else
145
- assert_equal response, result
146
- end
147
- ensure
148
- subscriber.close
149
- end
150
-
151
- def assert_response(subscriber, response = nil)
152
- assert_not_nil subscriber
153
- result = nil
154
- assert_nothing_raised { result = subscriber.receive }
155
- assert_not_nil result
156
- if block_given?
157
- yield result
158
- else
159
- assert_equal response, result
160
- end
161
- ensure
162
- subscriber.close
163
- end
164
-
165
- def assert_server_disconnected(subscriber)
166
- assert_not_nil subscriber
167
- assert_raise(Errno::ECONNRESET, EOFError) { subscriber.receive }
168
- end
169
-
170
- # Convenience method to create a new DirectClient instance with overridable options.
171
- # If a block is passed, control is yielded, passing the new client in. This method
172
- # returns the value returned from that block, or the new client if no block was given.
173
- def new_client(options = { })
174
- c = DirectClient.new(OPTIONS.merge(options))
175
- if block_given?
176
- yield(c)
177
- else
178
- c
179
- end
180
- end
181
-
182
- # Shortcut to run tests that require setting up, starting, then shutting down EventMachine.
183
- # So ugly, but EventMachine doesn't have test examples on code that require back-and-forth
184
- # communication over a long-running connection.
185
- def with_server(options = { }, &block)
186
- # We should not have any clients before we start
187
- Juggernaut::Client.reset!
188
-
189
- # Save the current options. This is an obvious hack.
190
- old_options, Juggernaut.options = Juggernaut.options, OPTIONS.merge(options)
191
- Juggernaut.logger.level = Logger::DEBUG
192
-
193
- # Initialize an array to keep track of connections made to the server in this instance.
194
- @connections = [ ]
195
-
196
- EM.run do
197
- # Start the server, and save each connection made so we can refer to it later.
198
- EM.start_server(Juggernaut.options[:host], Juggernaut.options[:port], Juggernaut::Server) { |c| @connections << c }
199
-
200
- # Guard against never-ending tests by shutting off at 2 seconds.
201
- EM.add_timer(2) do
202
- Juggernaut::Client.send_logouts_to_all_clients
203
- EM.stop
204
- end
205
-
206
- # Deferred: evaluate the block and then run the shutdown proc. By using instance_eval,
207
- # our block gets access to assert_* methods and the +@connections+ variable above.
208
- EM.defer proc {
209
- instance_eval(&block)
210
- }, proc {
211
- # There's probably a better way of doing this, but without this line, different
212
- # clients may create a race condition in tests, causing some of them to sometimes
213
- # fail. This isn't foolproof either, should any client take more than 200 ms.
214
- EM.add_timer(0.2) do
215
- Juggernaut::Client.send_logouts_to_all_clients
216
- EM.stop
217
- end
218
- }
219
- end
220
- ensure
221
- # Restore old options.
222
- Juggernaut.options = old_options if old_options
223
- end
224
-
225
- context "Server" do
226
-
227
- should "accept a connection" do
228
- with_server do
229
- self.new_client do |c|
230
- c.transmit :command => :subscribe, :channels => [ ]
231
- end
232
- assert_equal 1, @connections.select { |c| c.alive? }.size
233
- assert_equal true, @connections.first.alive?
234
- end
235
- assert_equal false, @connections.first.alive?
236
- end
237
-
238
- should "register channels correctly" do
239
- with_server do
240
- self.new_client { |c| c.transmit :command => :subscribe, :channels => ["master", "slave"] }
241
- end
242
- assert @connections.first.has_channel?("master")
243
- assert_equal false, @connections.first.has_channel?("non_existant")
244
- assert @connections.first.has_channels?(["non_existant", "master", "slave"])
245
- assert_equal false, @connections.first.has_channels?(["non_existant", "invalid"])
246
- end
247
-
248
- context "channel-wide broadcast" do
249
-
250
- body = "This is a channel-wide broadcast test!"
251
-
252
- should "be received by client in the same channel" do
253
- subscriber = nil
254
- with_server do
255
- subscriber = self.new_client(:client_id => "broadcast_channel") { |c| c.subscribe %w(master) }
256
- self.new_client { |c| c.broadcast_to_channels %w(master), body }
257
- end
258
- assert_not_nil subscriber
259
- result = subscriber.receive
260
- subscriber.close
261
- assert_respond_to result, :[]
262
- assert_equal body, result["body"]
263
- end
264
-
265
- should "not be received by client not in a channel" do
266
- subscriber = nil
267
- with_server do
268
- subscriber = self.new_client(:client_id => "broadcast_channel") { |c| c.subscribe %w() }
269
- self.new_client { |c| c.broadcast_to_channels %w(master), body }
270
- end
271
- assert_no_response subscriber
272
- end
273
-
274
- should "not be received by client in a different channel" do
275
- subscriber = nil
276
- with_server do
277
- subscriber = self.new_client(:client_id => "broadcast_test") { |c| c.subscribe %w(slave) }
278
- self.new_client { |c| c.broadcast_to_channels %w(broadcast_channel), body }
279
- end
280
- assert_no_response subscriber
281
- end
282
-
283
- end
284
-
285
- # For some reason, these refuse to pass:
286
- context "broadcast with no specific channel" do
287
-
288
- body = "This is a broadcast test!"
289
-
290
- should "be received by client not in any channels" do
291
- subscriber = nil
292
- with_server do
293
- subscriber = self.new_client(:client_id => "broadcast_all") { |c| c.subscribe %w() }
294
- self.new_client { |c| c.broadcast_to_channels %w(), body }
295
- end
296
- assert_body body, subscriber
297
- end
298
-
299
- should "be received by client in a channel" do
300
- subscriber = nil
301
- with_server do
302
- subscriber = self.new_client(:client_id => "broadcast_all") { |c| c.subscribe %w(master) }
303
- self.new_client { |c| c.broadcast_to_channels %w(), body }
304
- end
305
- assert_body body, subscriber
306
- end
307
-
308
- end
309
-
310
- context "broadcast to a client" do
311
-
312
- body = "This is a client-specific broadcast test!"
313
-
314
- should "be received by the target client" do
315
- subscriber = nil
316
- with_server do
317
- subscriber = self.new_client(:client_id => "broadcast_client") { |c| c.subscribe %w() }
318
- self.new_client { |c| c.broadcast_to_clients %w(broadcast_client), body }
319
- end
320
- assert_body body, subscriber
321
- end
322
-
323
- should "not be received by other clients" do
324
- subscriber = nil
325
- with_server do
326
- subscriber = self.new_client(:client_id => "broadcast_faker") { |c| c.subscribe %w() }
327
- self.new_client { |c| c.broadcast_to_clients %w(broadcast_client), body }
328
- end
329
- assert_no_response subscriber
330
- end
331
-
332
- should "be saved until the client reconnects" do
333
- subscriber = nil
334
- with_server :store_messages => true do
335
- self.new_client(:client_id => "broadcast_client") { |c| c.subscribe %w() }.close
336
- self.new_client { |c| c.broadcast_to_clients %w(broadcast_client), body }
337
- subscriber = self.new_client(:client_id => "broadcast_client") { |c| c.subscribe %w() }
338
- end
339
- assert_body body, subscriber
340
- end
341
-
342
- should "only be sent to new client connection" do
343
- old_subscriber = nil
344
- new_subscriber = nil
345
-
346
- with_server :store_messages => true, :timeout => 30 do
347
- old_subscriber = self.new_client(:client_id => "broadcast_client", :session_id => "1") { |c| c.subscribe %w() }
348
- self.new_client { |c| c.broadcast_to_clients %w(broadcast_client), body }
349
- @connections.first.client.expects(:send_message_to_connection).times(2)
350
- new_subscriber = self.new_client(:client_id => "broadcast_client", :session_id => "2") { |c| c.subscribe %w() }
351
- end
352
- end
353
-
354
- end
355
-
356
- context "querying client list" do
357
-
358
- should "return all clients" do
359
- subscriber = nil
360
- with_server do
361
- self.new_client(:client_id => "alex") { |c| c.subscribe %w() }
362
- self.new_client(:client_id => "bob") { |c| c.subscribe %w() }
363
- subscriber = self.new_client(:client_id => "cindy") { |c| c.subscribe %w(); c.query_show_clients }
364
- end
365
- assert_not_nil subscriber
366
- result = subscriber.receive
367
- assert_not_nil result
368
- assert_equal 3, result.size
369
- assert_same_elements %w(alex bob cindy), result.collect { |r| r["client_id"] }
370
- end
371
-
372
- should "not include disconnected clients" do
373
- subscriber = nil
374
- with_server(:timeout => 0) do
375
- self.new_client(:client_id => "sandra") { |c| c.subscribe %w() }
376
- self.new_client(:client_id => "tom") { |c| c.subscribe %w() }.close
377
- subscriber = self.new_client(:client_id => "vivian") { |c| c.subscribe %w(); c.query_show_clients }
378
- end
379
- assert_not_nil subscriber
380
- result = subscriber.receive
381
- assert_not_nil result
382
- assert_equal 2, result.size
383
- assert_same_elements %w(sandra vivian), result.collect { |r| r["client_id"] }
384
- end
385
-
386
- should "only return requested clients" do
387
- subscriber = nil
388
- with_server do
389
- self.new_client(:client_id => "dixie") { |c| c.subscribe %w() }
390
- self.new_client(:client_id => "eamon") { |c| c.subscribe %w() }
391
- self.new_client(:client_id => "fanny") { |c| c.subscribe %w() }
392
- subscriber = self.new_client(:client_id => "zelda") { |c| c.subscribe %w(); c.query_show_clients %w(dixie fanny) }
393
- end
394
- assert_not_nil subscriber
395
- result = subscriber.receive
396
- assert_not_nil result
397
- assert_equal 2, result.size
398
- assert_same_elements %w(dixie fanny), result.collect { |r| r["client_id"] }
399
- end
400
-
401
- should "never return non-existant clients even when requested" do
402
- subscriber = nil
403
- with_server do
404
- self.new_client(:client_id => "dixie") { |c| c.subscribe %w() }
405
- self.new_client(:client_id => "eamon") { |c| c.subscribe %w() }
406
- self.new_client(:client_id => "fanny") { |c| c.subscribe %w() }
407
- subscriber = self.new_client(:client_id => "zelda") { |c| c.subscribe %w(); c.query_show_clients %w(ginny homer) }
408
- end
409
- assert_not_nil subscriber
410
- result = subscriber.receive
411
- assert_not_nil result
412
- assert_equal 0, result.size
413
- end
414
-
415
- should "return correct number of active connections" do
416
- subscriber = nil
417
- with_server do
418
- 5.times { self.new_client(:client_id => "homer") { |c| c.subscribe %w() } }
419
- subscriber = self.new_client(:client_id => "zelda") { |c| c.subscribe %w(); c.query_show_clients %w(homer) }
420
- end
421
- assert_not_nil subscriber
422
- result = subscriber.receive
423
- assert_not_nil result
424
- assert_equal 1, result.size
425
- assert_equal 5, result.first["num_connections"]
426
- end
427
-
428
- should "be equivalent when querying one client" do
429
- s1, s2 = nil
430
- with_server do
431
- 5.times { self.new_client(:client_id => "homer") { |c| c.subscribe %w() } }
432
- s1 = self.new_client(:client_id => "zelda") { |c| c.subscribe %w(); c.query_show_client "homer" }
433
- s2 = self.new_client(:client_id => "zelda") { |c| c.subscribe %w(); c.query_show_clients %w(homer) }
434
- end
435
- assert_not_nil s1
436
- assert_not_nil s2
437
- r1 = s1.receive
438
- assert_not_nil r1
439
- r2 = s2.receive
440
- assert_not_nil r2
441
- assert_equal 1, r2.size
442
- assert_equal r1, r2.first
443
- end
444
-
445
- should "only return clients in specific channels" do
446
- subscriber = nil
447
- with_server do
448
- self.new_client(:client_id => "alexa") { |c| c.subscribe %w(master slave zoo) }
449
- self.new_client(:client_id => "bobby") { |c| c.subscribe %w(master slave) }
450
- self.new_client(:client_id => "cindy") { |c| c.subscribe %w(master zoo) }
451
- self.new_client(:client_id => "dixon") { |c| c.subscribe %w(slave zoo) }
452
- self.new_client(:client_id => "eamon") { |c| c.subscribe %w(slave) }
453
- self.new_client(:client_id => "flack") { |c| c.subscribe %w(decoy slave) }
454
- subscriber = self.new_client(:client_id => "geoff") { |c| c.subscribe %w(zoo); c.query_show_clients_for_channels %w(master zoo) }
455
- end
456
- assert_response subscriber do |result|
457
- assert_equal 5, result.size
458
- assert_same_elements %w(alexa bobby cindy dixon geoff), result.collect { |r| r["client_id"] }
459
- end
460
- end
461
-
462
- end
463
-
464
- context "upon processing an invalid command" do
465
-
466
- should "disconnect immediately" do
467
- subscriber = nil
468
- with_server do
469
- subscriber = self.new_client(:client_id => "pinocchio") { |c| c.transmit :command => :some_undefined_command; c.subscribe %w(); c }
470
- end
471
- assert_server_disconnected subscriber
472
- end
473
-
474
- end
475
-
476
- %w(broadcast subscribe query).each do |type|
477
-
478
- context "upon receiving malformed #{type}" do
479
-
480
- should "disconnect immediately" do
481
- subscriber = nil
482
- with_server do
483
- subscriber = self.new_client(:client_id => "pinocchio") { |c| c.transmit :command => type, :type => :unknown; c.subscribe %w(); c }
484
- end
485
- assert_server_disconnected subscriber
486
- end
487
-
488
- end
489
-
490
- end
491
-
492
- context "upon receiving invalid JSON" do
493
-
494
- should "disconnect immediately" do
495
- subscriber = nil
496
- with_server do
497
- subscriber = self.new_client(:client_id => "pinocchio") { |c| c.send_raw "invalid json..."; c }
498
- end
499
- assert_server_disconnected subscriber
500
- end
501
-
502
- end
503
-
504
- context "crossdomain file request" do
505
-
506
- should "return contents of crossdomain file" do
507
- subscriber = nil
508
- with_server do
509
- subscriber = self.new_client(:client_id => "pinocchio") { |c| c.request_crossdomain_file }
510
- end
511
- assert_raw_response subscriber, <<-EOF
512
- <cross-domain-policy>
513
- <allow-access-from domain="*" to-ports="#{OPTIONS[:port]}" />
514
- </cross-domain-policy>
515
- EOF
516
- end
517
-
518
- end
519
-
520
- context "querying channel list" do
521
-
522
- should "return channel list" do
523
- subscribe = nil
524
- with_server do
525
- self.new_client(:client_id => "homer") { |c| c.subscribe %w(groupie master slave1 slave2) }
526
- self.new_client(:client_id => "marge") { |c| c.subscribe %w(master slave1 slave2) }
527
- subscribe = self.new_client(:client_id => "pinocchio") { |c|
528
- c.subscribe %w(master slave1)
529
- c.query_show_channels_for_client "marge"
530
- }
531
- end
532
- assert_response subscribe do |result|
533
- assert_equal 3, result.size
534
- assert_same_elements %w(master slave1 slave2), result
535
- end
536
- end
537
-
538
- end
539
-
540
- context "remove channel request" do
541
-
542
- should "work on all clients when requested" do
543
- with_server do
544
- self.new_client(:client_id => "homer") { |c| c.subscribe %w(groupie master slave1 slave2) }
545
- self.new_client(:client_id => "marge") { |c| c.subscribe %w(master slave1 slave2) }
546
- self.new_client(:client_id => "pinocchio") { |c|
547
- c.subscribe %w(master slave1 slave2)
548
- c.query_remove_channels_from_all_clients %w(slave1 slave2)
549
- }
550
- end
551
- @connections.each do |connection|
552
- assert_does_not_contain connection.channels, /slave/
553
- end
554
- end
555
-
556
- should "work on specific clients when requested" do
557
- with_server do
558
- self.new_client(:client_id => "homer") { |c| c.subscribe %w(groupie master slave1 slave2) }
559
- self.new_client(:client_id => "marge") { |c| c.subscribe %w(master slave1 slave2) }
560
- self.new_client(:client_id => "pinocchio") { |c|
561
- c.subscribe %w(master slave1 slave2)
562
- c.query_remove_channels_from_client %w(slave1 slave2), %w(homer)
563
- }
564
- end
565
- assert_does_not_contain @connections.find { |c| c.instance_eval("@request[:client_id]") == "homer" }.channels, /slave/
566
- assert_contains @connections.find { |c| c.instance_eval("@request[:client_id]") == "marge" }.channels, /slave/
567
- end
568
-
569
- end
570
-
571
- end
572
-
573
- end