gouda 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +8 -0
- data/gouda.gemspec +1 -1
- data/lib/gouda/version.rb +1 -1
- data/test/gouda/concurrency_extension_test.rb +160 -0
- data/test/gouda/gouda_test.rb +686 -0
- data/test/gouda/scheduler_test.rb +187 -0
- data/test/gouda/seconds_to_start_distribution.csv +280 -0
- data/test/gouda/test_helper.rb +70 -0
- data/test/gouda/worker_test.rb +116 -0
- data/test/gouda/workload_test.rb +67 -0
- data/test/support/assert_helper.rb +51 -0
- metadata +11 -3
@@ -0,0 +1,187 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gouda/test_helper"
|
4
|
+
|
5
|
+
class GoudaSchedulerTest < ActiveSupport::TestCase
|
6
|
+
include AssertHelper
|
7
|
+
|
8
|
+
setup do
|
9
|
+
Gouda::Workload.delete_all
|
10
|
+
Gouda::JobFuse.delete_all
|
11
|
+
end
|
12
|
+
|
13
|
+
class TestJob < ActiveJob::Base
|
14
|
+
self.queue_adapter = Gouda::Adapter.new
|
15
|
+
|
16
|
+
def perform(regular = "ok", mandatory:, optional: "hidden")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class FailingJob < ActiveJob::Base
|
21
|
+
include Gouda::ActiveJobExtensions::Concurrency
|
22
|
+
self.queue_adapter = Gouda::Adapter.new
|
23
|
+
|
24
|
+
class MegaError < StandardError
|
25
|
+
end
|
26
|
+
|
27
|
+
gouda_control_concurrency_with(enqueue_limit: 1, key: -> { self.class.to_s })
|
28
|
+
|
29
|
+
retry_on StandardError, wait: :polynomially_longer, attempts: 5
|
30
|
+
retry_on Gouda::InterruptError, wait: 0, attempts: 5
|
31
|
+
retry_on MegaError, attempts: 3, wait: 0
|
32
|
+
|
33
|
+
def perform
|
34
|
+
raise MegaError.new "Kaboom!"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
test "keeps re-enqueueing cron jobs after failed job (also with kwargs)" do
|
39
|
+
tab = {
|
40
|
+
second_minutely: {
|
41
|
+
cron: "*/1 * * * * *", # every second
|
42
|
+
class: "GoudaSchedulerTest::FailingJob"
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
assert_nothing_raised do
|
47
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
48
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
49
|
+
end
|
50
|
+
|
51
|
+
assert_equal 1, Gouda::Workload.enqueued.count
|
52
|
+
Gouda.worker_loop(n_threads: 1, check_shutdown: Gouda::TimerShutdownCheck.new(2))
|
53
|
+
|
54
|
+
refute_empty Gouda::Workload.enqueued
|
55
|
+
assert Gouda::Workload.count > 3
|
56
|
+
end
|
57
|
+
|
58
|
+
test "re-inserts the next subsequent job after executing the queued one" do
|
59
|
+
tab = {
|
60
|
+
second_minutely: {
|
61
|
+
cron: "*/1 * * * * *", # every second
|
62
|
+
class: "GoudaSchedulerTest::TestJob",
|
63
|
+
args: ["omg"],
|
64
|
+
kwargs: {mandatory: "WOOHOO", optional: "yeah"},
|
65
|
+
set: {priority: 150}
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
assert_nothing_raised do
|
70
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
71
|
+
end
|
72
|
+
|
73
|
+
assert_changes_by(-> { Gouda::Workload.count }, exactly: 1) do
|
74
|
+
3.times do
|
75
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
job = Gouda::Workload.first
|
80
|
+
assert_equal 1, Gouda::Workload.count
|
81
|
+
|
82
|
+
sleep 1
|
83
|
+
|
84
|
+
assert_equal job, Gouda::Workload.checkout_and_lock_one(executing_on: "test")
|
85
|
+
|
86
|
+
assert_changes_by(-> { Gouda::Workload.count }, exactly: 1) do
|
87
|
+
3.times do
|
88
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
assert_equal 2, Gouda::Workload.count
|
93
|
+
Gouda::Workload.all.each(&:perform_and_update_state!)
|
94
|
+
assert_equal 0, Gouda::Workload.errored.count
|
95
|
+
workload = Gouda::Workload.find_by(scheduler_key: "second_minutely_*/1 * * * * *_GoudaSchedulerTest::TestJob")
|
96
|
+
assert_equal 150, workload.priority
|
97
|
+
assert_equal ["omg", {
|
98
|
+
"optional" => "yeah",
|
99
|
+
"mandatory" => "WOOHOO",
|
100
|
+
"_aj_ruby2_keywords" => ["mandatory", "optional"]
|
101
|
+
}], workload.serialized_params["arguments"]
|
102
|
+
end
|
103
|
+
|
104
|
+
test "accepts crontab with nil args" do
|
105
|
+
tab = {
|
106
|
+
first_hourly: {
|
107
|
+
cron: "@hourly",
|
108
|
+
class: "GoudaSchedulerTest::TestJob",
|
109
|
+
args: [nil, nil]
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
assert_nothing_raised do
|
114
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
115
|
+
end
|
116
|
+
|
117
|
+
assert_changes_by(-> { Gouda::Workload.count }, exactly: 1) do
|
118
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
119
|
+
end
|
120
|
+
|
121
|
+
assert_equal [nil, nil], Gouda::Workload.first.serialized_params["arguments"]
|
122
|
+
end
|
123
|
+
|
124
|
+
test "is able to accept a crontab" do
|
125
|
+
tab = {
|
126
|
+
first_hourly: {
|
127
|
+
cron: "@hourly",
|
128
|
+
class: "GoudaSchedulerTest::TestJob",
|
129
|
+
args: ["one"],
|
130
|
+
kwargs: {mandatory: "Yeah"}
|
131
|
+
},
|
132
|
+
second_minutely: {
|
133
|
+
cron: "*/1 * * * *",
|
134
|
+
class: "GoudaSchedulerTest::TestJob",
|
135
|
+
args: [6],
|
136
|
+
kwargs: {mandatory: "Yeah", optional: "something"}
|
137
|
+
},
|
138
|
+
third_hourly_with_args_and_kwargs: {
|
139
|
+
cron: "@hourly",
|
140
|
+
class: "GoudaSchedulerTest::TestJob",
|
141
|
+
args: [1],
|
142
|
+
kwargs: {mandatory: "alright"}
|
143
|
+
},
|
144
|
+
interval: {
|
145
|
+
interval_seconds: 250,
|
146
|
+
class: "GoudaSchedulerTest::TestJob",
|
147
|
+
args: [4],
|
148
|
+
kwargs: {mandatory: "tasty"}
|
149
|
+
}
|
150
|
+
}
|
151
|
+
assert_nothing_raised do
|
152
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
153
|
+
end
|
154
|
+
|
155
|
+
travel_to Time.utc(2023, 6, 23, 20, 0)
|
156
|
+
assert_changes_by(-> { Gouda::Workload.count }, exactly: 4) do
|
157
|
+
3.times do
|
158
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
tab[:fifth] = {
|
163
|
+
cron: "@hourly",
|
164
|
+
class: "GoudaSchedulerTest::TestJob",
|
165
|
+
kwargs: {mandatory: "good"}
|
166
|
+
}
|
167
|
+
|
168
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
169
|
+
assert_changes_by(-> { Gouda::Workload.count }, exactly: 1) do
|
170
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
171
|
+
end
|
172
|
+
|
173
|
+
assert tab.delete(:fifth)
|
174
|
+
Gouda::Scheduler.build_scheduler_entries_list!(tab)
|
175
|
+
assert_changes_by(-> { Gouda::Workload.count }, exactly: -1) do
|
176
|
+
Gouda::Scheduler.upsert_workloads_from_entries_list!
|
177
|
+
end
|
178
|
+
|
179
|
+
Gouda::Workload.all.each(&:perform_and_update_state!)
|
180
|
+
assert_equal 0, Gouda::Workload.errored.count
|
181
|
+
assert_equal [6, {
|
182
|
+
"optional" => "something",
|
183
|
+
"mandatory" => "Yeah",
|
184
|
+
"_aj_ruby2_keywords" => ["mandatory", "optional"]
|
185
|
+
}], Gouda::Workload.find_by(scheduler_key: "second_minutely_*/1 * * * *_GoudaSchedulerTest::TestJob").serialized_params["arguments"]
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
"count","seconds_to_execution"
|
2
|
+
1,29146
|
3
|
+
1,28471
|
4
|
+
1,27796
|
5
|
+
1,27121
|
6
|
+
1,26446
|
7
|
+
1,25771
|
8
|
+
1,25096
|
9
|
+
1,24421
|
10
|
+
1,23746
|
11
|
+
1,23071
|
12
|
+
1,22396
|
13
|
+
1,21720
|
14
|
+
1,21045
|
15
|
+
1,20370
|
16
|
+
1,19695
|
17
|
+
1,19020
|
18
|
+
1,18345
|
19
|
+
1,17670
|
20
|
+
1,16995
|
21
|
+
1,16320
|
22
|
+
1,15645
|
23
|
+
1,14970
|
24
|
+
1,14295
|
25
|
+
1,13620
|
26
|
+
1,12945
|
27
|
+
1,12270
|
28
|
+
1,11595
|
29
|
+
1,10920
|
30
|
+
1,10245
|
31
|
+
1,9570
|
32
|
+
1,8895
|
33
|
+
1,8220
|
34
|
+
1,7545
|
35
|
+
1,6870
|
36
|
+
1,6195
|
37
|
+
1,5915
|
38
|
+
1,5752
|
39
|
+
1,5520
|
40
|
+
1,5438
|
41
|
+
1,5190
|
42
|
+
1,5114
|
43
|
+
2,4929
|
44
|
+
1,4845
|
45
|
+
1,4609
|
46
|
+
1,4489
|
47
|
+
1,4373
|
48
|
+
1,4170
|
49
|
+
1,4133
|
50
|
+
1,4106
|
51
|
+
1,4090
|
52
|
+
1,3956
|
53
|
+
1,3945
|
54
|
+
2,3834
|
55
|
+
1,3598
|
56
|
+
1,3495
|
57
|
+
1,3383
|
58
|
+
1,3377
|
59
|
+
1,3341
|
60
|
+
1,3302
|
61
|
+
1,3174
|
62
|
+
1,3139
|
63
|
+
1,2987
|
64
|
+
1,2869
|
65
|
+
1,2861
|
66
|
+
1,2820
|
67
|
+
2,2805
|
68
|
+
1,2328
|
69
|
+
1,2179
|
70
|
+
1,2145
|
71
|
+
1,1982
|
72
|
+
1,1947
|
73
|
+
1,1678
|
74
|
+
1,1673
|
75
|
+
1,1470
|
76
|
+
1,1459
|
77
|
+
1,1441
|
78
|
+
1,1318
|
79
|
+
1,1270
|
80
|
+
1,1170
|
81
|
+
1,1123
|
82
|
+
1,1079
|
83
|
+
1,974
|
84
|
+
1,970
|
85
|
+
1,957
|
86
|
+
1,893
|
87
|
+
1,795
|
88
|
+
1,778
|
89
|
+
1,740
|
90
|
+
1,478
|
91
|
+
1,442
|
92
|
+
1,262
|
93
|
+
1,207
|
94
|
+
4,197
|
95
|
+
1,166
|
96
|
+
1,131
|
97
|
+
2,123
|
98
|
+
1,122
|
99
|
+
3,120
|
100
|
+
2,119
|
101
|
+
1,118
|
102
|
+
1,117
|
103
|
+
1,116
|
104
|
+
1,114
|
105
|
+
3,111
|
106
|
+
1,110
|
107
|
+
2,109
|
108
|
+
1,108
|
109
|
+
2,107
|
110
|
+
5,106
|
111
|
+
3,105
|
112
|
+
3,104
|
113
|
+
4,103
|
114
|
+
1,102
|
115
|
+
1,101
|
116
|
+
1,100
|
117
|
+
3,99
|
118
|
+
1,98
|
119
|
+
2,97
|
120
|
+
2,96
|
121
|
+
2,95
|
122
|
+
2,94
|
123
|
+
1,93
|
124
|
+
1,92
|
125
|
+
4,91
|
126
|
+
1,90
|
127
|
+
2,89
|
128
|
+
2,88
|
129
|
+
1,87
|
130
|
+
1,86
|
131
|
+
2,85
|
132
|
+
3,84
|
133
|
+
2,83
|
134
|
+
1,82
|
135
|
+
2,81
|
136
|
+
1,80
|
137
|
+
3,79
|
138
|
+
1,78
|
139
|
+
5,77
|
140
|
+
1,76
|
141
|
+
3,75
|
142
|
+
2,74
|
143
|
+
1,73
|
144
|
+
3,72
|
145
|
+
5,71
|
146
|
+
1,70
|
147
|
+
3,69
|
148
|
+
4,68
|
149
|
+
3,67
|
150
|
+
2,66
|
151
|
+
1,65
|
152
|
+
3,64
|
153
|
+
2,63
|
154
|
+
2,62
|
155
|
+
2,61
|
156
|
+
1,60
|
157
|
+
2,59
|
158
|
+
1,58
|
159
|
+
1,57
|
160
|
+
3,56
|
161
|
+
1,55
|
162
|
+
8,54
|
163
|
+
2,53
|
164
|
+
2,52
|
165
|
+
2,51
|
166
|
+
1,50
|
167
|
+
2,49
|
168
|
+
2,48
|
169
|
+
2,47
|
170
|
+
3,46
|
171
|
+
3,45
|
172
|
+
4,43
|
173
|
+
3,42
|
174
|
+
4,41
|
175
|
+
2,40
|
176
|
+
4,39
|
177
|
+
2,37
|
178
|
+
2,35
|
179
|
+
4,34
|
180
|
+
2,33
|
181
|
+
4,32
|
182
|
+
4,31
|
183
|
+
1,30
|
184
|
+
2,29
|
185
|
+
2,28
|
186
|
+
5,27
|
187
|
+
1,26
|
188
|
+
1,25
|
189
|
+
4,24
|
190
|
+
4,23
|
191
|
+
1,22
|
192
|
+
3,21
|
193
|
+
2,20
|
194
|
+
2,19
|
195
|
+
3,17
|
196
|
+
2,16
|
197
|
+
3,15
|
198
|
+
3,13
|
199
|
+
1,12
|
200
|
+
2,11
|
201
|
+
5,10
|
202
|
+
3,9
|
203
|
+
4,8
|
204
|
+
4,7
|
205
|
+
4,6
|
206
|
+
3,5
|
207
|
+
2,4
|
208
|
+
3,3
|
209
|
+
3,2
|
210
|
+
2,1
|
211
|
+
3,0
|
212
|
+
2,-2
|
213
|
+
1,-5
|
214
|
+
2,-7
|
215
|
+
1,-8
|
216
|
+
3,-10
|
217
|
+
1,-12
|
218
|
+
3,-13
|
219
|
+
2,-16
|
220
|
+
1,-18
|
221
|
+
1,-21
|
222
|
+
2,-28
|
223
|
+
2,-30
|
224
|
+
2,-35
|
225
|
+
1,-36
|
226
|
+
3,-40
|
227
|
+
1,-41
|
228
|
+
1,-42
|
229
|
+
3,-45
|
230
|
+
1,-46
|
231
|
+
2,-47
|
232
|
+
1,-49
|
233
|
+
3,-50
|
234
|
+
2,-51
|
235
|
+
1,-52
|
236
|
+
2,-54
|
237
|
+
2,-55
|
238
|
+
1,-56
|
239
|
+
2,-57
|
240
|
+
2,-58
|
241
|
+
2,-59
|
242
|
+
2,-60
|
243
|
+
2,-62
|
244
|
+
5,-63
|
245
|
+
2,-64
|
246
|
+
3,-65
|
247
|
+
1,-66
|
248
|
+
2,-68
|
249
|
+
4,-69
|
250
|
+
2,-70
|
251
|
+
1,-71
|
252
|
+
3,-75
|
253
|
+
1,-76
|
254
|
+
1,-77
|
255
|
+
1,-80
|
256
|
+
1,-82
|
257
|
+
2,-83
|
258
|
+
2,-84
|
259
|
+
1,-85
|
260
|
+
1,-86
|
261
|
+
2,-87
|
262
|
+
1,-88
|
263
|
+
1,-89
|
264
|
+
2,-90
|
265
|
+
1,-91
|
266
|
+
2,-92
|
267
|
+
3,-93
|
268
|
+
1,-94
|
269
|
+
4,-95
|
270
|
+
1,-100
|
271
|
+
2,-101
|
272
|
+
2,-102
|
273
|
+
1,-107
|
274
|
+
1,-180
|
275
|
+
1,-270
|
276
|
+
2,-275
|
277
|
+
1,-302
|
278
|
+
2,-308
|
279
|
+
1,-455
|
280
|
+
1,-1129
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
4
|
+
require "debug"
|
5
|
+
require "active_record"
|
6
|
+
require "active_job"
|
7
|
+
require "active_support/test_case"
|
8
|
+
require "minitest/autorun"
|
9
|
+
require "minitest"
|
10
|
+
require "support/assert_helper"
|
11
|
+
require_relative "../../lib/gouda"
|
12
|
+
|
13
|
+
class ActiveSupport::TestCase
|
14
|
+
SEED_DB_NAME = -> { "gouda_tests_%s" % Random.new(Minitest.seed).hex(4) }
|
15
|
+
|
16
|
+
def self.adapter
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :case_random
|
20
|
+
|
21
|
+
setup do
|
22
|
+
create_postgres_database_if_none
|
23
|
+
@adapter || Gouda::Adapter.new
|
24
|
+
@case_random = Random.new(Minitest.seed)
|
25
|
+
Gouda::Railtie.initializers.each(&:run)
|
26
|
+
ActiveJob::Base.logger = nil
|
27
|
+
Gouda.config.logger.level = 4
|
28
|
+
end
|
29
|
+
|
30
|
+
teardown do
|
31
|
+
truncate_test_tables
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_postgres_database_if_none
|
35
|
+
ActiveRecord::Base.establish_connection(adapter: "postgresql", encoding: "unicode", database: SEED_DB_NAME.call)
|
36
|
+
ActiveRecord::Base.connection.execute("SELECT 1 FROM gouda_workloads")
|
37
|
+
rescue ActiveRecord::NoDatabaseError, ActiveRecord::ConnectionNotEstablished
|
38
|
+
create_postgres_database
|
39
|
+
retry
|
40
|
+
rescue ActiveRecord::StatementInvalid
|
41
|
+
ActiveRecord::Schema.define(version: 1) do |via_definer|
|
42
|
+
Gouda.create_tables(via_definer)
|
43
|
+
end
|
44
|
+
retry
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_postgres_database
|
48
|
+
ActiveRecord::Migration.verbose = false
|
49
|
+
ActiveRecord::Base.establish_connection(adapter: "postgresql", database: "postgres")
|
50
|
+
ActiveRecord::Base.connection.create_database(SEED_DB_NAME.call, charset: :unicode)
|
51
|
+
ActiveRecord::Base.connection.close
|
52
|
+
ActiveRecord::Base.establish_connection(adapter: "postgresql", encoding: "unicode", database: SEED_DB_NAME.call)
|
53
|
+
end
|
54
|
+
|
55
|
+
def truncate_test_tables
|
56
|
+
ActiveRecord::Base.connection.execute("TRUNCATE TABLE gouda_workloads")
|
57
|
+
ActiveRecord::Base.connection.execute("TRUNCATE TABLE gouda_job_fuses")
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_create_tables
|
61
|
+
ActiveRecord::Base.transaction do
|
62
|
+
ActiveRecord::Base.connection.execute("DROP TABLE gouda_workloads")
|
63
|
+
ActiveRecord::Base.connection.execute("DROP TABLE gouda_job_fuses")
|
64
|
+
# The adapter has to be in a variable as the schema definition is scoped to the migrator, not self
|
65
|
+
ActiveRecord::Schema.define(version: 1) do |via_definer|
|
66
|
+
Gouda.create_tables(via_definer)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gouda/test_helper"
|
4
|
+
|
5
|
+
class GoudaWorkerTest < ActiveSupport::TestCase
|
6
|
+
include AssertHelper
|
7
|
+
|
8
|
+
# self.use_transactional_tests = false
|
9
|
+
|
10
|
+
# This is a bit obtuse but we need to be able to compute this value from inside the ActiveJob
|
11
|
+
# and the job won't have access to the test case object. To avoid mistakes, we can put it
|
12
|
+
# in a constant - which is lexically-scoped. We also evaluate it lazily since the Rails
|
13
|
+
# constant is possibly not initialized yet when this code loads.
|
14
|
+
# We need to include the PID in the path, because we might be running
|
15
|
+
# multiple test processes on the same box - and they might start touching
|
16
|
+
# files from each other.
|
17
|
+
PATH_TO_TEST_FILE = -> { File.expand_path(File.join("tmp", "#{Process.pid}-gouda-worker-test-output.bin")) }
|
18
|
+
|
19
|
+
class JobWithEnqueueKey < ActiveJob::Base
|
20
|
+
self.queue_adapter = :gouda
|
21
|
+
|
22
|
+
def enqueue_concurrency_key
|
23
|
+
"zombie"
|
24
|
+
end
|
25
|
+
|
26
|
+
def perform
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class SimpleJob < ActiveJob::Base
|
31
|
+
self.queue_adapter = :gouda
|
32
|
+
|
33
|
+
def perform
|
34
|
+
File.open(PATH_TO_TEST_FILE.call, "a") do |f|
|
35
|
+
f.write("A")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
test "runs workloads from all queues without a queue constraint" do
|
41
|
+
Gouda.in_bulk do
|
42
|
+
6.times { SimpleJob.perform_later }
|
43
|
+
6.times { SimpleJob.set(queue: "urgent").perform_later }
|
44
|
+
end
|
45
|
+
assert_equal 12, Gouda::Workload.where(state: "enqueued").count
|
46
|
+
|
47
|
+
Gouda.worker_loop(n_threads: 1, check_shutdown: Gouda::EmptyQueueShutdownCheck.new)
|
48
|
+
|
49
|
+
# Check that the side effects of the job have been performed - in this case, that
|
50
|
+
# 12 chars have been written into the file. Every job writes a char.
|
51
|
+
assert_equal 12, File.size(PATH_TO_TEST_FILE.call)
|
52
|
+
|
53
|
+
assert_equal 0, Gouda::Workload.where(state: "enqueued").count
|
54
|
+
assert_equal 12, Gouda::Workload.where(state: "finished").count
|
55
|
+
end
|
56
|
+
|
57
|
+
test "does not run workloads destined for a different queue" do
|
58
|
+
only_from_bravo = Gouda.parse_queue_constraint("queue-bravo")
|
59
|
+
bravo_queue_has_no_jobs = Gouda::EmptyQueueShutdownCheck.new(only_from_bravo)
|
60
|
+
|
61
|
+
Gouda.in_bulk do
|
62
|
+
12.times { SimpleJob.set(queue: "queue-alpha").perform_later }
|
63
|
+
end
|
64
|
+
assert_equal 12, Gouda::Workload.where(state: "enqueued").count
|
65
|
+
|
66
|
+
Gouda.worker_loop(n_threads: 1, queue_constraint: only_from_bravo, check_shutdown: bravo_queue_has_no_jobs)
|
67
|
+
|
68
|
+
assert_equal 12, Gouda::Workload.where(state: "enqueued").count
|
69
|
+
assert_equal 0, Gouda::Workload.where(state: "finished").count
|
70
|
+
end
|
71
|
+
|
72
|
+
test "reaps zombie workloads and then executes replacements" do
|
73
|
+
past = 10.minutes.ago
|
74
|
+
2.times do
|
75
|
+
zombie_job = JobWithEnqueueKey.new
|
76
|
+
Gouda::Workload.create!(
|
77
|
+
scheduled_at: past,
|
78
|
+
active_job_id: zombie_job.job_id,
|
79
|
+
execution_started_at: past,
|
80
|
+
last_execution_heartbeat_at: past,
|
81
|
+
queue_name: "default",
|
82
|
+
active_job_class_name: "GoudaWorkerTest::JobWithEnqueueKey",
|
83
|
+
serialized_params: {
|
84
|
+
job_id: zombie_job.job_id,
|
85
|
+
locale: "en",
|
86
|
+
priority: nil,
|
87
|
+
timezone: "UTC",
|
88
|
+
arguments: [],
|
89
|
+
job_class: zombie_job.class.to_s,
|
90
|
+
executions: 0,
|
91
|
+
queue_name: "default",
|
92
|
+
enqueued_at: past,
|
93
|
+
exception_executions: {}
|
94
|
+
},
|
95
|
+
state: "executing",
|
96
|
+
execution_concurrency_key: nil,
|
97
|
+
enqueue_concurrency_key: nil,
|
98
|
+
executing_on: "unit test",
|
99
|
+
position_in_bulk: 0
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
assert_equal 2, Gouda::Workload.where(state: "executing").count
|
104
|
+
|
105
|
+
Gouda.worker_loop(n_threads: 2, check_shutdown: Gouda::TimerShutdownCheck.new(2.0))
|
106
|
+
|
107
|
+
assert_equal 0, Gouda::Workload.where(state: "enqueued").count
|
108
|
+
assert_equal 0, Gouda::Workload.where(state: "executing").count
|
109
|
+
|
110
|
+
# The original 2 workloads got zombie-reaped and marked "finished".
|
111
|
+
# There should have been only 1 workload enqueued as a retry, because due to
|
112
|
+
# the enqueue concurrency key there would only be 1 allowed into the queue. It
|
113
|
+
# then should have executed normally and marked "finished".
|
114
|
+
assert_equal 3, Gouda::Workload.where(state: "finished").count
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gouda/test_helper"
|
4
|
+
|
5
|
+
class GoudaWorkloadTest < ActiveSupport::TestCase
|
6
|
+
include AssertHelper
|
7
|
+
|
8
|
+
class TestJob < ActiveJob::Base
|
9
|
+
self.queue_adapter = Gouda::Adapter.new
|
10
|
+
|
11
|
+
def perform
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
test "#schedule_now!" do
|
16
|
+
freeze_time
|
17
|
+
create_enqueued_workload
|
18
|
+
create_enqueued_workload
|
19
|
+
workload = create_enqueued_workload
|
20
|
+
workload.schedule_now!
|
21
|
+
assert_equal 3, Gouda::Workload.enqueued.size
|
22
|
+
assert_equal Time.now.utc, workload.scheduled_at
|
23
|
+
end
|
24
|
+
|
25
|
+
test "#mark_finished!" do
|
26
|
+
freeze_time
|
27
|
+
create_enqueued_workload
|
28
|
+
create_enqueued_workload
|
29
|
+
workload = create_enqueued_workload
|
30
|
+
workload.mark_finished!
|
31
|
+
assert_equal 2, Gouda::Workload.enqueued.size
|
32
|
+
assert_equal 1, Gouda::Workload.finished.size
|
33
|
+
assert_equal 1, Gouda::Workload.errored.size
|
34
|
+
assert_equal Time.now.utc, workload.execution_finished_at
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_enqueued_workload
|
38
|
+
now = Time.now.utc
|
39
|
+
test_job = TestJob.new
|
40
|
+
|
41
|
+
Gouda::Workload.create!(
|
42
|
+
scheduled_at: now + 1.hour,
|
43
|
+
active_job_id: test_job.job_id,
|
44
|
+
execution_started_at: nil,
|
45
|
+
last_execution_heartbeat_at: nil,
|
46
|
+
queue_name: "default",
|
47
|
+
active_job_class_name: "GoudaWorkloadTest::TestJob",
|
48
|
+
serialized_params: {
|
49
|
+
job_id: test_job.job_id,
|
50
|
+
locale: "en",
|
51
|
+
priority: nil,
|
52
|
+
timezone: "UTC",
|
53
|
+
arguments: [],
|
54
|
+
job_class: test_job.class.to_s,
|
55
|
+
executions: 0,
|
56
|
+
queue_name: "default",
|
57
|
+
enqueued_at: now - 1.hour,
|
58
|
+
exception_executions: {}
|
59
|
+
},
|
60
|
+
state: "enqueued",
|
61
|
+
execution_concurrency_key: nil,
|
62
|
+
enqueue_concurrency_key: nil,
|
63
|
+
executing_on: "unit test",
|
64
|
+
position_in_bulk: 0
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|