qs 0.0.1 → 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 (65) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +6 -1
  3. data/LICENSE.txt +1 -1
  4. data/bench/config.qs +46 -0
  5. data/bench/queue.rb +8 -0
  6. data/bench/report.rb +114 -0
  7. data/bench/report.txt +11 -0
  8. data/bin/qs +7 -0
  9. data/lib/qs/cli.rb +124 -0
  10. data/lib/qs/client.rb +121 -0
  11. data/lib/qs/config_file.rb +79 -0
  12. data/lib/qs/daemon.rb +350 -0
  13. data/lib/qs/daemon_data.rb +46 -0
  14. data/lib/qs/error_handler.rb +58 -0
  15. data/lib/qs/job.rb +70 -0
  16. data/lib/qs/job_handler.rb +90 -0
  17. data/lib/qs/logger.rb +23 -0
  18. data/lib/qs/payload_handler.rb +136 -0
  19. data/lib/qs/pid_file.rb +42 -0
  20. data/lib/qs/process.rb +136 -0
  21. data/lib/qs/process_signal.rb +20 -0
  22. data/lib/qs/qs_runner.rb +49 -0
  23. data/lib/qs/queue.rb +69 -0
  24. data/lib/qs/redis_item.rb +33 -0
  25. data/lib/qs/route.rb +52 -0
  26. data/lib/qs/runner.rb +26 -0
  27. data/lib/qs/test_helpers.rb +17 -0
  28. data/lib/qs/test_runner.rb +43 -0
  29. data/lib/qs/version.rb +1 -1
  30. data/lib/qs.rb +92 -2
  31. data/qs.gemspec +7 -2
  32. data/test/helper.rb +8 -1
  33. data/test/support/app_daemon.rb +74 -0
  34. data/test/support/config.qs +7 -0
  35. data/test/support/config_files/empty.qs +0 -0
  36. data/test/support/config_files/invalid.qs +1 -0
  37. data/test/support/config_files/valid.qs +7 -0
  38. data/test/support/config_invalid_run.qs +3 -0
  39. data/test/support/config_no_run.qs +0 -0
  40. data/test/support/factory.rb +14 -0
  41. data/test/support/pid_file_spy.rb +19 -0
  42. data/test/support/runner_spy.rb +17 -0
  43. data/test/system/daemon_tests.rb +226 -0
  44. data/test/unit/cli_tests.rb +188 -0
  45. data/test/unit/client_tests.rb +269 -0
  46. data/test/unit/config_file_tests.rb +59 -0
  47. data/test/unit/daemon_data_tests.rb +96 -0
  48. data/test/unit/daemon_tests.rb +702 -0
  49. data/test/unit/error_handler_tests.rb +163 -0
  50. data/test/unit/job_handler_tests.rb +253 -0
  51. data/test/unit/job_tests.rb +132 -0
  52. data/test/unit/logger_tests.rb +38 -0
  53. data/test/unit/payload_handler_tests.rb +276 -0
  54. data/test/unit/pid_file_tests.rb +70 -0
  55. data/test/unit/process_signal_tests.rb +61 -0
  56. data/test/unit/process_tests.rb +371 -0
  57. data/test/unit/qs_runner_tests.rb +166 -0
  58. data/test/unit/qs_tests.rb +217 -0
  59. data/test/unit/queue_tests.rb +132 -0
  60. data/test/unit/redis_item_tests.rb +49 -0
  61. data/test/unit/route_tests.rb +81 -0
  62. data/test/unit/runner_tests.rb +63 -0
  63. data/test/unit/test_helper_tests.rb +61 -0
  64. data/test/unit/test_runner_tests.rb +128 -0
  65. metadata +180 -15
@@ -0,0 +1,163 @@
1
+ require 'assert'
2
+ require 'qs/error_handler'
3
+
4
+ require 'qs/daemon_data'
5
+ require 'qs/queue'
6
+
7
+ class Qs::ErrorHandler
8
+
9
+ class UnitTests < Assert::Context
10
+ desc "Qs::ErrorHandler"
11
+ setup do
12
+ @exception = Factory.exception
13
+ @daemon_data = Qs::DaemonData.new
14
+ @queue_name = Factory.string
15
+ @queue_redis_key = Qs::Queue::RedisKey.new(@queue_name)
16
+ @context_hash = {
17
+ :daemon_data => @daemon_data,
18
+ :queue_redis_key => @queue_redis_key,
19
+ :serialized_payload => Factory.string,
20
+ :job => Factory.string,
21
+ :handler_class => Factory.string
22
+ }
23
+
24
+ @handler_class = Qs::ErrorHandler
25
+ end
26
+ subject{ @handler_class }
27
+
28
+ end
29
+
30
+ class InitTests < UnitTests
31
+ desc "when init"
32
+ setup do
33
+ @call_count = 0
34
+ @first_called_at = nil
35
+ @second_called_at = nil
36
+ @args_passed_to_error_proc = nil
37
+ first_error_proc = proc do |*args|
38
+ @args_passed_to_error_proc = args
39
+ @first_called_at = (@call_count += 1)
40
+ end
41
+ second_error_proc = proc{ @second_called_at = (@call_count += 1) }
42
+ Assert.stub(@daemon_data, :error_procs){ [first_error_proc, second_error_proc] }
43
+
44
+ @handler = @handler_class.new(@exception, @context_hash)
45
+ end
46
+ subject{ @handler }
47
+
48
+ should have_readers :exception, :context
49
+ should have_imeths :run
50
+
51
+ should "know its exception and context" do
52
+ assert_equal @exception, subject.exception
53
+ exp = Qs::ErrorContext.new(@context_hash)
54
+ assert_equal exp, subject.context
55
+ end
56
+
57
+ should "know its error procs" do
58
+ assert_equal @daemon_data.error_procs.reverse, subject.error_procs
59
+ end
60
+
61
+ end
62
+
63
+ class RunTests < InitTests
64
+ desc "and run"
65
+ setup do
66
+ @handler.run
67
+ end
68
+
69
+ should "pass its exception and context to the error procs" do
70
+ assert_not_nil @args_passed_to_error_proc
71
+ assert_includes subject.exception, @args_passed_to_error_proc
72
+ assert_includes subject.context, @args_passed_to_error_proc
73
+ end
74
+
75
+ should "call each of its error procs" do
76
+ assert_equal 1, @second_called_at
77
+ assert_equal 2, @first_called_at
78
+ end
79
+
80
+ end
81
+
82
+ class RunAndErrorProcThrowsExceptionTests < UnitTests
83
+ desc "run with an error proc that throws an exception"
84
+ setup do
85
+ @proc_exception = Factory.exception
86
+ error_proc = proc{ raise @proc_exception }
87
+ Assert.stub(@daemon_data, :error_procs){ [error_proc] }
88
+
89
+ @handler = @handler_class.new(@exception, @context_hash).tap(&:run)
90
+ end
91
+ subject{ @handler }
92
+
93
+ should "set its exception to the exception thrown by the error proc" do
94
+ assert_equal @proc_exception, subject.exception
95
+ end
96
+
97
+ end
98
+
99
+ class RunWithMultipleErrorProcsThatThrowExceptionsTests < UnitTests
100
+ desc "run with multiple error procs that throw an exception"
101
+ setup do
102
+ @first_caught_exception = nil
103
+ @second_caught_exception = nil
104
+ @third_caught_exception = nil
105
+
106
+ @third_proc_exception = Factory.exception
107
+ third_proc = proc do |exception, context|
108
+ @third_caught_exception = exception
109
+ raise @third_proc_exception
110
+ end
111
+
112
+ @second_proc_exception = Factory.exception
113
+ second_proc = proc do |exception, context|
114
+ @second_caught_exception = exception
115
+ raise @second_proc_exception
116
+ end
117
+
118
+ first_proc = proc{ |exception, context| @first_caught_exception = exception }
119
+
120
+ Assert.stub(@daemon_data, :error_procs){ [first_proc, second_proc, third_proc] }
121
+ @handler = @handler_class.new(@exception, @context_hash).tap(&:run)
122
+ end
123
+ subject{ @handler }
124
+
125
+ should "call each proc, passing the previously raised exception to the next" do
126
+ assert_equal @exception, @third_caught_exception
127
+ assert_equal @third_proc_exception, @second_caught_exception
128
+ assert_equal @second_proc_exception, @first_caught_exception
129
+ end
130
+
131
+ end
132
+
133
+ class ErrorContextTests < UnitTests
134
+ desc "ErrorContext"
135
+ setup do
136
+ @context = Qs::ErrorContext.new(@context_hash)
137
+ end
138
+ subject{ @context }
139
+
140
+ should have_readers :daemon_data
141
+ should have_readers :queue_name, :serialized_payload
142
+ should have_readers :job, :handler_class
143
+
144
+ should "know its attributes" do
145
+ assert_equal @context_hash[:daemon_data], subject.daemon_data
146
+ exp = Qs::Queue::RedisKey.parse_name(@context_hash[:queue_redis_key])
147
+ assert_equal exp, subject.queue_name
148
+ assert_equal @context_hash[:serialized_payload], subject.serialized_payload
149
+ assert_equal @context_hash[:job], subject.job
150
+ assert_equal @context_hash[:handler_class], subject.handler_class
151
+ end
152
+
153
+ should "know if it equals another context" do
154
+ exp = Qs::ErrorContext.new(@context_hash)
155
+ assert_equal exp, subject
156
+
157
+ exp = Qs::ErrorContext.new({})
158
+ assert_not_equal exp, subject
159
+ end
160
+
161
+ end
162
+
163
+ end
@@ -0,0 +1,253 @@
1
+ require 'assert'
2
+ require 'qs/job_handler'
3
+
4
+ module Qs::JobHandler
5
+
6
+ class UnitTests < Assert::Context
7
+ desc "Qs::JobHandler"
8
+ setup do
9
+ @handler_class = Class.new{ include Qs::JobHandler }
10
+ end
11
+ subject{ @handler_class }
12
+
13
+ should have_imeths :timeout
14
+ should have_imeths :before_callbacks, :after_callbacks
15
+ should have_imeths :before_init_callbacks, :after_init_callbacks
16
+ should have_imeths :before_run_callbacks, :after_run_callbacks
17
+ should have_imeths :before, :after
18
+ should have_imeths :before_init, :after_init
19
+ should have_imeths :before_run, :after_run
20
+ should have_imeths :prepend_before, :prepend_after
21
+ should have_imeths :prepend_before_init, :prepend_after_init
22
+ should have_imeths :prepend_before_run, :prepend_after_run
23
+
24
+ should "allow reading/writing its timeout" do
25
+ assert_nil subject.timeout
26
+ value = Factory.integer
27
+ subject.timeout(value)
28
+ assert_equal value, subject.timeout
29
+ end
30
+
31
+ should "convert timeout values to floats" do
32
+ value = Factory.float.to_s
33
+ subject.timeout(value)
34
+ assert_equal value.to_f, subject.timeout
35
+ end
36
+
37
+ should "return an empty array by default using `before_callbacks`" do
38
+ assert_equal [], subject.before_callbacks
39
+ end
40
+
41
+ should "return an empty array by default using `after_callbacks`" do
42
+ assert_equal [], subject.after_callbacks
43
+ end
44
+
45
+ should "return an empty array by default using `before_init_callbacks`" do
46
+ assert_equal [], subject.before_init_callbacks
47
+ end
48
+
49
+ should "return an empty array by default using `after_init_callbacks`" do
50
+ assert_equal [], subject.after_init_callbacks
51
+ end
52
+
53
+ should "return an empty array by default using `before_run_callbacks`" do
54
+ assert_equal [], subject.before_run_callbacks
55
+ end
56
+
57
+ should "return an empty array by default using `after_run_callbacks`" do
58
+ assert_equal [], subject.after_run_callbacks
59
+ end
60
+
61
+ should "append a block to the before callbacks using `before`" do
62
+ subject.before_callbacks << proc{ Factory.string }
63
+ block = Proc.new{ Factory.string }
64
+ subject.before(&block)
65
+ assert_equal block, subject.before_callbacks.last
66
+ end
67
+
68
+ should "append a block to the after callbacks using `after`" do
69
+ subject.after_callbacks << proc{ Factory.string }
70
+ block = Proc.new{ Factory.string }
71
+ subject.after(&block)
72
+ assert_equal block, subject.after_callbacks.last
73
+ end
74
+
75
+ should "append a block to the before init callbacks using `before_init`" do
76
+ subject.before_init_callbacks << proc{ Factory.string }
77
+ block = Proc.new{ Factory.string }
78
+ subject.before_init(&block)
79
+ assert_equal block, subject.before_init_callbacks.last
80
+ end
81
+
82
+ should "append a block to the after init callbacks using `after_init`" do
83
+ subject.after_init_callbacks << proc{ Factory.string }
84
+ block = Proc.new{ Factory.string }
85
+ subject.after_init(&block)
86
+ assert_equal block, subject.after_init_callbacks.last
87
+ end
88
+
89
+ should "append a block to the before run callbacks using `before_run`" do
90
+ subject.before_run_callbacks << proc{ Factory.string }
91
+ block = Proc.new{ Factory.string }
92
+ subject.before_run(&block)
93
+ assert_equal block, subject.before_run_callbacks.last
94
+ end
95
+
96
+ should "append a block to the after run callbacks using `after_run`" do
97
+ subject.after_run_callbacks << proc{ Factory.string }
98
+ block = Proc.new{ Factory.string }
99
+ subject.after_run(&block)
100
+ assert_equal block, subject.after_run_callbacks.last
101
+ end
102
+
103
+ should "prepend a block to the before callbacks using `prepend_before`" do
104
+ subject.before_callbacks << proc{ Factory.string }
105
+ block = Proc.new{ Factory.string }
106
+ subject.prepend_before(&block)
107
+ assert_equal block, subject.before_callbacks.first
108
+ end
109
+
110
+ should "prepend a block to the after callbacks using `prepend_after`" do
111
+ subject.after_callbacks << proc{ Factory.string }
112
+ block = Proc.new{ Factory.string }
113
+ subject.prepend_after(&block)
114
+ assert_equal block, subject.after_callbacks.first
115
+ end
116
+
117
+ should "prepend a block to the before init callbacks using `prepend_before_init`" do
118
+ subject.before_init_callbacks << proc{ Factory.string }
119
+ block = Proc.new{ Factory.string }
120
+ subject.prepend_before_init(&block)
121
+ assert_equal block, subject.before_init_callbacks.first
122
+ end
123
+
124
+ should "prepend a block to the after init callbacks using `prepend_after_init`" do
125
+ subject.after_init_callbacks << proc{ Factory.string }
126
+ block = Proc.new{ Factory.string }
127
+ subject.prepend_after_init(&block)
128
+ assert_equal block, subject.after_init_callbacks.first
129
+ end
130
+
131
+ should "prepend a block to the before run callbacks using `prepend_before_run`" do
132
+ subject.before_run_callbacks << proc{ Factory.string }
133
+ block = Proc.new{ Factory.string }
134
+ subject.prepend_before_run(&block)
135
+ assert_equal block, subject.before_run_callbacks.first
136
+ end
137
+
138
+ should "prepend a block to the after run callbacks using `prepend_after_run`" do
139
+ subject.after_run_callbacks << proc{ Factory.string }
140
+ block = Proc.new{ Factory.string }
141
+ subject.prepend_after_run(&block)
142
+ assert_equal block, subject.after_run_callbacks.first
143
+ end
144
+
145
+ end
146
+
147
+ class InitTests < UnitTests
148
+ desc "when init"
149
+ setup do
150
+ @runner = FakeRunner.new
151
+ @handler = TestJobHandler.new(@runner)
152
+ end
153
+ subject{ @handler }
154
+
155
+ should have_imeths :init, :init!, :run, :run!
156
+
157
+ should "know its job, params and logger" do
158
+ assert_equal @runner.job, subject.public_job
159
+ assert_equal @runner.params, subject.public_params
160
+ assert_equal @runner.logger, subject.public_logger
161
+ end
162
+
163
+ should "call `init!` and its before/after init callbacks using `init`" do
164
+ subject.init
165
+ assert_equal 1, subject.first_before_init_call_order
166
+ assert_equal 2, subject.second_before_init_call_order
167
+ assert_equal 3, subject.init_call_order
168
+ assert_equal 4, subject.first_after_init_call_order
169
+ assert_equal 5, subject.second_after_init_call_order
170
+ end
171
+
172
+ should "call `run!` and its before/after run callbacks using `run`" do
173
+ subject.run
174
+ assert_equal 1, subject.first_before_run_call_order
175
+ assert_equal 2, subject.second_before_run_call_order
176
+ assert_equal 3, subject.run_call_order
177
+ assert_equal 4, subject.first_after_run_call_order
178
+ assert_equal 5, subject.second_after_run_call_order
179
+ end
180
+
181
+ should "have a custom inspect" do
182
+ reference = '0x0%x' % (subject.object_id << 1)
183
+ expected = "#<#{subject.class}:#{reference} " \
184
+ "@job=#{@runner.job.inspect}>"
185
+ assert_equal expected, subject.inspect
186
+ end
187
+
188
+ should "raise a not implemented error when `run!` by default" do
189
+ assert_raises(NotImplementedError){ @handler_class.new(@runner).run! }
190
+ end
191
+
192
+ end
193
+
194
+ class TestJobHandler
195
+ include Qs::JobHandler
196
+
197
+ attr_reader :first_before_init_call_order, :second_before_init_call_order
198
+ attr_reader :first_after_init_call_order, :second_after_init_call_order
199
+ attr_reader :first_before_run_call_order, :second_before_run_call_order
200
+ attr_reader :first_after_run_call_order, :second_after_run_call_order
201
+ attr_reader :init_call_order, :run_call_order
202
+
203
+ before_init{ @first_before_init_call_order = next_call_order }
204
+ before_init{ @second_before_init_call_order = next_call_order }
205
+
206
+ after_init{ @first_after_init_call_order = next_call_order }
207
+ after_init{ @second_after_init_call_order = next_call_order }
208
+
209
+ before_run{ @first_before_run_call_order = next_call_order }
210
+ before_run{ @second_before_run_call_order = next_call_order }
211
+
212
+ after_run{ @first_after_run_call_order = next_call_order }
213
+ after_run{ @second_after_run_call_order = next_call_order }
214
+
215
+ def init!
216
+ @init_call_order = next_call_order
217
+ end
218
+
219
+ def run!
220
+ @run_call_order = next_call_order
221
+ end
222
+
223
+ def public_job
224
+ job
225
+ end
226
+
227
+ def public_params
228
+ params
229
+ end
230
+
231
+ def public_logger
232
+ logger
233
+ end
234
+
235
+ private
236
+
237
+ def next_call_order
238
+ @order ||= 0
239
+ @order += 1
240
+ end
241
+ end
242
+
243
+ class FakeRunner
244
+ attr_accessor :job, :params, :logger
245
+
246
+ def initialize
247
+ @job = Factory.string
248
+ @params = Factory.string
249
+ @logger = Factory.string
250
+ end
251
+ end
252
+
253
+ end
@@ -0,0 +1,132 @@
1
+ require 'assert'
2
+ require 'qs/job'
3
+
4
+ class Qs::Job
5
+
6
+ class UnitTests < Assert::Context
7
+ desc "Qs::Job"
8
+ setup do
9
+ @name = Factory.string
10
+ @params = { Factory.string => Factory.string }
11
+ @created_at = Factory.time
12
+
13
+ @job_class = Qs::Job
14
+ end
15
+ subject{ @job_class }
16
+
17
+ should have_imeths :parse
18
+
19
+ should "parse a job from a payload hash" do
20
+ payload = {
21
+ 'name' => @name,
22
+ 'params' => @params,
23
+ 'created_at' => @created_at.to_i
24
+ }
25
+ job = subject.parse(payload)
26
+ assert_instance_of subject, job
27
+ assert_equal payload['name'], job.name
28
+ assert_equal payload['params'], job.params
29
+ assert_equal Time.at(payload['created_at']), job.created_at
30
+ end
31
+
32
+ end
33
+
34
+ class InitTests < UnitTests
35
+ desc "when init"
36
+ setup do
37
+ @current_time = Factory.time
38
+ Assert.stub(Time, :now).with{ @current_time }
39
+
40
+ @job = @job_class.new(@name, @params, @created_at)
41
+ end
42
+ subject{ @job }
43
+
44
+ should have_readers :name, :params, :created_at
45
+ should have_imeths :to_payload
46
+
47
+ should "know its name, params and created at" do
48
+ assert_equal @name, subject.name
49
+ assert_equal @params, subject.params
50
+ assert_equal @created_at, subject.created_at
51
+ end
52
+
53
+ should "default its created at" do
54
+ job = @job_class.new(@name, @params)
55
+ assert_equal @current_time, job.created_at
56
+ end
57
+
58
+ should "return a payload hash using `to_payload`" do
59
+ payload_hash = subject.to_payload
60
+ expected = {
61
+ 'name' => @name,
62
+ 'params' => @params,
63
+ 'created_at' => @created_at.to_i
64
+ }
65
+ assert_equal expected, payload_hash
66
+ end
67
+
68
+ should "convert job names to strings using `to_payload`" do
69
+ job = @job_class.new(@name.to_sym, @params)
70
+ assert_equal @name, job.to_payload['name']
71
+ end
72
+
73
+ should "convert stringify its params using `to_payload`" do
74
+ params = { Factory.string.to_sym => Factory.string }
75
+ job = @job_class.new(@name, params)
76
+ exp = StringifyParams.new(params)
77
+ assert_equal exp, job.to_payload['params']
78
+ end
79
+
80
+ should "raise an error when given an invalid name or params" do
81
+ assert_raises(Qs::BadJobError){ @job_class.new(nil, @params) }
82
+ assert_raises(Qs::BadJobError){ @job_class.new(@name, nil) }
83
+ assert_raises(Qs::BadJobError){ @job_class.new(@name, Factory.string) }
84
+ end
85
+
86
+ should "have a custom inspect" do
87
+ reference = '0x0%x' % (subject.object_id << 1)
88
+ expected = "#<Qs::Job:#{reference} " \
89
+ "@name=#{subject.name.inspect} " \
90
+ "@params=#{subject.params.inspect} " \
91
+ "@created_at=#{subject.created_at.inspect}>"
92
+ assert_equal expected, subject.inspect
93
+ end
94
+
95
+ should "be comparable" do
96
+ matching = @job_class.new(@name, @params, @created_at)
97
+ assert_equal matching, subject
98
+ non_matching = @job_class.new(Factory.string, @params, @created_at)
99
+ assert_not_equal non_matching, subject
100
+ params = { Factory.string => Factory.string }
101
+ non_matching = @job_class.new(@name, params, @created_at)
102
+ assert_not_equal non_matching, subject
103
+ non_matching = @job_class.new(@name, @params, Factory.time)
104
+ assert_not_equal non_matching, subject
105
+ end
106
+
107
+ end
108
+
109
+ class StringifyParamsTests < UnitTests
110
+ desc "StringifyParams"
111
+ subject{ StringifyParams }
112
+
113
+ should have_imeths :new
114
+
115
+ should "convert all hash keys to strings" do
116
+ key, value = Factory.string.to_sym, Factory.string
117
+ result = subject.new({
118
+ key => value,
119
+ :hash => { key => [value] },
120
+ :array => [{ key => value }]
121
+ })
122
+ exp = {
123
+ key.to_s => value,
124
+ 'hash' => { key.to_s => [value] },
125
+ 'array' => [{ key.to_s => value }]
126
+ }
127
+ assert_equal exp, result
128
+ end
129
+
130
+ end
131
+
132
+ end
@@ -0,0 +1,38 @@
1
+ require 'assert'
2
+ require 'qs/logger'
3
+
4
+ class Qs::Logger
5
+
6
+ class UnitTests < Assert::Context
7
+ desc "Qs::Logger"
8
+ setup do
9
+ @real_logger = Factory.string
10
+ end
11
+ subject{ Qs::Logger }
12
+
13
+ should "set its loggers correctly in summary mode" do
14
+ logger = subject.new(@real_logger, false)
15
+ assert_equal @real_logger, logger.summary
16
+ assert_instance_of Qs::NullLogger, logger.verbose
17
+ end
18
+
19
+ should "set its loggers correctly in verbose mode" do
20
+ logger = subject.new(@real_logger, true)
21
+ assert_instance_of Qs::NullLogger, logger.summary
22
+ assert_equal @real_logger, logger.verbose
23
+ end
24
+
25
+ end
26
+
27
+ class NullLoggerTests < Assert::Context
28
+ desc "Qs::NullLogger"
29
+ setup do
30
+ @null_logger = Qs::NullLogger.new
31
+ end
32
+ subject{ @null_logger }
33
+
34
+ should have_imeths :debug, :info, :error
35
+
36
+ end
37
+
38
+ end