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,185 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
# These tests verify assumptions about the Redis APIs.
|
4
|
+
class RedisApiTest < Test::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
Redis.new.flushall
|
7
|
+
@trace = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def trace(s)
|
11
|
+
puts("- #{s}") if @trace
|
12
|
+
end
|
13
|
+
|
14
|
+
def publisher(channel, quantity)
|
15
|
+
Thread.new do
|
16
|
+
trace "Publisher started"
|
17
|
+
redis = Redis.new
|
18
|
+
|
19
|
+
quantity.times do |i|
|
20
|
+
trace "Publishing..."
|
21
|
+
redis.publish(channel, "news #{i + 1}")
|
22
|
+
end
|
23
|
+
|
24
|
+
trace "Publisher done"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def subscriber(channel, quantity, messages)
|
29
|
+
started = false
|
30
|
+
|
31
|
+
thread = Thread.new do
|
32
|
+
redis = Redis.new
|
33
|
+
|
34
|
+
redis.subscribe(channel) do |on|
|
35
|
+
on.subscribe do |channel, subscriptions|
|
36
|
+
trace "Subscribed to ##{channel} (#{subscriptions} subscriptions)"
|
37
|
+
end
|
38
|
+
|
39
|
+
on.message do |channel, message|
|
40
|
+
trace "##{channel}: #{message}"
|
41
|
+
messages << message
|
42
|
+
|
43
|
+
redis.unsubscribe if (messages.length == quantity)
|
44
|
+
end
|
45
|
+
|
46
|
+
on.unsubscribe do |channel, subscriptions|
|
47
|
+
trace "Unsubscribed from ##{channel} (#{subscriptions} subscriptions)"
|
48
|
+
end
|
49
|
+
|
50
|
+
trace "Subscriber started"
|
51
|
+
started = true
|
52
|
+
end
|
53
|
+
|
54
|
+
trace "Subscriber done"
|
55
|
+
end
|
56
|
+
|
57
|
+
Thread.pass while !started
|
58
|
+
|
59
|
+
thread
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_publish_subscribe
|
63
|
+
messages = Queue.new
|
64
|
+
|
65
|
+
subscriber = subscriber('test', 1, messages)
|
66
|
+
publisher = publisher('test', 1)
|
67
|
+
|
68
|
+
publisher.join
|
69
|
+
subscriber.join
|
70
|
+
|
71
|
+
assert_equal 'news 1', messages.pop
|
72
|
+
end
|
73
|
+
|
74
|
+
def consume(list, quantity, values)
|
75
|
+
Thread.new do
|
76
|
+
trace "Consumer started"
|
77
|
+
redis = Redis.new
|
78
|
+
|
79
|
+
quantity.times do
|
80
|
+
trace "Consuming..."
|
81
|
+
list, value = redis.blpop list, 10
|
82
|
+
trace "Consumed: #{value}"
|
83
|
+
values << value
|
84
|
+
end
|
85
|
+
|
86
|
+
trace "Consumer done"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def produce(list, quantity, delay = 0.0)
|
91
|
+
Thread.new do
|
92
|
+
trace "Producer started"
|
93
|
+
redis = Redis.new
|
94
|
+
|
95
|
+
quantity.times do
|
96
|
+
sleep(delay) unless (delay == 0.0)
|
97
|
+
|
98
|
+
trace "Producing..."
|
99
|
+
redis.rpush list, "hello"
|
100
|
+
trace "Produced"
|
101
|
+
end
|
102
|
+
|
103
|
+
trace "Producer done"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_blocking_pop_consumer_starts_first
|
108
|
+
values = Queue.new
|
109
|
+
|
110
|
+
consumer = consume('test', 1, values)
|
111
|
+
sleep 0.1
|
112
|
+
producer = produce('test', 1)
|
113
|
+
|
114
|
+
consumer.join
|
115
|
+
producer.join
|
116
|
+
|
117
|
+
assert_equal "hello", values.pop
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_blocking_pop_producer_starts_first
|
121
|
+
values = Queue.new
|
122
|
+
|
123
|
+
producer = produce('test', 1)
|
124
|
+
sleep 0.1
|
125
|
+
consumer = consume('test', 1, values)
|
126
|
+
|
127
|
+
consumer.join
|
128
|
+
producer.join
|
129
|
+
|
130
|
+
assert_equal "hello", values.pop
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_blocking_pop_producer_starts_first
|
134
|
+
values = Queue.new
|
135
|
+
|
136
|
+
producer = produce('test', 1)
|
137
|
+
sleep 0.1
|
138
|
+
consumer = consume('test', 1, values)
|
139
|
+
|
140
|
+
consumer.join
|
141
|
+
producer.join
|
142
|
+
|
143
|
+
assert_equal "hello", values.pop
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_blocking_pop_stress
|
147
|
+
values = Queue.new
|
148
|
+
|
149
|
+
producer = produce('test', 1000)
|
150
|
+
consumer = consume('test', 1000, values)
|
151
|
+
|
152
|
+
consumer.join
|
153
|
+
producer.join
|
154
|
+
|
155
|
+
assert_equal "hello", values.pop
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_optimistic_locking
|
159
|
+
redis = Redis.new
|
160
|
+
redis['key'] = 1;
|
161
|
+
|
162
|
+
# Test successful case
|
163
|
+
redis.watch 'key'
|
164
|
+
val = redis['key'].to_i;
|
165
|
+
val = val + 1;
|
166
|
+
redis.multi
|
167
|
+
redis['key'] = val;
|
168
|
+
exec_return = redis.exec
|
169
|
+
trace "exec (1): #{exec_return.inspect}"
|
170
|
+
assert_not_nil exec_return
|
171
|
+
|
172
|
+
# Test failure case
|
173
|
+
redis.watch 'key'
|
174
|
+
val = redis['key'].to_i;
|
175
|
+
val = val + 1;
|
176
|
+
|
177
|
+
redis['key'] = 10; # someone else hops in and modifies 'key'
|
178
|
+
|
179
|
+
redis.multi
|
180
|
+
redis['key'] = val;
|
181
|
+
exec_return = redis.exec
|
182
|
+
trace "exec (2): #{exec_return.inspect}"
|
183
|
+
assert_nil exec_return
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class FavoriteColorServer
|
4
|
+
include LiveResource::Attribute
|
5
|
+
|
6
|
+
remote_writer :color
|
7
|
+
remote_accessor :foo, :bar
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
self.namespace = "colors.favorite"
|
11
|
+
end
|
12
|
+
|
13
|
+
def start
|
14
|
+
ready = Queue.new
|
15
|
+
|
16
|
+
thread = Thread.new do
|
17
|
+
self.color = 'blue'
|
18
|
+
ready << true
|
19
|
+
sleep 0.1
|
20
|
+
self.color = 'green'
|
21
|
+
end
|
22
|
+
|
23
|
+
ready.pop
|
24
|
+
thread
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class FavoriteColorClient
|
29
|
+
include LiveResource::Attribute
|
30
|
+
|
31
|
+
remote_reader :color
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
self.namespace = "colors.favorite"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class SimpleResourceTest < Test::Unit::TestCase
|
39
|
+
def setup
|
40
|
+
Redis.new.flushall
|
41
|
+
|
42
|
+
@client = FavoriteColorClient.new
|
43
|
+
@server = FavoriteColorServer.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_accessors_defined
|
47
|
+
assert @client.respond_to? :color
|
48
|
+
assert @server.respond_to? :color=
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_simple_read_write
|
52
|
+
assert_equal nil, @client.color
|
53
|
+
|
54
|
+
thread = @server.start
|
55
|
+
|
56
|
+
assert_equal 'blue', @client.color
|
57
|
+
sleep 0.2
|
58
|
+
assert_equal 'green', @client.color
|
59
|
+
|
60
|
+
thread.join
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_accessor
|
64
|
+
assert @server.respond_to? :foo
|
65
|
+
assert @server.respond_to? :foo=
|
66
|
+
assert @server.respond_to? :bar
|
67
|
+
assert @server.respond_to? :bar=
|
68
|
+
|
69
|
+
@server.foo = 'foo'
|
70
|
+
assert_equal 'foo', @server.foo
|
71
|
+
|
72
|
+
@server.bar = @server.foo
|
73
|
+
assert_equal 'foo', @server.bar
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class AttributeTest < Test::Unit::TestCase
|
4
|
+
class MyClass
|
5
|
+
attr_accessor :a, :b
|
6
|
+
|
7
|
+
def initialize(a, b)
|
8
|
+
@a = a
|
9
|
+
@b = b
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class AttributeProvider
|
14
|
+
include LiveResource::Resource
|
15
|
+
|
16
|
+
resource_class :attribute_provider
|
17
|
+
resource_name :object_id
|
18
|
+
|
19
|
+
remote_accessor :string, :integer, :float, :my_class, :nil
|
20
|
+
remote_reader :foo
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
self.string = "string"
|
24
|
+
self.integer = 42
|
25
|
+
self.float = 3.14
|
26
|
+
self.my_class = MyClass.new("foo", 42)
|
27
|
+
self.nil = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def set_foo(value, overwrite=true)
|
31
|
+
if overwrite
|
32
|
+
remote_attribute_write(:foo, value)
|
33
|
+
else
|
34
|
+
remote_attribute_writenx(:foo, value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def setup
|
40
|
+
Redis.new.flushall
|
41
|
+
|
42
|
+
LiveResource::RedisClient.logger.level = Logger::INFO
|
43
|
+
|
44
|
+
AttributeProvider.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def teardown
|
48
|
+
LiveResource::stop
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_string_attribute
|
52
|
+
ap = LiveResource::any(:attribute_provider)
|
53
|
+
|
54
|
+
assert ap.string.kind_of?(String), "String attribute is not a string"
|
55
|
+
assert_equal "string", ap.string
|
56
|
+
|
57
|
+
ap.string = "new string"
|
58
|
+
assert_equal "new string", ap.string
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_numeric_attributes
|
62
|
+
ap = LiveResource::any(:attribute_provider)
|
63
|
+
|
64
|
+
assert ap.integer.kind_of?(Numeric), "Integer attribute is not a numeric"
|
65
|
+
assert ap.float.kind_of?(Numeric), "Float attribute is not a numeric"
|
66
|
+
assert ap.float.kind_of?(Float), "Float attribute is not a float"
|
67
|
+
|
68
|
+
assert_equal 42, ap.integer
|
69
|
+
assert_equal 3.14, ap.float
|
70
|
+
|
71
|
+
ap.integer = 24
|
72
|
+
assert_equal 24, ap.integer
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_custom_attribute
|
76
|
+
ap = LiveResource::any(:attribute_provider)
|
77
|
+
mc = ap.my_class
|
78
|
+
|
79
|
+
assert mc.kind_of?(MyClass), "MyClass attribute is not a MyClass"
|
80
|
+
|
81
|
+
assert_equal "foo", mc.a
|
82
|
+
assert_equal 42, mc.b
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_nil_attribute
|
86
|
+
ap = LiveResource::any(:attribute_provider)
|
87
|
+
|
88
|
+
assert_equal nil, ap.nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_no_overwrite
|
92
|
+
ap = LiveResource::any(:attribute_provider)
|
93
|
+
|
94
|
+
# Foo should be not set.
|
95
|
+
assert_equal nil, ap.foo
|
96
|
+
|
97
|
+
# Set it for the first time, telling it not overwrite. This should work, as it
|
98
|
+
# has never been set before
|
99
|
+
ap.set_foo(23, false)
|
100
|
+
assert_equal 23, ap.foo
|
101
|
+
|
102
|
+
# Try again, it should not be overwritten
|
103
|
+
ap.set_foo(13, false)
|
104
|
+
assert_equal 23, ap.foo
|
105
|
+
|
106
|
+
# Now overwrite it
|
107
|
+
ap.set_foo(13, true)
|
108
|
+
assert_equal 13, ap.foo
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class AttributeModifyTest < Test::Unit::TestCase
|
113
|
+
class Incrementer
|
114
|
+
include LiveResource::Resource
|
115
|
+
|
116
|
+
resource_class :incrementer
|
117
|
+
resource_name :object_id
|
118
|
+
|
119
|
+
remote_accessor :value1, :value2
|
120
|
+
|
121
|
+
def initialize(value1, value2)
|
122
|
+
self.value1 = value1
|
123
|
+
self.value2 = value2
|
124
|
+
end
|
125
|
+
|
126
|
+
def increment(*values)
|
127
|
+
remote_attribute_modify(*values) do |a, v|
|
128
|
+
v + 1
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def increment_with_interference
|
133
|
+
modified = false
|
134
|
+
remote_attribute_modify(:value1, :value2) do |a, v|
|
135
|
+
# modify the value from a different redis the
|
136
|
+
# first time through
|
137
|
+
unless modified
|
138
|
+
self.redis.clone.attribute_write(a, 10, {})
|
139
|
+
modified = true
|
140
|
+
end
|
141
|
+
v + 1
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def setup
|
147
|
+
Redis.new.flushall
|
148
|
+
|
149
|
+
LiveResource::RedisClient.logger.level = Logger::INFO
|
150
|
+
|
151
|
+
Incrementer.new(1, 1)
|
152
|
+
end
|
153
|
+
|
154
|
+
def teardown
|
155
|
+
LiveResource::stop
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_modify_without_interference
|
159
|
+
i = LiveResource::any(:incrementer)
|
160
|
+
|
161
|
+
assert_equal 1, i.value1
|
162
|
+
|
163
|
+
i.increment(:value1)
|
164
|
+
|
165
|
+
assert_equal 2, i.value1
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_multi_modify_without_interference
|
169
|
+
i = LiveResource::any(:incrementer)
|
170
|
+
|
171
|
+
assert_equal 1, i.value1
|
172
|
+
assert_equal 1, i.value2
|
173
|
+
|
174
|
+
i.increment(:value1, :value2)
|
175
|
+
|
176
|
+
assert_equal 2, i.value1
|
177
|
+
assert_equal 2, i.value2
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_modify_with_interference
|
181
|
+
i = LiveResource::any(:incrementer)
|
182
|
+
|
183
|
+
assert_equal 1, i.value1
|
184
|
+
assert_equal 1, i.value2
|
185
|
+
|
186
|
+
i.increment_with_interference
|
187
|
+
|
188
|
+
# Because of the interference, we should now be at 10
|
189
|
+
# for value1 but still only be at 2 for value2
|
190
|
+
assert_equal 11, i.value1
|
191
|
+
assert_equal 2, i.value2
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_modify_invalid_attributes
|
195
|
+
i = LiveResource::any(:incrementer)
|
196
|
+
|
197
|
+
# Just a single invalid attr
|
198
|
+
assert_raise(ArgumentError) do
|
199
|
+
i.increment(:value3)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Now two
|
203
|
+
assert_raise(ArgumentError) do
|
204
|
+
i.increment(:value3, :value4)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Now mix with a valid attr
|
208
|
+
assert_raise(ArgumentError) do
|
209
|
+
i.increment(:value1, :value3)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|