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