updater 0.2.2 → 0.3.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/Rakefile +14 -0
- data/VERSION +1 -1
- data/lib/updater/fork_worker.rb +427 -0
- data/lib/updater/orm/datamapper.rb +172 -0
- data/lib/updater/{worker.rb → thread_worker.rb} +4 -8
- data/lib/updater/update.rb +170 -149
- data/lib/updater/update_dm.rb +298 -0
- data/lib/updater/util.rb +22 -0
- data/lib/updater.rb +0 -3
- data/spec/chained_spec.rb +81 -0
- data/spec/errors_spec.rb +31 -0
- data/spec/fooclass.rb +14 -0
- data/spec/fork_worker_instance_spec.rb +56 -0
- data/spec/fork_worker_spec.rb +290 -0
- data/spec/lock_spec.rb +18 -35
- data/spec/named_request_spec.rb +36 -0
- data/spec/params_sub_spec.rb +27 -0
- data/spec/schedule_spec.rb +89 -0
- data/spec/spec_helper.rb +6 -1
- data/spec/{worker_spec.rb → thread_worker_spec.rb} +11 -11
- data/spec/update_runner_spec.rb +48 -0
- data/spec/update_spec.rb +11 -173
- data/spec/util_spec.rb +11 -0
- metadata +18 -4
@@ -0,0 +1,290 @@
|
|
1
|
+
require File.join( File.dirname(__FILE__), "spec_helper" )
|
2
|
+
require 'logger'
|
3
|
+
include Updater
|
4
|
+
|
5
|
+
def fake_process_status(estat=0)
|
6
|
+
stub("Process Status",:pid=>1234,:exit_status=>estat)
|
7
|
+
end
|
8
|
+
|
9
|
+
def fake_iostream
|
10
|
+
stub("IOStream").as_null_object
|
11
|
+
end
|
12
|
+
|
13
|
+
ForkWorker.logger = Logger.new(nil)
|
14
|
+
|
15
|
+
describe ForkWorker do
|
16
|
+
|
17
|
+
describe "#reap_all_workers" do
|
18
|
+
it "should remove workers" do
|
19
|
+
Process.should_receive(:waitpid2).with(-1,Process::WNOHANG).twice\
|
20
|
+
.and_return([1234,fake_process_status],nil)
|
21
|
+
ForkWorker.should_receive(:remove_worker).with(1234).once
|
22
|
+
ForkWorker.reap_all_workers
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not fail if there are no child processes" do
|
26
|
+
Process.should_receive(:waitpid2).with(-1,Process::WNOHANG).once\
|
27
|
+
.and_raise(Errno::ECHILD)
|
28
|
+
lambda{ForkWorker.reap_all_workers}.should_not raise_error
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#remove_worker" do
|
34
|
+
|
35
|
+
it "should silently ignore missing workers" do
|
36
|
+
lambda{ForkWorker.remove_worker(1234)}.should_not raise_error
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should remove the worker from the set" do
|
40
|
+
worker = ForkWorker::WorkerMonitor.new(1,stub("IOStream").as_null_object)
|
41
|
+
ForkWorker.instance_variable_set :@workers, {1234=>worker}
|
42
|
+
ForkWorker.remove_worker(1234)
|
43
|
+
ForkWorker.instance_variable_get(:@workers).should == {}
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should close the removed workers heartbeat file" do
|
47
|
+
ios = mock("IOStream")
|
48
|
+
ios.should_receive(:close).and_return(nil)
|
49
|
+
worker = ForkWorker::WorkerMonitor.new(1,ios)
|
50
|
+
ForkWorker.instance_variable_set :@workers, {1234=>worker}
|
51
|
+
ForkWorker.remove_worker(1234)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not fail if the heartbeat file is already closed" do
|
55
|
+
ios = mock("IOStream")
|
56
|
+
ios.should_receive(:close).and_raise(IOError)
|
57
|
+
worker = ForkWorker::WorkerMonitor.new(1,ios)
|
58
|
+
ForkWorker.instance_variable_set :@workers, {1234=>worker}
|
59
|
+
lambda{ForkWorker.remove_worker(1234)}.should_not raise_error
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "#add_worker" do
|
65
|
+
|
66
|
+
before :each do
|
67
|
+
ForkWorker.initial_setup({})
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should add the new worker to the set" do
|
71
|
+
Process.stub!(:fork).and_return(1234)
|
72
|
+
ForkWorker.add_worker(1)
|
73
|
+
ForkWorker.instance_variable_get(:@workers).values.should include(1)
|
74
|
+
ForkWorker.instance_variable_get(:@workers).keys.should include(1234)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should run a new worker instance in a fork" do
|
78
|
+
ForkWorker.instance_variable_set :@workers, {}
|
79
|
+
Process.should_receive(:fork).with(no_args()).and_yield.and_return(1234)
|
80
|
+
ForkWorker.should_receive(:fork_cleanup).and_return(nil) #
|
81
|
+
ForkWorker.should_receive(:new).with(anything(),duck_type(:number,:heartbeat)).and_return(stub("Worker",:run=>nil))
|
82
|
+
ForkWorker.add_worker(1)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "#spawn_missing_workers" do
|
88
|
+
|
89
|
+
it "should add a worker with an empty set" do
|
90
|
+
ForkWorker.should_receive(:add_worker).with(0)
|
91
|
+
ForkWorker.instance_variable_set :@current_workers, 1
|
92
|
+
ForkWorker.instance_variable_set :@workers, {}
|
93
|
+
ForkWorker.spawn_missing_workers
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should add a worker when there are fewer then needed" do
|
97
|
+
ForkWorker.should_receive(:add_worker).with(1)
|
98
|
+
ForkWorker.instance_variable_set :@current_workers, 2
|
99
|
+
ForkWorker.instance_variable_set :@workers, {1233=>ForkWorker::WorkerMonitor.new(0,nil)}
|
100
|
+
ForkWorker.spawn_missing_workers
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should add a worker when one has gon missing" do
|
104
|
+
ForkWorker.should_receive(:add_worker).with(0)
|
105
|
+
ForkWorker.should_receive(:add_worker).with(2)
|
106
|
+
ForkWorker.instance_variable_set :@current_workers, 3
|
107
|
+
ForkWorker.instance_variable_set :@workers, {1233=>ForkWorker::WorkerMonitor.new(1,nil)}
|
108
|
+
ForkWorker.spawn_missing_workers
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should not add workers if thier are already enough" do
|
112
|
+
ForkWorker.should_not_receive(:add_worker)
|
113
|
+
ForkWorker.instance_variable_set :@current_workers, 1
|
114
|
+
ForkWorker.instance_variable_set :@workers, {1233=>ForkWorker::WorkerMonitor.new(0,nil)}
|
115
|
+
ForkWorker.spawn_missing_workers
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "#initial_setup" do
|
121
|
+
|
122
|
+
it "should set up a logger when one does not exist" do
|
123
|
+
ForkWorker.initial_setup({})
|
124
|
+
ForkWorker.logger.should_not be_nil
|
125
|
+
%w{debug info warn error fatal}.each do |n|
|
126
|
+
ForkWorker.logger.should respond_to(n.to_sym)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should set workers set to empty" do
|
131
|
+
ForkWorker.initial_setup({})
|
132
|
+
ForkWorker.instance_variable_get(:@workers).should be_empty
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should create a pipe for children" do
|
136
|
+
pipe = ForkWorker.instance_variable_get(:@pipe)
|
137
|
+
pipe.length.should ==2
|
138
|
+
pipe.each {|io| io.should be_an IO}
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "#handle_signal_queue" do
|
144
|
+
|
145
|
+
before :each do
|
146
|
+
ForkWorker.initial_setup({})
|
147
|
+
end
|
148
|
+
|
149
|
+
[:QUIT, :INT].each do |sig|
|
150
|
+
it "it should exicute a graceful shutdown on #{sig.to_s}" do
|
151
|
+
ForkWorker.should_receive(:stop).with(true)
|
152
|
+
ForkWorker.stub!(:awaken_master, true)
|
153
|
+
|
154
|
+
ForkWorker.queue_signal(sig)
|
155
|
+
ForkWorker.handle_signal_queue.should be_false
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
it "it should exicute a rapid shutdown on TERM" do
|
160
|
+
ForkWorker.should_receive(:stop).with(false)
|
161
|
+
ForkWorker.stub!(:awaken_master, true)
|
162
|
+
|
163
|
+
ForkWorker.queue_signal(:TERM)
|
164
|
+
ForkWorker.handle_signal_queue.should be_false
|
165
|
+
end
|
166
|
+
|
167
|
+
[:USR2, :DATA].each do |sig|
|
168
|
+
it "should write to the pipe on #{sig.to_s}" do
|
169
|
+
ForkWorker.queue_signal(sig)
|
170
|
+
ForkWorker.handle_signal_queue.should be_true
|
171
|
+
pipe = ForkWorker.instance_variable_get(:@pipe)
|
172
|
+
lambda{pipe.first.read(1)}.should_not raise_error
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should do maintance when the queue is empty" do
|
177
|
+
ForkWorker.should_receive(:murder_lazy_workers)
|
178
|
+
ForkWorker.should_receive(:maintain_worker_count)
|
179
|
+
ForkWorker.should_receive(:master_sleep)
|
180
|
+
|
181
|
+
ForkWorker.handle_signal_queue.should be_true
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should increase max_workers on TTIN and decrease on TTOU" do
|
185
|
+
max = ForkWorker.instance_variable_get(:@max_workers)
|
186
|
+
ForkWorker.queue_signal(:TTIN)
|
187
|
+
ForkWorker.handle_signal_queue.should be_true
|
188
|
+
ForkWorker.instance_variable_get(:@max_workers).should == max+1
|
189
|
+
|
190
|
+
ForkWorker.queue_signal(:TTOU)
|
191
|
+
ForkWorker.handle_signal_queue.should be_true
|
192
|
+
ForkWorker.instance_variable_get(:@max_workers).should == max
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should never allow max_workers to be less then 1" do
|
196
|
+
ForkWorker.instance_variable_set(:@max_workers,1)
|
197
|
+
ForkWorker.queue_signal(:TTOU)
|
198
|
+
ForkWorker.handle_signal_queue.should be_true
|
199
|
+
ForkWorker.instance_variable_get(:@max_workers).should == 1
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "Master loop control" do
|
205
|
+
|
206
|
+
before(:each) do
|
207
|
+
ForkWorker.initial_setup({})
|
208
|
+
end
|
209
|
+
|
210
|
+
describe "#master_sleep" do
|
211
|
+
|
212
|
+
it "should return to run maintance if there is no signal" do
|
213
|
+
IO.should_receive(:select).and_return(nil)
|
214
|
+
ForkWorker.master_sleep.should be_nil
|
215
|
+
ForkWorker.instance_variable_get(:@signal_queue).should be_empty
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should return if there is data on self_pipe" do
|
219
|
+
self_pipe = ForkWorker.instance_variable_get(:@self_pipe)
|
220
|
+
IO.should_receive(:select).and_return([[self_pipe.first],[],[]])
|
221
|
+
ForkWorker.master_sleep.should be_nil
|
222
|
+
ForkWorker.instance_variable_get(:@signal_queue).should be_empty
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should also add ':DATA' to queue when a stream other then self_pipe is ready." do
|
226
|
+
# pending "Testing Error stream not raising error?"
|
227
|
+
stream = stub('Extern IO')
|
228
|
+
stream.should_receive(:read_nonblock).and_raise(Errno::EAGAIN)
|
229
|
+
IO.should_receive(:select).and_return([[stream],[],[]])
|
230
|
+
ForkWorker.master_sleep.should be_nil
|
231
|
+
ForkWorker.instance_variable_get(:@signal_queue).should include(:DATA)
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
[true,false].each do |graceful|
|
240
|
+
|
241
|
+
describe "#stop(#{graceful})" do
|
242
|
+
before :each do
|
243
|
+
ForkWorker.initial_setup({})
|
244
|
+
end
|
245
|
+
|
246
|
+
describe "with no workers" do
|
247
|
+
it "should not fail" do
|
248
|
+
[true,false].each {|graceful| lambda{ForkWorker.stop(graceful)}.should_not raise_error}
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should not signal any workers" do
|
252
|
+
ForkWorker.should_not_receive(:signal_worker)
|
253
|
+
[true,false].each {|graceful| ForkWorker.stop(graceful)}
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
describe "with workers" do
|
258
|
+
|
259
|
+
before :each do
|
260
|
+
ForkWorker.instance_variable_set :@workers,
|
261
|
+
{
|
262
|
+
1233=>ForkWorker::WorkerMonitor.new(0,fake_iostream),
|
263
|
+
1234=>ForkWorker::WorkerMonitor.new(0,fake_iostream)
|
264
|
+
}
|
265
|
+
Process.stub!(:waitpid2) do |_1,_2|
|
266
|
+
[ ForkWorker.instance_variable_get(:@workers).keys.first,
|
267
|
+
fake_process_status
|
268
|
+
]
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
it "should signals each worker to end" do
|
273
|
+
ForkWorker.instance_variable_get(:@workers).keys.each do |pid|
|
274
|
+
ForkWorker.should_receive(:signal_worker).with(graceful ? :QUIT : :TERM,pid)
|
275
|
+
end
|
276
|
+
ForkWorker.stop(graceful)
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should not kill workers that successfully quit" do
|
280
|
+
ForkWorker.should_not_receive(:signal_worker).with(:KILL,anything())
|
281
|
+
ForkWorker.stop(graceful)
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
end
|
data/spec/lock_spec.rb
CHANGED
@@ -2,23 +2,29 @@ require File.join( File.dirname(__FILE__), "spec_helper" )
|
|
2
2
|
|
3
3
|
include Updater
|
4
4
|
|
5
|
+
require File.join( File.dirname(__FILE__), "fooclass" )
|
6
|
+
|
5
7
|
describe "Update Locking:" do
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
property :name, String
|
9
|
+
|
10
|
+
class Worker
|
11
|
+
attr_accessor :pid
|
12
|
+
attr_accessor :name
|
12
13
|
|
13
|
-
def
|
14
|
-
|
14
|
+
def initialize(options={})
|
15
|
+
@quiet = options[:quiet]
|
16
|
+
@name = options[:name] || "host:#{Socket.gethostname} pid:#{Process.pid}" rescue "pid:#{Process.pid}"
|
17
|
+
@pid = Process.pid
|
15
18
|
end
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
def say(text)
|
21
|
+
puts text
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
20
25
|
|
21
26
|
before :each do
|
27
|
+
Foo.all.destroy!
|
22
28
|
@u = Update.immidiate(Foo,:bar,[])
|
23
29
|
@w = Worker.new(:name=>"first", :quiet=>true)
|
24
30
|
end
|
@@ -44,36 +50,13 @@ describe "Update Locking:" do
|
|
44
50
|
@u.lock(@w).should be_true
|
45
51
|
@u.lock(@w).should be_true
|
46
52
|
end
|
47
|
-
|
48
|
-
describe "#run_with_lock" do
|
49
|
-
|
50
|
-
it "should run an unlocked record" do
|
51
|
-
u = Update.immidiate(Foo,:bar,[:arg1,:arg2])
|
52
|
-
Foo.should_receive(:bar).with(:arg1,:arg2)
|
53
|
-
u.run_with_lock(@w).should be_true
|
54
|
-
end
|
55
|
-
|
56
|
-
it "should NOT run an already locked record" do
|
57
|
-
u = Update.immidiate(Foo,:bar,[:arg1,:arg2])
|
58
|
-
u.lock(Worker.new)
|
59
|
-
Foo.should_not_receive(:bar)
|
60
|
-
u.run_with_lock(@w).should be_nil
|
61
|
-
end
|
62
|
-
|
63
|
-
it "should return false if the update ran but there was an error" do
|
64
|
-
u = Update.immidiate(Foo,:bar,[:arg1,:arg2])
|
65
|
-
Foo.should_receive(:bar).with(:arg1,:arg2).and_raise(RuntimeError)
|
66
|
-
u.run_with_lock(@w).should be_false
|
67
|
-
end
|
68
|
-
|
69
|
-
end
|
70
|
-
|
53
|
+
|
71
54
|
it "#clear_locks should clear all locks from a worker" do
|
72
55
|
@v = Update.immidiate(Foo,:bar,[:arg1,:arg2])
|
73
56
|
@u.lock(@w)
|
74
57
|
@v.lock(@w)
|
75
58
|
@u.locked?.should be_true
|
76
|
-
@w
|
59
|
+
Update.clear_locks(@w)
|
77
60
|
@u.reload.locked?.should be_false
|
78
61
|
@v.reload.locked?.should be_false
|
79
62
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.join( File.dirname(__FILE__), "spec_helper" )
|
2
|
+
|
3
|
+
include Updater
|
4
|
+
|
5
|
+
require File.join( File.dirname(__FILE__), "fooclass" )
|
6
|
+
|
7
|
+
describe "named request" do
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
Foo.all.destroy!
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should be found by name when target is an instance" do
|
14
|
+
f = Foo.create(:name=>'Honey')
|
15
|
+
u = Update.immidiate(f,:bar,[:named],:name=>'Now')
|
16
|
+
u.name.should ==("Now")
|
17
|
+
pending "'for' not implemented"
|
18
|
+
Update.for(f, "Now").should ==(u)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should be found by name when target is a class" do
|
22
|
+
u = Update.immidiate(Foo,:bar,[:named],:name=>'Now')
|
23
|
+
u.name.should ==("Now")
|
24
|
+
pending "'for' not implemented"
|
25
|
+
Update.for(Foo, "Now").should ==(u)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return all updates for a given target" do
|
29
|
+
u1 = Update.immidiate(Foo,:bar,[:arg1,:arg2])
|
30
|
+
u2 = Update.immidiate(Foo,:bar,[:arg3,:arg4])
|
31
|
+
pending "'for' not implemented"
|
32
|
+
Update.for(Foo).should include(u1,u2)
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.join( File.dirname(__FILE__), "spec_helper" )
|
2
|
+
|
3
|
+
include Updater
|
4
|
+
|
5
|
+
require File.join( File.dirname(__FILE__), "fooclass" )
|
6
|
+
|
7
|
+
describe "Special Parameter Substitution" do
|
8
|
+
before :each do
|
9
|
+
Update.clear_all
|
10
|
+
@u = Update.chain(Foo,:chained, [:__job__,:__params__,:__self__, 'job params'])
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should substitute __job__ with job that chained in" do
|
14
|
+
Foo.should_receive(:chained).with(:arg1,anything(),anything(),'job params')
|
15
|
+
@u.run(:arg1)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should substitute __params__ with params" do
|
19
|
+
Foo.should_receive(:chained).with(anything(),:arg2,anything(), 'job params')
|
20
|
+
@u.run(:arg1,:arg2)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should substitute __self__ with the current job" do
|
24
|
+
Foo.should_receive(:chained).with(anything(),anything(),@u, 'job params')
|
25
|
+
@u.run
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.join( File.dirname(__FILE__), "spec_helper" )
|
2
|
+
|
3
|
+
include Updater
|
4
|
+
|
5
|
+
require File.join( File.dirname(__FILE__), "fooclass" )
|
6
|
+
|
7
|
+
describe "adding an immidiate update request" do
|
8
|
+
before(:each) do
|
9
|
+
Foo.all.destroy!
|
10
|
+
end
|
11
|
+
it "with a class target" do
|
12
|
+
u = Update.immidiate(Foo,:bar,[])
|
13
|
+
u.target.should == Foo
|
14
|
+
Update.current.get(u.id).should_not be_nil
|
15
|
+
Update.delayed.should == 0
|
16
|
+
end
|
17
|
+
|
18
|
+
it "with an conforming instance target" do
|
19
|
+
f = Foo.create
|
20
|
+
u = Update.immidiate(f,:bar,[])
|
21
|
+
u.target.should == f
|
22
|
+
Update.current.get(u.id).should_not be_nil
|
23
|
+
Update.delayed.should == 0
|
24
|
+
end
|
25
|
+
|
26
|
+
it "with an custome finder" do
|
27
|
+
f = Foo.create(:name=>'baz')
|
28
|
+
u = Update.immidiate(Foo,:bar,[],:finder=>:first, :finder_args=>[{:name=>'baz'}])
|
29
|
+
u.target.should == f
|
30
|
+
Update.current.get(u.id).should_not be_nil
|
31
|
+
Update.delayed.should == 0
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "chained request" do
|
37
|
+
before :each do
|
38
|
+
Update.clear_all
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should not be in current or delayed queue" do
|
42
|
+
u = Update.chain(Foo,:bar,[:error])
|
43
|
+
u.time.should be_nil
|
44
|
+
Update.current.should_not include(u)
|
45
|
+
Update.delayed.should == 0
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should be persistant" do
|
49
|
+
u = Update.chain(Foo,:bar,[:error])
|
50
|
+
u.should be_persistant
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "adding an delayed update request" do
|
56
|
+
before :each do
|
57
|
+
Update.clear_all
|
58
|
+
Foo.all.destroy
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
it "with a class target" do
|
63
|
+
u = Update.at(Chronic.parse('tomorrow'),Foo,:bar,[])
|
64
|
+
u.target.should == Foo
|
65
|
+
Update.current.should_not include(u)
|
66
|
+
Update.delayed.should == 1
|
67
|
+
end
|
68
|
+
|
69
|
+
it "with an conforming instance target" do
|
70
|
+
f = Foo.create
|
71
|
+
u = Update.at(Chronic.parse('tomorrow'),f,:bar,[])
|
72
|
+
u.target.should == f
|
73
|
+
Update.current.should_not include(u)
|
74
|
+
Update.delayed.should == 1
|
75
|
+
end
|
76
|
+
|
77
|
+
it "with an custome finder" do
|
78
|
+
f = Foo.create(:name=>'baz')
|
79
|
+
u = Update.at(Chronic.parse('tomorrow'),Foo,:bar,[],:finder=>:first, :finder_args=>[{:name=>'baz'}])
|
80
|
+
u.target.should == f
|
81
|
+
Update.current.should_not include(u)
|
82
|
+
Update.delayed.should == 1
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -7,7 +7,11 @@ require "spec" # Satisfies Autotest and anyone else not using the Rake tasks
|
|
7
7
|
require "dm-core"
|
8
8
|
|
9
9
|
require 'updater'
|
10
|
-
require 'updater/
|
10
|
+
require 'updater/thread_worker'
|
11
|
+
require 'updater/fork_worker'
|
12
|
+
require 'updater/orm/datamapper'
|
13
|
+
|
14
|
+
Updater::Update.orm = Updater::ORM::DataMapper
|
11
15
|
|
12
16
|
DataMapper.setup(:default, 'sqlite3::memory:')
|
13
17
|
DataMapper.auto_migrate!
|
@@ -15,3 +19,4 @@ DataMapper.auto_migrate!
|
|
15
19
|
require 'timecop'
|
16
20
|
require 'chronic'
|
17
21
|
|
22
|
+
|
@@ -2,10 +2,10 @@ require File.join( File.dirname(__FILE__), "spec_helper" )
|
|
2
2
|
|
3
3
|
include Updater
|
4
4
|
|
5
|
-
describe
|
5
|
+
describe ThreadWorker do
|
6
6
|
|
7
7
|
it "should not print anything when quiet" do
|
8
|
-
w =
|
8
|
+
w = ThreadWorker.new :quiet=>true
|
9
9
|
out = StringIO.new
|
10
10
|
$stdout = out
|
11
11
|
w.say "hello world"
|
@@ -14,7 +14,7 @@ describe Worker do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
it "should have a name" do
|
17
|
-
|
17
|
+
ThreadWorker.new.name.should be_a String
|
18
18
|
end
|
19
19
|
|
20
20
|
describe "loop thread control:" do
|
@@ -35,7 +35,7 @@ describe Worker do
|
|
35
35
|
|
36
36
|
specify "The loop should run when the worker is started" do
|
37
37
|
pending
|
38
|
-
worker =
|
38
|
+
worker = ThreadWorker.new(:quiet=>true, :name=>"testing")
|
39
39
|
Update.should_receive(:work_off).with(worker).once.and_return(nil)
|
40
40
|
t = Thread.new do
|
41
41
|
worker.start
|
@@ -73,13 +73,13 @@ describe "working off jobs:" do
|
|
73
73
|
describe "Update#work_off" do
|
74
74
|
|
75
75
|
before :each do
|
76
|
-
Update.
|
76
|
+
Update.clear_all
|
77
77
|
end
|
78
78
|
|
79
79
|
it "should run and immidiate job"do
|
80
80
|
u = Update.immidiate(Foo,:bar,[:arg1,:arg2])
|
81
81
|
Foo.should_receive(:bar).with(:arg1,:arg2)
|
82
|
-
Update.work_off(
|
82
|
+
Update.work_off(ThreadWorker.new)
|
83
83
|
end
|
84
84
|
|
85
85
|
it "should aviod conflicts among mutiple workers" do
|
@@ -87,14 +87,14 @@ describe "working off jobs:" do
|
|
87
87
|
u2 = Update.immidiate(Foo,:baz,[:arg2])
|
88
88
|
Foo.should_receive(:bar).with(:arg1)
|
89
89
|
Foo.should_receive(:baz).with(:arg2)
|
90
|
-
Update.work_off(
|
91
|
-
Update.work_off(
|
90
|
+
Update.work_off(ThreadWorker.new(:name=>"first", :quiet=>true))
|
91
|
+
Update.work_off(ThreadWorker.new(:name=>"second", :quiet=>true))
|
92
92
|
end
|
93
93
|
|
94
94
|
it "should return 0 if there are more jobs waiting" do
|
95
95
|
u1 = Update.immidiate(Foo,:bar,[:arg1])
|
96
96
|
u2 = Update.immidiate(Foo,:baz,[:arg2])
|
97
|
-
Update.work_off(
|
97
|
+
Update.work_off(ThreadWorker.new(:name=>"first", :quiet=>true)).should == 0
|
98
98
|
end
|
99
99
|
|
100
100
|
it "should return the number of seconds till the next job if there are no jobs to be run" do
|
@@ -102,12 +102,12 @@ describe "working off jobs:" do
|
|
102
102
|
u1 = Update.at(Time.now + 30, Foo,:bar,[:arg1])
|
103
103
|
Update.at(Time.now + 35, Foo,:bar,[:arg1])
|
104
104
|
Update.at(Time.now + 1000, Foo,:bar,[:arg1])
|
105
|
-
Update.work_off(
|
105
|
+
Update.work_off(ThreadWorker.new(:name=>"first", :quiet=>true)).should == 30
|
106
106
|
end
|
107
107
|
|
108
108
|
it "should return nil if the job queue is empty" do
|
109
109
|
u1 = Update.immidiate(Foo,:bar,[:arg1])
|
110
|
-
Update.work_off(
|
110
|
+
Update.work_off(ThreadWorker.new(:name=>"first", :quiet=>true)).should be_nil
|
111
111
|
end
|
112
112
|
|
113
113
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.join( File.dirname(__FILE__), "spec_helper" )
|
2
|
+
|
3
|
+
include Updater
|
4
|
+
|
5
|
+
require File.join( File.dirname(__FILE__), "fooclass" )
|
6
|
+
|
7
|
+
describe "running an update" do
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
Update.clear_all
|
11
|
+
Foo.all.destroy!
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should call the named method with a class target" do
|
15
|
+
u = Update.immidiate(Foo,:bar,[:arg1,:arg2])
|
16
|
+
Foo.should_receive(:bar).with(:arg1,:arg2)
|
17
|
+
u.run
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should call the named method with an conforming instance target" do
|
21
|
+
f = Foo.create
|
22
|
+
u = Update.immidiate(f,:bar,[:arg1,:arg2])
|
23
|
+
Foo.should_receive(:bar).with(:instance,:arg1,:arg2)
|
24
|
+
u.run
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should delete the record once it is run" do
|
28
|
+
u = Update.immidiate(Foo,:bar,[:arg1,:arg2])
|
29
|
+
Foo.should_receive(:bar).with(:arg1,:arg2)
|
30
|
+
u.run
|
31
|
+
u.should_not be_saved #NOTE: not a theological statment
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should delete the record if there is a failure" do
|
35
|
+
u = Update.immidiate(Foo,:bar,[:arg1,:arg2])
|
36
|
+
Foo.should_receive(:bar).with(:arg1,:arg2).and_raise(RuntimeError)
|
37
|
+
u.run
|
38
|
+
u.should_not be_saved #NOTE: not a theological statment
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should NOT delete the record if it is a chain record" do
|
42
|
+
u = Update.chain(Foo,:bar,[:arg1,:arg2])
|
43
|
+
Foo.should_receive(:bar).with(:arg1,:arg2).and_raise(RuntimeError)
|
44
|
+
u.run
|
45
|
+
u.should be_saved
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|