updater 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|