resque_unit 0.3.3 → 0.3.4

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.
data/README.md CHANGED
@@ -27,7 +27,7 @@ example, if you have code that queues a resque job:
27
27
  @queue = :low
28
28
 
29
29
  def self.perform(x)
30
- // do stuff
30
+ # do stuff
31
31
  end
32
32
  end
33
33
 
@@ -65,11 +65,48 @@ You can also access the queues directly:
65
65
  assert_equal 1, Resque.queue(:low).length
66
66
  end
67
67
 
68
+ Finally, you can enable hooks:
69
+
70
+ Resque.enable_hooks!
71
+
72
+ class MyJobWithHooks
73
+ @queue = :hooked
74
+
75
+ def self.perform(x)
76
+ # do stuff
77
+ end
78
+
79
+ def self.after_enqueue_mark(*args)
80
+ # called when the job is enqueued
81
+ end
82
+
83
+ def self.before_perform_mark(*args)
84
+ # called just before the +perform+ method
85
+ end
86
+
87
+ def self.after_perform_mark(*args)
88
+ # called just after the +perform+ method
89
+ end
90
+
91
+ def self.failure_perform_mark(*args)
92
+ # called if the +perform+ method raised
93
+ end
94
+
95
+ end
96
+
97
+ def queue_job
98
+ Resque.enqueue(MyJobWithHooks, 1)
99
+ end
100
+
68
101
  Caveats
69
102
  =======
70
103
 
71
104
  * You should make sure that you call `Resque.reset!` in your test's
72
105
  setup method to clear all of the test queues.
106
+ * Hooks support is optional. Just because you probably don't want to call
107
+ them during unit tests if they play with a DB. Call `Resque.enable_hooks!`
108
+ in your tests's setup method to enable hooks. To disable hooks, call
109
+ `Resque.disable_hooks!`.
73
110
 
74
111
  Resque-Scheduler Support
75
112
  ========================
@@ -12,6 +12,7 @@ require 'resque_unit/helpers'
12
12
  require 'resque_unit/resque'
13
13
  require 'resque_unit/errors'
14
14
  require 'resque_unit/assertions'
15
+ require 'resque_unit/plugin'
15
16
 
16
17
  Test::Unit::TestCase.send(:include, ResqueUnit::Assertions)
17
18
 
@@ -0,0 +1,65 @@
1
+ # A copy of the original Resque:Plugin class from the "resque" gem
2
+ # No need to redefine this class if the resque gem was already loaded
3
+ unless defined?(Resque::Plugin)
4
+
5
+ module Resque
6
+ module Plugin
7
+ extend self
8
+
9
+ LintError = Class.new(RuntimeError)
10
+
11
+ # Ensure that your plugin conforms to good hook naming conventions.
12
+ #
13
+ # Resque::Plugin.lint(MyResquePlugin)
14
+ def lint(plugin)
15
+ hooks = before_hooks(plugin) + around_hooks(plugin) + after_hooks(plugin)
16
+
17
+ hooks.each do |hook|
18
+ if hook =~ /perform$/
19
+ raise LintError, "#{plugin}.#{hook} is not namespaced"
20
+ end
21
+ end
22
+
23
+ failure_hooks(plugin).each do |hook|
24
+ if hook =~ /failure$/
25
+ raise LintError, "#{plugin}.#{hook} is not namespaced"
26
+ end
27
+ end
28
+ end
29
+
30
+ # Given an object, returns a list `before_perform` hook names.
31
+ def before_hooks(job)
32
+ job.methods.grep(/^before_perform/).sort
33
+ end
34
+
35
+ # Given an object, returns a list `around_perform` hook names.
36
+ def around_hooks(job)
37
+ job.methods.grep(/^around_perform/).sort
38
+ end
39
+
40
+ # Given an object, returns a list `after_perform` hook names.
41
+ def after_hooks(job)
42
+ job.methods.grep(/^after_perform/).sort
43
+ end
44
+
45
+ # Given an object, returns a list `on_failure` hook names.
46
+ def failure_hooks(job)
47
+ job.methods.grep(/^on_failure/).sort
48
+ end
49
+
50
+ # Given an object, returns a list `after_enqueue` hook names.
51
+ def after_enqueue_hooks(job)
52
+ job.methods.grep(/^after_enqueue/).sort
53
+ end
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ unless defined?(Resque::Job::DontPerform)
60
+ module Resque
61
+ class Job
62
+ DontPerform = Class.new(StandardError)
63
+ end
64
+ end
65
+ end
@@ -31,6 +31,15 @@ module Resque
31
31
  queues[queue_name]
32
32
  end
33
33
 
34
+ # Yes, all Resque hooks!
35
+ def enable_hooks!
36
+ @hooks_enabled = true
37
+ end
38
+
39
+ def disable_hooks!
40
+ @hooks_enabled = nil
41
+ end
42
+
34
43
  # Executes all jobs in all queues in an undefined order.
35
44
  def run!
36
45
  old_queue = @queue.dup
@@ -38,7 +47,7 @@ module Resque
38
47
 
39
48
  old_queue.each do |k, v|
40
49
  while job = v.shift
41
- job[:klass].perform(*job[:args])
50
+ @hooks_enabled ? perform_with_hooks(job) : perform_without_hooks(job)
42
51
  end
43
52
  end
44
53
  end
@@ -49,7 +58,7 @@ module Resque
49
58
  self.reset!(queue_name)
50
59
 
51
60
  while job = jobs.shift
52
- job[:klass].perform(*job[:args])
61
+ @hooks_enabled ? perform_with_hooks(job) : perform_without_hooks(job)
53
62
  end
54
63
  end
55
64
 
@@ -74,7 +83,7 @@ module Resque
74
83
  queue_name = queue_for(klass)
75
84
  # Behaves like Resque, raise if no queue was specifed
76
85
  raise NoQueueError.new("Jobs must be placed onto a queue.") unless queue_name
77
- queue(queue_name) << {:klass => klass, :args => normalized_args(args) }
86
+ enqueue_unit(queue_name, {:klass => klass, :args => normalized_args(args) })
78
87
  end
79
88
 
80
89
  def normalized_args(args)
@@ -93,4 +102,77 @@ module Resque
93
102
  end
94
103
  end
95
104
 
105
+ def enqueue_unit(queue_name, hash)
106
+ queue(queue_name) << hash
107
+ if @hooks_enabled
108
+ Plugin.after_enqueue_hooks(hash[:klass]).each do |hook|
109
+ hash[:klass].send(hook, *hash[:args])
110
+ end
111
+ end
112
+ end
113
+
114
+ # Call perform on the job class
115
+ def perform_without_hooks(job)
116
+ job[:klass].perform(*job[:args])
117
+ end
118
+
119
+ # Call perform on the job class, and adds support for Resque hooks.
120
+ def perform_with_hooks(job)
121
+ before_hooks = Resque::Plugin.before_hooks(job[:klass])
122
+ around_hooks = Resque::Plugin.around_hooks(job[:klass])
123
+ after_hooks = Resque::Plugin.after_hooks(job[:klass])
124
+ failure_hooks = Resque::Plugin.failure_hooks(job[:klass])
125
+
126
+ begin
127
+ # Execute before_perform hook. Abort the job gracefully if
128
+ # Resque::DontPerform is raised.
129
+ begin
130
+ before_hooks.each do |hook|
131
+ job[:klass].send(hook, *job[:args])
132
+ end
133
+ rescue Resque::Job::DontPerform
134
+ return false
135
+ end
136
+
137
+ # Execute the job. Do it in an around_perform hook if available.
138
+ if around_hooks.empty?
139
+ perform_without_hooks(job)
140
+ job_was_performed = true
141
+ else
142
+ # We want to nest all around_perform plugins, with the last one
143
+ # finally calling perform
144
+ stack = around_hooks.reverse.inject(nil) do |last_hook, hook|
145
+ if last_hook
146
+ lambda do
147
+ job[:klass].send(hook, *job[:args]) { last_hook.call }
148
+ end
149
+ else
150
+ lambda do
151
+ job[:klass].send(hook, *job[:args]) do
152
+ result = perform_without_hooks(job)
153
+ job_was_performed = true
154
+ result
155
+ end
156
+ end
157
+ end
158
+ end
159
+ stack.call
160
+ end
161
+
162
+ # Execute after_perform hook
163
+ after_hooks.each do |hook|
164
+ job[:klass].send(hook, *job[:args])
165
+ end
166
+
167
+ # Return true if the job was performed
168
+ return job_was_performed
169
+
170
+ # If an exception occurs during the job execution, look for an
171
+ # on_failure hook then re-raise.
172
+ rescue => e
173
+ failure_hooks.each { |hook| job[:klass].send(hook, e, *job[:args]) }
174
+ raise e
175
+ end
176
+ end
177
+
96
178
  end
@@ -19,7 +19,7 @@ module ResqueUnit
19
19
  end
20
20
 
21
21
  def enqueue_with_timestamp(timestamp, klass, *args)
22
- queue(queue_for(klass)) << {:klass => klass, :args => decode(encode(args)), :timestamp => timestamp}
22
+ enqueue_unit(queue_for(klass), {:klass => klass, :args => decode(encode(args)), :timestamp => timestamp})
23
23
  end
24
24
 
25
25
  end
@@ -57,6 +57,54 @@ class ResqueUnitTest < Test::Unit::TestCase
57
57
  # assert number of jobs?
58
58
  end
59
59
 
60
+ context "A task that schedules a resque job with hooks" do
61
+ setup do
62
+ Resque.enable_hooks!
63
+ JobWithHooks.clear_markers
64
+ Resque.enqueue(JobWithHooks)
65
+ end
66
+
67
+ teardown do
68
+ Resque.disable_hooks!
69
+ end
70
+
71
+ should "have run the after_enqueue hook" do
72
+ assert_queued(JobWithHooks)
73
+ assert(JobWithHooks.markers[:after_enqueue], 'no after_queue marker set')
74
+ end
75
+
76
+ should "run the before and after hooks during a run" do
77
+ Resque.run!
78
+ assert(JobWithHooks.markers[:before], 'no before marker set')
79
+ assert(JobWithHooks.markers[:around], 'no around marker set')
80
+ assert(JobWithHooks.markers[:after], 'no after marker set')
81
+ assert(!JobWithHooks.markers[:failed], 'failed marker set, and it should not')
82
+ end
83
+
84
+ should "run the before and failed hooks during a run" do
85
+ JobWithHooks.make_it_fail do
86
+ assert_raise(RuntimeError) do
87
+ Resque.run!
88
+ assert(JobWithHooks.markers[:before], 'no before marker set')
89
+ assert(JobWithHooks.markers[:around], 'no around marker set')
90
+ assert(!JobWithHooks.markers[:after], 'after marker set, and it should not')
91
+ assert(JobWithHooks.markers[:failed], 'no failed marker set')
92
+ end
93
+ end
94
+ end
95
+
96
+ should "not call perform if the around hook raised Resque::Job::DontPerform" do
97
+ JobWithHooks.make_it_dont_perform do
98
+ Resque.run!
99
+ assert(JobWithHooks.markers[:before], 'no before marker set')
100
+ assert(JobWithHooks.markers[:around], 'no around marker set')
101
+ assert(!JobWithHooks.markers[:after], 'after marker set, and it should not')
102
+ assert(!JobWithHooks.markers[:failed], 'failed marker set, and it should not')
103
+ end
104
+ end
105
+
106
+ end
107
+
60
108
  context "Block assertions" do
61
109
  should "pass the assert_queued(job) assertion when queued in block" do
62
110
  assert_queues(HighPriorityJob) do
@@ -47,4 +47,60 @@ class JobThatDoesNotSpecifyAQueue
47
47
 
48
48
  def self.perform
49
49
  end
50
+ end
51
+
52
+ class JobWithHooks
53
+ @queue = :with_hooks
54
+ @markers = {}
55
+
56
+ def self.perform
57
+ raise 'FAIL!' if @will_fail
58
+ end
59
+
60
+ def self.markers
61
+ @markers
62
+ end
63
+
64
+ def self.clear_markers
65
+ @markers = {}
66
+ end
67
+
68
+ def self.after_enqueue_mark(*args)
69
+ markers[:after_enqueue] = true
70
+ end
71
+
72
+ def self.before_perform_mark(*args)
73
+ markers[:before] = true
74
+ end
75
+
76
+ def self.around_perform_mark(*args)
77
+ markers[:around] = true
78
+ if @dont_perform
79
+ raise Resque::Job::DontPerform
80
+ else
81
+ yield
82
+ end
83
+ end
84
+
85
+ def self.after_perform_mark(*args)
86
+ markers[:after] = true
87
+ end
88
+
89
+ def self.failure_perform_mark(*args)
90
+ markers[:failure] = true
91
+ end
92
+
93
+ def self.make_it_fail(&block)
94
+ @will_fail = true
95
+ yield
96
+ ensure
97
+ @will_fail = false
98
+ end
99
+
100
+ def self.make_it_dont_perform(&block)
101
+ @dont_perform = true
102
+ ensure
103
+ @dont_perform = false
104
+ end
105
+
50
106
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 3
8
- - 3
9
- version: 0.3.3
8
+ - 4
9
+ version: 0.3.4
10
10
  platform: ruby
11
11
  authors:
12
12
  - Justin Weiss
@@ -14,12 +14,11 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-12-17 00:00:00 -08:00
17
+ date: 2011-02-14 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: json
22
- prerelease: false
23
22
  requirement: &id001 !ruby/object:Gem::Requirement
24
23
  none: false
25
24
  requirements:
@@ -31,10 +30,10 @@ dependencies:
31
30
  - 6
32
31
  version: 1.4.6
33
32
  type: :runtime
33
+ prerelease: false
34
34
  version_requirements: *id001
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: bundler
37
- prerelease: false
38
37
  requirement: &id002 !ruby/object:Gem::Requirement
39
38
  none: false
40
39
  requirements:
@@ -44,10 +43,10 @@ dependencies:
44
43
  - 0
45
44
  version: "0"
46
45
  type: :development
46
+ prerelease: false
47
47
  version_requirements: *id002
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: shoulda
50
- prerelease: false
51
50
  requirement: &id003 !ruby/object:Gem::Requirement
52
51
  none: false
53
52
  requirements:
@@ -57,6 +56,7 @@ dependencies:
57
56
  - 0
58
57
  version: "0"
59
58
  type: :development
59
+ prerelease: false
60
60
  version_requirements: *id003
61
61
  description:
62
62
  email: justin@uberweiss.org
@@ -70,6 +70,7 @@ files:
70
70
  - lib/resque_unit/assertions.rb
71
71
  - lib/resque_unit/errors.rb
72
72
  - lib/resque_unit/helpers.rb
73
+ - lib/resque_unit/plugin.rb
73
74
  - lib/resque_unit/resque.rb
74
75
  - lib/resque_unit/scheduler.rb
75
76
  - lib/resque_unit/scheduler_assertions.rb
@@ -94,6 +95,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
95
  requirements:
95
96
  - - ">="
96
97
  - !ruby/object:Gem::Version
98
+ hash: 1471830878985269609
97
99
  segments:
98
100
  - 0
99
101
  version: "0"
@@ -102,6 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
104
  requirements:
103
105
  - - ">="
104
106
  - !ruby/object:Gem::Version
107
+ hash: 1471830878985269609
105
108
  segments:
106
109
  - 0
107
110
  version: "0"