tuber 0.5.1 → 0.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -0
- data/Gemfile +3 -1
- data/README.md +145 -0
- data/lib/tuber/configuration.rb +18 -0
- data/lib/tuber/connection.rb +243 -23
- data/lib/tuber/errors.rb +57 -3
- data/lib/tuber/schedules.rb +288 -0
- data/lib/tuber/tube/collection.rb +6 -2
- data/lib/tuber/tube/record.rb +21 -10
- data/lib/tuber/version.rb +1 -1
- data/lib/tuber.rb +31 -1
- data/test/connection_test.rb +356 -0
- data/test/errors_test.rb +7 -0
- data/test/schedules_test.rb +382 -0
- data/test/test_helper.rb +18 -0
- data/test/tubes_test.rb +15 -2
- metadata +4 -3
- data/TODO +0 -1
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# test/schedules_test.rb
|
|
4
|
+
|
|
5
|
+
require File.expand_path('../test_helper', __FILE__)
|
|
6
|
+
|
|
7
|
+
describe Tuber::Schedules do
|
|
8
|
+
before do
|
|
9
|
+
@beanstalk = Tuber.new(tuber_address)
|
|
10
|
+
@schedules = @beanstalk.schedules
|
|
11
|
+
# Owners are namespaces, so a unique one keeps tests apart.
|
|
12
|
+
@owner = "gemtest.#{Process.pid}.#{rand(1 << 30)}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
after do
|
|
16
|
+
begin
|
|
17
|
+
@schedules.sync(@owner, allow_empty: true) if $tuber_schedules
|
|
18
|
+
rescue Tuber::UnexpectedResponse, Tuber::NotConnected
|
|
19
|
+
# the test already failed; don't bury its error under this one
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_two(sync)
|
|
24
|
+
sync.add "rollup", tube: "gemtest.sched", cron: "5 * * * *", body: "rollup"
|
|
25
|
+
sync.add "checkin", tube: "gemtest.sched", every: 60, body: "checkin"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe "for #sync" do
|
|
29
|
+
before { require_schedules! }
|
|
30
|
+
|
|
31
|
+
it "adds, keeps, changes and removes entries" do
|
|
32
|
+
assert_equal [2, 0, 0, 0], @schedules.sync(@owner) { |s| add_two(s) }.to_a
|
|
33
|
+
assert_equal [0, 0, 0, 2], @schedules.sync(@owner) { |s| add_two(s) }.to_a
|
|
34
|
+
assert_equal [[@owner, "checkin"], [@owner, "rollup"]], @schedules.list(@owner).sort
|
|
35
|
+
assert_includes @schedules.list, [@owner, "rollup"]
|
|
36
|
+
|
|
37
|
+
result = @schedules.sync(@owner) do |s|
|
|
38
|
+
s.add "rollup", tube: "gemtest.sched", cron: "15 * * * *", body: "rollup"
|
|
39
|
+
end
|
|
40
|
+
assert_equal [0, 1, 1, 0], result.to_a
|
|
41
|
+
assert_equal [[@owner, "rollup"]], @schedules.list(@owner)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "lands each option in the entry tuber stores" do
|
|
45
|
+
@schedules.sync(@owner) do |s|
|
|
46
|
+
s.add "nightly", tube: "gemtest.sched", cron: "0 2 * * *", zone: "Australia/Melbourne",
|
|
47
|
+
body: "nightly", idp: ["nightly", 300], con: ["maintenance", 2], catchup: 300,
|
|
48
|
+
pri: 1024, delay: 5, ttr: 60
|
|
49
|
+
s.add "hourly", tube: "gemtest.sched", every: "1h", offset: "5m", body: "hourly", catchup: false
|
|
50
|
+
s.add "daily", tube: "gemtest.sched", spec: "@daily", body: "daily"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
nightly = @schedules.stats(@owner, "nightly")
|
|
54
|
+
assert_equal "0 2 * * * Australia/Melbourne", nightly.spec
|
|
55
|
+
assert_equal "gemtest.sched", nightly.tube
|
|
56
|
+
assert_equal [1024, 5, 60], [nightly.pri, nightly.delay, nightly.ttr]
|
|
57
|
+
assert_equal "idp:nightly:300 con:maintenance:2", nightly.tags
|
|
58
|
+
assert_equal "once within 300s", nightly.catchup
|
|
59
|
+
|
|
60
|
+
hourly = @schedules.stats(@owner, "hourly")
|
|
61
|
+
assert_equal "every 1h offset 5m", hourly.spec
|
|
62
|
+
assert_equal "never", hourly.catchup
|
|
63
|
+
assert_nil hourly.tags
|
|
64
|
+
|
|
65
|
+
daily = @schedules.stats(@owner, "daily")
|
|
66
|
+
assert_equal "once", daily.catchup
|
|
67
|
+
config = Tuber.configuration
|
|
68
|
+
assert_equal [config.default_put_pri, config.default_put_delay, config.default_put_ttr],
|
|
69
|
+
[daily.pri, daily.delay, daily.ttr]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "raises naming the refused entry, and leaves the previous set in place" do
|
|
73
|
+
nightly = lambda do |s|
|
|
74
|
+
s.add "nightly", tube: "gemtest.sched", cron: "0 2 * * *", zone: "Australia/Melbourne", body: "n"
|
|
75
|
+
end
|
|
76
|
+
@schedules.sync(@owner, &nightly)
|
|
77
|
+
|
|
78
|
+
err = assert_raises(Tuber::ScheduleSyncError) do
|
|
79
|
+
@schedules.sync(@owner) do |s|
|
|
80
|
+
s.add "nightly", tube: "gemtest.sched", cron: "0 3 * * *", zone: "Australia/Melbourne", body: "n"
|
|
81
|
+
s.add "typo", tube: "gemtest.sched", cron: "0 2 * * *", zone: "Australia/Melborne", body: "t"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
assert_equal "BAD_SCHEDULE", err.status
|
|
85
|
+
assert_equal "typo", err.entry
|
|
86
|
+
assert_equal @owner, err.owner
|
|
87
|
+
refute err.applied?
|
|
88
|
+
assert_match %r{Australia/Melborne}, err.message
|
|
89
|
+
|
|
90
|
+
assert_equal [[@owner, "nightly"]], @schedules.list(@owner)
|
|
91
|
+
assert_equal "0 2 * * * Australia/Melbourne", @schedules.stats(@owner, "nightly").spec
|
|
92
|
+
# The connection left sync mode: the next sync goes through.
|
|
93
|
+
assert_equal [0, 0, 0, 1], @schedules.sync(@owner, &nightly).to_a
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "refuses an empty set unless allow_empty" do
|
|
97
|
+
@schedules.sync(@owner) { |s| add_two(s) }
|
|
98
|
+
|
|
99
|
+
assert_raises(ArgumentError) { @schedules.sync(@owner) { |_s| } }
|
|
100
|
+
assert_raises(ArgumentError) { @schedules.sync(@owner) }
|
|
101
|
+
assert_equal 2, @schedules.list(@owner).size
|
|
102
|
+
|
|
103
|
+
assert_equal [0, 0, 2, 0], @schedules.sync(@owner, allow_empty: true).to_a
|
|
104
|
+
assert_equal [], @schedules.list(@owner)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "sends nothing when an entry is invalid" do
|
|
108
|
+
@schedules.sync(@owner) { |s| add_two(s) }
|
|
109
|
+
|
|
110
|
+
assert_raises(ArgumentError) do
|
|
111
|
+
@schedules.sync(@owner) do |s|
|
|
112
|
+
s.add "rollup", tube: "gemtest.sched", cron: "5 * * * *", body: "rollup"
|
|
113
|
+
s.add "broken", tube: "gemtest.sched", cron: "5 * * * *", every: 60, body: "broken"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
assert_equal 2, @schedules.list(@owner).size
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it "puts the body into the tube when an entry fires" do
|
|
120
|
+
serializer, parser = Tuber.configuration.job_serializer, Tuber.configuration.job_parser
|
|
121
|
+
Tuber.configure do |config|
|
|
122
|
+
config.job_serializer = lambda { |body| JSON.dump(body) }
|
|
123
|
+
config.job_parser = lambda { |body| JSON.parse(body) }
|
|
124
|
+
end
|
|
125
|
+
tube = "gemtest.fire.#{rand(1 << 30)}"
|
|
126
|
+
@schedules.sync(@owner) do |s|
|
|
127
|
+
s.add "tick", tube: tube, every: 1, body: { "class" => "TickJob", "args" => [1] }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
job = @beanstalk.tubes.find(tube).reserve(3)
|
|
131
|
+
assert_equal({ "class" => "TickJob", "args" => [1] }, job.body)
|
|
132
|
+
job.delete
|
|
133
|
+
ensure
|
|
134
|
+
Tuber.configure do |config|
|
|
135
|
+
config.job_serializer = serializer
|
|
136
|
+
config.job_parser = parser
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "re-runs the whole sync when the connection drops partway" do
|
|
141
|
+
$sync_writes = []
|
|
142
|
+
$drop_mid_sync = true
|
|
143
|
+
TCPSocket.prepend Module.new {
|
|
144
|
+
def write(data)
|
|
145
|
+
$sync_writes << data.to_s if $sync_writes
|
|
146
|
+
super
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def readline
|
|
150
|
+
if $drop_mid_sync && $sync_writes.last.to_s.start_with?("schedule-put")
|
|
151
|
+
$drop_mid_sync = false
|
|
152
|
+
close # the server sees the connection go and discards what was staged
|
|
153
|
+
raise EOFError
|
|
154
|
+
end
|
|
155
|
+
super
|
|
156
|
+
end
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
result = @schedules.sync(@owner) { |s| add_two(s) }
|
|
160
|
+
|
|
161
|
+
assert_equal 2, $sync_writes.count { |w| w.start_with?("schedule-begin") }
|
|
162
|
+
assert_equal [2, 0, 0, 0], result.to_a
|
|
163
|
+
assert_equal [[@owner, "checkin"], [@owner, "rollup"]], @schedules.list(@owner).sort
|
|
164
|
+
ensure
|
|
165
|
+
# Leave the prepended module inert for the rest of the suite.
|
|
166
|
+
$drop_mid_sync = false
|
|
167
|
+
$sync_writes = nil
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it "isn't held up by a blocking reserve on the client's connection" do
|
|
171
|
+
tube = "gemtest.empty.#{rand(1 << 30)}"
|
|
172
|
+
reserver = Thread.new do
|
|
173
|
+
@beanstalk.tubes.find(tube).reserve(3)
|
|
174
|
+
rescue Tuber::TimedOutError
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
sleep 0.3 # let the reserve take the connection
|
|
178
|
+
|
|
179
|
+
started = Time.now
|
|
180
|
+
@schedules.sync(@owner) { |s| add_two(s) }
|
|
181
|
+
assert_operator Time.now - started, :<, 1.5, "the sync waited for the reserve to time out"
|
|
182
|
+
ensure
|
|
183
|
+
reserver.join if reserver
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "closes the connection it syncs on, refused or not" do
|
|
187
|
+
# A leaked socket closes only when it is garbage collected, which would
|
|
188
|
+
# hide the leak; and earlier tests' unclosed clients must go first.
|
|
189
|
+
GC.start
|
|
190
|
+
GC.disable
|
|
191
|
+
sleep 0.2 # let the server notice anything just closed
|
|
192
|
+
before = @beanstalk.stats["current-connections"]
|
|
193
|
+
|
|
194
|
+
@schedules.sync(@owner) { |s| add_two(s) }
|
|
195
|
+
assert_raises(Tuber::ScheduleSyncError) do
|
|
196
|
+
@schedules.sync(@owner) do |s|
|
|
197
|
+
s.add "typo", tube: "gemtest.sched", cron: "0 2 * * *", zone: "Australia/Melborne", body: "t"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
after = nil
|
|
202
|
+
10.times do # the server notices a close a moment later
|
|
203
|
+
after = @beanstalk.stats["current-connections"]
|
|
204
|
+
break if after <= before
|
|
205
|
+
sleep 0.1
|
|
206
|
+
end
|
|
207
|
+
assert_operator after, :<=, before
|
|
208
|
+
ensure
|
|
209
|
+
GC.enable
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it "leaves nothing behind when an unexpected error interrupts it" do
|
|
213
|
+
$fail_mid_sync = true
|
|
214
|
+
$sync_last_write = nil
|
|
215
|
+
TCPSocket.prepend Module.new {
|
|
216
|
+
def write(data)
|
|
217
|
+
$sync_last_write = data.to_s if $fail_mid_sync
|
|
218
|
+
super
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def readline
|
|
222
|
+
if $fail_mid_sync && $sync_last_write.to_s.start_with?("schedule-put")
|
|
223
|
+
$fail_mid_sync = false
|
|
224
|
+
raise IOError, "simulated read failure" # e.g. a read timeout: not a dropped socket
|
|
225
|
+
end
|
|
226
|
+
super
|
|
227
|
+
end
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
assert_raises(IOError) { @schedules.sync(@owner) { |s| add_two(s) } }
|
|
231
|
+
|
|
232
|
+
# The client's connection still answers its own commands in step...
|
|
233
|
+
assert_equal "default", @beanstalk.connection.transmit("list-tube-used")[:id]
|
|
234
|
+
# ...nothing of the interrupted sync was committed, and the next sync starts clean.
|
|
235
|
+
assert_equal [], @schedules.list(@owner)
|
|
236
|
+
assert_equal [2, 0, 0, 0], @schedules.sync(@owner) { |s| add_two(s) }.to_a
|
|
237
|
+
ensure
|
|
238
|
+
$fail_mid_sync = false
|
|
239
|
+
end
|
|
240
|
+
end # sync
|
|
241
|
+
|
|
242
|
+
describe "for #stats" do
|
|
243
|
+
before { require_schedules! }
|
|
244
|
+
|
|
245
|
+
it "returns times as Time in the entry's zone, and counters as Integers" do
|
|
246
|
+
@schedules.sync(@owner) do |s|
|
|
247
|
+
s.add "nightly", tube: "gemtest.sched", cron: "0 2 * * *", zone: "Australia/Melbourne", body: "n"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
stats = @schedules.stats(@owner, "nightly")
|
|
251
|
+
assert_equal @owner, stats.owner
|
|
252
|
+
assert_kind_of Time, stats.synced_at
|
|
253
|
+
assert_kind_of Time, stats.next_fire
|
|
254
|
+
assert_includes [10 * 3600, 11 * 3600], stats.next_fire.utc_offset
|
|
255
|
+
assert_includes [2, 3], stats.next_fire.hour # 3 on a spring-forward day
|
|
256
|
+
assert stats.next_fire < stats.next_fire_2
|
|
257
|
+
assert stats.next_fire_2 < stats.next_fire_3
|
|
258
|
+
assert_equal stats.next_fire_2, stats["next-fire-2"]
|
|
259
|
+
assert_nil stats.last_fire
|
|
260
|
+
assert_nil stats.last_result
|
|
261
|
+
assert_equal 0, stats.total_fires
|
|
262
|
+
assert_equal 0, stats.total_deduped_buried
|
|
263
|
+
assert_equal 0, stats.buried_streak
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
it "returns nil for an entry it doesn't hold" do
|
|
267
|
+
assert_nil @schedules.stats(@owner, "nope")
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it "keeps a name and tube made of digits as Strings" do
|
|
271
|
+
@schedules.sync(@owner) { |s| s.add "2026", tube: "123", every: 60, body: "b" }
|
|
272
|
+
stats = @schedules.stats(@owner, "2026")
|
|
273
|
+
assert_equal "2026", stats.name
|
|
274
|
+
assert_equal "123", stats.tube
|
|
275
|
+
end
|
|
276
|
+
end # stats
|
|
277
|
+
end # Tuber::Schedules
|
|
278
|
+
|
|
279
|
+
# Replies a healthy server won't produce on demand. No @beanstalk here, so the
|
|
280
|
+
# suite's teardown doesn't talk through the stubbed socket.
|
|
281
|
+
describe "Tuber::Schedules sync refusals" do
|
|
282
|
+
before do
|
|
283
|
+
@client = Tuber.new(tuber_address)
|
|
284
|
+
@owner = "gemtest.stubbed"
|
|
285
|
+
TCPSocket.any_instance.stubs(:write)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
after do
|
|
289
|
+
@client.close
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def sync_one
|
|
293
|
+
@client.schedules.sync(@owner) { |s| s.add "a", tube: "gemtest.sched", every: 60, body: "a" }
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it "says the set is live when the commit couldn't be written" do
|
|
297
|
+
TCPSocket.any_instance.stubs(:readline).returns("SYNCING #{@owner}\r\n", "STAGED\r\n", "INTERNAL_ERROR\r\n")
|
|
298
|
+
err = assert_raises(Tuber::ScheduleSyncError) { sync_one }
|
|
299
|
+
assert_equal "INTERNAL_ERROR", err.status
|
|
300
|
+
assert_nil err.entry
|
|
301
|
+
assert err.applied?
|
|
302
|
+
assert_match(/is live/, err.message)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
it "says nothing changed when the commit is refused" do
|
|
306
|
+
TCPSocket.any_instance.stubs(:readline).returns("SYNCING #{@owner}\r\n", "STAGED\r\n", "OUT_OF_STORAGE\r\n")
|
|
307
|
+
err = assert_raises(Tuber::ScheduleSyncError) { sync_one }
|
|
308
|
+
assert_equal "OUT_OF_STORAGE", err.status
|
|
309
|
+
refute err.applied?
|
|
310
|
+
assert_match(/Nothing changed/, err.message)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it "aborts the sync before raising for a refused entry" do
|
|
314
|
+
TCPSocket.any_instance.expects(:write).with("schedule-abort\r\n")
|
|
315
|
+
TCPSocket.any_instance.stubs(:readline).returns("SYNCING #{@owner}\r\n", "JOB_TOO_BIG\r\n", "ABORTED\r\n")
|
|
316
|
+
err = assert_raises(Tuber::ScheduleSyncError) { sync_one }
|
|
317
|
+
assert_equal "JOB_TOO_BIG", err.status
|
|
318
|
+
assert_equal "a", err.entry
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
it "explains a server without schedules" do
|
|
322
|
+
TCPSocket.any_instance.stubs(:readline).returns("UNKNOWN_COMMAND\r\n")
|
|
323
|
+
err = assert_raises(Tuber::ScheduleSyncError) { sync_one }
|
|
324
|
+
assert_equal "UNKNOWN_COMMAND", err.status
|
|
325
|
+
assert_match(/no recurring schedules/, err.message)
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
describe Tuber::Schedules::Builder do
|
|
330
|
+
before do
|
|
331
|
+
@builder = Tuber::Schedules::Builder.new
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
it "writes the schedule-put arguments: tags before the spec" do
|
|
335
|
+
@builder.add "nightly", tube: "splat.maintenance", cron: "0 2 * * *", zone: "Australia/Melbourne",
|
|
336
|
+
body: "{}", idp: ["nightly", 300], con: "maintenance", catchup: 300, pri: 1024
|
|
337
|
+
entry = @builder.entries.first
|
|
338
|
+
assert_equal "nightly", entry.name
|
|
339
|
+
assert_equal "nightly splat.maintenance 1024 0 120 2 idp:nightly:300 con:maintenance catchup:300 " \
|
|
340
|
+
"0 2 * * * Australia/Melbourne", entry.line
|
|
341
|
+
assert_equal Encoding::BINARY, entry.body.encoding
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
it "counts the body in bytes" do
|
|
345
|
+
@builder.add "utf8", tube: "t", every: 60, body: "café"
|
|
346
|
+
assert_equal "utf8 t 65536 0 120 5 every 60s", @builder.entries.first.line
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
it "writes every: and offset:, and catchup: :never" do
|
|
350
|
+
@builder.add :hourly, tube: "t", every: 3600, offset: 300, body: "b", catchup: :never
|
|
351
|
+
assert_equal "hourly t 65536 0 120 1 catchup:0 every 3600s offset 300s", @builder.entries.first.line
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
it "refuses an entry tuber would refuse, or that would break the line" do
|
|
355
|
+
bad = [
|
|
356
|
+
{ tube: "t", body: "b" }, # no spec
|
|
357
|
+
{ tube: "t", body: "b", cron: "5 * * * *", every: 60 }, # two specs
|
|
358
|
+
{ tube: "t", body: "b", every: 60, zone: "UTC" }, # zone without cron
|
|
359
|
+
{ tube: "t", body: "b", cron: "5 * * * *", offset: "5m" }, # offset without every
|
|
360
|
+
{ tube: "t", body: "b", spec: "5 * * * * UTC+10:00" }, # a colon reads as a tag
|
|
361
|
+
{ tube: "t", body: "b", spec: "5 * * * *\r\nflush-tube t" }, # a second command
|
|
362
|
+
{ tube: "-t", body: "b", every: 60 }, # bad tube name
|
|
363
|
+
{ tube: "t", body: { "a" => 1 }, every: 60 }, # body isn't a String
|
|
364
|
+
{ tube: "t", body: "b", every: 60, idp: "two words" }, # tag with a space
|
|
365
|
+
{ tube: "t", body: "b", every: 60, con: "" }, # empty tag key
|
|
366
|
+
{ tube: "t", body: "b", every: 60, catchup: -1 },
|
|
367
|
+
{ tube: "t", body: "b", every: 60, catchup: "soon" },
|
|
368
|
+
]
|
|
369
|
+
bad.each do |options|
|
|
370
|
+
assert_raises(ArgumentError, options.inspect) { Tuber::Schedules::Builder.new.add("name", **options) }
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
["", "-name", "has space", "x" * 201, nil].each do |name|
|
|
374
|
+
assert_raises(ArgumentError, name.inspect) { @builder.add(name, tube: "t", every: 60, body: "b") }
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
it "refuses a name added twice" do
|
|
379
|
+
@builder.add "once", tube: "t", every: 60, body: "b"
|
|
380
|
+
assert_raises(ArgumentError) { @builder.add "once", tube: "t", every: 120, body: "b" }
|
|
381
|
+
end
|
|
382
|
+
end
|
data/test/test_helper.rb
CHANGED
|
@@ -43,6 +43,24 @@ class MiniTest::Unit::TestCase
|
|
|
43
43
|
skip "requires a Tuber server, not stock beanstalkd" unless tuber_server?
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
# Skips the current test unless the server runs recurring schedules. A
|
|
47
|
+
# Tuber older than the scheduler answers UNKNOWN_COMMAND to list-schedules.
|
|
48
|
+
def require_schedules!
|
|
49
|
+
require_tuber!
|
|
50
|
+
if $tuber_schedules.nil?
|
|
51
|
+
client = Tuber.new(tuber_address)
|
|
52
|
+
begin
|
|
53
|
+
client.connection.transmit_raw('list-schedules')
|
|
54
|
+
$tuber_schedules = true
|
|
55
|
+
rescue Tuber::UnknownCommandError
|
|
56
|
+
$tuber_schedules = false
|
|
57
|
+
ensure
|
|
58
|
+
client.close
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
skip "requires a Tuber server with recurring schedules" unless $tuber_schedules
|
|
62
|
+
end
|
|
63
|
+
|
|
46
64
|
# Cleans up all jobs from specific tubes
|
|
47
65
|
#
|
|
48
66
|
# @example
|
data/test/tubes_test.rb
CHANGED
|
@@ -128,13 +128,13 @@ describe Tuber::Tubes do
|
|
|
128
128
|
|
|
129
129
|
it 'should send watch command with weight' do
|
|
130
130
|
@connection.expects(:transmit).with("watch foo 4").returns({status: "WATCHING", id: "2"})
|
|
131
|
-
@connection.expects(:add_to_watched).with("foo")
|
|
131
|
+
@connection.expects(:add_to_watched).with("foo", 4)
|
|
132
132
|
@tubes.watch('foo', weight: 4)
|
|
133
133
|
end
|
|
134
134
|
|
|
135
135
|
it 'should send watch command without weight by default' do
|
|
136
136
|
@connection.expects(:transmit).with("watch foo").returns({status: "WATCHING", id: "2"})
|
|
137
|
-
@connection.expects(:add_to_watched).with("foo")
|
|
137
|
+
@connection.expects(:add_to_watched).with("foo", nil)
|
|
138
138
|
@tubes.watch('foo')
|
|
139
139
|
end
|
|
140
140
|
end # watch with weight
|
|
@@ -149,13 +149,26 @@ describe Tuber::Tubes do
|
|
|
149
149
|
|
|
150
150
|
it 'should send reserve-mode weighted command' do
|
|
151
151
|
@connection.expects(:transmit).with("reserve-mode weighted").returns({status: "USING", id: "weighted"})
|
|
152
|
+
@connection.expects(:reserve_mode=).with(:weighted)
|
|
152
153
|
@tubes.reserve_mode(:weighted)
|
|
153
154
|
end
|
|
154
155
|
|
|
155
156
|
it 'should send reserve-mode fifo command' do
|
|
156
157
|
@connection.expects(:transmit).with("reserve-mode fifo").returns({status: "USING", id: "fifo"})
|
|
158
|
+
@connection.expects(:reserve_mode=).with(:fifo)
|
|
157
159
|
@tubes.reserve_mode(:fifo)
|
|
158
160
|
end
|
|
161
|
+
|
|
162
|
+
it 'should record the mode on the connection so a reconnect can replay it' do
|
|
163
|
+
require_tuber!
|
|
164
|
+
client = Tuber.new(tuber_address)
|
|
165
|
+
begin
|
|
166
|
+
client.tubes.reserve_mode(:weighted)
|
|
167
|
+
assert_equal :weighted, client.connection.reserve_mode
|
|
168
|
+
ensure
|
|
169
|
+
client.close
|
|
170
|
+
end
|
|
171
|
+
end
|
|
159
172
|
end # reserve_mode
|
|
160
173
|
|
|
161
174
|
describe "for #reserve" do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tuber
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dan Milne
|
|
@@ -97,7 +97,6 @@ files:
|
|
|
97
97
|
- LICENSE.txt
|
|
98
98
|
- README.md
|
|
99
99
|
- Rakefile
|
|
100
|
-
- TODO
|
|
101
100
|
- examples/demo.rb
|
|
102
101
|
- lib/tuber.rb
|
|
103
102
|
- lib/tuber/configuration.rb
|
|
@@ -106,6 +105,7 @@ files:
|
|
|
106
105
|
- lib/tuber/job.rb
|
|
107
106
|
- lib/tuber/job/collection.rb
|
|
108
107
|
- lib/tuber/job/record.rb
|
|
108
|
+
- lib/tuber/schedules.rb
|
|
109
109
|
- lib/tuber/stats.rb
|
|
110
110
|
- lib/tuber/stats/fast_struct.rb
|
|
111
111
|
- lib/tuber/stats/stat_struct.rb
|
|
@@ -118,6 +118,7 @@ files:
|
|
|
118
118
|
- test/job_test.rb
|
|
119
119
|
- test/jobs_test.rb
|
|
120
120
|
- test/prompt_regexp_test.rb
|
|
121
|
+
- test/schedules_test.rb
|
|
121
122
|
- test/stat_struct_test.rb
|
|
122
123
|
- test/stats_test.rb
|
|
123
124
|
- test/test_helper.rb
|
|
@@ -146,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
146
147
|
- !ruby/object:Gem::Version
|
|
147
148
|
version: '0'
|
|
148
149
|
requirements: []
|
|
149
|
-
rubygems_version: 4.0.
|
|
150
|
+
rubygems_version: 4.0.20
|
|
150
151
|
specification_version: 4
|
|
151
152
|
summary: Ruby client for the Tuber job queue server.
|
|
152
153
|
test_files: []
|