liveresource 2.0.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.
Files changed (54) hide show
  1. data/.gitignore +1 -0
  2. data/BSDL +24 -0
  3. data/COPYING +59 -0
  4. data/GPL +339 -0
  5. data/README.md +289 -0
  6. data/Rakefile +47 -0
  7. data/benchmark/benchmark_helper.rb +19 -0
  8. data/benchmark/method_benchmark.rb +94 -0
  9. data/lib/live_resource.rb +66 -0
  10. data/lib/live_resource/attributes.rb +77 -0
  11. data/lib/live_resource/declarations.rb +200 -0
  12. data/lib/live_resource/finders.rb +43 -0
  13. data/lib/live_resource/log_helper.rb +24 -0
  14. data/lib/live_resource/methods.rb +41 -0
  15. data/lib/live_resource/methods/dispatcher.rb +176 -0
  16. data/lib/live_resource/methods/forward.rb +23 -0
  17. data/lib/live_resource/methods/future.rb +27 -0
  18. data/lib/live_resource/methods/method.rb +93 -0
  19. data/lib/live_resource/methods/token.rb +22 -0
  20. data/lib/live_resource/redis_client.rb +100 -0
  21. data/lib/live_resource/redis_client/attributes.rb +40 -0
  22. data/lib/live_resource/redis_client/methods.rb +194 -0
  23. data/lib/live_resource/redis_client/registration.rb +25 -0
  24. data/lib/live_resource/resource.rb +44 -0
  25. data/lib/live_resource/resource_proxy.rb +180 -0
  26. data/old/benchmark/attribute_benchmark.rb +58 -0
  27. data/old/benchmark/thread_benchmark.rb +89 -0
  28. data/old/examples/attribute.rb +22 -0
  29. data/old/examples/attribute_rmw.rb +30 -0
  30. data/old/examples/attribute_subscriber.rb +32 -0
  31. data/old/examples/method_provider_sleep.rb +22 -0
  32. data/old/examples/methods.rb +37 -0
  33. data/old/lib/live_resource/subscriber.rb +98 -0
  34. data/old/redis_test.rb +127 -0
  35. data/old/state_publisher_test.rb +139 -0
  36. data/old/test/attribute_modify_test.rb +52 -0
  37. data/old/test/attribute_options_test.rb +54 -0
  38. data/old/test/attribute_subscriber_test.rb +94 -0
  39. data/old/test/composite_resource_test.rb +61 -0
  40. data/old/test/method_sender_test.rb +41 -0
  41. data/old/test/redis_api_test.rb +185 -0
  42. data/old/test/simple_attribute_test.rb +75 -0
  43. data/test/attribute_test.rb +212 -0
  44. data/test/declarations_test.rb +119 -0
  45. data/test/logger_test.rb +44 -0
  46. data/test/method_call_test.rb +223 -0
  47. data/test/method_forward_continue_test.rb +83 -0
  48. data/test/method_params_test.rb +81 -0
  49. data/test/method_routing_test.rb +59 -0
  50. data/test/multiple_class_test.rb +47 -0
  51. data/test/new_api_DISABLED.rb +127 -0
  52. data/test/test_helper.rb +9 -0
  53. data/test/volume_create_DISABLED.rb +74 -0
  54. metadata +129 -0
@@ -0,0 +1,139 @@
1
+ require 'live_resource'
2
+ require 'test/unit'
3
+ require 'thread'
4
+
5
+ Thread.abort_on_exception = true
6
+
7
+ class StatePublisherTest < Test::Unit::TestCase
8
+ def setup
9
+ Redis.new.flushall
10
+ @trace = false
11
+ end
12
+
13
+ def trace(s)
14
+ puts("- #{s}") if @trace
15
+ end
16
+
17
+ def test_get_state
18
+ resource = LiveResource.new('test_get_state')
19
+ resource.set :foo
20
+ assert_equal :foo, resource.get
21
+ end
22
+
23
+ def subscribe(name, states)
24
+ thread = Thread.new do
25
+ trace "Subscriber started"
26
+
27
+ resource = LiveResource.new(name)
28
+
29
+ resource.subscribe do |new_state|
30
+ trace "Subscriber saw change to #{new_state}"
31
+ states << new_state
32
+
33
+ resource.unsubscribe if (new_state == :dead)
34
+ end
35
+
36
+ trace "Subscriber done"
37
+ end
38
+
39
+ sleep 0.1 # Give subscriber chance to start
40
+ thread
41
+ end
42
+
43
+ def test_subscribe_to_state
44
+ resource = LiveResource.new('test_subscribe_to_state')
45
+ states = Queue.new
46
+
47
+ resource.set :ok # Starting state
48
+
49
+ subscriber = subscribe('test_subscribe_to_state', states)
50
+
51
+ trace "Setting state (1)"
52
+ resource.set :warning
53
+
54
+ Thread.pass while (states.length < 1)
55
+
56
+ trace "Setting state (2)"
57
+ resource.set :dead
58
+
59
+ subscriber.join
60
+
61
+ assert_equal :warning, states.pop
62
+ assert_equal :dead, states.pop
63
+ end
64
+
65
+ def test_unsubscribe_with_no_subscription
66
+ resource = LiveResource.new('test_unsubscribe_with_no_subscription')
67
+
68
+ assert_raise(RuntimeError) do
69
+ resource.unsubscribe
70
+ end
71
+ end
72
+
73
+ def test_subscribe_with_no_initial_state
74
+ resource = LiveResource.new('test_subscribe_with_no_initial_state')
75
+ states = Queue.new
76
+
77
+ subscriber = subscribe('test_subscribe_to_state', states)
78
+
79
+ resource.set :dead
80
+ subscriber.join
81
+
82
+ assert_equal 0, states.length
83
+ end
84
+
85
+ def test_publish_eliminates_duplicates
86
+ resource = LiveResource.new('test_publish_eliminates_duplicates')
87
+ states = Queue.new
88
+ subscriber = subscribe('test_publish_eliminates_duplicates', states)
89
+
90
+ resource.set :foo
91
+ resource.set :foo
92
+ resource.set :foo
93
+
94
+ # Resource should only publish one instance of :foo
95
+ assert_equal 1, states.length
96
+ assert_equal :foo, states.pop
97
+
98
+ resource.set :dead
99
+ subscriber.join
100
+ end
101
+
102
+ def test_multiple_subscribers
103
+ resource = LiveResource.new('test_multiple_subscribers')
104
+ num_subscribers = 5
105
+ num_messages = 100
106
+ states = []
107
+ subscribers = []
108
+
109
+ num_subscribers.times do
110
+ state_queue = Queue.new
111
+ states << state_queue
112
+ subscribers << subscribe('test_multiple_subscribers', state_queue)
113
+ end
114
+
115
+ (num_messages - 1).times do |i|
116
+ resource.set "state #{i + 1}"
117
+ Thread.pass if (i % 4) # Pass once in a while
118
+ end
119
+
120
+ # Even though we have multiple subscribers, there should only
121
+ # be one channel in Redis.
122
+ assert_equal 1, Redis.new.info["pubsub_channels"].to_i
123
+
124
+ resource.set :dead
125
+
126
+ sleep 0.1
127
+
128
+ num_subscribers.times do |i|
129
+ subscribers[i].join
130
+ end
131
+
132
+ assert_equal num_messages, states[0].length
133
+ assert_equal num_messages, states[1].length
134
+ assert_equal num_messages, states[num_subscribers - 1].length
135
+
136
+ # Should have no junk left over in Redis
137
+ assert_equal 0, Redis.new.info["pubsub_channels"].to_i
138
+ end
139
+ end
@@ -0,0 +1,52 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class Incrementer
4
+ include LiveResource::Attribute
5
+
6
+ remote_accessor :value
7
+
8
+ def initialize(initial_value)
9
+ self.namespace = 'test'
10
+ self.value = initial_value
11
+ end
12
+
13
+ def increment(&block)
14
+ remote_modify(:value) do |v|
15
+ # Allow someone else to mess with Redis while we're
16
+ # in the modify block.
17
+ block.call(redis_space.clone) if block
18
+
19
+ v + 1
20
+ end
21
+ end
22
+ end
23
+
24
+ class AttributeModifyTest < Test::Unit::TestCase
25
+ def setup
26
+ Redis.new.flushall
27
+ end
28
+
29
+ def test_modify_without_interference
30
+ i = Incrementer.new(1)
31
+ i.increment
32
+ assert_equal 2, i.value
33
+ end
34
+
35
+ def test_modify_with_interference
36
+ i = Incrementer.new(1)
37
+ messed_with = false
38
+
39
+ i.increment do |redis_space|
40
+ # Mess with value first time around
41
+ unless messed_with
42
+ redis_space.attribute_set('value', 10)
43
+ messed_with = true
44
+ end
45
+ end
46
+
47
+ # Since the increment is replayed after LiveResource determines
48
+ # the attribute was messed with, the final value should be one
49
+ # increment from 10, not the original value 1.
50
+ assert_equal 11, i.value
51
+ end
52
+ end
@@ -0,0 +1,54 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class TransientColorServer
4
+ include LiveResource::Attribute
5
+
6
+ # Currently the only option is TTL
7
+ remote_writer :color, :ttl => 1
8
+ remote_accessor :color2, :ttl => 1
9
+
10
+ def initialize
11
+ self.namespace = "colors.favorite.transient"
12
+ end
13
+ end
14
+
15
+ class TransientColorClient
16
+ include LiveResource::Attribute
17
+
18
+ remote_reader :color, :color2
19
+
20
+ def initialize
21
+ self.namespace = "colors.favorite.transient"
22
+ end
23
+ end
24
+
25
+ class AttributeOptionsTest < Test::Unit::TestCase
26
+ def setup
27
+ Redis.new.flushall
28
+
29
+ @client = TransientColorClient.new
30
+ @server = TransientColorServer.new
31
+ end
32
+
33
+ def test_accessors_defined
34
+ assert @server.respond_to? :color=
35
+ assert_equal false, @server.respond_to?(:color)
36
+ assert @server.respond_to? :color2
37
+ assert @server.respond_to? :color2=
38
+
39
+ assert @client.respond_to? :color
40
+ assert_equal false, @client.respond_to?(:color=)
41
+ assert @client.respond_to? :color2
42
+ assert_equal false, @client.respond_to?(:color2=)
43
+ end
44
+
45
+ def test_simple_read_write
46
+ assert_equal nil, @client.color
47
+
48
+ @server.color = 'blue'
49
+ assert_equal 'blue', @client.color
50
+
51
+ sleep 2
52
+ assert_equal nil, @client.color
53
+ end
54
+ end
@@ -0,0 +1,94 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class UserLogin
4
+ include LiveResource::Attribute
5
+
6
+ remote_writer :user_logged_in
7
+ remote_writer :user_logged_out
8
+ remote_writer :sudo
9
+
10
+ def initialize(name)
11
+ self.namespace = name
12
+ end
13
+
14
+ def start
15
+ Thread.new do
16
+ self.user_logged_in = "Bob"
17
+ self.user_logged_in = "Fred"
18
+ self.user_logged_out = "Fred"
19
+
20
+ sleep 0.1
21
+
22
+ self.sudo = ["Bob", "rm"]
23
+ self.user_logged_in = "Susan"
24
+ self.user_logged_out = "Bob"
25
+ self.user_logged_out = "Susan"
26
+ end
27
+ end
28
+ end
29
+
30
+ class AuditLog
31
+ include LiveResource::Subscriber
32
+
33
+ remote_subscription :user_logged_in, :login
34
+ remote_subscription :user_logged_out, :logout
35
+ remote_subscription :sudo # Implies method name
36
+
37
+ def initialize(name)
38
+ @backing_store = StringIO.new("", "r+")
39
+ @audit_log = Logger.new(@backing_store)
40
+ self.namespace = name
41
+ end
42
+
43
+ def login(user)
44
+ @audit_log.info "User #{user} logged in"
45
+ end
46
+
47
+ def logout(user)
48
+ @audit_log.info "User #{user} logged out"
49
+ end
50
+
51
+ def sudo(params)
52
+ @audit_log.info "User #{params[0]} ran #{params[1]} as superuser"
53
+ end
54
+
55
+ def dump
56
+ @backing_store.rewind
57
+ @backing_store.readlines
58
+ end
59
+ end
60
+
61
+ class AttributeSubscriberTest < Test::Unit::TestCase
62
+ def setup
63
+ Redis.new.flushall
64
+ end
65
+
66
+ def test_attribute_publishes_to_redis
67
+ # Setting attribute should both send a set and publish to Redis.
68
+ Redis.any_instance.expects(:[]=).once
69
+ Redis.any_instance.expects(:publish).once
70
+
71
+ UserLogin.new("users").user_logged_in = "Bob"
72
+ end
73
+
74
+ def test_subscriber_receives_events
75
+ login = UserLogin.new("users")
76
+ audit_log = AuditLog.new("users")
77
+
78
+ audit_log.subscribe
79
+ login.start
80
+
81
+ sleep 0.25
82
+ audit_log.unsubscribe
83
+
84
+ # This sequence should match what's in UserLogin.start
85
+ audit_log = audit_log.dump
86
+ assert_match "User Bob logged in", audit_log[0]
87
+ assert_match "User Fred logged in", audit_log[1]
88
+ assert_match "User Fred logged out", audit_log[2]
89
+ assert_match "User Bob ran rm as superuser", audit_log[3]
90
+ assert_match "User Susan logged in", audit_log[4]
91
+ assert_match "User Bob logged out", audit_log[5]
92
+ assert_match "User Susan logged out", audit_log[6]
93
+ end
94
+ end
@@ -0,0 +1,61 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class FavoriteColor
4
+ include LiveResource::Attribute
5
+
6
+ remote_writer :color
7
+
8
+ def initialize
9
+ self.namespace = 'colors.favorite'
10
+ end
11
+ end
12
+
13
+ class UpcasedFavoriteColor
14
+ include LiveResource::Attribute
15
+ include LiveResource::Subscriber
16
+ include LiveResource::MethodProvider
17
+ include LiveResource::MethodSender
18
+
19
+ remote_accessor :upcased_color
20
+ remote_subscription :color, :update_color
21
+ remote_method :foo
22
+
23
+ def initialize
24
+ self.namespace = 'colors.favorite'
25
+ end
26
+
27
+ def update_color(new_color)
28
+ self.upcased_color = new_color.upcase
29
+ end
30
+
31
+ # I know this has nothing to do with colors. Sue me. -jdc
32
+ def foo
33
+ 'foo'
34
+ end
35
+ end
36
+
37
+ class CompositeResourceTest < Test::Unit::TestCase
38
+ def setup
39
+ Redis.new.flushall
40
+ end
41
+
42
+ def test_composite_resource
43
+ fave = FavoriteColor.new
44
+ upcased_fave = UpcasedFavoriteColor.new
45
+ upcased_fave.subscribe
46
+ upcased_fave.start_method_dispatcher
47
+
48
+ fave.color = 'blue'
49
+ sleep(0.25) # Takes a moment to propogate
50
+ assert_equal 'BLUE', upcased_fave.upcased_color
51
+
52
+ assert_equal 'foo', upcased_fave.remote_send(:foo)
53
+
54
+ fave.color = 'green'
55
+ sleep(0.25) # Takes a moment to propogate
56
+ assert_equal 'GREEN', upcased_fave.upcased_color
57
+
58
+ upcased_fave.unsubscribe
59
+ upcased_fave.stop_method_dispatcher
60
+ end
61
+ end
@@ -0,0 +1,41 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class Sender
4
+ include LiveResource::MethodSender
5
+
6
+ def initialize
7
+ self.namespace = 'test'
8
+ end
9
+ end
10
+
11
+ class MethodSenderTest < Test::Unit::TestCase
12
+ def setup
13
+ Redis.new.flushall
14
+ end
15
+
16
+ def test_send_sequence
17
+ sender = Sender.new
18
+ rs = sender.redis_space.clone
19
+
20
+ # Put method on waiting queue
21
+ token = sender.remote_send_async(:method, 1, 2, 3)
22
+ assert_not_nil token
23
+ assert_equal false, sender.done_with?(token)
24
+ assert_equal :method, rs.method_get(token, :method)
25
+ assert_equal [1, 2, 3], rs.method_get(token, :params)
26
+
27
+ # Now play provider, move token from waiting to in-progress.
28
+ token2 = rs.method_wait
29
+ assert_equal token2, token
30
+ assert_equal false, sender.done_with?(token)
31
+
32
+ # Set and fetch result
33
+ rs.result_set token, 42
34
+ rs.method_done token
35
+ assert_equal true, sender.done_with?(token)
36
+ assert_equal 42, sender.wait_for_done(token)
37
+
38
+ # No crud left around in Redis
39
+ assert_equal 0, Redis.new.dbsize
40
+ end
41
+ end