kicks 3.0.0.pre
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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +24 -0
- data/.gitignore +12 -0
- data/ChangeLog.md +142 -0
- data/Dockerfile +24 -0
- data/Dockerfile.slim +20 -0
- data/Gemfile +8 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +209 -0
- data/Rakefile +12 -0
- data/bin/sneakers +6 -0
- data/docker-compose.yml +24 -0
- data/examples/benchmark_worker.rb +22 -0
- data/examples/max_retry_handler.rb +68 -0
- data/examples/metrics_worker.rb +34 -0
- data/examples/middleware_worker.rb +36 -0
- data/examples/newrelic_metrics_worker.rb +40 -0
- data/examples/profiling_worker.rb +69 -0
- data/examples/sneakers.conf.rb.example +11 -0
- data/examples/title_scraper.rb +36 -0
- data/examples/workflow_worker.rb +23 -0
- data/kicks.gemspec +44 -0
- data/lib/sneakers/cli.rb +122 -0
- data/lib/sneakers/concerns/logging.rb +34 -0
- data/lib/sneakers/concerns/metrics.rb +34 -0
- data/lib/sneakers/configuration.rb +125 -0
- data/lib/sneakers/content_encoding.rb +47 -0
- data/lib/sneakers/content_type.rb +47 -0
- data/lib/sneakers/error_reporter.rb +33 -0
- data/lib/sneakers/errors.rb +2 -0
- data/lib/sneakers/handlers/maxretry.rb +219 -0
- data/lib/sneakers/handlers/oneshot.rb +26 -0
- data/lib/sneakers/metrics/logging_metrics.rb +16 -0
- data/lib/sneakers/metrics/newrelic_metrics.rb +32 -0
- data/lib/sneakers/metrics/null_metrics.rb +13 -0
- data/lib/sneakers/metrics/statsd_metrics.rb +21 -0
- data/lib/sneakers/middleware/config.rb +23 -0
- data/lib/sneakers/publisher.rb +49 -0
- data/lib/sneakers/queue.rb +87 -0
- data/lib/sneakers/runner.rb +91 -0
- data/lib/sneakers/spawner.rb +30 -0
- data/lib/sneakers/support/production_formatter.rb +11 -0
- data/lib/sneakers/support/utils.rb +18 -0
- data/lib/sneakers/tasks.rb +66 -0
- data/lib/sneakers/version.rb +3 -0
- data/lib/sneakers/worker.rb +162 -0
- data/lib/sneakers/workergroup.rb +60 -0
- data/lib/sneakers.rb +125 -0
- data/log/.gitkeep +0 -0
- data/scripts/local_integration +2 -0
- data/scripts/local_worker +3 -0
- data/spec/fixtures/integration_worker.rb +18 -0
- data/spec/fixtures/require_worker.rb +23 -0
- data/spec/gzip_helper.rb +15 -0
- data/spec/sneakers/cli_spec.rb +75 -0
- data/spec/sneakers/concerns/logging_spec.rb +39 -0
- data/spec/sneakers/concerns/metrics_spec.rb +38 -0
- data/spec/sneakers/configuration_spec.rb +97 -0
- data/spec/sneakers/content_encoding_spec.rb +81 -0
- data/spec/sneakers/content_type_spec.rb +81 -0
- data/spec/sneakers/integration_spec.rb +158 -0
- data/spec/sneakers/publisher_spec.rb +179 -0
- data/spec/sneakers/queue_spec.rb +169 -0
- data/spec/sneakers/runner_spec.rb +70 -0
- data/spec/sneakers/sneakers_spec.rb +77 -0
- data/spec/sneakers/support/utils_spec.rb +44 -0
- data/spec/sneakers/tasks/sneakers_run_spec.rb +115 -0
- data/spec/sneakers/worker_handlers_spec.rb +469 -0
- data/spec/sneakers/worker_spec.rb +712 -0
- data/spec/sneakers/workergroup_spec.rb +83 -0
- data/spec/spec_helper.rb +21 -0
- metadata +352 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'sneakers'
|
2
|
+
require 'sneakers/runner'
|
3
|
+
|
4
|
+
task :environment
|
5
|
+
|
6
|
+
namespace :sneakers do
|
7
|
+
desc "Start work (set $WORKERS=Klass1,Klass2)"
|
8
|
+
task :run do
|
9
|
+
Sneakers.server = true
|
10
|
+
Rake::Task['environment'].invoke
|
11
|
+
|
12
|
+
if defined?(::Rails)
|
13
|
+
if Rails.autoloaders.zeitwerk_enabled?
|
14
|
+
::Zeitwerk::Loader.eager_load_all
|
15
|
+
else
|
16
|
+
::Rails.application.eager_load!
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
workers, missing_workers = get_worker_classes
|
21
|
+
|
22
|
+
unless missing_workers.nil? || missing_workers.empty?
|
23
|
+
puts "Missing workers: #{missing_workers.join(', ')}" if missing_workers
|
24
|
+
puts "Did you `require` properly?"
|
25
|
+
exit(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
if workers.empty?
|
29
|
+
puts <<EOF
|
30
|
+
Error: No workers found.
|
31
|
+
Please set the classes of the workers you want to run like so:
|
32
|
+
|
33
|
+
$ export WORKERS=MyWorker,FooWorker
|
34
|
+
$ rake sneakers:run
|
35
|
+
|
36
|
+
You can also configure them with
|
37
|
+
$ Sneakers.rake_worker_classes
|
38
|
+
|
39
|
+
If you use something that responds to :call it will execute that
|
40
|
+
|
41
|
+
Eventually, if nothing before applied, every class is used where you directly included the Sneakers::Worker
|
42
|
+
EOF
|
43
|
+
exit(1)
|
44
|
+
end
|
45
|
+
opts = (!ENV['WORKER_COUNT'].nil? ? {:workers => ENV['WORKER_COUNT'].to_i} : {})
|
46
|
+
r = Sneakers::Runner.new(workers, opts)
|
47
|
+
|
48
|
+
r.run
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def get_worker_classes
|
54
|
+
if ENV["WORKERS"]
|
55
|
+
Sneakers::Utils.parse_workers(ENV['WORKERS'])
|
56
|
+
elsif Sneakers.rake_worker_classes
|
57
|
+
if Sneakers.rake_worker_classes.respond_to?(:call)
|
58
|
+
[Sneakers.rake_worker_classes.call]
|
59
|
+
else
|
60
|
+
[Sneakers.rake_worker_classes]
|
61
|
+
end
|
62
|
+
else
|
63
|
+
[Sneakers::Worker::Classes]
|
64
|
+
end || [[]]
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'sneakers/queue'
|
2
|
+
require 'sneakers/support/utils'
|
3
|
+
|
4
|
+
module Sneakers
|
5
|
+
module Worker
|
6
|
+
attr_reader :queue, :id, :opts
|
7
|
+
|
8
|
+
# For now, a worker is hardly dependant on these concerns
|
9
|
+
# (because it uses methods from them directly.)
|
10
|
+
include Concerns::Logging
|
11
|
+
include Concerns::Metrics
|
12
|
+
include Sneakers::ErrorReporter
|
13
|
+
|
14
|
+
def initialize(queue = nil, pool = nil, opts = {})
|
15
|
+
opts = opts.merge(self.class.queue_opts || {})
|
16
|
+
queue_name = self.class.queue_name
|
17
|
+
opts = Sneakers::CONFIG.merge(opts)
|
18
|
+
|
19
|
+
@should_ack = opts[:ack]
|
20
|
+
@pool = pool || Concurrent::FixedThreadPool.new(opts[:threads] || Sneakers::Configuration::DEFAULTS[:threads])
|
21
|
+
@call_with_params = respond_to?(:work_with_params)
|
22
|
+
@content_type = opts[:content_type]
|
23
|
+
@content_encoding = opts[:content_encoding]
|
24
|
+
|
25
|
+
@queue = queue || Sneakers::Queue.new(
|
26
|
+
queue_name,
|
27
|
+
opts
|
28
|
+
)
|
29
|
+
|
30
|
+
@opts = opts
|
31
|
+
@id = Utils.make_worker_id(queue_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def ack!; :ack end
|
35
|
+
def reject!; :reject; end
|
36
|
+
def requeue!; :requeue; end
|
37
|
+
|
38
|
+
def publish(msg, opts)
|
39
|
+
to_queue = opts.delete(:to_queue)
|
40
|
+
opts[:routing_key] ||= to_queue
|
41
|
+
return unless opts[:routing_key]
|
42
|
+
serialized_msg = Sneakers::ContentType.serialize(msg, opts[:content_type])
|
43
|
+
encoded_msg = Sneakers::ContentEncoding.encode(serialized_msg, opts[:content_encoding])
|
44
|
+
@queue.exchange.publish(encoded_msg, **opts)
|
45
|
+
end
|
46
|
+
|
47
|
+
def do_work(delivery_info, metadata, msg, handler)
|
48
|
+
worker_trace "Working off: #{msg.inspect}"
|
49
|
+
|
50
|
+
@pool.post do
|
51
|
+
process_work(delivery_info, metadata, msg, handler)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def process_work(delivery_info, metadata, msg, handler)
|
56
|
+
res = nil
|
57
|
+
error = nil
|
58
|
+
|
59
|
+
begin
|
60
|
+
metrics.increment("work.#{self.class.name}.started")
|
61
|
+
metrics.timing("work.#{self.class.name}.time") do
|
62
|
+
decoded_msg = ContentEncoding.decode(msg, @content_encoding || metadata && metadata[:content_encoding])
|
63
|
+
deserialized_msg = ContentType.deserialize(decoded_msg, @content_type || metadata && metadata[:content_type])
|
64
|
+
|
65
|
+
app = -> (deserialized_msg, delivery_info, metadata, handler) do
|
66
|
+
if @call_with_params
|
67
|
+
work_with_params(deserialized_msg, delivery_info, metadata)
|
68
|
+
else
|
69
|
+
work(deserialized_msg)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
middlewares = Sneakers.middleware.to_a
|
74
|
+
block_to_call = middlewares.reverse.reduce(app) do |mem, h|
|
75
|
+
h[:class].new(mem, *h[:args])
|
76
|
+
end
|
77
|
+
res = block_to_call.call(deserialized_msg, delivery_info, metadata, handler)
|
78
|
+
end
|
79
|
+
rescue SignalException, SystemExit
|
80
|
+
# ServerEngine handles these exceptions, so they are not expected to be raised within the worker.
|
81
|
+
# Nevertheless, they are listed here to ensure that they are not caught by the rescue block below.
|
82
|
+
raise
|
83
|
+
rescue Exception => ex
|
84
|
+
res = :error
|
85
|
+
error = ex
|
86
|
+
worker_error(ex, log_msg: log_msg(msg), class: self.class.name,
|
87
|
+
message: msg, delivery_info: delivery_info, metadata: metadata)
|
88
|
+
ensure
|
89
|
+
if @should_ack
|
90
|
+
case res
|
91
|
+
# note to future-self. never acknowledge multiple (multiple=true) messages under threads.
|
92
|
+
when :ack then handler.acknowledge(delivery_info, metadata, msg)
|
93
|
+
when :error then handler.error(delivery_info, metadata, msg, error)
|
94
|
+
when :reject then handler.reject(delivery_info, metadata, msg)
|
95
|
+
when :requeue then handler.reject(delivery_info, metadata, msg, true)
|
96
|
+
else
|
97
|
+
handler.noop(delivery_info, metadata, msg)
|
98
|
+
end
|
99
|
+
metrics.increment("work.#{self.class.name}.handled.#{res || 'noop'}")
|
100
|
+
end
|
101
|
+
|
102
|
+
metrics.increment("work.#{self.class.name}.ended")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def stop
|
107
|
+
worker_trace "Stopping worker: unsubscribing."
|
108
|
+
@queue.unsubscribe
|
109
|
+
worker_trace "Stopping worker: shutting down thread pool."
|
110
|
+
@pool.shutdown
|
111
|
+
@pool.wait_for_termination
|
112
|
+
worker_trace "Stopping worker: I'm gone."
|
113
|
+
end
|
114
|
+
|
115
|
+
def run
|
116
|
+
worker_trace "New worker: subscribing."
|
117
|
+
@queue.subscribe(self)
|
118
|
+
worker_trace "New worker: I'm alive."
|
119
|
+
end
|
120
|
+
|
121
|
+
# Construct a log message with some standard prefix for this worker
|
122
|
+
def log_msg(msg)
|
123
|
+
"[#{@id}][#{Thread.current}][#{@queue.name}][#{@queue.opts}] #{msg}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def worker_trace(msg)
|
127
|
+
logger.debug(log_msg(msg))
|
128
|
+
end
|
129
|
+
|
130
|
+
Classes = []
|
131
|
+
|
132
|
+
def self.included(base)
|
133
|
+
base.extend ClassMethods
|
134
|
+
Classes << base if base.is_a? Class
|
135
|
+
end
|
136
|
+
|
137
|
+
module ClassMethods
|
138
|
+
attr_reader :queue_opts
|
139
|
+
attr_reader :queue_name
|
140
|
+
|
141
|
+
def from_queue(q, opts={})
|
142
|
+
@queue_name = q.to_s
|
143
|
+
@queue_opts = opts
|
144
|
+
end
|
145
|
+
|
146
|
+
def enqueue(msg, opts={})
|
147
|
+
opts[:routing_key] ||= @queue_opts[:routing_key]
|
148
|
+
opts[:content_type] ||= @queue_opts[:content_type]
|
149
|
+
opts[:content_encoding] ||= @queue_opts[:content_encoding]
|
150
|
+
opts[:to_queue] ||= @queue_name
|
151
|
+
|
152
|
+
publisher.publish(msg, opts)
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def publisher
|
158
|
+
@publisher ||= Sneakers::Publisher.new(queue_opts)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Sneakers
|
2
|
+
module WorkerGroup
|
3
|
+
@workers = []
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@stop_flag = ServerEngine::BlockingFlag.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def before_fork
|
10
|
+
fbefore = Sneakers::CONFIG[:hooks][:before_fork]
|
11
|
+
fbefore.call if fbefore
|
12
|
+
end
|
13
|
+
|
14
|
+
def after_fork # note! this is not Serverengine#after_start, this is ours!
|
15
|
+
fafter = Sneakers::CONFIG[:hooks][:after_fork]
|
16
|
+
fafter.call if fafter
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
after_fork
|
21
|
+
|
22
|
+
# Allocate single thread pool if share_threads is set. This improves load balancing
|
23
|
+
# when used with many workers.
|
24
|
+
pool = config[:share_threads] ? Concurrent::FixedThreadPool.new(config[:threads]) : nil
|
25
|
+
|
26
|
+
worker_classes = config[:worker_classes]
|
27
|
+
|
28
|
+
if worker_classes.respond_to? :call
|
29
|
+
worker_classes = worker_classes.call
|
30
|
+
end
|
31
|
+
|
32
|
+
# If we don't provide a connection to a worker,
|
33
|
+
# the queue used in the worker will create a new one
|
34
|
+
|
35
|
+
@workers = worker_classes.map do |worker_class|
|
36
|
+
worker_class.new(nil, pool, { connection: config[:connection] })
|
37
|
+
end
|
38
|
+
|
39
|
+
# if more than one worker this should be per worker
|
40
|
+
# accumulate clients and consumers as well
|
41
|
+
@workers.each do |worker|
|
42
|
+
worker.run
|
43
|
+
end
|
44
|
+
# end per worker
|
45
|
+
#
|
46
|
+
until @stop_flag.wait_for_set(Sneakers::CONFIG[:amqp_heartbeat])
|
47
|
+
Sneakers.logger.debug("Heartbeat: running threads [#{Thread.list.count}]")
|
48
|
+
# report aggregated stats?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def stop
|
53
|
+
Sneakers.logger.info("Shutting down workers")
|
54
|
+
@workers.each do |worker|
|
55
|
+
worker.stop
|
56
|
+
end
|
57
|
+
@stop_flag.set!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/sneakers.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'sneakers/version'
|
2
|
+
require 'concurrent/executors'
|
3
|
+
require 'bunny'
|
4
|
+
require 'logger'
|
5
|
+
require 'serverengine'
|
6
|
+
|
7
|
+
module Sneakers
|
8
|
+
module Handlers
|
9
|
+
end
|
10
|
+
module Concerns
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'sneakers/configuration'
|
15
|
+
require 'sneakers/errors'
|
16
|
+
require 'sneakers/support/production_formatter'
|
17
|
+
require 'sneakers/concerns/logging'
|
18
|
+
require 'sneakers/concerns/metrics'
|
19
|
+
require 'sneakers/handlers/oneshot'
|
20
|
+
require 'sneakers/content_type'
|
21
|
+
require 'sneakers/content_encoding'
|
22
|
+
require 'sneakers/middleware/config'
|
23
|
+
require 'sneakers/worker'
|
24
|
+
require 'sneakers/publisher'
|
25
|
+
|
26
|
+
module Sneakers
|
27
|
+
extend self
|
28
|
+
|
29
|
+
CONFIG = Configuration.new
|
30
|
+
|
31
|
+
def configure(opts={})
|
32
|
+
# worker > userland > defaults
|
33
|
+
CONFIG.merge!(opts)
|
34
|
+
setup_general_logger!
|
35
|
+
setup_worker_concerns!
|
36
|
+
setup_general_publisher!
|
37
|
+
@configured = true
|
38
|
+
end
|
39
|
+
|
40
|
+
def clear!
|
41
|
+
CONFIG.clear
|
42
|
+
@logger = nil
|
43
|
+
@publisher = nil
|
44
|
+
@configured = false
|
45
|
+
end
|
46
|
+
|
47
|
+
def daemonize!(loglevel=Logger::INFO)
|
48
|
+
CONFIG[:log] = 'sneakers.log'
|
49
|
+
CONFIG[:daemonize] = true
|
50
|
+
setup_general_logger!
|
51
|
+
logger.level = loglevel
|
52
|
+
end
|
53
|
+
|
54
|
+
def rake_worker_classes=(worker_classes)
|
55
|
+
@rake_worker_classes = worker_classes
|
56
|
+
end
|
57
|
+
|
58
|
+
def rake_worker_classes
|
59
|
+
@rake_worker_classes
|
60
|
+
end
|
61
|
+
|
62
|
+
def logger=(logger)
|
63
|
+
@logger = logger
|
64
|
+
end
|
65
|
+
|
66
|
+
def logger
|
67
|
+
@logger
|
68
|
+
end
|
69
|
+
|
70
|
+
def publish(msg, routing)
|
71
|
+
@publisher.publish(msg, routing)
|
72
|
+
end
|
73
|
+
|
74
|
+
def configured?
|
75
|
+
@configured
|
76
|
+
end
|
77
|
+
|
78
|
+
def server=(server)
|
79
|
+
@server = server
|
80
|
+
end
|
81
|
+
|
82
|
+
def server?
|
83
|
+
@server
|
84
|
+
end
|
85
|
+
|
86
|
+
def configure_server
|
87
|
+
yield self if server?
|
88
|
+
end
|
89
|
+
|
90
|
+
# Register a proc to handle any error which occurs within the Sneakers process.
|
91
|
+
#
|
92
|
+
# Sneakers.error_reporters << proc { |exception, worker, context_hash| MyErrorService.notify(exception, context_hash) }
|
93
|
+
#
|
94
|
+
# The default error handler logs errors to Sneakers.logger.
|
95
|
+
# Ripped off from https://github.com/mperham/sidekiq/blob/6ad6a3aa330deebd76c6cf0d353f66abd3bef93b/lib/sidekiq.rb#L165-L174
|
96
|
+
def error_reporters
|
97
|
+
CONFIG[:error_reporters]
|
98
|
+
end
|
99
|
+
|
100
|
+
def middleware
|
101
|
+
@middleware ||= Sneakers::Middleware::Config
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def setup_general_logger!
|
107
|
+
if [:info, :debug, :error, :warn].all?{ |meth| CONFIG[:log].respond_to?(meth) }
|
108
|
+
@logger = CONFIG[:log]
|
109
|
+
else
|
110
|
+
@logger = ServerEngine::DaemonLogger.new(CONFIG[:log])
|
111
|
+
@logger.formatter = Sneakers::Support::ProductionFormatter
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def setup_worker_concerns!
|
116
|
+
Worker.configure_logger(Sneakers::logger)
|
117
|
+
Worker.configure_metrics(CONFIG[:metrics])
|
118
|
+
CONFIG[:handler] ||= Sneakers::Handlers::Oneshot
|
119
|
+
end
|
120
|
+
|
121
|
+
def setup_general_publisher!
|
122
|
+
@publisher = Sneakers::Publisher.new
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
data/log/.gitkeep
ADDED
File without changes
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'sneakers'
|
2
|
+
require 'thread'
|
3
|
+
require 'redis'
|
4
|
+
|
5
|
+
|
6
|
+
redis_addr = compose_or_localhost("redis")
|
7
|
+
puts "REDIS is at #{redis_addr}"
|
8
|
+
$redis = Redis.new(:host => redis_addr)
|
9
|
+
|
10
|
+
class IntegrationWorker
|
11
|
+
include Sneakers::Worker
|
12
|
+
|
13
|
+
def work(msg)
|
14
|
+
$redis.incr(self.class.queue_name)
|
15
|
+
ack!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'sneakers'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
|
5
|
+
class TitleScraper
|
6
|
+
include Sneakers::Worker
|
7
|
+
|
8
|
+
from_queue 'downloads'
|
9
|
+
|
10
|
+
def work(msg)
|
11
|
+
title = extract_title(open(msg))
|
12
|
+
logger.info "FOUND <#{title}>"
|
13
|
+
ack!
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def extract_title(html)
|
19
|
+
html =~ /<title>(.*?)<\/title>/
|
20
|
+
$1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
data/spec/gzip_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'zlib'
|
3
|
+
|
4
|
+
# Simple gzip encoder/decoder for testing
|
5
|
+
def gzip_compress(s)
|
6
|
+
io = StringIO.new('w')
|
7
|
+
w = Zlib::GzipWriter.new(io)
|
8
|
+
w.write(s)
|
9
|
+
w.close
|
10
|
+
io.string
|
11
|
+
end
|
12
|
+
|
13
|
+
def gzip_decompress(s)
|
14
|
+
Zlib::GzipReader.new(StringIO.new(s, 'rb')).read
|
15
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sneakers'
|
3
|
+
require 'sneakers/cli'
|
4
|
+
require 'sneakers/runner'
|
5
|
+
|
6
|
+
describe Sneakers::CLI do
|
7
|
+
describe "#work" do
|
8
|
+
before do
|
9
|
+
any_instance_of(Sneakers::Runner) do |runner|
|
10
|
+
stub(runner).run{ true }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
after do
|
15
|
+
# require cleanup
|
16
|
+
Object.send(:remove_const, :TitleScraper) if Object.constants.include?(:TitleScraper)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'with dirty class loading' do
|
20
|
+
it "should perform a run" do
|
21
|
+
any_instance_of(Sneakers::Runner) do |runner|
|
22
|
+
mock(runner).run{ true }
|
23
|
+
end
|
24
|
+
out = capture_io{ Sneakers::CLI.start [
|
25
|
+
'work',
|
26
|
+
"TitleScraper",
|
27
|
+
"--require=#{File.expand_path('../fixtures/require_worker.rb', File.dirname(__FILE__))}"
|
28
|
+
]}.join ''
|
29
|
+
|
30
|
+
_(out).must_match(/Workers.*:.*TitleScraper.*/)
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should be able to run as front-running process" do
|
35
|
+
out = capture_io{ Sneakers::CLI.start [
|
36
|
+
'work',
|
37
|
+
"TitleScraper",
|
38
|
+
"--require=#{File.expand_path('../fixtures/require_worker.rb', File.dirname(__FILE__))}"
|
39
|
+
]}.join ''
|
40
|
+
|
41
|
+
_(out).must_match(/Log.*Console/)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should be able to run as daemonized process" do
|
45
|
+
out = capture_io{ Sneakers::CLI.start [
|
46
|
+
'work',
|
47
|
+
"TitleScraper",
|
48
|
+
"--daemonize",
|
49
|
+
"--require=#{File.expand_path('../fixtures/require_worker.rb', File.dirname(__FILE__))}"
|
50
|
+
]}.join ''
|
51
|
+
|
52
|
+
_(out).must_match(/sneakers.log/)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should fail when no workers found" do
|
57
|
+
out = capture_io{ Sneakers::CLI.start ['work', 'TitleScraper'] }.join ''
|
58
|
+
_(out).must_match(/Missing workers: TitleScraper/)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should run all workers when run without specifying any" do
|
62
|
+
out = capture_io{ Sneakers::CLI.start [
|
63
|
+
"work",
|
64
|
+
"--require=#{File.expand_path("../fixtures/require_worker.rb", File.dirname(__FILE__))}"
|
65
|
+
]}.join ''
|
66
|
+
|
67
|
+
_(out).must_match(/Workers.*:.*TitleScraper.*/)
|
68
|
+
end
|
69
|
+
|
70
|
+
after do
|
71
|
+
# require cleanup
|
72
|
+
Object.send(:remove_const, :TitleScraper) if Object.constants.include?(:TitleScraper)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sneakers'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
|
6
|
+
class Foobar
|
7
|
+
include Sneakers::Concerns::Logging
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Sneakers::Concerns::Logging do
|
11
|
+
describe ".configure" do
|
12
|
+
before do
|
13
|
+
Foobar.logger = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should configure a default logger when included" do
|
17
|
+
_(Foobar.logger).must_be_nil
|
18
|
+
Foobar.configure_logger
|
19
|
+
_(Foobar.logger).wont_be_nil
|
20
|
+
_(Foobar.logger.formatter).must_equal Sneakers::Support::ProductionFormatter
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should supply accessible instance logger" do
|
24
|
+
_(Foobar.logger).must_be_nil
|
25
|
+
Foobar.configure_logger
|
26
|
+
f = Foobar.new
|
27
|
+
_(f.logger).must_equal Foobar.logger
|
28
|
+
_(f.logger).wont_be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should configure a given logger when specified" do
|
32
|
+
_(Foobar.logger).must_be_nil
|
33
|
+
log = Logger.new(STDOUT)
|
34
|
+
Foobar.configure_logger(log)
|
35
|
+
_(Foobar.logger).must_equal log
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sneakers'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
|
6
|
+
class Foometrics
|
7
|
+
include Sneakers::Concerns::Metrics
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Sneakers::Concerns::Metrics do
|
11
|
+
describe ".configure" do
|
12
|
+
before do
|
13
|
+
Foometrics.metrics = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should configure a default logger when included" do
|
17
|
+
_(Foometrics.metrics).must_be_nil
|
18
|
+
Foometrics.configure_metrics
|
19
|
+
_(Foometrics.metrics).wont_be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should supply accessible instance logger" do
|
23
|
+
_(Foometrics.metrics).must_be_nil
|
24
|
+
Foometrics.configure_metrics
|
25
|
+
f = Foometrics.new
|
26
|
+
_(f.metrics).must_equal Foometrics.metrics
|
27
|
+
_(f.metrics).wont_be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should configure a given metrics when specified" do
|
31
|
+
_(Foometrics.metrics).must_be_nil
|
32
|
+
o = Object.new
|
33
|
+
Foometrics.configure_metrics(o)
|
34
|
+
_(Foometrics.metrics).must_equal o
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|