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.
- data/.gitignore +1 -0
- data/BSDL +24 -0
- data/COPYING +59 -0
- data/GPL +339 -0
- data/README.md +289 -0
- data/Rakefile +47 -0
- data/benchmark/benchmark_helper.rb +19 -0
- data/benchmark/method_benchmark.rb +94 -0
- data/lib/live_resource.rb +66 -0
- data/lib/live_resource/attributes.rb +77 -0
- data/lib/live_resource/declarations.rb +200 -0
- data/lib/live_resource/finders.rb +43 -0
- data/lib/live_resource/log_helper.rb +24 -0
- data/lib/live_resource/methods.rb +41 -0
- data/lib/live_resource/methods/dispatcher.rb +176 -0
- data/lib/live_resource/methods/forward.rb +23 -0
- data/lib/live_resource/methods/future.rb +27 -0
- data/lib/live_resource/methods/method.rb +93 -0
- data/lib/live_resource/methods/token.rb +22 -0
- data/lib/live_resource/redis_client.rb +100 -0
- data/lib/live_resource/redis_client/attributes.rb +40 -0
- data/lib/live_resource/redis_client/methods.rb +194 -0
- data/lib/live_resource/redis_client/registration.rb +25 -0
- data/lib/live_resource/resource.rb +44 -0
- data/lib/live_resource/resource_proxy.rb +180 -0
- data/old/benchmark/attribute_benchmark.rb +58 -0
- data/old/benchmark/thread_benchmark.rb +89 -0
- data/old/examples/attribute.rb +22 -0
- data/old/examples/attribute_rmw.rb +30 -0
- data/old/examples/attribute_subscriber.rb +32 -0
- data/old/examples/method_provider_sleep.rb +22 -0
- data/old/examples/methods.rb +37 -0
- data/old/lib/live_resource/subscriber.rb +98 -0
- data/old/redis_test.rb +127 -0
- data/old/state_publisher_test.rb +139 -0
- data/old/test/attribute_modify_test.rb +52 -0
- data/old/test/attribute_options_test.rb +54 -0
- data/old/test/attribute_subscriber_test.rb +94 -0
- data/old/test/composite_resource_test.rb +61 -0
- data/old/test/method_sender_test.rb +41 -0
- data/old/test/redis_api_test.rb +185 -0
- data/old/test/simple_attribute_test.rb +75 -0
- data/test/attribute_test.rb +212 -0
- data/test/declarations_test.rb +119 -0
- data/test/logger_test.rb +44 -0
- data/test/method_call_test.rb +223 -0
- data/test/method_forward_continue_test.rb +83 -0
- data/test/method_params_test.rb +81 -0
- data/test/method_routing_test.rb +59 -0
- data/test/multiple_class_test.rb +47 -0
- data/test/new_api_DISABLED.rb +127 -0
- data/test/test_helper.rb +9 -0
- data/test/volume_create_DISABLED.rb +74 -0
- 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
|