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,78 @@
1
+ require_relative 'helper'
2
+ require 'roundhouse'
3
+ require 'roundhouse/worker'
4
+ require 'roundhouse/rails'
5
+
6
+ Roundhouse.hook_rails!
7
+
8
+ class TestTesting < Roundhouse::Test
9
+ describe 'roundhouse testing' do
10
+ describe 'require/load roundhouse/testing.rb' do
11
+ before do
12
+ require 'roundhouse/testing.rb'
13
+ end
14
+
15
+ after do
16
+ Roundhouse::Testing.disable!
17
+ end
18
+
19
+ it 'enables fake testing' do
20
+ Roundhouse::Testing.fake!
21
+ assert_equal true, Roundhouse::Testing.enabled?
22
+ assert_equal true, Roundhouse::Testing.fake?
23
+ end
24
+
25
+ it 'enables fake testing in a block' do
26
+ Roundhouse::Testing.disable!
27
+ assert_equal true, Roundhouse::Testing.disabled?
28
+
29
+ Roundhouse::Testing.fake! do
30
+ assert_equal true, Roundhouse::Testing.enabled?
31
+ assert_equal true, Roundhouse::Testing.fake?
32
+ end
33
+
34
+ assert_equal false, Roundhouse::Testing.enabled?
35
+ assert_equal false, Roundhouse::Testing.fake?
36
+ end
37
+
38
+ it 'disables testing in a block' do
39
+ Roundhouse::Testing.fake!
40
+
41
+ Roundhouse::Testing.disable! do
42
+ assert_equal true, Roundhouse::Testing.disabled?
43
+ end
44
+
45
+ assert_equal true, Roundhouse::Testing.enabled?
46
+ end
47
+ end
48
+
49
+ describe 'require/load roundhouse/testing/inline.rb' do
50
+ before do
51
+ require 'roundhouse/testing/inline.rb'
52
+ end
53
+
54
+ after do
55
+ Roundhouse::Testing.disable!
56
+ end
57
+
58
+ it 'enables inline testing' do
59
+ Roundhouse::Testing.inline!
60
+ assert_equal true, Roundhouse::Testing.enabled?
61
+ assert_equal true, Roundhouse::Testing.inline?
62
+ end
63
+
64
+ it 'enables inline testing in a block' do
65
+ Roundhouse::Testing.disable!
66
+ assert_equal true, Roundhouse::Testing.disabled?
67
+
68
+ Roundhouse::Testing.inline! do
69
+ assert_equal true, Roundhouse::Testing.enabled?
70
+ assert_equal true, Roundhouse::Testing.inline?
71
+ end
72
+
73
+ assert_equal false, Roundhouse::Testing.enabled?
74
+ assert_equal false, Roundhouse::Testing.inline?
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,240 @@
1
+ require_relative 'helper'
2
+ require 'roundhouse'
3
+ require 'roundhouse/worker'
4
+ require 'roundhouse/rails'
5
+
6
+ require 'active_support/core_ext/numeric/time'
7
+
8
+ Roundhouse.hook_rails!
9
+
10
+ class TestTesting < Roundhouse::Test
11
+ describe 'roundhouse testing' do
12
+ class PerformError < RuntimeError; end
13
+
14
+ class DirectWorker
15
+ include Roundhouse::Worker
16
+ def perform(a, b)
17
+ a + b
18
+ end
19
+ end
20
+
21
+ class EnqueuedWorker
22
+ include Roundhouse::Worker
23
+ def perform(a, b)
24
+ a + b
25
+ end
26
+ end
27
+
28
+ class StoredWorker
29
+ include Roundhouse::Worker
30
+ def perform(error)
31
+ raise PerformError if error
32
+ end
33
+ end
34
+
35
+ before do
36
+ require 'roundhouse/testing.rb'
37
+ Roundhouse::Testing.fake!
38
+ EnqueuedWorker.jobs.clear
39
+ DirectWorker.jobs.clear
40
+ end
41
+
42
+ after do
43
+ Roundhouse::Testing.disable!
44
+ end
45
+
46
+ it 'stubs the async call' do
47
+ assert_equal 0, DirectWorker.jobs.size
48
+ assert DirectWorker.perform_async(100, 1, 2)
49
+ assert_equal 1, DirectWorker.jobs.size
50
+ assert DirectWorker.perform_in(100, 10, 1, 2)
51
+ assert_equal 2, DirectWorker.jobs.size
52
+ assert DirectWorker.perform_at(100, 10, 1, 2)
53
+ assert_equal 3, DirectWorker.jobs.size
54
+ assert_in_delta 10.seconds.from_now.to_f, DirectWorker.jobs.last['at'], 0.01
55
+ end
56
+
57
+ it 'stubs the enqueue call' do
58
+ assert_equal 0, EnqueuedWorker.jobs.size
59
+ assert Roundhouse::Client.enqueue_to(100, EnqueuedWorker, 1, 2)
60
+ assert_equal 1, EnqueuedWorker.jobs.size
61
+ end
62
+
63
+ it 'stubs the enqueue_to call' do
64
+ assert_equal 0, EnqueuedWorker.jobs.size
65
+ assert Roundhouse::Client.enqueue_to(1000, EnqueuedWorker, 1, 2)
66
+ assert_equal 1, EnqueuedWorker.jobs.size
67
+ end
68
+
69
+ it 'executes all stored jobs' do
70
+ assert StoredWorker.perform_async(100, false)
71
+ assert StoredWorker.perform_async(100, true)
72
+
73
+ assert_equal 2, StoredWorker.jobs.size
74
+ assert_raises PerformError do
75
+ StoredWorker.drain
76
+ end
77
+ assert_equal 0, StoredWorker.jobs.size
78
+
79
+ end
80
+
81
+ class SpecificJidWorker
82
+ include Roundhouse::Worker
83
+ class_attribute :count
84
+ self.count = 0
85
+ def perform(worker_jid)
86
+ return unless worker_jid == self.jid
87
+ self.class.count += 1
88
+ end
89
+ end
90
+
91
+ it 'execute only jobs with assigned JID' do
92
+ 4.times do |i|
93
+ jid = SpecificJidWorker.perform_async(100, nil)
94
+ if i % 2 == 0
95
+ SpecificJidWorker.jobs[-1]["args"] = ["wrong_jid"]
96
+ else
97
+ SpecificJidWorker.jobs[-1]["args"] = [jid]
98
+ end
99
+ end
100
+
101
+ SpecificJidWorker.perform_one
102
+ assert_equal 0, SpecificJidWorker.count
103
+
104
+ SpecificJidWorker.perform_one
105
+ assert_equal 1, SpecificJidWorker.count
106
+
107
+ SpecificJidWorker.drain
108
+ assert_equal 2, SpecificJidWorker.count
109
+ end
110
+
111
+ it 'round trip serializes the job arguments' do
112
+ assert StoredWorker.perform_async(100, :mike)
113
+ job = StoredWorker.jobs.first
114
+ assert_equal "mike", job['args'].first
115
+ StoredWorker.clear
116
+ end
117
+
118
+ it 'perform_one runs only one job' do
119
+ DirectWorker.perform_async(100, 1, 2)
120
+ DirectWorker.perform_async(100, 3, 4)
121
+ assert_equal 2, DirectWorker.jobs.size
122
+
123
+ DirectWorker.perform_one
124
+ assert_equal 1, DirectWorker.jobs.size
125
+
126
+ DirectWorker.clear
127
+ end
128
+
129
+ it 'perform_one raise error upon empty queue' do
130
+ DirectWorker.clear
131
+ assert_raises Roundhouse::EmptyQueueError do
132
+ DirectWorker.perform_one
133
+ end
134
+ end
135
+
136
+ class FirstWorker
137
+ include Roundhouse::Worker
138
+ class_attribute :count
139
+ self.count = 0
140
+ def perform
141
+ self.class.count += 1
142
+ end
143
+ end
144
+
145
+ class SecondWorker
146
+ include Roundhouse::Worker
147
+ class_attribute :count
148
+ self.count = 0
149
+ def perform
150
+ self.class.count += 1
151
+ end
152
+ end
153
+
154
+ class ThirdWorker
155
+ include Roundhouse::Worker
156
+ class_attribute :count
157
+ def perform
158
+ FirstWorker.perform_async(100)
159
+ SecondWorker.perform_async(100)
160
+ end
161
+ end
162
+
163
+ it 'clears jobs across all workers' do
164
+ Roundhouse::Worker.jobs.clear
165
+ FirstWorker.count = 0
166
+ SecondWorker.count = 0
167
+
168
+ assert_equal 0, FirstWorker.jobs.size
169
+ assert_equal 0, SecondWorker.jobs.size
170
+
171
+ FirstWorker.perform_async(100)
172
+ SecondWorker.perform_async(100)
173
+
174
+ assert_equal 1, FirstWorker.jobs.size
175
+ assert_equal 1, SecondWorker.jobs.size
176
+
177
+ Roundhouse::Worker.clear_all
178
+
179
+ assert_equal 0, FirstWorker.jobs.size
180
+ assert_equal 0, SecondWorker.jobs.size
181
+
182
+ assert_equal 0, FirstWorker.count
183
+ assert_equal 0, SecondWorker.count
184
+ end
185
+
186
+ it 'drains jobs across all workers' do
187
+ Roundhouse::Worker.jobs.clear
188
+ FirstWorker.count = 0
189
+ SecondWorker.count = 0
190
+
191
+ assert_equal 0, FirstWorker.jobs.size
192
+ assert_equal 0, SecondWorker.jobs.size
193
+
194
+ assert_equal 0, FirstWorker.count
195
+ assert_equal 0, SecondWorker.count
196
+
197
+ FirstWorker.perform_async(100)
198
+ SecondWorker.perform_async(100)
199
+
200
+ assert_equal 1, FirstWorker.jobs.size
201
+ assert_equal 1, SecondWorker.jobs.size
202
+
203
+ Roundhouse::Worker.drain_all
204
+
205
+ assert_equal 0, FirstWorker.jobs.size
206
+ assert_equal 0, SecondWorker.jobs.size
207
+
208
+ assert_equal 1, FirstWorker.count
209
+ assert_equal 1, SecondWorker.count
210
+ end
211
+
212
+ it 'drains jobs across all workers even when workers create new jobs' do
213
+ Roundhouse::Worker.jobs.clear
214
+ FirstWorker.count = 0
215
+ SecondWorker.count = 0
216
+
217
+ assert_equal 0, ThirdWorker.jobs.size
218
+
219
+ assert_equal 0, FirstWorker.count
220
+ assert_equal 0, SecondWorker.count
221
+
222
+ ThirdWorker.perform_async(100)
223
+
224
+ assert_equal 1, ThirdWorker.jobs.size
225
+
226
+ Roundhouse::Worker.drain_all
227
+
228
+ assert_equal 0, ThirdWorker.jobs.size
229
+
230
+ assert_equal 1, FirstWorker.count
231
+ assert_equal 1, SecondWorker.count
232
+ end
233
+
234
+ it 'can execute a job' do
235
+ worker = Minitest::Mock.new
236
+ worker.expect(:perform, nil, [1, 2, 3])
237
+ DirectWorker.execute_job(worker, [1, 2, 3])
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,65 @@
1
+ require_relative 'helper'
2
+ require 'roundhouse'
3
+ require 'roundhouse/worker'
4
+ require 'roundhouse/rails'
5
+
6
+ Roundhouse.hook_rails!
7
+
8
+ class TestInline < Roundhouse::Test
9
+ describe 'roundhouse inline testing' do
10
+ class InlineError < RuntimeError; end
11
+ class ParameterIsNotString < RuntimeError; end
12
+
13
+ class InlineWorker
14
+ include Roundhouse::Worker
15
+ def perform(pass)
16
+ raise ArgumentError, "no jid" unless jid
17
+ raise InlineError unless pass
18
+ end
19
+ end
20
+
21
+ class InlineWorkerWithTimeParam
22
+ include Roundhouse::Worker
23
+ def perform(time)
24
+ raise ParameterIsNotString unless time.is_a?(String) || time.is_a?(Numeric)
25
+ end
26
+ end
27
+
28
+ before do
29
+ require 'roundhouse/testing/inline.rb'
30
+ Roundhouse::Testing.inline!
31
+ end
32
+
33
+ after do
34
+ Roundhouse::Testing.disable!
35
+ end
36
+
37
+ it 'stubs the async call when in testing mode' do
38
+ assert InlineWorker.perform_async(100, true)
39
+
40
+ assert_raises InlineError do
41
+ InlineWorker.perform_async(100, false)
42
+ end
43
+ end
44
+
45
+ it 'stubs the enqueue call when in testing mode' do
46
+ assert Roundhouse::Client.enqueue_to(100, InlineWorker, true)
47
+
48
+ assert_raises InlineError do
49
+ Roundhouse::Client.enqueue_to(100, InlineWorker, false)
50
+ end
51
+ end
52
+
53
+ it 'stubs the push_bulk call when in testing mode' do
54
+ assert Roundhouse::Client.push_bulk({'queue_id' => 100, 'class' => InlineWorker, 'args' => [[true], [true]]})
55
+
56
+ assert_raises InlineError do
57
+ Roundhouse::Client.push_bulk({'queue_id' => 100, 'class' => InlineWorker, 'args' => [[true], [false]]})
58
+ end
59
+ end
60
+
61
+ it 'should relay parameters through json' do
62
+ assert Roundhouse::Client.enqueue_to(100, InlineWorkerWithTimeParam, Time.now)
63
+ end
64
+ end
65
+ end
data/test/test_util.rb ADDED
@@ -0,0 +1,18 @@
1
+ require_relative 'helper'
2
+ require 'roundhouse'
3
+ require 'roundhouse/web_helpers'
4
+
5
+ class TestUtil < Roundhouse::Test
6
+
7
+ class Helpers
8
+ include Roundhouse::Util
9
+ end
10
+
11
+ def test_hertz_donut
12
+ obj = Helpers.new
13
+ output = capture_logging(Logger::DEBUG) do
14
+ assert_equal false, obj.want_a_hertz_donut?
15
+ end
16
+ assert_includes output, "hz: 10"
17
+ end
18
+ end