resque-scheduler 4.2.1 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of resque-scheduler might be problematic. Click here for more details.

Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/.github/dependabot.yml +12 -0
  3. data/.github/workflows/rubocop.yml +27 -0
  4. data/.github/workflows/ruby.yml +48 -0
  5. data/AUTHORS.md +7 -0
  6. data/CHANGELOG.md +495 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +8 -0
  9. data/README.md +37 -17
  10. data/Rakefile +1 -5
  11. data/{bin → exe}/resque-scheduler +0 -0
  12. data/lib/resque/scheduler/cli.rb +1 -0
  13. data/lib/resque/scheduler/delaying_extensions.rb +38 -6
  14. data/lib/resque/scheduler/env.rb +8 -4
  15. data/lib/resque/scheduler/lock/resilient.rb +19 -12
  16. data/lib/resque/scheduler/locking.rb +2 -2
  17. data/lib/resque/scheduler/scheduling_extensions.rb +4 -3
  18. data/lib/resque/scheduler/server/views/delayed.erb +1 -1
  19. data/lib/resque/scheduler/server/views/search_form.erb +1 -1
  20. data/lib/resque/scheduler/server.rb +1 -1
  21. data/lib/resque/scheduler/version.rb +1 -1
  22. data/lib/resque/scheduler.rb +39 -16
  23. data/resque-scheduler.gemspec +29 -10
  24. metadata +62 -66
  25. data/.gitignore +0 -17
  26. data/.rubocop.yml +0 -18
  27. data/.rubocop_todo.yml +0 -71
  28. data/.simplecov +0 -3
  29. data/.travis.yml +0 -26
  30. data/.vagrant-provision-as-vagrant.sh +0 -15
  31. data/.vagrant-provision.sh +0 -23
  32. data/.vagrant-skel/bash_profile +0 -7
  33. data/.vagrant-skel/bashrc +0 -7
  34. data/HISTORY.md +0 -303
  35. data/Vagrantfile +0 -14
  36. data/examples/Rakefile +0 -2
  37. data/examples/config/initializers/resque-web.rb +0 -37
  38. data/examples/dynamic-scheduling/README.md +0 -28
  39. data/examples/dynamic-scheduling/app/jobs/fix_schedules_job.rb +0 -52
  40. data/examples/dynamic-scheduling/app/jobs/send_email_job.rb +0 -9
  41. data/examples/dynamic-scheduling/app/models/user.rb +0 -16
  42. data/examples/dynamic-scheduling/config/resque.yml +0 -4
  43. data/examples/dynamic-scheduling/config/static_schedule.yml +0 -7
  44. data/examples/dynamic-scheduling/lib/tasks/resque.rake +0 -48
  45. data/examples/run-resque-web +0 -3
  46. data/script/migrate_to_timestamps_set.rb +0 -16
  47. data/tasks/resque_scheduler.rake +0 -2
  48. data/test/cli_test.rb +0 -231
  49. data/test/delayed_queue_test.rb +0 -925
  50. data/test/env_test.rb +0 -47
  51. data/test/multi_process_test.rb +0 -125
  52. data/test/resque-web_test.rb +0 -364
  53. data/test/scheduler_args_test.rb +0 -222
  54. data/test/scheduler_hooks_test.rb +0 -55
  55. data/test/scheduler_locking_test.rb +0 -316
  56. data/test/scheduler_setup_test.rb +0 -141
  57. data/test/scheduler_task_test.rb +0 -72
  58. data/test/scheduler_test.rb +0 -473
  59. data/test/test_helper.rb +0 -147
  60. data/test/util_test.rb +0 -17
@@ -1,222 +0,0 @@
1
- # vim:fileencoding=utf-8
2
-
3
- require_relative 'test_helper'
4
-
5
- context 'scheduling jobs with arguments' do
6
- setup do
7
- Resque::Scheduler.clear_schedule!
8
- Resque::Scheduler.configure do |c|
9
- c.dynamic = false
10
- c.quiet = true
11
- c.poll_sleep_amount = nil
12
- end
13
- end
14
-
15
- test 'enqueue_from_config puts stuff in resque without class loaded' do
16
- Resque::Job.stubs(:create).once.returns(true)
17
- .with('joes_queue', 'UndefinedJob', '/tmp')
18
- Resque::Scheduler.enqueue_from_config(
19
- 'cron' => '* * * * *',
20
- 'class' => 'UndefinedJob',
21
- 'args' => '/tmp',
22
- 'queue' => 'joes_queue'
23
- )
24
- end
25
-
26
- test 'enqueue_from_config with_every_syntax' do
27
- Resque::Job.stubs(:create).once.returns(true)
28
- .with('james_queue', 'JamesJob', '/tmp')
29
- Resque::Scheduler.enqueue_from_config(
30
- 'every' => '1m',
31
- 'class' => 'JamesJob',
32
- 'args' => '/tmp',
33
- 'queue' => 'james_queue'
34
- )
35
- end
36
-
37
- test 'enqueue_from_config puts jobs in the resque queue' do
38
- Resque::Job.stubs(:create).once.returns(true)
39
- .with(:ivar, SomeIvarJob, '/tmp')
40
- Resque::Scheduler.enqueue_from_config(
41
- 'cron' => '* * * * *',
42
- 'class' => 'SomeIvarJob',
43
- 'args' => '/tmp'
44
- )
45
- end
46
-
47
- test 'enqueue_from_config with custom_class_job in resque' do
48
- FakeCustomJobClass.stubs(:scheduled).once.returns(true)
49
- .with(:ivar, 'SomeIvarJob', '/tmp')
50
- Resque::Scheduler.enqueue_from_config(
51
- 'cron' => '* * * * *',
52
- 'class' => 'SomeIvarJob',
53
- 'custom_job_class' => 'FakeCustomJobClass',
54
- 'args' => '/tmp'
55
- )
56
- end
57
-
58
- test 'enqueue_from_config puts stuff in resque when env matches' do
59
- Resque::Scheduler.env = 'production'
60
- assert_equal(0, Resque::Scheduler.rufus_scheduler.jobs.size)
61
-
62
- Resque.schedule = {
63
- 'some_ivar_job' => {
64
- 'cron' => '* * * * *',
65
- 'class' => 'SomeIvarJob',
66
- 'args' => '/tmp',
67
- 'rails_env' => 'production'
68
- }
69
- }
70
-
71
- Resque::Scheduler.load_schedule!
72
- assert_equal(1, Resque::Scheduler.rufus_scheduler.jobs.size)
73
-
74
- Resque.schedule = {
75
- 'some_ivar_job' => {
76
- 'cron' => '* * * * *',
77
- 'class' => 'SomeIvarJob',
78
- 'args' => '/tmp',
79
- 'env' => 'staging, production'
80
- }
81
- }
82
-
83
- Resque::Scheduler.load_schedule!
84
- assert_equal(2, Resque::Scheduler.rufus_scheduler.jobs.size)
85
- end
86
-
87
- test 'enqueue_from_config does not enqueue when env does not match' do
88
- Resque::Scheduler.env = nil
89
- assert_equal(0, Resque::Scheduler.rufus_scheduler.jobs.size)
90
- Resque.schedule = {
91
- 'some_ivar_job' => {
92
- 'cron' => '* * * * *',
93
- 'class' => 'SomeIvarJob',
94
- 'args' => '/tmp',
95
- 'rails_env' => 'staging'
96
- }
97
- }
98
-
99
- Resque::Scheduler.load_schedule!
100
- assert_equal(0, Resque::Scheduler.rufus_scheduler.jobs.size)
101
-
102
- Resque::Scheduler.env = 'production'
103
- Resque.schedule = {
104
- 'some_ivar_job' => {
105
- 'cron' => '* * * * *',
106
- 'class' => 'SomeIvarJob',
107
- 'args' => '/tmp',
108
- 'env' => 'staging'
109
- }
110
- }
111
- Resque::Scheduler.load_schedule!
112
- assert_equal(0, Resque::Scheduler.rufus_scheduler.jobs.size)
113
- end
114
-
115
- test 'enqueue_from_config when env env arg is not set' do
116
- Resque::Scheduler.env = 'production'
117
- assert_equal(0, Resque::Scheduler.rufus_scheduler.jobs.size)
118
-
119
- Resque.schedule = {
120
- 'some_ivar_job' => {
121
- 'cron' => '* * * * *',
122
- 'class' => 'SomeIvarJob',
123
- 'args' => '/tmp'
124
- }
125
- }
126
- Resque::Scheduler.load_schedule!
127
- assert_equal(1, Resque::Scheduler.rufus_scheduler.jobs.size)
128
- end
129
-
130
- test "calls the worker without arguments when 'args' is missing " \
131
- 'from the config' do
132
- Resque::Scheduler.enqueue_from_config(YAML.load(<<-YAML))
133
- class: SomeIvarJob
134
- YAML
135
- SomeIvarJob.expects(:perform).once.with
136
- Resque.reserve('ivar').perform
137
- end
138
-
139
- test "calls the worker without arguments when 'args' is blank " \
140
- 'in the config' do
141
- Resque::Scheduler.enqueue_from_config(YAML.load(<<-YAML))
142
- class: SomeIvarJob
143
- args:
144
- YAML
145
- SomeIvarJob.expects(:perform).once.with
146
- Resque.reserve('ivar').perform
147
- end
148
-
149
- test 'calls the worker with a string when the config lists a string' do
150
- Resque::Scheduler.enqueue_from_config(YAML.load(<<-YAML))
151
- class: SomeIvarJob
152
- args: string
153
- YAML
154
- SomeIvarJob.expects(:perform).once.with('string')
155
- Resque.reserve('ivar').perform
156
- end
157
-
158
- test 'calls the worker with a Fixnum when the config lists an integer' do
159
- Resque::Scheduler.enqueue_from_config(YAML.load(<<-YAML))
160
- class: SomeIvarJob
161
- args: 1
162
- YAML
163
- SomeIvarJob.expects(:perform).once.with(1)
164
- Resque.reserve('ivar').perform
165
- end
166
-
167
- test 'calls the worker with multiple arguments when the config ' \
168
- 'lists an array' do
169
- Resque::Scheduler.enqueue_from_config(YAML.load(<<-YAML))
170
- class: SomeIvarJob
171
- args:
172
- - 1
173
- - 2
174
- YAML
175
- SomeIvarJob.expects(:perform).once.with(1, 2)
176
- Resque.reserve('ivar').perform
177
- end
178
-
179
- test 'calls the worker with an array when the config lists ' \
180
- 'a nested array' do
181
- Resque::Scheduler.enqueue_from_config(YAML.load(<<-YAML))
182
- class: SomeIvarJob
183
- args:
184
- - - 1
185
- - 2
186
- YAML
187
- SomeIvarJob.expects(:perform).once.with([1, 2])
188
- Resque.reserve('ivar').perform
189
- end
190
-
191
- test 'calls the worker with a hash when the config lists a hash' do
192
- Resque::Scheduler.enqueue_from_config(YAML.load(<<-YAML))
193
- class: SomeIvarJob
194
- args:
195
- key: value
196
- YAML
197
- SomeIvarJob.expects(:perform).once.with('key' => 'value')
198
- Resque.reserve('ivar').perform
199
- end
200
-
201
- test 'calls the worker with a nested hash when the config lists ' \
202
- 'a nested hash' do
203
- Resque::Scheduler.enqueue_from_config(YAML.load(<<-YAML))
204
- class: SomeIvarJob
205
- args:
206
- first_key:
207
- second_key: value
208
- YAML
209
- SomeIvarJob.expects(:perform).once
210
- .with('first_key' => { 'second_key' => 'value' })
211
- Resque.reserve('ivar').perform
212
- end
213
-
214
- test 'poll_sleep_amount defaults to 5' do
215
- assert_equal 5, Resque::Scheduler.poll_sleep_amount
216
- end
217
-
218
- test 'poll_sleep_amount is settable' do
219
- Resque::Scheduler.poll_sleep_amount = 1
220
- assert_equal 1, Resque::Scheduler.poll_sleep_amount
221
- end
222
- end
@@ -1,55 +0,0 @@
1
- # vim:fileencoding=utf-8
2
- require_relative 'test_helper'
3
-
4
- context 'scheduling jobs with hooks' do
5
- setup { Resque.redis.flushall }
6
-
7
- test 'before_schedule hook that does not return false should be enqueued' do
8
- enqueue_time = Time.now
9
- SomeRealClass.expects(:before_schedule_example).with(:foo)
10
- SomeRealClass.expects(:after_schedule_example).with(:foo)
11
- Resque.enqueue_at(enqueue_time.to_i, SomeRealClass, :foo)
12
- assert_equal(1, Resque.delayed_timestamp_size(enqueue_time.to_i),
13
- 'job should be enqueued')
14
- end
15
-
16
- test 'before_schedule hook that returns false should not be enqueued' do
17
- enqueue_time = Time.now
18
- SomeRealClass.expects(:before_schedule_example).with(:foo).returns(false)
19
- SomeRealClass.expects(:after_schedule_example).never
20
- Resque.enqueue_at(enqueue_time.to_i, SomeRealClass, :foo)
21
- assert_equal(0, Resque.delayed_timestamp_size(enqueue_time.to_i),
22
- 'job should not be enqueued')
23
- end
24
-
25
- test 'default failure hooks are called when enqueueing a job fails' do
26
- config = {
27
- 'cron' => '* * * * *',
28
- 'class' => 'SomeRealClass',
29
- 'args' => '/tmp'
30
- }
31
-
32
- e = RuntimeError.new('custom error')
33
- Resque::Scheduler.expects(:enqueue_from_config).raises(e)
34
-
35
- Resque::Scheduler::FailureHandler.expects(:on_enqueue_failure).with(config, e)
36
- Resque::Scheduler.enqueue(config)
37
- end
38
-
39
- test 'failure hooks are called when enqueueing a job fails' do
40
- with_failure_handler(ExceptionHandlerClass) do
41
- config = {
42
- 'cron' => '* * * * *',
43
- 'class' => 'SomeRealClass',
44
- 'args' => '/tmp'
45
- }
46
-
47
- e = RuntimeError.new('custom error')
48
- Resque::Scheduler.expects(:enqueue_from_config).raises(e)
49
-
50
- ExceptionHandlerClass.expects(:on_enqueue_failure).with(config, e)
51
-
52
- Resque::Scheduler.enqueue(config)
53
- end
54
- end
55
- end
@@ -1,316 +0,0 @@
1
- # vim:fileencoding=utf-8
2
- require_relative 'test_helper'
3
-
4
- module LockTestHelper
5
- def lock_is_not_held(lock)
6
- Resque.redis.set(lock.key, 'anothermachine:1234')
7
- end
8
- end
9
-
10
- context '#master_lock_key' do
11
- setup do
12
- @subject = Class.new { extend Resque::Scheduler::Locking }
13
- end
14
-
15
- teardown do
16
- Resque.redis.del(@subject.master_lock.key)
17
- end
18
-
19
- test 'should have resque prefix' do
20
- assert_equal(
21
- @subject.master_lock.key, 'resque:resque_scheduler_master_lock'
22
- )
23
- end
24
-
25
- context 'with a prefix set via ENV' do
26
- setup do
27
- ENV['RESQUE_SCHEDULER_MASTER_LOCK_PREFIX'] = 'my.prefix'
28
- @subject = Class.new { extend Resque::Scheduler::Locking }
29
- end
30
-
31
- teardown do
32
- Resque.redis.del(@subject.master_lock.key)
33
- end
34
-
35
- test 'should have ENV prefix' do
36
- assert_equal(
37
- @subject.master_lock.key,
38
- 'resque:my.prefix:resque_scheduler_master_lock'
39
- )
40
- end
41
- end
42
-
43
- context 'with a namespace set for resque' do
44
- setup do
45
- Resque.redis.namespace = 'my.namespace'
46
- @subject = Class.new { extend Resque::Scheduler::Locking }
47
- end
48
-
49
- teardown do
50
- Resque.redis.namespace = 'resque'
51
- Resque.redis.del(@subject.master_lock.key)
52
- end
53
-
54
- test 'should have resque prefix' do
55
- assert_equal(
56
- @subject.master_lock.key, 'my.namespace:resque_scheduler_master_lock'
57
- )
58
- end
59
-
60
- context 'with a prefix set via ENV' do
61
- setup do
62
- Resque.redis.namespace = 'my.namespace'
63
- ENV['RESQUE_SCHEDULER_MASTER_LOCK_PREFIX'] = 'my.prefix'
64
- @subject = Class.new { extend Resque::Scheduler::Locking }
65
- end
66
-
67
- teardown do
68
- Resque.redis.namespace = 'resque'
69
- Resque.redis.del(@subject.master_lock.key)
70
- end
71
-
72
- test 'should have ENV prefix' do
73
- assert_equal(
74
- @subject.master_lock.key,
75
- 'my.namespace:my.prefix:resque_scheduler_master_lock'
76
- )
77
- end
78
- end
79
- end
80
- end
81
-
82
- context 'Resque::Scheduler::Locking' do
83
- setup do
84
- @subject = Class.new { extend Resque::Scheduler::Locking }
85
- end
86
-
87
- teardown do
88
- Resque.redis.del(@subject.master_lock.key)
89
- end
90
-
91
- test 'should use the basic lock mechanism for <= Redis 2.4' do
92
- Resque.redis.stubs(:info).returns('redis_version' => '2.4.16')
93
-
94
- assert_equal @subject.master_lock.class, Resque::Scheduler::Lock::Basic
95
- end
96
-
97
- test 'should use the resilient lock mechanism for > Redis 2.4' do
98
- Resque.redis.stubs(:info).returns('redis_version' => '2.5.12')
99
-
100
- assert_equal(
101
- @subject.master_lock.class, Resque::Scheduler::Lock::Resilient
102
- )
103
- end
104
-
105
- test 'should be the master if the lock is held' do
106
- @subject.master_lock.acquire!
107
- assert @subject.master?, 'should be master'
108
- end
109
-
110
- test 'should not be the master if the lock is held by someone else' do
111
- Resque.redis.set(@subject.master_lock.key, 'somethingelse:1234')
112
- assert !@subject.master?, 'should not be master'
113
- end
114
-
115
- test 'release_master_lock should delegate to master_lock' do
116
- @subject.master_lock.expects(:release)
117
- @subject.release_master_lock
118
- end
119
-
120
- test 'release_master_lock! should delegate to master_lock' do
121
- @subject.expects(:warn)
122
- @subject.master_lock.expects(:release!)
123
- @subject.release_master_lock!
124
- end
125
- end
126
-
127
- context 'Resque::Scheduler::Lock::Base' do
128
- setup do
129
- @lock = Resque::Scheduler::Lock::Base.new('test_lock_key')
130
- end
131
-
132
- test '#acquire! should be not implemented' do
133
- assert_raises NotImplementedError do
134
- @lock.acquire!
135
- end
136
- end
137
-
138
- test '#locked? should be not implemented' do
139
- assert_raises NotImplementedError do
140
- @lock.locked?
141
- end
142
- end
143
- end
144
-
145
- context 'Resque::Scheduler::Lock::Basic' do
146
- include LockTestHelper
147
-
148
- setup do
149
- @lock = Resque::Scheduler::Lock::Basic.new('test_lock_key')
150
- end
151
-
152
- teardown do
153
- @lock.release!
154
- end
155
-
156
- test 'you should not have the lock if someone else holds it' do
157
- lock_is_not_held(@lock)
158
-
159
- assert !@lock.locked?
160
- end
161
-
162
- test 'you should not be able to acquire the lock if someone ' \
163
- 'else holds it' do
164
- lock_is_not_held(@lock)
165
-
166
- assert !@lock.acquire!
167
- end
168
-
169
- test 'the lock should receive a TTL on acquiring' do
170
- @lock.acquire!
171
-
172
- assert Resque.redis.ttl(@lock.key) > 0, 'lock should expire'
173
- end
174
-
175
- test 'releasing should release the master lock' do
176
- assert @lock.acquire!, 'should have acquired the master lock'
177
- assert @lock.locked?, 'should be locked'
178
-
179
- @lock.release!
180
-
181
- assert !@lock.locked?, 'should not be locked'
182
- end
183
-
184
- test 'checking the lock should increase the TTL if we hold it' do
185
- @lock.acquire!
186
- Resque.redis.setex(@lock.key, 10, @lock.value)
187
-
188
- @lock.locked?
189
-
190
- assert Resque.redis.ttl(@lock.key) > 10, 'TTL should have been updated'
191
- end
192
-
193
- test 'checking the lock should not increase the TTL if we do not hold it' do
194
- Resque.redis.setex(@lock.key, 10, @lock.value)
195
- lock_is_not_held(@lock)
196
-
197
- @lock.locked?
198
-
199
- assert Resque.redis.ttl(@lock.key) <= 10,
200
- 'TTL should not have been updated'
201
- end
202
- end
203
-
204
- context 'Resque::Scheduler::Lock::Resilient' do
205
- include LockTestHelper
206
-
207
- if !Resque::Scheduler.supports_lua?
208
- puts '*** Skipping Resque::Scheduler::Lock::Resilient ' \
209
- 'tests, as they require Redis >= 2.5.'
210
- else
211
- setup do
212
- @lock = Resque::Scheduler::Lock::Resilient.new('test_resilient_lock')
213
- end
214
-
215
- teardown do
216
- @lock.release!
217
- end
218
-
219
- test 'you should not have the lock if someone else holds it' do
220
- lock_is_not_held(@lock)
221
-
222
- assert !@lock.locked?, 'you should not have the lock'
223
- end
224
-
225
- test 'you should not be able to acquire the lock if someone ' \
226
- 'else holds it' do
227
- lock_is_not_held(@lock)
228
-
229
- assert !@lock.acquire!
230
- end
231
-
232
- test 'the lock should receive a TTL on acquiring' do
233
- @lock.acquire!
234
-
235
- assert Resque.redis.ttl(@lock.key) > 0, 'lock should expire'
236
- end
237
-
238
- test 'releasing should release the master lock' do
239
- assert @lock.acquire!, 'should have acquired the master lock'
240
- assert @lock.locked?, 'should be locked'
241
-
242
- @lock.release!
243
-
244
- assert !@lock.locked?, 'should not be locked'
245
- end
246
-
247
- test 'checking the lock should increase the TTL if we hold it' do
248
- @lock.acquire!
249
- Resque.redis.setex(@lock.key, 10, @lock.value)
250
-
251
- @lock.locked?
252
-
253
- assert Resque.redis.ttl(@lock.key) > 10, 'TTL should have been updated'
254
- end
255
-
256
- test 'checking the lock should not increase the TTL if we do ' \
257
- 'not hold it' do
258
- Resque.redis.setex(@lock.key, 10, @lock.value)
259
- lock_is_not_held(@lock)
260
-
261
- @lock.locked?
262
-
263
- assert Resque.redis.ttl(@lock.key) <= 10,
264
- 'TTL should not have been updated'
265
- end
266
-
267
- test 'setting the lock timeout changes the key TTL if we hold it' do
268
- @lock.acquire!
269
-
270
- @lock.stubs(:locked?).returns(true)
271
- @lock.timeout = 120
272
- ttl = Resque.redis.ttl(@lock.key)
273
- assert_send [ttl, :>, 100]
274
-
275
- @lock.stubs(:locked?).returns(true)
276
- @lock.timeout = 180
277
- ttl = Resque.redis.ttl(@lock.key)
278
- assert_send [ttl, :>, 120]
279
- end
280
-
281
- test 'setting lock timeout is a noop if not held' do
282
- @lock.acquire!
283
- @lock.timeout = 100
284
- @lock.stubs(:locked?).returns(false)
285
- @lock.timeout = 120
286
- assert_equal 100, @lock.timeout
287
- end
288
-
289
- test 'setting lock timeout nils out lock script' do
290
- @lock.acquire!
291
- @lock.timeout = 100
292
- assert_equal nil, @lock.instance_variable_get(:@locked_sha)
293
- end
294
-
295
- test 'setting lock timeout does not nil out lock script if not held' do
296
- @lock.acquire!
297
- @lock.locked?
298
- @lock.stubs(:locked?).returns(false)
299
- @lock.timeout = 100
300
- assert_not_nil @lock.instance_variable_get(:@locked_sha)
301
- end
302
-
303
- test 'setting lock timeout nils out acquire script' do
304
- @lock.acquire!
305
- @lock.timeout = 100
306
- assert_equal nil, @lock.instance_variable_get(:@acquire_sha)
307
- end
308
-
309
- test 'setting lock timeout does not nil out acquire script if not held' do
310
- @lock.acquire!
311
- @lock.stubs(:locked?).returns(false)
312
- @lock.timeout = 100
313
- assert_not_nil @lock.instance_variable_get(:@acquire_sha)
314
- end
315
- end
316
- end