rjr 0.12.2 → 0.15.1

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.
Files changed (65) hide show
  1. data/README.md +49 -36
  2. data/Rakefile +2 -0
  3. data/bin/rjr-client +11 -9
  4. data/bin/rjr-server +12 -10
  5. data/examples/amqp.rb +29 -0
  6. data/examples/client.rb +32 -0
  7. data/examples/complete.rb +36 -0
  8. data/examples/local.rb +29 -0
  9. data/examples/server.rb +26 -0
  10. data/examples/tcp.rb +29 -0
  11. data/examples/web.rb +22 -0
  12. data/examples/ws.rb +29 -0
  13. data/lib/rjr/common.rb +7 -12
  14. data/lib/rjr/dispatcher.rb +171 -239
  15. data/lib/rjr/em_adapter.rb +33 -66
  16. data/lib/rjr/message.rb +43 -12
  17. data/lib/rjr/node.rb +197 -103
  18. data/lib/rjr/nodes/amqp.rb +216 -0
  19. data/lib/rjr/nodes/easy.rb +159 -0
  20. data/lib/rjr/nodes/local.rb +118 -0
  21. data/lib/rjr/{missing_node.rb → nodes/missing.rb} +4 -2
  22. data/lib/rjr/nodes/multi.rb +79 -0
  23. data/lib/rjr/nodes/tcp.rb +211 -0
  24. data/lib/rjr/nodes/web.rb +197 -0
  25. data/lib/rjr/nodes/ws.rb +187 -0
  26. data/lib/rjr/stats.rb +70 -0
  27. data/lib/rjr/thread_pool.rb +178 -123
  28. data/site/index.html +45 -0
  29. data/site/jquery-latest.js +9404 -0
  30. data/site/jrw.js +297 -0
  31. data/site/json.js +199 -0
  32. data/specs/dispatcher_spec.rb +244 -198
  33. data/specs/em_adapter_spec.rb +52 -80
  34. data/specs/message_spec.rb +223 -197
  35. data/specs/node_spec.rb +67 -163
  36. data/specs/nodes/amqp_spec.rb +82 -0
  37. data/specs/nodes/easy_spec.rb +13 -0
  38. data/specs/nodes/local_spec.rb +72 -0
  39. data/specs/nodes/multi_spec.rb +65 -0
  40. data/specs/nodes/tcp_spec.rb +75 -0
  41. data/specs/nodes/web_spec.rb +77 -0
  42. data/specs/nodes/ws_spec.rb +78 -0
  43. data/specs/stats_spec.rb +59 -0
  44. data/specs/thread_pool_spec.rb +44 -35
  45. metadata +40 -30
  46. data/lib/rjr/amqp_node.rb +0 -330
  47. data/lib/rjr/inspect.rb +0 -65
  48. data/lib/rjr/local_node.rb +0 -150
  49. data/lib/rjr/multi_node.rb +0 -65
  50. data/lib/rjr/tcp_node.rb +0 -323
  51. data/lib/rjr/thread_pool2.rb +0 -272
  52. data/lib/rjr/util.rb +0 -104
  53. data/lib/rjr/web_node.rb +0 -266
  54. data/lib/rjr/ws_node.rb +0 -289
  55. data/lib/rjr.rb +0 -16
  56. data/specs/amqp_node_spec.rb +0 -31
  57. data/specs/inspect_spec.rb +0 -60
  58. data/specs/local_node_spec.rb +0 -43
  59. data/specs/multi_node_spec.rb +0 -45
  60. data/specs/tcp_node_spec.rb +0 -33
  61. data/specs/util_spec.rb +0 -46
  62. data/specs/web_node_spec.rb +0 -32
  63. data/specs/ws_node_spec.rb +0 -32
  64. /data/lib/rjr/{tcp_node2.rb → nodes/tcp2.rb} +0 -0
  65. /data/lib/rjr/{udp_node.rb → nodes/udp.rb} +0 -0
@@ -0,0 +1,82 @@
1
+ require 'rjr/nodes/amqp'
2
+ require 'rjr/nodes/missing'
3
+
4
+ if RJR::Nodes::AMQP == RJR::Nodes::Missing
5
+ puts "Missing AMQP node dependencies, skipping amqp tests"
6
+
7
+ else
8
+ module RJR::Nodes
9
+ describe AMQP do
10
+ describe "#send_msg" do
11
+ it "should send message to the specified queue"
12
+ end
13
+
14
+ describe "#listen" do
15
+ it "should listen for messages" do
16
+ ci = cp = rn = rni = rnt = p = invoked = nil
17
+ node = AMQP.new :node_id => 'server', :broker => 'localhost'
18
+ node.dispatcher.handle('test') do |param|
19
+ ci = @rjr_client_ip
20
+ cp = @rjr_client_port
21
+ rn = @rjr_node
22
+ rni = @rjr_node_id
23
+ rnt = @rjr_node_type
24
+ p = param
25
+ invoked = true
26
+ end
27
+ node.listen
28
+
29
+ # issue request
30
+ AMQP.new(:node_id => 'client',
31
+ :broker => 'localhost').invoke 'server-queue',
32
+ 'test',
33
+ 'myparam'
34
+ node.halt.join
35
+ invoked.should be_true
36
+ ci.should be_nil
37
+ cp.should be_nil
38
+ rn.should == node
39
+ rni.should == 'server'
40
+ rnt.should == :amqp
41
+ p.should == 'myparam'
42
+ end
43
+ end
44
+
45
+ describe "#invoke" do
46
+ it "should invoke request" do
47
+ server = AMQP.new :node_id => 'server', :broker => 'localhost'
48
+ server.dispatcher.handle('test') do |p|
49
+ 'retval'
50
+ end
51
+ server.listen
52
+
53
+ client = AMQP.new :node_id => 'client', :broker => 'localhost'
54
+ res = client.invoke 'server-queue', 'test', 'myparam'
55
+
56
+ server.halt.join
57
+ res.should == 'retval'
58
+ end
59
+ end
60
+
61
+ describe "#notify" do
62
+ it "should send notification" do
63
+ server = AMQP.new :node_id => 'server', :broker => 'localhost'
64
+ server.dispatcher.handle('test') do |p|
65
+ 'retval'
66
+ end
67
+ server.listen
68
+
69
+ client = AMQP.new :node_id => 'client', :broker => 'localhost'
70
+ res = client.notify 'server-queue', 'test', 'myparam'
71
+
72
+ server.halt.join
73
+ res.should == nil
74
+ end
75
+ end
76
+
77
+ # TODO test callbacks over amqp interface
78
+ # TODO ensure closed / error event handlers are invoked
79
+
80
+ end # describe AMQP
81
+ end # module RJR::Nodes
82
+ end # (!missing)
@@ -0,0 +1,13 @@
1
+ require 'rjr/nodes/easy'
2
+
3
+ module RJR::Nodes
4
+ describe Easy do
5
+ describe "#invoke" do
6
+ it "should invoke request"
7
+ end
8
+
9
+ describe "#notify" do
10
+ it "should send notification"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,72 @@
1
+ require 'rjr/nodes/local'
2
+
3
+ module RJR::Nodes
4
+ describe Local do
5
+ describe "#send_msg" do
6
+ it "should dispatch local notification"
7
+ end
8
+
9
+ describe "#invoke" do
10
+ it "should dispatch local request" do
11
+ invoked = rn = rni = rnt = p = nil
12
+ node = Local.new :node_id => 'aaa'
13
+ node.dispatcher.handle('foobar') { |param|
14
+ rn = @rjr_node
15
+ rni = @rjr_node_id
16
+ rnt = @rjr_node_type
17
+ p = param
18
+ invoked = true
19
+ 'retval'
20
+ }
21
+
22
+ res = node.invoke 'foobar', 'myparam'
23
+
24
+ invoked.should == true
25
+ res.should == 'retval'
26
+ rn.should == node
27
+ rni.should == 'aaa'
28
+ rnt.should == :local
29
+ p.should == 'myparam'
30
+ end
31
+ end
32
+
33
+ describe "#notify" do
34
+ it "should dispatch local notification" do
35
+ invoked = nil
36
+ node = Local.new :node_id => 'aaa'
37
+ node.dispatcher.handle('foobar') { |param|
38
+ invoked = true
39
+ 'retval'
40
+ }
41
+
42
+ res = node.notify 'foobar', 'myparam'
43
+ invoked.should == true
44
+ res.should == nil
45
+ end
46
+ end
47
+
48
+ it "should invoke callbacks" do
49
+ node = Local.new
50
+ cbp = nil
51
+ foobar_invoked = false
52
+ callback_invoked = false
53
+ node.dispatcher.handle('foobar') {
54
+ foobar_invoked = true
55
+ @rjr_callback.notify('callback', 'cp')
56
+ }
57
+ node.dispatcher.handle('callback') { |param|
58
+ callback_invoked = true
59
+ cbp = param
60
+ }
61
+
62
+ node.invoke 'foobar', 'myparam'
63
+ foobar_invoked.should be_true
64
+ callback_invoked.should be_true
65
+ cbp.should == 'cp'
66
+ end
67
+
68
+ # TODO make sure local parameters are not modified if altered
69
+ # on remote end of invoke/notify
70
+
71
+ end # desribe Local
72
+ end # module RJR::Nodes
@@ -0,0 +1,65 @@
1
+ require 'rjr/nodes/multi'
2
+ require 'rjr/nodes/amqp'
3
+ require 'rjr/nodes/web'
4
+ require 'rjr/nodes/missing'
5
+
6
+ if RJR::Nodes::AMQP == RJR::Nodes::Missing ||
7
+ RJR::Nodes::Web == RJR::Nodes::Missing
8
+ puts "Missing AMQP and/or web node dependencies, skipping multi tests"
9
+
10
+ else
11
+ module RJR::Nodes
12
+ describe Multi do
13
+ describe "#listen" do
14
+ it "should listen for messages" do
15
+ invoked1 = invoked2 = false
16
+ rni1 = rni2 = nil
17
+ rnt1 = rnt2 = nil
18
+ p1 = p2 = nil
19
+ amqp = AMQP.new :node_id => 'amqp',
20
+ :broker => 'localhost'
21
+ web = Web.new :node_id => 'web',
22
+ :host => 'localhost', :port => 9876
23
+ multi = Multi.new :node_id => 'multi',
24
+ :nodes => [amqp, web]
25
+
26
+ multi.dispatcher.handle('method1') { |param|
27
+ rni1 = @rjr_node_id
28
+ rnt1 = @rjr_node_type
29
+ p1 = param
30
+ invoked1 = true
31
+ 'retval1'
32
+ }
33
+ multi.dispatcher.handle('method2') { |param|
34
+ rni2 = @rjr_node_id
35
+ rnt2 = @rjr_node_type
36
+ p2 = param
37
+ invoked2 = true
38
+ 'retval2'
39
+ }
40
+ multi.listen
41
+ # TODO should wait until we know server is listening
42
+
43
+ web_client = Web.new
44
+ res = web_client.invoke 'http://localhost:9876', 'method2', 'myparam2'
45
+ res.should == 'retval2'
46
+ rni2.should == 'web'
47
+ rnt2.should == :web
48
+ p2.should == 'myparam2'
49
+
50
+ amqp_client = AMQP.new :node_id => 'client',
51
+ :broker => 'localhost'
52
+ res = amqp_client.invoke 'amqp-queue', 'method1', 'myparam1'
53
+ res.should == 'retval1'
54
+ invoked1.should be_true
55
+ rni1.should == 'amqp'
56
+ rnt1.should == :amqp
57
+ p1.should == 'myparam1'
58
+
59
+ multi.halt.join
60
+ end
61
+ end
62
+ end # describe Multi
63
+
64
+ end # module RJR::Nodes
65
+ end # (!missing)
@@ -0,0 +1,75 @@
1
+ require 'rjr/nodes/tcp'
2
+
3
+ module RJR::Nodes
4
+ describe TCP do
5
+ describe "#send_msg" do
6
+ it "should send message using the specifed connection"
7
+ end
8
+
9
+ describe "#listen" do
10
+ it "should listen for messages" do
11
+ ci = cp = rn = rni = rnt = p = invoked = nil
12
+ server = TCP.new :node_id => 'tcp',
13
+ :host => 'localhost', :port => 9987
14
+ server.dispatcher.handle('foobar') { |param|
15
+ ci = @rjr_client_ip
16
+ cp = @rjr_client_port
17
+ rn = @rjr_node
18
+ rni = @rjr_node_id
19
+ rnt = @rjr_node_type
20
+ p = param
21
+ invoked = true
22
+ }
23
+ server.listen
24
+
25
+ # issue request
26
+ TCP.new.invoke 'jsonrpc://localhost:9987', 'foobar', 'myparam'
27
+ server.halt.join
28
+ ci.should == "127.0.0.1"
29
+ #cp.should == 9987
30
+ rn.should == server
31
+ rni.should == 'tcp'
32
+ rnt.should == :tcp
33
+ p.should == 'myparam'
34
+ invoked.should == true
35
+ end
36
+ end
37
+
38
+ describe "#invoke" do
39
+ it "should invoke request" do
40
+ server = TCP.new :node_id => 'tcp',
41
+ :host => 'localhost', :port => 9987
42
+ server.dispatcher.handle('foobar') { |param|
43
+ 'retval'
44
+ }
45
+ server.listen
46
+
47
+ client = TCP.new
48
+ res = client.invoke 'jsonrpc://localhost:9987', 'foobar', 'myparam'
49
+ server.halt.join
50
+ res.should == 'retval'
51
+ end
52
+ end
53
+
54
+ describe "#notify" do
55
+ it "should send notification" do
56
+ server = TCP.new :node_id => 'tcp',
57
+ :host => 'localhost', :port => 9987
58
+ server.dispatcher.handle('foobar') { |param|
59
+ 'retval'
60
+ }
61
+ server.listen
62
+
63
+ client = TCP.new
64
+ res = client.notify 'jsonrpc://localhost:9987', 'foobar', 'myparam'
65
+ server.halt.join
66
+ res.should == nil
67
+ end
68
+ end
69
+
70
+ # TODO test callbacks over tcp interface
71
+ # TODO ensure closed / error event handlers are invoked
72
+ end
73
+ end
74
+
75
+
@@ -0,0 +1,77 @@
1
+ require 'rjr/nodes/web'
2
+ require 'rjr/nodes/missing'
3
+
4
+ if RJR::Nodes::Web == RJR::Nodes::Missing
5
+ puts "Missing Web node dependencies, skipping web tests"
6
+
7
+ else
8
+ module RJR::Nodes
9
+ describe Web do
10
+ describe "#send_msg" do
11
+ it "should send response using the specified connection"
12
+ end
13
+
14
+ describe "#listen" do
15
+ it "should listen for messages" do
16
+ ci = cp = rn = rni = rnt = p = invoked = nil
17
+ node = Web.new :node_id => 'www', :host => 'localhost', :port => 9678
18
+ node.dispatcher.handle('test') do |param|
19
+ ci = @rjr_client_ip
20
+ cp = @rjr_client_port
21
+ rn = @rjr_node
22
+ rni = @rjr_node_id
23
+ rnt = @rjr_node_type
24
+ p = param
25
+ invoked = true
26
+ end
27
+ node.listen
28
+
29
+ # issue request
30
+ Web.new.invoke 'http://localhost:9678', 'test', 'myparam'
31
+ node.halt.join
32
+ invoked.should be_true
33
+ ci.should == '127.0.0.1'
34
+ #cp.should
35
+ rn.should == node
36
+ rni.should == 'www'
37
+ rnt.should == :web
38
+ p.should == 'myparam'
39
+ end
40
+ end
41
+
42
+ describe "#invoke" do
43
+ it "should invoke request" do
44
+ server = Web.new :node_id => 'www', :host => 'localhost', :port => 9678
45
+ server.dispatcher.handle('test') do |p|
46
+ 'retval'
47
+ end
48
+ server.listen
49
+
50
+ client = Web.new
51
+ res = client.invoke 'http://localhost:9678', 'test', 'myparam'
52
+
53
+ server.halt.join
54
+ res.should == 'retval'
55
+ end
56
+ end
57
+
58
+ describe "#notify" do
59
+ it "should send notification" do
60
+ server = Web.new :node_id => 'www', :host => 'localhost', :port => 9678
61
+ server.dispatcher.handle('test') do |p|
62
+ 'retval'
63
+ end
64
+ server.listen
65
+
66
+ client = Web.new
67
+ res = client.notify 'http://localhost:9678', 'test', 'myparam'
68
+
69
+ server.halt.join
70
+ res.should == nil
71
+ end
72
+ end
73
+
74
+ # TODO ensure closed / error event handlers are invoked
75
+ end # describe Web
76
+ end # module RJR::Nodes
77
+ end # (!missing)
@@ -0,0 +1,78 @@
1
+ require 'rjr/nodes/ws'
2
+ require 'rjr/nodes/missing'
3
+
4
+ if RJR::Nodes::WS == RJR::Nodes::Missing
5
+ puts "Missing Ws node dependencies, skipping ws tests"
6
+
7
+ else
8
+ module RJR::Nodes
9
+ describe WS do
10
+ describe "#send_msg" do
11
+ it "should send response using the specified connection"
12
+ end
13
+
14
+ describe "#listen" do
15
+ it "should listen for messages" do
16
+ ci = cp = rn = rni = rnt = p = invoked = nil
17
+ node = WS.new :node_id => 'ws', :host => 'localhost', :port => 9678
18
+ node.dispatcher.handle('test') do |param|
19
+ ci = @rjr_client_ip
20
+ cp = @rjr_client_port
21
+ rn = @rjr_node
22
+ rni = @rjr_node_id
23
+ rnt = @rjr_node_type
24
+ p = param
25
+ invoked = true
26
+ end
27
+ node.listen
28
+
29
+ # issue request
30
+ WS.new.invoke 'http://localhost:9678', 'test', 'myparam'
31
+ node.halt.join
32
+ invoked.should be_true
33
+ ci.should == '127.0.0.1'
34
+ #cp.should
35
+ rn.should == node
36
+ rni.should == 'ws'
37
+ rnt.should == :ws
38
+ p.should == 'myparam'
39
+ end
40
+ end
41
+
42
+ describe "#invoke" do
43
+ it "should invoke request" do
44
+ server = WS.new :node_id => 'ws', :host => 'localhost', :port => 9678
45
+ server.dispatcher.handle('test') do |p|
46
+ 'retval'
47
+ end
48
+ server.listen
49
+
50
+ client = WS.new
51
+ res = client.invoke 'http://localhost:9678', 'test', 'myparam'
52
+
53
+ server.halt.join
54
+ res.should == 'retval'
55
+ end
56
+ end
57
+
58
+ describe "#notify" do
59
+ it "should send notification" do
60
+ server = WS.new :node_id => 'ws', :host => 'localhost', :port => 9678
61
+ server.dispatcher.handle('test') do |p|
62
+ 'retval'
63
+ end
64
+ server.listen
65
+
66
+ client = WS.new
67
+ res = client.notify 'http://localhost:9678', 'test', 'myparam'
68
+
69
+ server.halt.join
70
+ res.should == nil
71
+ end
72
+ end
73
+
74
+ # TODO test callbacks over ws interface
75
+ # TODO ensure closed / error event handlers are invoked
76
+ end # describe Ws
77
+ end # module RJR::Nodes
78
+ end # (!missing)
@@ -0,0 +1,59 @@
1
+ require 'rjr/dispatcher'
2
+ require 'rjr/stats'
3
+
4
+ describe "#select_stats" do
5
+ before(:each) do
6
+ @d = RJR::Dispatcher.new
7
+
8
+ @req1 = RJR::Request.new :rjr_node_type => :local, :rjr_method => 'foobar'
9
+ @req1.result = RJR::Result.new :result => true
10
+
11
+ @req2 = RJR::Request.new :rjr_node_type => :tcp, :rjr_method => 'barfoo'
12
+ @req2.result = RJR::Result.new :error_code => 123
13
+
14
+ @req3 = RJR::Request.new :rjr_node_type => :amqp, :rjr_method => 'foobar'
15
+ @req3.result = RJR::Result.new :error_code => 123
16
+
17
+ # XXX not prettiest but works for now
18
+ @d.instance_variable_set(:@requests, [@req1, @req2, @req3])
19
+ end
20
+
21
+ it "should get all requests" do
22
+ requests = select_stats(@d)
23
+ requests.size.should == 3
24
+ requests[0].should == @req1
25
+ requests[1].should == @req2
26
+ requests[2].should == @req3
27
+ end
28
+
29
+ it "should get all requests for a node" do
30
+ requests = select_stats @d, 'on_node', 'local'
31
+ requests.size.should == 1
32
+ requests.first.should == @req1
33
+ end
34
+
35
+ it "should get all requests for a method" do
36
+ requests = select_stats @d, 'for_method', 'foobar'
37
+ requests.size.should == 2
38
+ requests.first.should == @req1
39
+ requests.last.should == @req3
40
+ end
41
+
42
+ it "should get all successfull/failed requests" do
43
+ requests = select_stats @d, 'successful'
44
+ requests.size.should == 1
45
+ requests.first.should == @req1
46
+
47
+ requests = select_stats @d, 'failed'
48
+ requests.size.should == 2
49
+ requests.first.should == @req2
50
+ requests.last.should == @req3
51
+ end
52
+
53
+ it "should get dispatcher stats meeting multiple criteria" do
54
+ requests = select_stats @d, 'for_method', 'foobar', 'successful'
55
+ requests.size.should == 1
56
+ requests.first.should == @req1
57
+ end
58
+
59
+ end
@@ -1,41 +1,50 @@
1
- require 'rjr/dispatcher'
2
- require 'rjr/thread_pool2'
1
+ require 'thread'
2
+ require 'rjr/thread_pool'
3
3
 
4
4
  # TODO ? test ThreadPoolJob being_executed?, completed?, exec, handle_timeout!
5
5
 
6
- describe ThreadPool2 do
7
- it "should start and stop successfully" do
8
- tp = ThreadPool2.new 10, :timeout => 10
9
- tp.running?.should be_false
10
-
11
- tp.start
12
- tp.running?.should be_true
13
- tp.instance_variable_get(:@worker_threads).size.should == 10
14
- tp.instance_variable_get(:@manager_thread).should_not be_nil
15
- ['run', 'sleep'].should include(tp.instance_variable_get(:@manager_thread).status)
16
- sleep 0.5
17
-
18
- tp.stop
19
- tp.running?.should be_false
20
- tp.instance_variable_get(:@manager_thread).should be_nil
21
- tp.instance_variable_get(:@worker_threads).size.should == 0
22
- end
23
-
24
- it "should accept and run work" do
25
- tp = ThreadPool2.new 10, :timeout => 10
26
- tp.start
27
-
28
- tp.instance_variable_get(:@work_queue).size.should == 0
29
- jobs_executed = []
30
- tp << ThreadPool2Job.new { jobs_executed << 1 }
31
- tp << ThreadPool2Job.new { jobs_executed << 2 }
32
- tp.instance_variable_get(:@work_queue).size.should == 2
33
-
34
- sleep 0.5
35
- jobs_executed.should include(1)
36
- jobs_executed.should include(2)
37
- tp.instance_variable_get(:@work_queue).size.should == 0
6
+ module RJR
7
+ describe ThreadPool do
8
+ after(:each) do
9
+ ThreadPool.instance.stop
10
+ ThreadPool.instance.join
11
+ end
12
+
13
+ it "should be a singleton" do
14
+ tp = ThreadPool.instance
15
+ ThreadPool.instance.should == tp
16
+ end
17
+
18
+ it "should start the thread pool" do
19
+ tp = ThreadPool.instance
20
+ tp.start
21
+ tp.instance_variable_get(:@worker_threads).size.should == ThreadPool.num_threads
22
+ tp.should be_running
23
+ end
24
+
25
+ it "should stop the thread pool" do
26
+ tp = ThreadPool.instance
27
+ tp.start
28
+ tp.stop
29
+ tp.join
30
+ tp.instance_variable_get(:@worker_threads).size.should == 0
31
+ tp.should_not be_running
32
+ end
33
+
34
+ it "should run work" do
35
+ tp = ThreadPool.instance
36
+ tp.start
37
+
38
+ jobs_executed = []
39
+ m,c = Mutex.new, ConditionVariable.new
40
+ tp << ThreadPoolJob.new { jobs_executed << 1 ; m.synchronize { c.signal } }
41
+ tp << ThreadPoolJob.new { jobs_executed << 2 ; m.synchronize { c.signal } }
42
+
43
+ m.synchronize { c.wait m, 0.1 } unless jobs_executed.include?(1)
44
+ m.synchronize { c.wait m, 0.1 } unless jobs_executed.include?(2)
45
+ jobs_executed.should include(1)
46
+ jobs_executed.should include(2)
47
+ end
38
48
 
39
- tp.stop
40
49
  end
41
50
  end