disc 0.0.27 → 0.0.28

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 176f77e62f7e6d5d3fb12160f104e6f9920829f4
4
- data.tar.gz: 3f081fa1f1835c21d92c6b52d20650d09b697e5a
3
+ metadata.gz: 5afabb650aa27d3b020674c300b2c2f198da7562
4
+ data.tar.gz: 309002ddceb18dbbae790371d6761330044591ef
5
5
  SHA512:
6
- metadata.gz: 285379b94730d7a5e1eec6a5d551dfd42403452f91289a1fbce88c49d7f26457b59d9e32be25eb16c8a137888756f6ed851666ff64fc07532e0494e8deee8ef2
7
- data.tar.gz: ad8a5da1ea7ce655c093796186abd7eb292fd22b2f68e0e7f13c9473643e93c28345893283966196578058fa121bbce70a8971df7f57d2e20b0cb298eeff41f8
6
+ metadata.gz: 73c4688d1d3363c68f4927bfc9096efc1ac353371d1a4f0a67bcf22c48e5bd1f7e9d57f2771d2dbb831340f939531757d160797d0915753a41ef1e11ed9ab2cb
7
+ data.tar.gz: c4fa2f21bb5d56b00c658aa72c3e66f307b5c86a77af9f9e9ef917794fd63faa14a2edb16d60a7bd792a78ecdc8cfc29d450a58dea8040ac9c5debd1aade71be
data/README.md CHANGED
@@ -23,7 +23,7 @@ Disc fills the gap between your Ruby service objects and [antirez](http://antire
23
23
  include Disc::Job
24
24
  disc queue: 'urgent'
25
25
 
26
- def perform(type)
26
+ def self.perform(type)
27
27
  # perform rather lengthy operations here.
28
28
  end
29
29
  end
@@ -40,7 +40,8 @@ Disc fills the gap between your Ruby service objects and [antirez](http://antire
40
40
 
41
41
  ```ruby
42
42
  # disc_init.rb
43
- require 'disc/worker'
43
+ # Require here anything that your application needs to run,
44
+ # like ORMs and your models, database configuration, etc.
44
45
  Dir['./jobs/**/*.rb'].each { |job| require job }
45
46
  ```
46
47
 
@@ -79,7 +80,7 @@ Signature documentation follows:
79
80
  ## Parameters:
80
81
  #
81
82
  ## `arguments` - an optional array of arguments with which to execute
82
- # the job's #perform method.
83
+ # the job's `self.perform` method.
83
84
  #
84
85
  # `at` - an optional named parameter specifying a moment in the
85
86
  # future in which to run the job, must respond to
@@ -117,7 +118,7 @@ Signature documentation follows:
117
118
  ### CAVEATS
118
119
  #
119
120
  ## For convenience, any object can be passed as the `arguments` parameter,
120
- # `Array.wrap` will be used internally to preserve the array structure.
121
+ # `Array()` will be used internally to preserve the array structure.
121
122
  #
122
123
  ## The `arguments` parameter is serialized for storage using `Disc.serialize`
123
124
  # and Disc workers picking it up use `Disc.deserialize` on it, both methods
@@ -127,7 +128,7 @@ Signature documentation follows:
127
128
 
128
129
  You can see [Disque's ADDJOB documentation](https://github.com/antirez/disque#addjob-queue_name-job-ms-timeout-replicate-count-delay-sec-retry-sec-ttl-sec-maxlen-count-async) for more details
129
130
 
130
- When a Disc worker process is assigned a job, it will create a new intance of the job's class and execute the `#perform` method with whatever arguments were previously passed to `#enqueue`.
131
+ When a Disc worker process is assigned a job, it will create a new intance of the job's class and execute the `self.perform` method with whatever arguments were previously passed to `#enqueue`.
131
132
 
132
133
  Example:
133
134
 
@@ -136,7 +137,7 @@ class ComplexJob
136
137
  include Disc::Job
137
138
  disc queue: 'urgent'
138
139
 
139
- def perform(first_parameter, second_parameter)
140
+ def self.perform(first_parameter, second_parameter)
140
141
  # do things...
141
142
  end
142
143
  end
@@ -145,6 +146,18 @@ end
145
146
  ComplexJob.enqueue(['first argument', { second: 'argument' }])
146
147
  ```
147
148
 
149
+ ### Job Serialization
150
+
151
+ Job information (their arguments, and class) need to be serialized in order to be stored
152
+ in Disque, to this end Disc uses the `Disc.serialize` and `Disc.deserialize` methods.
153
+
154
+ By default, these methods use by default the Ruby standard library json implementation in order to serialize and deserialize job data, this has a few implications:
155
+
156
+ 1. Arguments passed to a job's `#enqueue` method need to be serializable by `Disc.serialize` and parsed back by `Disc.deserialize`, so by default you can't pass complex Ruby objects like a `user` model, instead, pass `user.id`, and use that from your job code.
157
+
158
+ 2. You can override `Disc.serialize` and `Disc.deserialize` to use a different JSON implementation, or MessagePack, or whatever else you wish.
159
+
160
+
148
161
  ## Settings
149
162
 
150
163
  Disc takes its configuration from environment variables.
@@ -173,7 +186,7 @@ end
173
186
  Dir["./jobs/**/*.rb"].each { |job| require job }
174
187
  ```
175
188
 
176
- ## Job Definition
189
+ ### Job Definition
177
190
 
178
191
  The error handler function gets the data of the current job as a Hash, that has the following schema.
179
192
 
@@ -182,7 +195,60 @@ The error handler function gets the data of the current job as a Hash, that has
182
195
  | `'class'` | (String) The Job class. |
183
196
  | `'arguments'` | (Array) The arguments passed to perform. |
184
197
  | `'queue'` | (String) The queue from which this job was picked up. |
185
- | `'id'` | (String) Disque's job ID. |
198
+ | `'disque_id'` | (String) Disque's job ID. |
199
+
200
+
201
+ ## Testing modes
202
+
203
+ Disc includes a testing mode, so you can run your test suite without a need to run a Disque server.
204
+
205
+ ### Enqueue mode
206
+
207
+ By default, Disc places your jobs in an in-memory hash, with each queue being a key in the hash and values being an array.
208
+
209
+ ```ruby
210
+ require 'disc'
211
+ require 'disc/testing'
212
+
213
+ require_relative 'examples/returner'
214
+ Disc.enqueue! #=> This is the default mode for disc/testing so you don't need to specify it,
215
+ # you can use this method to go back to the enqueue mode if you switch it.
216
+
217
+
218
+ Returner.enqueue('test argument')
219
+
220
+ Disc.queues
221
+ #=> {"default"=>[{:arguments=>["test argument"], :class=>"Returner", :options=>{}}]}
222
+
223
+ Returner.enqueue('another test')
224
+ #=> => {"default"=>[{:arguments=>["test argument"], :class=>"Returner", :options=>{}}, {:arguments=>["another test"], :class=>"Returner", :options=>{}}]}
225
+
226
+
227
+ ```
228
+
229
+ You can still flush the queues just as you would running on regular mode.
230
+
231
+ ```ruby
232
+ Disc.flush
233
+
234
+ Disc.queues
235
+ #=> {}
236
+ ```
237
+
238
+ ### Inline mode
239
+
240
+ You also have the option for Disc to execute jobs immediately when `#enqueue` is called.
241
+
242
+ ```ruby
243
+ require 'disc'
244
+ require 'disc/testing'
245
+
246
+ require_relative 'examples/returner'
247
+ Disc.inline!
248
+
249
+ Returner.enqueue('test argument')
250
+ #=> 'test argument'
251
+ ```
186
252
 
187
253
  ## [Optional] Celluloid integration
188
254
 
@@ -220,7 +286,7 @@ end
220
286
  class CluJob < ActiveJob::Base
221
287
  queue_as :urgent
222
288
 
223
- def perform(*args)
289
+ def self.perform(*args)
224
290
  # Try to take over The Grid here...
225
291
  end
226
292
  end
@@ -238,10 +304,6 @@ Disc is run in the exact same way, for this example it'd be:
238
304
  $ QUEUES=urgent disc -r ./disc_init.rb
239
305
  ```
240
306
 
241
- ## A note on stability
242
-
243
- The version of Disque at the time of this writing is `0.0.1`. It is a wonderful project, I know of people running it in production and I expect it will only get better and better with time, but please do not expect Disc to be more production ready than Disque is.
244
-
245
307
  ## Similar Projects
246
308
 
247
309
  If you want to use Disque but Disc isn't cutting it for you then you should take a look at [Havanna](https://github.com/djanowski/havanna), a project by my friend [@djanowski](https://twitter.com/djanowski).
data/bin/disc CHANGED
@@ -19,6 +19,7 @@ trap(:TERM, &stop)
19
19
 
20
20
  require 'clap'
21
21
  require_relative '../lib/disc'
22
+ require_relative '../lib/disc/worker'
22
23
 
23
24
  Clap.run ARGV,
24
25
  "-r" => lambda { |file| require file }
data/examples/echoer.rb CHANGED
@@ -4,7 +4,7 @@ class Echoer
4
4
  include Disc::Job
5
5
  disc queue: 'test'
6
6
 
7
- def perform(first, second, third)
7
+ def self.perform(first, second, third)
8
8
  puts "First: #{ first }, Second: #{ second }, Third: #{ third }"
9
9
  end
10
10
  end
data/examples/failer.rb CHANGED
@@ -10,7 +10,7 @@ class Failer
10
10
  include Disc::Job
11
11
  disc queue: 'test'
12
12
 
13
- def perform(string)
13
+ def self.perform(string)
14
14
  raise string
15
15
  end
16
16
  end
data/examples/greeter.rb CHANGED
@@ -4,7 +4,7 @@ class Greeter
4
4
  include Disc::Job
5
5
  disc queue: 'test_medium'
6
6
 
7
- def perform(string)
7
+ def self.perform(string)
8
8
  $stdout.puts(string)
9
9
  end
10
10
  end
@@ -0,0 +1,7 @@
1
+ class Returner
2
+ include Disc::Job
3
+
4
+ def self.perform(argument)
5
+ return argument
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ class Disc
2
+ class Error < StandardError; end
3
+
4
+ class UnknownJobClassError < Error; end
5
+ class NonParsableJobError < Error; end
6
+ end
data/lib/disc/job.rb CHANGED
@@ -1,35 +1,10 @@
1
1
  module Disc::Job
2
- attr_accessor :disque_id,
3
- :arguments
4
2
 
5
3
  def self.included(base)
6
4
  base.extend(ClassMethods)
7
5
  end
8
6
 
9
- def info
10
- return nil if disque_id.nil?
11
-
12
- Hash[*self.class.disque.call("SHOW", disque_id)]
13
- end
14
-
15
- def state
16
- info.fetch('state')
17
- end
18
-
19
7
  module ClassMethods
20
- def [](disque_id)
21
- job_data = disque.call("SHOW", disque_id)
22
- return nil if job_data.nil?
23
-
24
- job = self.new
25
- job_data = Hash[*job_data]
26
-
27
- job.disque_id = disque_id
28
- job.arguments = Disc.deserialize(job_data.fetch('body')).fetch('arguments')
29
-
30
- return job
31
- end
32
-
33
8
  def disque
34
9
  defined?(@disque) ? @disque : Disc.disque
35
10
  end
@@ -51,10 +26,6 @@ module Disc::Job
51
26
  @queue || Disc.default_queue
52
27
  end
53
28
 
54
- def perform(arguments)
55
- self.new.perform(*arguments)
56
- end
57
-
58
29
  ## Disc's `#enqueue` is the main user-facing method of a Disc job, it
59
30
  # enqueues a job with a given set of arguments in Disque, so it can be
60
31
  # picked up by a Disc worker process.
@@ -100,7 +71,7 @@ module Disc::Job
100
71
  ### CAVEATS
101
72
  #
102
73
  ## For convenience, any object can be passed as the `arguments` parameter,
103
- # `Array.wrap` will be used internally to preserve the array structure.
74
+ # `Array()` will be used internally to preserve the array structure.
104
75
  #
105
76
  ## The `arguments` parameter is serialized for storage using `Disc.serialize`
106
77
  # and Disc workers picking it up use `Disc.deserialize` on it, both methods
@@ -111,7 +82,7 @@ module Disc::Job
111
82
  opt[:delay] = at.to_time.to_i - DateTime.now.to_time.to_i unless at.nil?
112
83
  end
113
84
 
114
- disque_id = disque.push(
85
+ disque.push(
115
86
  queue || self.queue,
116
87
  Disc.serialize({
117
88
  class: self.name,
@@ -120,8 +91,6 @@ module Disc::Job
120
91
  Disc.disque_timeout,
121
92
  options
122
93
  )
123
-
124
- return self[disque_id]
125
94
  end
126
95
  end
127
96
  end
data/lib/disc/testing.rb CHANGED
@@ -1,9 +1,56 @@
1
- module Disc::Job::ClassMethods
2
- def mocked_queue
3
- @_mocked_queue ||= []
1
+ class Disc
2
+ def self.queues
3
+ @queues ||= {}
4
+ end
5
+
6
+ def self.testing_mode
7
+ @testing_mode ||= 'enqueue'
8
+ end
9
+
10
+ def self.enqueue!
11
+ @testing_mode = 'enqueue'
12
+ end
13
+
14
+ def self.inline!
15
+ @testing_mode = 'inline'
16
+ end
17
+
18
+ def self.flush
19
+ @queues = {}
4
20
  end
5
21
 
22
+ def self.qlen(queue)
23
+ return 0 if Disc.queues[queue].nil?
24
+
25
+ Disc.queues[queue].length
26
+ end
27
+
28
+ def self.enqueue(klass, arguments, at: nil, queue: nil, **options)
29
+ job_attrs = { arguments: arguments, class: klass, options: options }
30
+ if queues[queue].nil?
31
+ queues[queue] = [job_attrs]
32
+ else
33
+ queues[queue] << job_attrs
34
+ end
35
+
36
+ job_attrs
37
+ end
38
+ end
39
+
40
+ module Disc::Job::ClassMethods
6
41
  def enqueue(args = [], at: nil, queue: nil, **options)
7
- mocked_queue << { args: args, at: at, options: options }
42
+ case Disc.testing_mode
43
+ when 'enqueue'
44
+ Disc.enqueue(
45
+ self.name,
46
+ Array(args),
47
+ queue: queue || self.queue,
48
+ at: at,
49
+ **options)
50
+ when 'inline'
51
+ self.perform(*args)
52
+ else
53
+ raise "Unknown Disc testing mode, this shouldn't happen"
54
+ end
8
55
  end
9
56
  end
data/lib/disc/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Disc
2
- VERSION = "0.0.27"
2
+ VERSION = "0.0.28"
3
3
  end
data/lib/disc/worker.rb CHANGED
@@ -52,13 +52,17 @@ class Disc::Worker
52
52
  jobs = disque.fetch(from: queues, timeout: timeout, count: count)
53
53
  Array(jobs).each do |queue, msgid, serialized_job|
54
54
  begin
55
- job = Disc.deserialize(serialized_job)
56
- instance = Object.const_get(job['class']).new
57
- instance.perform(*job['arguments'])
55
+ job_class, arguments = Disc.load_job(serialized_job)
56
+ job_class.perform(*arguments)
58
57
  disque.call('ACKJOB', msgid)
59
- $stdout.puts("Completed #{job['class']} id #{msgid}")
58
+ $stdout.puts("Completed #{ job_class.name } id #{ msgid }")
60
59
  rescue => err
61
- Disc.on_error(err, job.update('id' => msgid, 'queue' => queue))
60
+ Disc.on_error(err, {
61
+ disque_id: msgid,
62
+ queue: queue,
63
+ class: defined?(job_class) ? job_class.name : '',
64
+ arguments: defined?(arguments) ? arguments : []
65
+ })
62
66
  end
63
67
  end
64
68
 
data/lib/disc.rb CHANGED
@@ -16,7 +16,7 @@ class Disc
16
16
  end
17
17
 
18
18
  def self.disque_timeout
19
- @disque_timeout ||= ENV.fetch('DISQUE_TIMEOUT', 100)
19
+ @disque_timeout ||= 100
20
20
  end
21
21
 
22
22
  def self.disque_timeout=(timeout)
@@ -50,8 +50,46 @@ class Disc
50
50
  def self.deserialize(data)
51
51
  JSON.parse(data)
52
52
  end
53
+
54
+ def self.[](disque_id)
55
+ job_data = disque.call("SHOW", disque_id)
56
+ return nil if job_data.nil?
57
+
58
+ job_data = Hash[*job_data]
59
+ job_data['arguments'] = Disc.deserialize(job_data['body'])['arguments']
60
+ job_data['class'] = Disc.deserialize(job_data['body'])['class']
61
+
62
+ job_data
63
+ end
64
+
65
+ ## Receives:
66
+ #
67
+ # A string containing data serialized by `Disc.serialize`
68
+ #
69
+ ## Returns:
70
+ #
71
+ # An array containing:
72
+ #
73
+ # * An instance of the given job class
74
+ # * An array of arguments to pass to the job's `#perorm` class.
75
+ #
76
+ def self.load_job(serialized_job)
77
+ begin
78
+ job_data = Disc.deserialize(serialized_job)
79
+ rescue => err
80
+ raise Disc::NonParsableJobError.new(err)
81
+ end
82
+
83
+ begin
84
+ job_class = Object.const_get(job_data['class'])
85
+ rescue => err
86
+ raise Disc::UnknownJobClassError.new(err)
87
+ end
88
+
89
+ return [job_class, job_data['arguments']]
90
+ end
53
91
  end
54
92
 
93
+ require_relative 'disc/errors'
55
94
  require_relative 'disc/job'
56
95
  require_relative 'disc/version'
57
-
data/test/disc_test.rb CHANGED
@@ -1,33 +1,7 @@
1
1
  require 'cutest'
2
2
  require 'disc'
3
- require 'pty'
4
- require 'timeout'
5
3
 
6
4
  require_relative '../examples/echoer'
7
- # class Echoer
8
- # include Disc::Job
9
- # disc queue: 'test'
10
- #
11
- # def perform(first, second, third)
12
- # puts "First: #{ first }, Second: #{ second }, Third: #{ third }"
13
- # end
14
- # end
15
-
16
- require_relative '../examples/failer'
17
- # def Disc.on_error(exception, job)
18
- # $stdout.puts('<insert error reporting here>')
19
- # $stdout.puts(exception.message)
20
- # $stdout.puts(job)
21
- # end
22
- #
23
- # class Failer
24
- # include Disc::Job
25
- # disc queue: 'test'
26
- #
27
- # def perform(string)
28
- # raise string
29
- # end
30
- # end
31
5
 
32
6
  prepare do
33
7
  Disc.disque_timeout = 1 # 1ms so we don't wait at all.
@@ -35,180 +9,67 @@ prepare do
35
9
  end
36
10
 
37
11
  scope do
38
- # Runs a given command, yielding the stdout (as an IO) and the PID (a String).
39
- # Makes sure the process finishes after the block runs.
40
- def run(command)
41
- out, _, pid = PTY.spawn(command)
42
- yield out, pid
43
- ensure
44
- Process.kill("KILL", pid)
45
- sleep 0.1 # Make sure we give it time to finish.
46
- end
47
-
48
- # Checks whether a process is running.
49
- def is_running?(pid)
50
- Process.getpgid(pid)
51
- true
52
- rescue Errno::ESRCH
53
- false
54
- end
55
-
56
- test 'jobs are enqueued to the correct Disque queue with appropriate parameters and class' do
57
- job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3])
58
-
59
- jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
60
- assert jobs.any?
61
- assert_equal 1, jobs.count
12
+ test 'Disc should be able to communicate with Disque' do
13
+ assert !Disc.disque.nil?
62
14
 
63
- jobs.first.tap do |queue, id, serialized_job|
64
- job = Disc.deserialize(serialized_job)
65
-
66
- assert job.has_key?('class')
67
- assert job.has_key?('arguments')
68
-
69
- assert_equal 'Echoer', job['class']
70
- assert_equal job_instance.disque_id, id
71
-
72
- args = job['arguments']
73
- assert_equal 3, args.size
74
- assert_equal 'one argument', args[0]
75
- assert_equal({ 'random' => 'data' }, args[1])
76
- assert_equal(3, args[2])
77
- end
15
+ assert_equal 'PONG', Disc.disque.call('PING')
78
16
  end
79
17
 
80
- test 'we get easy access to the job via the job id' do
81
- job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3])
82
-
83
- assert job_instance.is_a?(Echoer)
84
- assert !job_instance.disque_id.nil?
85
- assert !job_instance.info.nil?
18
+ test 'we get easy access to the job via the job id with Disc[job_id]' do
19
+ job_id = Echoer.enqueue(['one argument', { random: 'data' }, 3])
86
20
 
87
- job = Echoer[job_instance.disque_id]
21
+ job_data = Disc[job_id]
88
22
 
89
- assert job.is_a?(Echoer)
90
- assert_equal 'queued', job.state
91
- assert_equal 3, job.arguments.count
92
- assert_equal 'one argument', job.arguments.first
23
+ assert_equal 'Echoer', job_data['class']
24
+ assert_equal 'queued', job_data['state']
25
+ assert_equal 3, job_data['arguments'].count
26
+ assert_equal 'one argument', job_data['arguments'].first
93
27
  end
94
28
 
95
- test 'we can query the lenght of a given queue' do
96
- job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3])
29
+ test 'we can query the length of a given queue with Disc.qlen' do
30
+ Echoer.enqueue(['one argument', { random: 'data' }, 3])
97
31
 
98
32
  assert_equal 1, Disc.qlen(Echoer.queue)
99
33
  end
100
34
 
101
- test 'enqueue at timestamp behaves properly' do
102
- job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], at: Time.now + 1)
103
-
104
- jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
105
- assert jobs.empty?
106
-
107
- sleep 0.5
108
- jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
109
- assert jobs.empty?
110
-
111
- sleep 0.5
112
- jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
113
- assert jobs.any?
114
- assert_equal 1, jobs.size
115
-
116
- jobs.first.tap do |queue, id, serialized_job|
117
- assert_equal 'test', queue
118
- assert_equal job_instance.disque_id, id
119
- job = Disc.deserialize(serialized_job)
120
- assert job.has_key?('class')
121
- assert job.has_key?('arguments')
122
- assert_equal 'Echoer', job['class']
123
- assert_equal 3, job['arguments'].size
124
- end
125
- end
126
-
127
- test 'jobs are executed' do
35
+ test 'Disc.flush deletes everything in the queue' do
128
36
  Echoer.enqueue(['one argument', { random: 'data' }, 3])
37
+ Disc.flush
129
38
 
130
- run('QUEUES=test ruby -Ilib bin/disc -r disc/worker -r ./examples/echoer') do |cout, pid|
131
- output = Timeout.timeout(1) { cout.take(3) }
132
- jobs = Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1)
133
- assert jobs.nil?
134
- assert output.grep(/First: one argument, Second: {"random"=>"data"}, Third: 3/).any?
135
- end
39
+ assert_equal 0, Disc.qlen(Echoer.queue)
136
40
  end
137
41
 
138
- test 'Disc.on_error will catch unhandled exceptions and keep disc alive' do
139
- failer = Failer.enqueue('this can only end positively')
42
+ test 'Disc.load_job returns a job instance and arguments' do
43
+ serialized_job = Disc.serialize(
44
+ { class: 'Echoer', arguments: ['one argument', { random: 'data' }, 3] }
45
+ )
140
46
 
141
- run('QUEUES=test ruby -Ilib bin/disc -r disc/worker -r ./examples/failer') do |cout, pid|
142
- output = Timeout.timeout(1) { cout.take(5) }
143
- jobs = Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1)
144
- assert jobs.nil?
47
+ job_class, arguments = Disc.load_job(serialized_job)
145
48
 
146
- assert output.grep(/<insert error reporting here>/).any?
147
- assert output.grep(/this can only end positively/).any?
148
- assert output.grep(/Failer/).any?
149
-
150
- assert is_running?(pid)
151
- end
49
+ assert_equal Echoer, job_class
50
+ assert arguments.is_a?(Array)
51
+ assert_equal 3, arguments.count
52
+ assert_equal 'one argument', arguments.first
152
53
  end
153
54
 
154
- test 'enqueue supports replicate' do
155
- error = Echoer.enqueue(['one argument', { random: 'data' }, 3], replicate: 100) rescue $!
156
-
157
- assert_equal RuntimeError, error.class
158
- assert_equal "NOREPL Not enough reachable nodes for the requested replication level", error.message
159
- end
160
-
161
- test 'enqueue supports delay' do
162
- job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], delay: 2)
163
-
164
- jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
165
- assert jobs.empty?
166
-
167
- sleep 1
168
- jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
169
- assert jobs.empty?
170
-
171
- sleep 2
172
- jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
173
- assert jobs.any?
174
- assert_equal 1, jobs.size
175
- end
176
-
177
- test 'enqueue supports retry' do
178
- job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], retry: 1)
179
-
180
- jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
181
- assert jobs.any?
182
- assert_equal 1, jobs.size
183
-
184
- sleep 1.5
185
- jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
186
- assert jobs.any?
187
- assert_equal 1, jobs.size
188
- end
189
-
190
- test 'enqueue supports ttl' do
191
- job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], ttl: 1)
192
-
193
- sleep 1.5
194
- jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
195
- assert jobs.empty?
196
- end
197
-
198
- test 'enqueue supports maxlen' do
199
- Echoer.enqueue(['one argument', { random: 'data' }, 3], maxlen: 1)
200
- error = Echoer.enqueue(['one argument', { random: 'data' }, 3], maxlen: 1) rescue $!
201
-
202
- assert_equal RuntimeError, error.class
203
- assert_equal "MAXLEN Queue is already longer than the specified MAXLEN count", error.message
204
- end
205
-
206
- test 'enqueue supports async' do
207
- job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], async: true)
55
+ test 'Disc.load_job raises appropriate errors ' do
56
+ begin
57
+ job_instance, arguments = Disc.load_job('gibberish')
58
+ assert_equal 'Should not reach this point', false
59
+ rescue => err
60
+ assert err.is_a?(Disc::Error)
61
+ assert err.is_a?(Disc::NonParsableJobError)
62
+ end
208
63
 
209
- sleep 1 # async is too fast to reliably assert an empty queue, let's wait instead
210
- jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
211
- assert jobs.any?
212
- assert_equal 1, jobs.size
64
+ serialized_job = Disc.serialize(
65
+ { class: 'NonExistantDiscJobClass', arguments: [] }
66
+ )
67
+ begin
68
+ job_instance, arguments = Disc.load_job(serialized_job)
69
+ assert_equal 'Should not reach this point', false
70
+ rescue => err
71
+ assert err.is_a?(Disc::Error)
72
+ assert err.is_a?(Disc::UnknownJobClassError)
73
+ end
213
74
  end
214
75
  end
data/test/job_test.rb ADDED
@@ -0,0 +1,122 @@
1
+ require 'cutest'
2
+ require 'disc'
3
+
4
+ require_relative '../examples/echoer'
5
+
6
+ prepare do
7
+ Disc.disque_timeout = 1 # 1ms so we don't wait at all.
8
+ Disc.flush
9
+ end
10
+
11
+ scope do
12
+ test 'jobs are enqueued to the correct Disque queue with appropriate parameters and class' do
13
+ job_id = Echoer.enqueue(['one argument', { random: 'data' }, 3])
14
+
15
+ jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
16
+ assert jobs.any?
17
+ assert_equal 1, jobs.count
18
+
19
+ jobs.first.tap do |queue, id, serialized_job|
20
+ job = Disc.deserialize(serialized_job)
21
+
22
+ assert job.has_key?('class')
23
+ assert job.has_key?('arguments')
24
+
25
+ assert_equal 'Echoer', job['class']
26
+ assert_equal job_id, id
27
+
28
+ args = job['arguments']
29
+ assert_equal 3, args.size
30
+ assert_equal 'one argument', args[0]
31
+ assert_equal({ 'random' => 'data' }, args[1])
32
+ assert_equal(3, args[2])
33
+ end
34
+ end
35
+
36
+ test 'enqueue at timestamp behaves properly' do
37
+ job_id = Echoer.enqueue(['one argument', { random: 'data' }, 3], at: Time.now + 1)
38
+
39
+ jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
40
+ assert jobs.empty?
41
+
42
+ sleep 0.5
43
+ jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
44
+ assert jobs.empty?
45
+
46
+ sleep 0.5
47
+ jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
48
+ assert jobs.any?
49
+ assert_equal 1, jobs.size
50
+
51
+ jobs.first.tap do |queue, id, serialized_job|
52
+ assert_equal 'test', queue
53
+ assert_equal job_id, id
54
+ job = Disc.deserialize(serialized_job)
55
+ assert job.has_key?('class')
56
+ assert job.has_key?('arguments')
57
+ assert_equal 'Echoer', job['class']
58
+ assert_equal 3, job['arguments'].size
59
+ end
60
+ end
61
+
62
+ test 'enqueue supports replicate' do
63
+ error = Echoer.enqueue(['one argument', { random: 'data' }, 3], replicate: 100) rescue $!
64
+
65
+ assert_equal RuntimeError, error.class
66
+ assert_equal "NOREPL Not enough reachable nodes for the requested replication level", error.message
67
+ end
68
+
69
+ test 'enqueue supports delay' do
70
+ job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], delay: 2)
71
+
72
+ jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
73
+ assert jobs.empty?
74
+
75
+ sleep 1
76
+ jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
77
+ assert jobs.empty?
78
+
79
+ sleep 2
80
+ jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
81
+ assert jobs.any?
82
+ assert_equal 1, jobs.size
83
+ end
84
+
85
+ test 'enqueue supports retry' do
86
+ job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], retry: 1)
87
+
88
+ jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
89
+ assert jobs.any?
90
+ assert_equal 1, jobs.size
91
+
92
+ sleep 1.5
93
+ jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
94
+ assert jobs.any?
95
+ assert_equal 1, jobs.size
96
+ end
97
+
98
+ test 'enqueue supports ttl' do
99
+ job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], ttl: 1)
100
+
101
+ sleep 1.5
102
+ jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
103
+ assert jobs.empty?
104
+ end
105
+
106
+ test 'enqueue supports maxlen' do
107
+ Echoer.enqueue(['one argument', { random: 'data' }, 3], maxlen: 1)
108
+ error = Echoer.enqueue(['one argument', { random: 'data' }, 3], maxlen: 1) rescue $!
109
+
110
+ assert_equal RuntimeError, error.class
111
+ assert_equal "MAXLEN Queue is already longer than the specified MAXLEN count", error.message
112
+ end
113
+
114
+ test 'enqueue supports async' do
115
+ job_instance = Echoer.enqueue(['one argument', { random: 'data' }, 3], async: true)
116
+
117
+ sleep 1 # async is too fast to reliably assert an empty queue, let's wait instead
118
+ jobs = Array(Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1))
119
+ assert jobs.any?
120
+ assert_equal 1, jobs.size
121
+ end
122
+ end
@@ -0,0 +1,59 @@
1
+ require 'cutest'
2
+ require 'disc'
3
+ require 'pty'
4
+ require 'timeout'
5
+
6
+ require_relative '../examples/echoer'
7
+ require_relative '../examples/failer'
8
+
9
+ prepare do
10
+ Disc.disque_timeout = 1 # 1ms so we don't wait at all.
11
+ Disc.flush
12
+ end
13
+
14
+ scope do
15
+ # Runs a given command, yielding the stdout (as an IO) and the PID (a String).
16
+ # Makes sure the process finishes after the block runs.
17
+ def run(command)
18
+ out, _, pid = PTY.spawn(command)
19
+ yield out, pid
20
+ ensure
21
+ Process.kill("KILL", pid)
22
+ sleep 0.1 # Make sure we give it time to finish.
23
+ end
24
+
25
+ # Checks whether a process is running.
26
+ def is_running?(pid)
27
+ Process.getpgid(pid)
28
+ true
29
+ rescue Errno::ESRCH
30
+ false
31
+ end
32
+
33
+ test 'Disc.on_error will catch unhandled exceptions and keep disc alive' do
34
+ failer = Failer.enqueue('this can only end positively')
35
+
36
+ run('QUEUES=test ruby -Ilib bin/disc -r ./examples/failer') do |cout, pid|
37
+ output = Timeout.timeout(1) { cout.take(5) }
38
+ jobs = Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1)
39
+ assert jobs.nil?
40
+
41
+ assert output.grep(/<insert error reporting here>/).any?
42
+ assert output.grep(/this can only end positively/).any?
43
+ assert output.grep(/Failer/).any?
44
+
45
+ assert is_running?(pid)
46
+ end
47
+ end
48
+
49
+ test 'jobs are executed' do
50
+ Echoer.enqueue(['one argument', { random: 'data' }, 3])
51
+
52
+ run('QUEUES=test ruby -Ilib bin/disc -r ./examples/echoer') do |cout, pid|
53
+ output = Timeout.timeout(1) { cout.take(3) }
54
+ jobs = Disc.disque.fetch(from: ['test'], timeout: Disc.disque_timeout, count: 1)
55
+ assert jobs.nil?
56
+ assert output.grep(/First: one argument, Second: {"random"=>"data"}, Third: 3/).any?
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,45 @@
1
+ # Yo dawg I put some testing in your testing so you can test while you test.
2
+
3
+ require 'disc'
4
+ require 'disc/testing'
5
+
6
+ require_relative '../examples/echoer'
7
+ require_relative '../examples/returner'
8
+
9
+ prepare do
10
+ Disc.disque_timeout = 1 # 1ms so we don't wait at all.
11
+ Disc.enqueue!
12
+ Disc.flush
13
+ end
14
+
15
+ scope do
16
+ test "testing mode should not enqueue jobs into Disque" do
17
+ Echoer.enqueue(['one argument', { random: 'data' }, 3])
18
+ assert_equal 0, Disc.disque.call('QLEN', 'test')
19
+ assert_equal 1, Disc.qlen('test')
20
+
21
+ # Flush should still work though
22
+ Disc.flush
23
+ assert_equal 0, Disc.qlen('test')
24
+ end
25
+
26
+ test "testing mode enqueue jobs into an in-memory list by default" do
27
+ Echoer.enqueue(['one argument', { random: 'data' }, 3])
28
+
29
+ assert_equal 1, Disc.queues['test'].count
30
+ assert Disc.queues['test'].first.has_key?(:arguments)
31
+ assert_equal 3, Disc.queues['test'].first[:arguments].count
32
+ assert_equal 'one argument', Disc.queues['test'].first[:arguments].first
33
+ assert_equal 'Echoer', Disc.queues['test'].first[:class]
34
+ end
35
+
36
+ test "testing mode enqueue jobs into an in-memory list by default" do
37
+ Echoer.enqueue(['one argument', { random: 'data' }, 3])
38
+ assert_equal 'one argument', Disc.queues['test'].first[:arguments].first
39
+ end
40
+
41
+ test "ability to run jobs inline" do
42
+ Disc.inline!
43
+ assert_equal 'this is an argument', Returner.enqueue('this is an argument')
44
+ end
45
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: disc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.27
4
+ version: 0.0.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - pote
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-01 00:00:00.000000000 Z
11
+ date: 2016-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: disque
@@ -61,13 +61,18 @@ files:
61
61
  - examples/echoer.rb
62
62
  - examples/failer.rb
63
63
  - examples/greeter.rb
64
+ - examples/returner.rb
64
65
  - lib/active_job/queue_adapters/disc_adapter.rb
65
66
  - lib/disc.rb
67
+ - lib/disc/errors.rb
66
68
  - lib/disc/job.rb
67
69
  - lib/disc/testing.rb
68
70
  - lib/disc/version.rb
69
71
  - lib/disc/worker.rb
70
72
  - test/disc_test.rb
73
+ - test/job_test.rb
74
+ - test/process_test.rb
75
+ - test/testing_test.rb
71
76
  homepage: https://github.com/pote/disc
72
77
  licenses:
73
78
  - MIT