roundhouse-x 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +16 -0
  4. data/3.0-Upgrade.md +70 -0
  5. data/Changes.md +1127 -0
  6. data/Gemfile +27 -0
  7. data/LICENSE +7 -0
  8. data/README.md +52 -0
  9. data/Rakefile +9 -0
  10. data/bin/roundhouse +19 -0
  11. data/bin/roundhousectl +93 -0
  12. data/lib/generators/roundhouse/templates/worker.rb.erb +9 -0
  13. data/lib/generators/roundhouse/templates/worker_spec.rb.erb +6 -0
  14. data/lib/generators/roundhouse/templates/worker_test.rb.erb +8 -0
  15. data/lib/generators/roundhouse/worker_generator.rb +49 -0
  16. data/lib/roundhouse/actor.rb +39 -0
  17. data/lib/roundhouse/api.rb +859 -0
  18. data/lib/roundhouse/cli.rb +396 -0
  19. data/lib/roundhouse/client.rb +210 -0
  20. data/lib/roundhouse/core_ext.rb +105 -0
  21. data/lib/roundhouse/exception_handler.rb +30 -0
  22. data/lib/roundhouse/fetch.rb +154 -0
  23. data/lib/roundhouse/launcher.rb +98 -0
  24. data/lib/roundhouse/logging.rb +104 -0
  25. data/lib/roundhouse/manager.rb +236 -0
  26. data/lib/roundhouse/middleware/chain.rb +149 -0
  27. data/lib/roundhouse/middleware/i18n.rb +41 -0
  28. data/lib/roundhouse/middleware/server/active_record.rb +13 -0
  29. data/lib/roundhouse/middleware/server/logging.rb +40 -0
  30. data/lib/roundhouse/middleware/server/retry_jobs.rb +206 -0
  31. data/lib/roundhouse/monitor.rb +124 -0
  32. data/lib/roundhouse/paginator.rb +42 -0
  33. data/lib/roundhouse/processor.rb +159 -0
  34. data/lib/roundhouse/rails.rb +24 -0
  35. data/lib/roundhouse/redis_connection.rb +77 -0
  36. data/lib/roundhouse/scheduled.rb +115 -0
  37. data/lib/roundhouse/testing/inline.rb +28 -0
  38. data/lib/roundhouse/testing.rb +193 -0
  39. data/lib/roundhouse/util.rb +68 -0
  40. data/lib/roundhouse/version.rb +3 -0
  41. data/lib/roundhouse/web.rb +264 -0
  42. data/lib/roundhouse/web_helpers.rb +249 -0
  43. data/lib/roundhouse/worker.rb +90 -0
  44. data/lib/roundhouse.rb +177 -0
  45. data/roundhouse.gemspec +27 -0
  46. data/test/config.yml +9 -0
  47. data/test/env_based_config.yml +11 -0
  48. data/test/fake_env.rb +0 -0
  49. data/test/fixtures/en.yml +2 -0
  50. data/test/helper.rb +49 -0
  51. data/test/test_api.rb +521 -0
  52. data/test/test_cli.rb +389 -0
  53. data/test/test_client.rb +294 -0
  54. data/test/test_exception_handler.rb +55 -0
  55. data/test/test_fetch.rb +206 -0
  56. data/test/test_logging.rb +34 -0
  57. data/test/test_manager.rb +169 -0
  58. data/test/test_middleware.rb +160 -0
  59. data/test/test_monitor.rb +258 -0
  60. data/test/test_processor.rb +176 -0
  61. data/test/test_rails.rb +23 -0
  62. data/test/test_redis_connection.rb +127 -0
  63. data/test/test_retry.rb +390 -0
  64. data/test/test_roundhouse.rb +87 -0
  65. data/test/test_scheduled.rb +120 -0
  66. data/test/test_scheduling.rb +75 -0
  67. data/test/test_testing.rb +78 -0
  68. data/test/test_testing_fake.rb +240 -0
  69. data/test/test_testing_inline.rb +65 -0
  70. data/test/test_util.rb +18 -0
  71. data/test/test_web.rb +605 -0
  72. data/test/test_web_helpers.rb +52 -0
  73. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  74. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  75. data/web/assets/images/logo.png +0 -0
  76. data/web/assets/images/status/active.png +0 -0
  77. data/web/assets/images/status/idle.png +0 -0
  78. data/web/assets/images/status-sd8051fd480.png +0 -0
  79. data/web/assets/javascripts/application.js +83 -0
  80. data/web/assets/javascripts/dashboard.js +300 -0
  81. data/web/assets/javascripts/locales/README.md +27 -0
  82. data/web/assets/javascripts/locales/jquery.timeago.ar.js +96 -0
  83. data/web/assets/javascripts/locales/jquery.timeago.bg.js +18 -0
  84. data/web/assets/javascripts/locales/jquery.timeago.bs.js +49 -0
  85. data/web/assets/javascripts/locales/jquery.timeago.ca.js +18 -0
  86. data/web/assets/javascripts/locales/jquery.timeago.cs.js +18 -0
  87. data/web/assets/javascripts/locales/jquery.timeago.cy.js +20 -0
  88. data/web/assets/javascripts/locales/jquery.timeago.da.js +18 -0
  89. data/web/assets/javascripts/locales/jquery.timeago.de.js +18 -0
  90. data/web/assets/javascripts/locales/jquery.timeago.el.js +18 -0
  91. data/web/assets/javascripts/locales/jquery.timeago.en-short.js +20 -0
  92. data/web/assets/javascripts/locales/jquery.timeago.en.js +20 -0
  93. data/web/assets/javascripts/locales/jquery.timeago.es.js +18 -0
  94. data/web/assets/javascripts/locales/jquery.timeago.et.js +18 -0
  95. data/web/assets/javascripts/locales/jquery.timeago.fa.js +22 -0
  96. data/web/assets/javascripts/locales/jquery.timeago.fi.js +28 -0
  97. data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +16 -0
  98. data/web/assets/javascripts/locales/jquery.timeago.fr.js +17 -0
  99. data/web/assets/javascripts/locales/jquery.timeago.he.js +18 -0
  100. data/web/assets/javascripts/locales/jquery.timeago.hr.js +49 -0
  101. data/web/assets/javascripts/locales/jquery.timeago.hu.js +18 -0
  102. data/web/assets/javascripts/locales/jquery.timeago.hy.js +18 -0
  103. data/web/assets/javascripts/locales/jquery.timeago.id.js +18 -0
  104. data/web/assets/javascripts/locales/jquery.timeago.it.js +16 -0
  105. data/web/assets/javascripts/locales/jquery.timeago.ja.js +19 -0
  106. data/web/assets/javascripts/locales/jquery.timeago.ko.js +17 -0
  107. data/web/assets/javascripts/locales/jquery.timeago.lt.js +20 -0
  108. data/web/assets/javascripts/locales/jquery.timeago.mk.js +20 -0
  109. data/web/assets/javascripts/locales/jquery.timeago.nl.js +20 -0
  110. data/web/assets/javascripts/locales/jquery.timeago.no.js +18 -0
  111. data/web/assets/javascripts/locales/jquery.timeago.pl.js +31 -0
  112. data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +16 -0
  113. data/web/assets/javascripts/locales/jquery.timeago.pt.js +16 -0
  114. data/web/assets/javascripts/locales/jquery.timeago.ro.js +18 -0
  115. data/web/assets/javascripts/locales/jquery.timeago.rs.js +49 -0
  116. data/web/assets/javascripts/locales/jquery.timeago.ru.js +34 -0
  117. data/web/assets/javascripts/locales/jquery.timeago.sk.js +18 -0
  118. data/web/assets/javascripts/locales/jquery.timeago.sl.js +44 -0
  119. data/web/assets/javascripts/locales/jquery.timeago.sv.js +18 -0
  120. data/web/assets/javascripts/locales/jquery.timeago.th.js +20 -0
  121. data/web/assets/javascripts/locales/jquery.timeago.tr.js +16 -0
  122. data/web/assets/javascripts/locales/jquery.timeago.uk.js +34 -0
  123. data/web/assets/javascripts/locales/jquery.timeago.uz.js +19 -0
  124. data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +20 -0
  125. data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +20 -0
  126. data/web/assets/stylesheets/application.css +746 -0
  127. data/web/assets/stylesheets/bootstrap.css +9 -0
  128. data/web/locales/cs.yml +68 -0
  129. data/web/locales/da.yml +68 -0
  130. data/web/locales/de.yml +69 -0
  131. data/web/locales/el.yml +68 -0
  132. data/web/locales/en.yml +77 -0
  133. data/web/locales/es.yml +69 -0
  134. data/web/locales/fr.yml +69 -0
  135. data/web/locales/hi.yml +75 -0
  136. data/web/locales/it.yml +69 -0
  137. data/web/locales/ja.yml +69 -0
  138. data/web/locales/ko.yml +68 -0
  139. data/web/locales/nl.yml +68 -0
  140. data/web/locales/no.yml +69 -0
  141. data/web/locales/pl.yml +59 -0
  142. data/web/locales/pt-br.yml +68 -0
  143. data/web/locales/pt.yml +67 -0
  144. data/web/locales/ru.yml +75 -0
  145. data/web/locales/sv.yml +68 -0
  146. data/web/locales/ta.yml +75 -0
  147. data/web/locales/zh-cn.yml +68 -0
  148. data/web/locales/zh-tw.yml +68 -0
  149. data/web/views/_footer.erb +22 -0
  150. data/web/views/_job_info.erb +84 -0
  151. data/web/views/_nav.erb +66 -0
  152. data/web/views/_paging.erb +23 -0
  153. data/web/views/_poll_js.erb +5 -0
  154. data/web/views/_poll_link.erb +7 -0
  155. data/web/views/_status.erb +4 -0
  156. data/web/views/_summary.erb +40 -0
  157. data/web/views/busy.erb +90 -0
  158. data/web/views/dashboard.erb +75 -0
  159. data/web/views/dead.erb +34 -0
  160. data/web/views/layout.erb +31 -0
  161. data/web/views/morgue.erb +71 -0
  162. data/web/views/queue.erb +45 -0
  163. data/web/views/queues.erb +27 -0
  164. data/web/views/retries.erb +74 -0
  165. data/web/views/retry.erb +34 -0
  166. data/web/views/scheduled.erb +54 -0
  167. data/web/views/scheduled_job_info.erb +8 -0
  168. metadata +404 -0
@@ -0,0 +1,390 @@
1
+ # encoding: utf-8
2
+ require_relative 'helper'
3
+ require 'roundhouse/scheduled'
4
+ require 'roundhouse/middleware/server/retry_jobs'
5
+
6
+ class TestRetry < Roundhouse::Test
7
+ describe 'middleware' do
8
+ before do
9
+ @redis = Minitest::Mock.new
10
+ # Ugh, this is terrible.
11
+ Roundhouse.instance_variable_set(:@redis, @redis)
12
+
13
+ def @redis.with; yield self; end
14
+ def @redis.multi; yield self; end
15
+ end
16
+
17
+ after do
18
+ Roundhouse.redis = REDIS
19
+ end
20
+
21
+ let(:worker) do
22
+ Class.new do
23
+ include ::Roundhouse::Worker
24
+ end
25
+ end
26
+
27
+ it 'allows disabling retry' do
28
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => false }
29
+ msg2 = msg.dup
30
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
31
+ assert_raises RuntimeError do
32
+ handler.call(worker, msg2, 'default') do
33
+ raise "kerblammo!"
34
+ end
35
+ end
36
+ assert_equal msg, msg2
37
+ end
38
+
39
+ it 'allows a numeric retry' do
40
+ @redis.expect :zadd, 1, ['retry', String, String]
41
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => 2 }
42
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
43
+ assert_raises RuntimeError do
44
+ handler.call(worker, msg, 'default') do
45
+ raise "kerblammo!"
46
+ end
47
+ end
48
+ msg.delete('failed_at')
49
+ assert_equal({"class"=>"Bob", "args"=>[1, 2, "foo"], "retry"=>2, "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "retry_count"=>0}, msg)
50
+ @redis.verify
51
+ end
52
+
53
+ it 'allows 0 retry => no retry and dead queue' do
54
+ @redis.expect :zadd, 1, ['dead', Float, String]
55
+ @redis.expect :zremrangebyscore, 0, ['dead', String, Float]
56
+ @redis.expect :zremrangebyrank, 0, ['dead', Numeric, Numeric]
57
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => 0 }
58
+ msg2 = msg.dup
59
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
60
+ assert_raises RuntimeError do
61
+ handler.call(worker, msg, 'default') do
62
+ raise "kerblammo!"
63
+ end
64
+ end
65
+ msg.delete('failed_at')
66
+ expected = msg2.merge "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "retry_count"=>0
67
+ assert_equal expected, msg
68
+ @redis.verify # not called
69
+ end
70
+
71
+ it 'handles zany characters in error message, #1705' do
72
+ skip 'skipped! test requires ruby 2.1+' if RUBY_VERSION <= '2.1.0'
73
+ @redis.expect :zadd, 1, ['retry', String, String]
74
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => 2 }
75
+ msg2 = msg.dup
76
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
77
+ assert_raises RuntimeError do
78
+ handler.call(worker, msg2, 'default') do
79
+ raise "kerblammo! #{195.chr}"
80
+ end
81
+ end
82
+ msg2.delete('failed_at')
83
+ assert_equal({"class"=>"Bob", "args"=>[1, 2, "foo"], "retry"=>2, "queue"=>"default", "error_message"=>"kerblammo! �", "error_class"=>"RuntimeError", "retry_count"=>0}, msg2)
84
+ @redis.verify
85
+ end
86
+
87
+
88
+ it 'allows a max_retries option in initializer' do
89
+ max_retries = 7
90
+ 1.upto(max_retries) do
91
+ @redis.expect :zadd, 1, ['retry', String, String]
92
+ end
93
+ @redis.expect :zadd, 1, ['dead', Float, String]
94
+ @redis.expect :zremrangebyscore, 0, ['dead', String, Float]
95
+ @redis.expect :zremrangebyrank, 0, ['dead', Numeric, Numeric]
96
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true }
97
+ handler = Roundhouse::Middleware::Server::RetryJobs.new({:max_retries => max_retries})
98
+ 1.upto(max_retries + 1) do
99
+ assert_raises RuntimeError do
100
+ handler.call(worker, msg, 'default') do
101
+ raise "kerblammo!"
102
+ end
103
+ end
104
+ end
105
+ @redis.verify
106
+ end
107
+
108
+ it 'saves backtraces' do
109
+ @redis.expect :zadd, 1, ['retry', String, String]
110
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true, 'backtrace' => true }
111
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
112
+ c = nil
113
+ assert_raises RuntimeError do
114
+ handler.call(worker, msg, 'default') do
115
+ c = caller(0); raise "kerblammo!"
116
+ end
117
+ end
118
+ assert msg["error_backtrace"]
119
+ assert_equal c[0], msg["error_backtrace"][0]
120
+ @redis.verify
121
+ end
122
+
123
+ it 'saves partial backtraces' do
124
+ @redis.expect :zadd, 1, ['retry', String, String]
125
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true, 'backtrace' => 3 }
126
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
127
+ c = nil
128
+ assert_raises RuntimeError do
129
+ handler.call(worker, msg, 'default') do
130
+ c = caller(0)[0...3]; raise "kerblammo!"
131
+ end
132
+ end
133
+ assert msg["error_backtrace"]
134
+ assert_equal c, msg["error_backtrace"]
135
+ assert_equal 3, c.size
136
+ assert_equal 3, msg["error_backtrace"].size
137
+ end
138
+
139
+ it 'handles a new failed message' do
140
+ @redis.expect :zadd, 1, ['retry', String, String]
141
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true }
142
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
143
+ assert_raises RuntimeError do
144
+ handler.call(worker, msg, 'default') do
145
+ raise "kerblammo!"
146
+ end
147
+ end
148
+ assert_equal 'default', msg["queue"]
149
+ assert_equal 'kerblammo!', msg["error_message"]
150
+ assert_equal 'RuntimeError', msg["error_class"]
151
+ assert_equal 0, msg["retry_count"]
152
+ refute msg["error_backtrace"]
153
+ assert msg["failed_at"]
154
+ @redis.verify
155
+ end
156
+
157
+ it 'shuts down without retrying work-in-progress, which will resume' do
158
+ @redis.expect :zadd, 1, ['retry', String, String]
159
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true }
160
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
161
+ assert_raises Roundhouse::Shutdown do
162
+ handler.call(worker, msg, 'default') do
163
+ raise Roundhouse::Shutdown
164
+ end
165
+ end
166
+ assert_raises(MockExpectationError, "zadd should not be called") do
167
+ @redis.verify
168
+ end
169
+ end
170
+
171
+ it 'shuts down cleanly when shutdown causes exception' do
172
+ skip('Not supported in Ruby < 2.1.0') if RUBY_VERSION < '2.1.0'
173
+
174
+ @redis.expect :zadd, 1, ['retry', String, String]
175
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true }
176
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
177
+ assert_raises Roundhouse::Shutdown do
178
+ handler.call(worker, msg, 'default') do
179
+ begin
180
+ raise Roundhouse::Shutdown
181
+ rescue Interrupt
182
+ raise "kerblammo!"
183
+ end
184
+ end
185
+ end
186
+ assert_raises(MockExpectationError, "zadd should not be called") do
187
+ @redis.verify
188
+ end
189
+ end
190
+
191
+ it 'shuts down cleanly when shutdown causes chained exceptions' do
192
+ skip('Not supported in Ruby < 2.1.0') if RUBY_VERSION < '2.1.0'
193
+
194
+ @redis.expect :zadd, 1, ['retry', String, String]
195
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true }
196
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
197
+ assert_raises Roundhouse::Shutdown do
198
+ handler.call(worker, msg, 'default') do
199
+ begin
200
+ raise Roundhouse::Shutdown
201
+ rescue Interrupt
202
+ begin
203
+ raise "kerblammo!"
204
+ rescue
205
+ raise "kablooie!"
206
+ end
207
+ end
208
+ end
209
+ end
210
+ assert_raises(MockExpectationError, "zadd should not be called") do
211
+ @redis.verify
212
+ end
213
+ end
214
+
215
+ it 'allows a retry queue' do
216
+ @redis.expect :zadd, 1, ['retry', String, String]
217
+ msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true, 'retry_queue' => 'retry' }
218
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
219
+ assert_raises RuntimeError do
220
+ handler.call(worker, msg, 'default') do
221
+ raise "kerblammo!"
222
+ end
223
+ end
224
+ assert_equal 'retry', msg["queue"]
225
+ assert_equal 'kerblammo!', msg["error_message"]
226
+ assert_equal 'RuntimeError', msg["error_class"]
227
+ assert_equal 0, msg["retry_count"]
228
+ refute msg["error_backtrace"]
229
+ assert msg["failed_at"]
230
+ @redis.verify
231
+ end
232
+
233
+ it 'handles a recurring failed message' do
234
+ @redis.expect :zadd, 1, ['retry', String, String]
235
+ now = Time.now.to_f
236
+ msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], 'retry' => true, "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry_count"=>10}
237
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
238
+ assert_raises RuntimeError do
239
+ handler.call(worker, msg, 'default') do
240
+ raise "kerblammo!"
241
+ end
242
+ end
243
+ assert_equal 'default', msg["queue"]
244
+ assert_equal 'kerblammo!', msg["error_message"]
245
+ assert_equal 'RuntimeError', msg["error_class"]
246
+ assert_equal 11, msg["retry_count"]
247
+ assert msg["failed_at"]
248
+ @redis.verify
249
+ end
250
+
251
+ it 'handles a recurring failed message before reaching user-specifed max' do
252
+ @redis.expect :zadd, 1, ['retry', String, String]
253
+ now = Time.now.to_f
254
+ msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], 'retry' => 10, "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry_count"=>8}
255
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
256
+ assert_raises RuntimeError do
257
+ handler.call(worker, msg, 'default') do
258
+ raise "kerblammo!"
259
+ end
260
+ end
261
+ assert_equal 'default', msg["queue"]
262
+ assert_equal 'kerblammo!', msg["error_message"]
263
+ assert_equal 'RuntimeError', msg["error_class"]
264
+ assert_equal 9, msg["retry_count"]
265
+ assert msg["failed_at"]
266
+ @redis.verify
267
+ end
268
+
269
+ it 'throws away old messages after too many retries (using the default)' do
270
+ now = Time.now.to_f
271
+ msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry"=>true, "retry_count"=>25}
272
+ @redis.expect :zadd, 1, ['dead', Float, String]
273
+ @redis.expect :zremrangebyscore, 0, ['dead', String, Float]
274
+ @redis.expect :zremrangebyrank, 0, ['dead', Numeric, Numeric]
275
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
276
+ assert_raises RuntimeError do
277
+ handler.call(worker, msg, 'default') do
278
+ raise "kerblammo!"
279
+ end
280
+ end
281
+ @redis.verify
282
+ end
283
+
284
+ it 'throws away old messages after too many retries (using user-specified max)' do
285
+ now = Time.now.to_f
286
+ msg = {"class"=>"Bob", "args"=>[1, 2, "foo"], "queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry"=>3, "retry_count"=>3}
287
+ @redis.expect :zadd, 1, ['dead', Float, String]
288
+ @redis.expect :zremrangebyscore, 0, ['dead', String, Float]
289
+ @redis.expect :zremrangebyrank, 0, ['dead', Numeric, Numeric]
290
+ handler = Roundhouse::Middleware::Server::RetryJobs.new
291
+ assert_raises RuntimeError do
292
+ handler.call(worker, msg, 'default') do
293
+ raise "kerblammo!"
294
+ end
295
+ end
296
+ @redis.verify
297
+ end
298
+
299
+ describe "custom retry delay" do
300
+ before do
301
+ @old_logger = Roundhouse.logger
302
+ @tmp_log_path = '/tmp/roundhouse-retries.log'
303
+ Roundhouse.logger = Logger.new(@tmp_log_path)
304
+ end
305
+
306
+ after do
307
+ Roundhouse.logger = @old_logger
308
+ Roundhouse.options.delete(:logfile)
309
+ File.unlink @tmp_log_path if File.exist?(@tmp_log_path)
310
+ end
311
+
312
+ let(:custom_worker) do
313
+ Class.new do
314
+ include ::Roundhouse::Worker
315
+
316
+ roundhouse_retry_in do |count|
317
+ count * 2
318
+ end
319
+ end
320
+ end
321
+
322
+ let(:error_worker) do
323
+ Class.new do
324
+ include ::Roundhouse::Worker
325
+
326
+ roundhouse_retry_in do |count|
327
+ count / 0
328
+ end
329
+ end
330
+ end
331
+
332
+ let(:handler) { Roundhouse::Middleware::Server::RetryJobs.new }
333
+
334
+ it "retries with a default delay" do
335
+ refute_equal 4, handler.__send__(:delay_for, worker, 2)
336
+ end
337
+
338
+ it "retries with a custom delay" do
339
+ assert_equal 4, handler.__send__(:delay_for, custom_worker, 2)
340
+ end
341
+
342
+ it "falls back to the default retry on exception" do
343
+ refute_equal 4, handler.__send__(:delay_for, error_worker, 2)
344
+ assert_match(/Failure scheduling retry using the defined `roundhouse_retry_in`/,
345
+ File.read(@tmp_log_path), 'Log entry missing for roundhouse_retry_in')
346
+ end
347
+ end
348
+
349
+ describe 'handles errors withouth cause' do
350
+ before do
351
+ @error = nil
352
+ begin
353
+ raise ::StandardError, 'Error'
354
+ rescue ::StandardError => e
355
+ @error = e
356
+ end
357
+ end
358
+
359
+ it "does not recurse infinitely checking if it's a shutdown" do
360
+ assert(!Roundhouse::Middleware::Server::RetryJobs.new.send(
361
+ :exception_caused_by_shutdown?, @error))
362
+ end
363
+ end
364
+
365
+ describe 'handles errors with circular causes' do
366
+ before do
367
+ @error = nil
368
+ begin
369
+ begin
370
+ raise ::StandardError, 'Error 1'
371
+ rescue ::StandardError => e1
372
+ begin
373
+ raise ::StandardError, 'Error 2'
374
+ rescue ::StandardError
375
+ raise e1
376
+ end
377
+ end
378
+ rescue ::StandardError => e
379
+ @error = e
380
+ end
381
+ end
382
+
383
+ it "does not recurse infinitely checking if it's a shutdown" do
384
+ assert(!Roundhouse::Middleware::Server::RetryJobs.new.send(
385
+ :exception_caused_by_shutdown?, @error))
386
+ end
387
+ end
388
+ end
389
+
390
+ end
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+ require_relative 'helper'
3
+
4
+ class TestRoundhouse < Roundhouse::Test
5
+ describe 'json processing' do
6
+ it 'loads json' do
7
+ assert_equal ({"foo" => "bar"}), Roundhouse.load_json("{\"foo\":\"bar\"}")
8
+ end
9
+
10
+ it 'dumps json' do
11
+ assert_equal "{\"foo\":\"bar\"}", Roundhouse.dump_json({ "foo" => "bar" })
12
+ end
13
+ end
14
+
15
+ describe "redis connection" do
16
+ it "returns error without creating a connection if block is not given" do
17
+ mock = Minitest::Mock.new
18
+ mock.expect :create, nil #Roundhouse::RedisConnection, create
19
+ assert_raises(ArgumentError) {
20
+ Roundhouse.redis
21
+ }
22
+ assert_raises(MockExpectationError, "create should not be called") do
23
+ mock.verify
24
+ end
25
+ end
26
+ end
27
+
28
+ describe "❨╯°□°❩╯︵┻━┻" do
29
+ before { $stdout = StringIO.new }
30
+ after { $stdout = STDOUT }
31
+
32
+ it "allows angry developers to express their emotional constitution and remedies it" do
33
+ Roundhouse.❨╯°□°❩╯︵┻━┻
34
+ assert_equal "Calm down, yo.\n", $stdout.string
35
+ end
36
+ end
37
+
38
+ describe 'lifecycle events' do
39
+ it 'handles invalid input' do
40
+ Roundhouse.options[:lifecycle_events][:startup].clear
41
+
42
+ e = assert_raises ArgumentError do
43
+ Roundhouse.on(:startp)
44
+ end
45
+ assert_match(/Invalid event name/, e.message)
46
+ e = assert_raises ArgumentError do
47
+ Roundhouse.on('startup')
48
+ end
49
+ assert_match(/Symbols only/, e.message)
50
+ Roundhouse.on(:startup) do
51
+ 1 + 1
52
+ end
53
+
54
+ assert_equal 2, Roundhouse.options[:lifecycle_events][:startup].first.call
55
+ end
56
+ end
57
+
58
+ describe 'default_worker_options' do
59
+ before do
60
+ @old_options = Roundhouse.default_worker_options
61
+ end
62
+ after { Roundhouse.default_worker_options = @old_options }
63
+
64
+ it 'stringify keys' do
65
+ Roundhouse.default_worker_options = { queue: 'cat'}
66
+ assert_equal 'cat', Roundhouse.default_worker_options['queue']
67
+ end
68
+ end
69
+
70
+ describe 'error handling' do
71
+ it 'deals with user-specified error handlers which raise errors' do
72
+ output = capture_logging do
73
+ begin
74
+ Roundhouse.error_handlers << proc {|x, hash|
75
+ raise 'boom'
76
+ }
77
+ cli = Roundhouse::CLI.new
78
+ cli.handle_exception(RuntimeError.new("hello"))
79
+ ensure
80
+ Roundhouse.error_handlers.pop
81
+ end
82
+ end
83
+ assert_includes output, "boom"
84
+ assert_includes output, "ERROR"
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,120 @@
1
+ require_relative 'helper'
2
+ require 'roundhouse/scheduled'
3
+
4
+ class TestScheduled < Roundhouse::Test
5
+ class ScheduledWorker
6
+ include Roundhouse::Worker
7
+ def perform(x)
8
+ end
9
+ end
10
+
11
+ describe 'poller' do
12
+ before do
13
+ Roundhouse.redis = REDIS
14
+ Roundhouse.redis do |conn|
15
+ conn.flushdb
16
+ end
17
+
18
+ @error_1 = { 'class' => ScheduledWorker.name, 'args' => [0], 'queue_id' => 100 }
19
+ @error_2 = { 'class' => ScheduledWorker.name, 'args' => [1], 'queue_id' => 200 }
20
+ @error_3 = { 'class' => ScheduledWorker.name, 'args' => [2], 'queue_id' => 300 }
21
+ @future_1 = { 'class' => ScheduledWorker.name, 'args' => [3], 'queue_id' => 400 }
22
+ @future_2 = { 'class' => ScheduledWorker.name, 'args' => [4], 'queue_id' => 500 }
23
+ @future_3 = { 'class' => ScheduledWorker.name, 'args' => [5], 'queue_id' => 600 }
24
+
25
+ @retry = Roundhouse::RetrySet.new
26
+ @scheduled = Roundhouse::ScheduledSet.new
27
+ @poller = Roundhouse::Scheduled::Poller.new
28
+ end
29
+
30
+ class Stopper
31
+ def call(worker_class, message, queue, r)
32
+ yield if message['args'].first.odd?
33
+ end
34
+ end
35
+
36
+ it 'executes client middleware' do
37
+ Roundhouse.client_middleware.add Stopper
38
+ begin
39
+ @retry.schedule (Time.now - 60).to_f, @error_1
40
+ @retry.schedule (Time.now - 60).to_f, @error_2
41
+ @scheduled.schedule (Time.now - 60).to_f, @future_2
42
+ @scheduled.schedule (Time.now - 60).to_f, @future_3
43
+
44
+ @poller.poll
45
+
46
+ Roundhouse.redis do |conn|
47
+ assert_equal 0, conn.llen("queue:100")
48
+ assert_equal 1, conn.llen("queue:200")
49
+ assert_equal 0, conn.llen("queue:500")
50
+ assert_equal 1, conn.llen("queue:600")
51
+ end
52
+ ensure
53
+ Roundhouse.client_middleware.remove Stopper
54
+ end
55
+ end
56
+
57
+ it 'should empty the retry and scheduled queues up to the current time' do
58
+ created_time = Time.new(2013, 2, 3)
59
+ enqueued_time = Time.new(2013, 2, 4)
60
+
61
+ Time.stub(:now, created_time) do
62
+ @retry.schedule (enqueued_time - 60).to_f, @error_1.merge!('created_at' => created_time.to_f)
63
+ @retry.schedule (enqueued_time - 50).to_f, @error_2.merge!('created_at' => created_time.to_f)
64
+ @retry.schedule (enqueued_time + 60).to_f, @error_3.merge!('created_at' => created_time.to_f)
65
+ @scheduled.schedule (enqueued_time - 60).to_f, @future_1.merge!('created_at' => created_time.to_f)
66
+ @scheduled.schedule (enqueued_time - 50).to_f, @future_2.merge!('created_at' => created_time.to_f)
67
+ @scheduled.schedule (enqueued_time + 60).to_f, @future_3.merge!('created_at' => created_time.to_f)
68
+ end
69
+
70
+ Time.stub(:now, enqueued_time) do
71
+ @poller.poll
72
+
73
+ Roundhouse.redis do |conn|
74
+ %w(queue:100 queue:200 queue:400 queue:500).each do |queue_id|
75
+ assert_equal 1, conn.llen(queue_id)
76
+ job = Roundhouse.load_json(conn.lrange(queue_id, 0, -1)[0])
77
+ assert_equal enqueued_time.to_f, job['enqueued_at']
78
+ assert_equal created_time.to_f, job['created_at']
79
+ end
80
+ end
81
+
82
+ assert_equal 1, @retry.size
83
+ assert_equal 1, @scheduled.size
84
+ end
85
+ end
86
+
87
+ def with_roundhouse_option(name, value)
88
+ _original, Roundhouse.options[name] = Roundhouse.options[name], value
89
+ begin
90
+ yield
91
+ ensure
92
+ Roundhouse.options[name] = _original
93
+ end
94
+ end
95
+
96
+ it 'generates random intervals that target a configured average' do
97
+ with_roundhouse_option(:poll_interval_average, 10) do
98
+ i = 500
99
+ intervals = i.times.map{ @poller.send(:random_poll_interval) }
100
+
101
+ assert intervals.all?{|x| x >= 5}
102
+ assert intervals.all?{|x| x <= 15}
103
+ assert_in_delta 10, intervals.reduce(&:+).to_f / i, 0.5
104
+ end
105
+ end
106
+
107
+ it 'calculates an average poll interval based on the number of known Roundhouse processes' do
108
+ with_roundhouse_option(:average_scheduled_poll_interval, 10) do
109
+ 3.times do |i|
110
+ Roundhouse.redis do |conn|
111
+ conn.sadd("processes", "process-#{i}")
112
+ conn.hset("process-#{i}", "info", nil)
113
+ end
114
+ end
115
+
116
+ assert_equal 30, @poller.send(:scaled_poll_interval)
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,75 @@
1
+ require_relative 'helper'
2
+ require 'roundhouse/scheduled'
3
+ require 'active_support/core_ext/numeric/time'
4
+ require 'active_support/core_ext/integer/time'
5
+ require 'active_support/core_ext/date'
6
+
7
+ class TestScheduling < Roundhouse::Test
8
+ describe 'middleware' do
9
+ before do
10
+ Roundhouse::Client.instance_variable_set(:@default, nil)
11
+ @redis = Minitest::Mock.new
12
+ # Ugh, this is terrible.
13
+ Roundhouse.instance_variable_set(:@redis, @redis)
14
+ def @redis.multi; [yield] * 2 if block_given?; end
15
+ def @redis.with; yield self; end
16
+ end
17
+
18
+ after do
19
+ Roundhouse::Client.instance_variable_set(:@default, nil)
20
+ Roundhouse.instance_variable_set(:@redis, REDIS)
21
+ end
22
+
23
+ class ScheduledWorker
24
+ include Roundhouse::Worker
25
+ roundhouse_options :queue => :custom_queue
26
+ def perform(x)
27
+ end
28
+ end
29
+
30
+ it 'schedules a job via interval' do
31
+ @redis.expect :zadd, true, ['schedule', Array]
32
+ assert ScheduledWorker.perform_in(100, 600, 'mike')
33
+ @redis.verify
34
+ end
35
+
36
+ it 'schedules a job in one month' do
37
+ @redis.expect :zadd, true do |key, args|
38
+ assert_equal 'schedule', key
39
+ assert_in_delta 1.month.since.to_f, args[0][0].to_f, 1
40
+ end
41
+ assert ScheduledWorker.perform_in(100, 1.month, 'mike')
42
+ @redis.verify
43
+ end
44
+
45
+ it 'schedules a job via timestamp' do
46
+ @redis.expect :zadd, true, ['schedule', Array]
47
+ assert ScheduledWorker.perform_in(100, 5.days.from_now, 'mike')
48
+ @redis.verify
49
+ end
50
+
51
+ it 'schedules job right away on negative timestamp/interval' do
52
+ Roundhouse::Monitor.stub :maybe_add_to_rotation, true do
53
+ @redis.expect :lpush, true, ['queue:100', Array]
54
+ assert ScheduledWorker.perform_in(100, -300, 'mike')
55
+ end
56
+ @redis.verify
57
+ end
58
+
59
+ it 'schedules multiple jobs at once' do
60
+ @redis.expect :zadd, true, ['schedule', Array]
61
+ assert Roundhouse::Client.push_bulk('queue_id' => 100, 'class' => ScheduledWorker, 'args' => [['mike'], ['mike']], 'at' => 600)
62
+ @redis.verify
63
+ end
64
+
65
+ it 'removes the enqueued_at field when scheduling' do
66
+ @redis.expect :zadd, true do |key, args|
67
+ job = Roundhouse.load_json(args.first.last)
68
+ job.key?('created_at') && !job.key?('enqueued_at')
69
+ end
70
+ assert ScheduledWorker.perform_in(100, 1.month, 'mike')
71
+ @redis.verify
72
+ end
73
+ end
74
+
75
+ end