roundhouse-x 0.1.0

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.
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