liveresource 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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