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,58 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'benchmark_helper')
|
2
|
+
|
3
|
+
class Resource
|
4
|
+
include LiveResource::Attribute
|
5
|
+
|
6
|
+
remote_accessor :attribute
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
self.namespace = 'test'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class AttributeTest < Test::Unit::TestCase
|
14
|
+
def setup
|
15
|
+
Redis.new.flushall
|
16
|
+
end
|
17
|
+
|
18
|
+
def run_with_threads(n_threads, n_total, &block)
|
19
|
+
threads = []
|
20
|
+
|
21
|
+
n_threads.times do
|
22
|
+
threads << Thread.new do
|
23
|
+
(n_total / n_threads).times { block.call }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
threads.each { |t| t.join }
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_attribute_performance
|
31
|
+
resource = Resource.new
|
32
|
+
n = 10000
|
33
|
+
|
34
|
+
puts "Attribute get/set performance".title
|
35
|
+
|
36
|
+
Benchmark.bm do |x|
|
37
|
+
x.report("attr read (n=#{n})".pad) do
|
38
|
+
n.times { resource.attribute }
|
39
|
+
end
|
40
|
+
|
41
|
+
x.report("attr write (n=#{n})".pad) do
|
42
|
+
n.times { resource.attribute = 1 }
|
43
|
+
end
|
44
|
+
|
45
|
+
[1, 5, 10].each do |threads|
|
46
|
+
x.report("attr read (n=#{n}, #{threads} threads):".pad) do
|
47
|
+
run_with_threads(threads, n) { resource.attribute }
|
48
|
+
end
|
49
|
+
|
50
|
+
x.report("attr write (n=#{n}, #{threads} threads):".pad) do
|
51
|
+
run_with_threads(threads, n) { resource.attribute = 1 }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
assert true
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'benchmark_helper')
|
2
|
+
|
3
|
+
class Supervisor
|
4
|
+
def main(total_jobs, max_workers)
|
5
|
+
redis = Redis.new
|
6
|
+
mutex = Mutex.new
|
7
|
+
workers = 0
|
8
|
+
|
9
|
+
# Very roughly simulate having a pool of worker threads.
|
10
|
+
loop do
|
11
|
+
return if (redis.llen("results") == total_jobs)
|
12
|
+
|
13
|
+
Thread.pass while (mutex.synchronize { workers >= max_workers })
|
14
|
+
|
15
|
+
Thread.new do
|
16
|
+
mutex.synchronize { workers += 1 }
|
17
|
+
# puts "Doing work (#{workers} workers)"
|
18
|
+
Worker.new.work(1)
|
19
|
+
mutex.synchronize { workers -= 1 }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Worker
|
26
|
+
def work(loops)
|
27
|
+
redis = Redis.new
|
28
|
+
|
29
|
+
loops.times do
|
30
|
+
job = redis.brpop "work", 0
|
31
|
+
redis.lpush "results", job
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class ThreadTest < Test::Unit::TestCase
|
37
|
+
def setup
|
38
|
+
Redis.new.flushall
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_single_thread(jobs)
|
42
|
+
redis = Redis.new
|
43
|
+
|
44
|
+
jobs.times do
|
45
|
+
redis.lpush "work", "foo"
|
46
|
+
end
|
47
|
+
|
48
|
+
thread = Thread.new do
|
49
|
+
Worker.new.work(jobs)
|
50
|
+
end
|
51
|
+
|
52
|
+
thread.join
|
53
|
+
end
|
54
|
+
|
55
|
+
def run_thread_spawn_on_demand(jobs, threads)
|
56
|
+
redis = Redis.new
|
57
|
+
|
58
|
+
jobs.times do
|
59
|
+
redis.lpush "work", "foo"
|
60
|
+
end
|
61
|
+
|
62
|
+
Supervisor.new.main(jobs, threads)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_thread_performance
|
66
|
+
n = 10000
|
67
|
+
threads = 10
|
68
|
+
|
69
|
+
puts "Redis push/pop performance, single thread vs. multi".title
|
70
|
+
|
71
|
+
# Test which is faster: run one thread sitting on Redis vs. many
|
72
|
+
# threads (one per job) dogpiling on Redis. That is, is the cost
|
73
|
+
# of spawning one thread per job more expensive than the IO to
|
74
|
+
# Redis? (On my machine, thread pool is nearly 4x faster. -jdc)
|
75
|
+
Benchmark.bm do |x|
|
76
|
+
x.report("single thread (n=#{n}):".pad) do
|
77
|
+
run_single_thread(n)
|
78
|
+
end
|
79
|
+
|
80
|
+
[1, 2, 5, 10].each do |threads|
|
81
|
+
x.report("thread pool (n=#{n}, #{threads} threads):".pad) do
|
82
|
+
run_thread_spawn_on_demand(n, threads)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
assert true
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'lib/live_resource'
|
3
|
+
|
4
|
+
class FavoriteColorPublisher
|
5
|
+
include LiveResource::Attribute
|
6
|
+
|
7
|
+
remote_writer :favorite
|
8
|
+
end
|
9
|
+
|
10
|
+
publisher = FavoriteColorPublisher.new
|
11
|
+
publisher.namespace = "color"
|
12
|
+
publisher.favorite = "blue"
|
13
|
+
|
14
|
+
class FavoriteColor
|
15
|
+
include LiveResource::Attribute
|
16
|
+
|
17
|
+
remote_reader :favorite
|
18
|
+
end
|
19
|
+
|
20
|
+
reader = FavoriteColor.new
|
21
|
+
reader.namespace = "color"
|
22
|
+
puts reader.favorite # --> "blue"
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'lib/live_resource'
|
3
|
+
|
4
|
+
class FavoriteColorPublisher
|
5
|
+
include LiveResource::Attribute
|
6
|
+
|
7
|
+
remote_accessor :favorite
|
8
|
+
|
9
|
+
# Update favorite color to anything except the currently-published
|
10
|
+
# favorite.
|
11
|
+
def update_favorite
|
12
|
+
colors = ['red', 'blue', 'green']
|
13
|
+
|
14
|
+
remote_modify(:favorite) do |current_favorite|
|
15
|
+
colors.delete(current_favorite)
|
16
|
+
|
17
|
+
# Value of block will become the new value of the attribute.
|
18
|
+
colors.shuffle.first
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
color = FavoriteColorPublisher.new
|
24
|
+
color.namespace = "color"
|
25
|
+
color.favorite = "blue"
|
26
|
+
|
27
|
+
10.times do
|
28
|
+
puts "Current fave: #{color.favorite}"
|
29
|
+
color.update_favorite
|
30
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'lib/live_resource'
|
3
|
+
|
4
|
+
class FavoriteColorPublisher
|
5
|
+
include LiveResource::Attribute
|
6
|
+
|
7
|
+
remote_writer :favorite
|
8
|
+
end
|
9
|
+
|
10
|
+
publisher = FavoriteColorPublisher.new
|
11
|
+
publisher.namespace = "color"
|
12
|
+
publisher.favorite = "blue"
|
13
|
+
|
14
|
+
class FavoriteColorSubscriber
|
15
|
+
include LiveResource::Subscriber
|
16
|
+
|
17
|
+
remote_subscription :favorite
|
18
|
+
|
19
|
+
def favorite(new_favorite)
|
20
|
+
puts "Publisher changed its favorite to #{new_favorite}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
subscriber = FavoriteColorSubscriber.new
|
25
|
+
subscriber.namespace = "color"
|
26
|
+
subscriber.subscribe # Spawns thread
|
27
|
+
|
28
|
+
# Publisher object from the "Attribute" section above.
|
29
|
+
publisher.favorite = "red"
|
30
|
+
publisher.favorite = "green"
|
31
|
+
|
32
|
+
subscriber.unsubscribe
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'lib/live_resource'
|
3
|
+
|
4
|
+
class Server
|
5
|
+
include LiveResource::MethodProvider
|
6
|
+
|
7
|
+
remote_method :divide
|
8
|
+
|
9
|
+
def divide(dividend, divisor)
|
10
|
+
raise ArgumentError.new("cannot divide by zero") if divisor == 0
|
11
|
+
|
12
|
+
dividend / divisor
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
s = Server.new
|
17
|
+
s.namespace = "math"
|
18
|
+
s.logger.level = Logger::INFO
|
19
|
+
|
20
|
+
Signal.trap("INT") { s.stop_method_dispatcher }
|
21
|
+
|
22
|
+
s.start_method_dispatcher.join
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'lib/live_resource'
|
3
|
+
|
4
|
+
class Server
|
5
|
+
include LiveResource::MethodProvider
|
6
|
+
|
7
|
+
remote_method :divide
|
8
|
+
|
9
|
+
def divide(dividend, divisor)
|
10
|
+
raise ArgumentError.new("cannot divide by zero") if divisor == 0
|
11
|
+
|
12
|
+
dividend / divisor
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Client
|
17
|
+
include LiveResource::MethodSender
|
18
|
+
|
19
|
+
def fancy_process(a, b)
|
20
|
+
begin
|
21
|
+
puts remote_send :divide, a, b
|
22
|
+
rescue ArgumentError => e
|
23
|
+
puts "oops, I messed up: #{e}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
s = Server.new
|
29
|
+
s.namespace = "math"
|
30
|
+
s.start_method_dispatcher
|
31
|
+
|
32
|
+
c = Client.new
|
33
|
+
c.namespace = "math"
|
34
|
+
c.fancy_process(10, 5)
|
35
|
+
c.fancy_process(1, 0)
|
36
|
+
|
37
|
+
s.stop_method_dispatcher
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'common')
|
2
|
+
|
3
|
+
module LiveResource
|
4
|
+
module Subscriber
|
5
|
+
include LiveResource::Common
|
6
|
+
|
7
|
+
UNSUBSCRIBE_KEY = :unsubscribe_key
|
8
|
+
|
9
|
+
def subscribe
|
10
|
+
ready = false
|
11
|
+
subscriptions = self.class.instance_variable_get :@subscriptions
|
12
|
+
channels = [UNSUBSCRIBE_KEY] + subscriptions.keys
|
13
|
+
|
14
|
+
@thread = Thread.new do
|
15
|
+
redis_space.subscribe(channels) do |on|
|
16
|
+
on.subscribe do |key, total|
|
17
|
+
debug "Subscribed to #{key} (#{total} subscriptions)"
|
18
|
+
|
19
|
+
if (channels.length == total)
|
20
|
+
debug "Subscriber ready"
|
21
|
+
ready = true
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
on.message do |key, new_value|
|
26
|
+
# Need to strip namespace from key; callbacks were registered
|
27
|
+
# before namespace was known (at initialize).
|
28
|
+
namespace_length = @namespace.length + 1 # Include '.' separator
|
29
|
+
key = key.to_s
|
30
|
+
key = key[namespace_length, key.length - namespace_length]
|
31
|
+
key = key.to_sym
|
32
|
+
|
33
|
+
# De-serialize value
|
34
|
+
new_value = new_value.nil? ? nil : YAML::load(new_value)
|
35
|
+
|
36
|
+
debug "#{key.inspect} changed value to #{new_value.inspect}"
|
37
|
+
|
38
|
+
if key.to_s.end_with? UNSUBSCRIBE_KEY.to_s
|
39
|
+
redis_space.unsubscribe
|
40
|
+
next
|
41
|
+
end
|
42
|
+
|
43
|
+
m = subscriptions[key]
|
44
|
+
|
45
|
+
if m.nil?
|
46
|
+
warn "Received subscription update for unknown key #{key}"
|
47
|
+
next
|
48
|
+
end
|
49
|
+
|
50
|
+
# Need to send method on new thread, as that method may do
|
51
|
+
# additional LiveResource/Redis operations, and this thread's
|
52
|
+
# Redis client is in subscribe mode.
|
53
|
+
Thread.new { send m, new_value }
|
54
|
+
end
|
55
|
+
|
56
|
+
on.unsubscribe do |key, total|
|
57
|
+
debug "Unsubscribed from #{key} (#{total} subscriptions)"
|
58
|
+
end
|
59
|
+
|
60
|
+
debug "Subscriber thread started"
|
61
|
+
end
|
62
|
+
|
63
|
+
debug "Subscriber thread done"
|
64
|
+
end
|
65
|
+
|
66
|
+
Thread.pass while !ready
|
67
|
+
|
68
|
+
@thread
|
69
|
+
end
|
70
|
+
|
71
|
+
def unsubscribe
|
72
|
+
# Need to publish on secondary RedisSpace because the first one is
|
73
|
+
# blocked waiting for subscription updates.
|
74
|
+
@rs_secondary = redis_space.clone
|
75
|
+
@rs_secondary.publish UNSUBSCRIBE_KEY, true
|
76
|
+
@thread.join
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.included(base)
|
80
|
+
base.extend(ClassMethods)
|
81
|
+
end
|
82
|
+
|
83
|
+
module ClassMethods
|
84
|
+
def remote_subscription(attribute, method = nil)
|
85
|
+
@subscriptions ||= Hash.new
|
86
|
+
|
87
|
+
if @subscriptions[attribute]
|
88
|
+
throw ArgumentError, "Subscription callback already defined for attribute #{attribute}"
|
89
|
+
end
|
90
|
+
|
91
|
+
# If method isn't specified, assume it matches the attribute name.
|
92
|
+
method ||= attribute
|
93
|
+
|
94
|
+
@subscriptions[attribute.to_sym] = method.to_sym
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/old/redis_test.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class FancyClass
|
4
|
+
attr_reader :value
|
5
|
+
|
6
|
+
def initialize(value)
|
7
|
+
@value = value
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class RedisClientTest < Test::Unit::TestCase
|
12
|
+
def setup
|
13
|
+
Redis.new.flushall
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_global_redis_space
|
17
|
+
assert_equal LiveResource::RedisClient, LiveResource::redis.class
|
18
|
+
end
|
19
|
+
|
20
|
+
# def test_method_get_set_with_same_key
|
21
|
+
# rc = mock()
|
22
|
+
# rc.expects :attribute_set, "--- 123\n...\n"
|
23
|
+
#
|
24
|
+
#
|
25
|
+
# logger = Logger.new(STDOUT)
|
26
|
+
# logger.level = Logger::WARN
|
27
|
+
# r = LiveResource::Client.new('test', logger)
|
28
|
+
#
|
29
|
+
# # Set with same token, differing keys
|
30
|
+
# rs.method_set 123, 'key 1', 'value 1'
|
31
|
+
# rs.method_set 123, 'key 2', FancyClass.new(42)
|
32
|
+
#
|
33
|
+
# assert_equal 'value 1', rs.method_get(123, 'key 1')
|
34
|
+
# assert_equal 42, rs.method_get(123, 'key 2').value
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# def test_method_tokens_independent
|
38
|
+
# rs = LiveResource::RedisSpace.new('test')
|
39
|
+
#
|
40
|
+
# # Set several value, same keys but different tokens
|
41
|
+
# rs.method_set 1, 'key', 'value 1'
|
42
|
+
# rs.method_set 2, 'key', 'value 2'
|
43
|
+
# rs.method_set 3, 'key', 'value 3'
|
44
|
+
#
|
45
|
+
# assert_equal 'value 1', rs.method_get(1, 'key')
|
46
|
+
# assert_equal 'value 2', rs.method_get(2, 'key')
|
47
|
+
# assert_equal 'value 3', rs.method_get(3, 'key')
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# def test_method_set_exclusive
|
51
|
+
# rs = LiveResource::RedisSpace.new('test')
|
52
|
+
#
|
53
|
+
# assert_equal true, rs.method_set_exclusive(1, 'key', 'value 1')
|
54
|
+
# assert_equal false, rs.method_set_exclusive(1, 'key', 'value 2')
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# def test_method_push_pop_simple
|
58
|
+
# rs = LiveResource::RedisSpace.new('test')
|
59
|
+
# assert_equal 0, Redis.new.dbsize
|
60
|
+
#
|
61
|
+
# rs.method_push '1'
|
62
|
+
# assert_equal 1, Redis.new.dbsize
|
63
|
+
#
|
64
|
+
# token = rs.method_wait
|
65
|
+
# assert_equal '1', token
|
66
|
+
#
|
67
|
+
# rs.method_done token
|
68
|
+
# assert_equal 0, Redis.new.dbsize
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# def test_method_push_pop_multiple
|
72
|
+
# rs = LiveResource::RedisSpace.new('test')
|
73
|
+
# assert_equal 0, Redis.new.dbsize
|
74
|
+
#
|
75
|
+
# rs.method_push '1'
|
76
|
+
# rs.method_push '2'
|
77
|
+
# rs.method_push '3'
|
78
|
+
#
|
79
|
+
# assert_equal ['3', '2', '1'], rs.method_tokens_waiting
|
80
|
+
# assert_equal [], rs.method_tokens_in_progress
|
81
|
+
#
|
82
|
+
# assert_equal '1', rs.method_wait
|
83
|
+
# assert_equal '2', rs.method_wait
|
84
|
+
# assert_equal '3', rs.method_wait
|
85
|
+
#
|
86
|
+
# assert_equal [], rs.method_tokens_waiting
|
87
|
+
# assert_equal ['3', '2', '1'], rs.method_tokens_in_progress
|
88
|
+
#
|
89
|
+
# # Redis should have one key here (the methods_in_progress list)
|
90
|
+
# assert_equal 1, Redis.new.dbsize
|
91
|
+
#
|
92
|
+
# # Now start marking methods done; after the last one, the key
|
93
|
+
# # count should go down to zero.
|
94
|
+
# rs.method_done '1'
|
95
|
+
# assert_equal 1, Redis.new.dbsize
|
96
|
+
# rs.method_done '2'
|
97
|
+
# assert_equal 1, Redis.new.dbsize
|
98
|
+
# rs.method_done '3'
|
99
|
+
# assert_equal 0, Redis.new.dbsize
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# def test_serializes_exceptions_properly
|
103
|
+
# rs = LiveResource::RedisSpace.new('test')
|
104
|
+
#
|
105
|
+
# rs.method_set('1', 'key', 'value') # just need something there
|
106
|
+
# rs.result_set('1', RuntimeError.new('foo'))
|
107
|
+
# result = rs.result_get '1'
|
108
|
+
#
|
109
|
+
# assert_equal RuntimeError, result.class
|
110
|
+
# assert_equal 'foo', result.message
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# def test_find_token_in_lists
|
114
|
+
# r = Redis.new
|
115
|
+
# rs = LiveResource::RedisSpace.new('test')
|
116
|
+
#
|
117
|
+
# r.lpush('test.methods', '1')
|
118
|
+
# r.lpush('test.methods_in_progress', '2')
|
119
|
+
# r.lpush('test.results.3', 'result')
|
120
|
+
#
|
121
|
+
# assert_equal :methods, rs.find_token('1')
|
122
|
+
# assert_equal :methods_in_progress, rs.find_token('2')
|
123
|
+
# assert_equal :results, rs.find_token('3')
|
124
|
+
# assert_equal nil, rs.find_token('4')
|
125
|
+
# end
|
126
|
+
end
|
127
|
+
|