parallel_minion 1.1.0 → 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 540c677d0eab24f63240820396a79c81beaf12fd
4
- data.tar.gz: 5e523e81bb240522758a1a2ef52b9ccfaa67ecab
3
+ metadata.gz: c4ec6fafacfe5cf2b3e090a1e1ed733fd6c3bc33
4
+ data.tar.gz: a054a763962d6ee10fb8bc75587ec4d1c5f3d456
5
5
  SHA512:
6
- metadata.gz: b509dc355be13737e83c715479d660dfc390a0fd0fb3efc0d9f19b4c29d1c51f8009ed09f8f889d714ad1aff46a2a9735f270691cb30ba02c0004cf1c908611f
7
- data.tar.gz: 299fb7d5ff78e5e2ebeed0db274f6481f258afc9d5b82bb2ceaeb6265c2fb68b2c41f2f2231c9627fe3d7a97b51a1650694886d51a3b5c3888b61970c3277865
6
+ metadata.gz: 13909e2ad5221826dd9ac6cbbd15364566e4cf6b76fc33bc77d7cc8c35acc6ddb06cc3b53845bb6df053b031a13fb20808ce10c7e2f7bbb141912e2a86a03aff
7
+ data.tar.gz: 01b7b72f77160109e4bfa9ab995c6f98be128a7e837bc284d3932f399cdae9389b866d83a534d5ec618425c201d3607d5ff19b06bf803a3054da4edc409bcaa5
data/README.md CHANGED
@@ -15,9 +15,11 @@ existing code to use minions.
15
15
  ## Example
16
16
 
17
17
  ```ruby
18
- minion = ParallelMinion::Minion.new(10.days.ago,
19
- description: 'Doing something else in parallel',
20
- timeout: 1000) do |date|
18
+ minion = ParallelMinion::Minion.new(
19
+ 10.days.ago,
20
+ description: 'Doing something else in parallel',
21
+ timeout: 1000
22
+ ) do |date|
21
23
  MyTable.where('created_at <= ?', date).count
22
24
  end
23
25
 
@@ -59,7 +61,7 @@ This project uses [Semantic Versioning](http://semver.org/).
59
61
 
60
62
  ## License
61
63
 
62
- Copyright 2013, 2014, 2105 Reid Morrison
64
+ Copyright 2013, 2014, 2015, 2016, 2017 Reid Morrison
63
65
 
64
66
  Licensed under the Apache License, Version 2.0 (the "License");
65
67
  you may not use this file except in compliance with the License.
data/Rakefile CHANGED
@@ -1,28 +1,31 @@
1
- require 'rake/clean'
2
- require 'rake/testtask'
1
+ # Setup bundler to avoid having to run bundle exec all the time.
2
+ require 'rubygems'
3
+ require 'bundler/setup'
3
4
 
4
- $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
5
- require 'parallel_minion/version'
5
+ require 'rake/testtask'
6
+ require_relative 'lib/parallel_minion/version'
6
7
 
7
8
  task :gem do
8
- system "gem build parallel_minion.gemspec"
9
+ system 'gem build parallel_minion.gemspec'
9
10
  end
10
11
 
11
12
  task :publish => :gem do
12
13
  system "git tag -a v#{ParallelMinion::VERSION} -m 'Tagging #{ParallelMinion::VERSION}'"
13
- system "git push --tags"
14
+ system 'git push --tags'
14
15
  system "gem push parallel_minion-#{ParallelMinion::VERSION}.gem"
15
16
  system "rm parallel_minion-#{ParallelMinion::VERSION}.gem"
16
17
  end
17
18
 
18
- desc "Run Test Suite"
19
- task :test do
20
- Rake::TestTask.new(:functional) do |t|
21
- t.test_files = FileList['test/*_test.rb']
22
- t.verbose = true
23
- end
24
-
25
- Rake::Task['functional'].invoke
19
+ Rake::TestTask.new(:test) do |t|
20
+ t.pattern = 'test/**/*_test.rb'
21
+ t.verbose = true
22
+ t.warning = false
26
23
  end
27
24
 
28
- task :default => :test
25
+ # By default run tests against all appraisals
26
+ if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
27
+ require 'appraisal'
28
+ task default: :appraisal
29
+ else
30
+ task default: :test
31
+ end
@@ -2,7 +2,7 @@ require 'thread'
2
2
  require 'semantic_logger'
3
3
 
4
4
  module ParallelMinion
5
- autoload :Minion, 'parallel_minion/minion'
5
+ autoload :Minion, 'parallel_minion/minion'
6
6
  end
7
7
 
8
8
  require 'parallel_minion/railtie' if defined?(Rails)
@@ -19,26 +19,34 @@ module ParallelMinion
19
19
  # Returns nil if the minion is still running
20
20
  attr_reader :duration
21
21
 
22
+ # Metrics [String]
23
+ attr_reader :metric, :wait_metric
24
+
25
+ attr_reader :on_timeout, :log_exception, :start_time
26
+
22
27
  # Give an infinite amount of time to wait for a Minion to complete a task
23
28
  INFINITE = 0
24
29
 
25
- # Sets whether minions are enabled to run in their own threads
26
- #
27
- # By Setting _enabled_ to false all Minions that have not yet been started
28
- # will run in the thread from which it is created and not on its own thread
30
+ # Sets whether Minions should run in a separate thread.
29
31
  #
30
- # This is useful:
31
- # - to run tests under the Capybara gem
32
- # - when debugging code so that all code is run sequentially in the current thread
33
- #
34
- # Note: Not recommended to set this setting to false in Production
32
+ # By Setting _enabled_ to false all Minions that have not yet been created
33
+ # will run in the thread in which it is created.
34
+ # - Development:
35
+ # Use a debugger, since the code will run in the current thread.
36
+ # - Test:
37
+ # Keep test execution in the current thread.
38
+ # Supports rolling back database changes after each test, since all changes are
39
+ # performed on the same database connection.
40
+ # - Production:
41
+ # Batch processing in Rocket Job where throughput is more important than latency.
42
+ # http://rocketjob.io
35
43
  def self.enabled=(enabled)
36
- @@enabled = enabled
44
+ @enabled = enabled
37
45
  end
38
46
 
39
47
  # Returns whether minions are enabled to run in their own threads
40
48
  def self.enabled?
41
- @@enabled
49
+ @enabled
42
50
  end
43
51
 
44
52
  # The list of classes for which the current scope must be copied into the
@@ -47,25 +55,85 @@ module ParallelMinion
47
55
  # Example:
48
56
  # ...
49
57
  def self.scoped_classes
50
- @@scoped_classes
58
+ @scoped_classes
51
59
  end
52
60
 
53
- # Create a new thread and
54
- # Log the time for the thread to complete processing
55
- # The exception without stack trace is logged whenever an exception is
56
- # thrown in the thread
57
- # Re-raises any unhandled exception in the calling thread when it call #result
58
- # Copy the logging tags and specified ActiveRecord scopes to the new thread
61
+ def self.scoped_classes=(scoped_classes)
62
+ @scoped_classes = scoped_classes.dup
63
+ end
64
+
65
+ # Change the log level for the Started log message.
66
+ #
67
+ # Default: :info
68
+ #
69
+ # Valid levels:
70
+ # :trace, :debug, :info, :warn, :error, :fatal
71
+ def self.started_log_level=(level)
72
+ raise(ArgumentError, "Invalid log level: #{level}") unless SemanticLogger::LEVELS.include?(level)
73
+ @started_log_level = level
74
+ end
75
+
76
+ def self.started_log_level
77
+ @started_log_level
78
+ end
79
+
80
+ # Change the log level for the Completed log message.
81
+ #
82
+ # Default: :info
83
+ #
84
+ # Valid levels:
85
+ # :trace, :debug, :info, :warn, :error, :fatal
86
+ def self.completed_log_level=(level)
87
+ raise(ArgumentError, "Invalid log level: #{level}") unless SemanticLogger::LEVELS.include?(level)
88
+ @completed_log_level = level
89
+ end
90
+
91
+ def self.completed_log_level
92
+ @completed_log_level
93
+ end
94
+
95
+ self.started_log_level = :info
96
+ self.completed_log_level = :info
97
+ self.enabled = true
98
+ self.scoped_classes = []
99
+ logger.name = 'Minion'
100
+
101
+ # Create a new Minion
102
+ #
103
+ # Creates a new thread and logs the time for the supplied block to complete processing.
104
+ # The exception without stack trace is logged whenever an exception is thrown in the thread.
105
+ #
106
+ # Re-raises any unhandled exception in the calling thread when `#result` is called.
107
+ # Copies the logging tags and specified ActiveRecord scopes to the new thread.
59
108
  #
60
109
  # Parameters
110
+ # *arguments
111
+ # Any number of arguments can be supplied that are passed into the block
112
+ # in the order they are listed.
113
+ #
114
+ # Note:
115
+ # All arguments must be supplied by copy and not by reference.
116
+ # For example, use `#dup` to create copies of passed data.
117
+ # Pass by copy is critical to prevent concurrency issues when multiple threads
118
+ # attempt to update the same object at the same time.
119
+ #
120
+ # Proc / lambda
121
+ # A block of code must be supplied that the Minion will execute.
122
+ #
123
+ # Note:
124
+ # This block will be executed within the scope of the created minion instance
125
+ # and _not_ within the scope of where the Proc/lambda was originally created.
126
+ # This is done to force all parameters to be passed in explicitly
127
+ # and should be read-only or duplicates of the original data.
128
+ #
61
129
  # :description [String]
62
- # Description for this task that the Minion is performing
63
- # Put in the log file along with the time take to complete the task
130
+ # Description for this task that the Minion is performing.
131
+ # Written to the log file along with the time take to complete the task.
64
132
  #
65
133
  # :timeout [Integer]
66
134
  # Maximum amount of time in milli-seconds that the task may take to complete
67
- # before #result times out
68
- # Set to 0 to give the thread an infinite amount of time to complete
135
+ # before #result times out.
136
+ # Set to 0 to give the thread an infinite amount of time to complete.
69
137
  # Default: 0 ( Wait forever )
70
138
  #
71
139
  # Notes:
@@ -76,11 +144,6 @@ module ParallelMinion
76
144
  # then :timeout is ignored and assumed to be Minion::INFINITE
77
145
  # since the code is run in the calling thread when the Minion is created
78
146
  #
79
- # :enabled [Boolean]
80
- # Whether the minion should run in a separate thread
81
- # Not recommended in Production, but is useful for debugging purposes
82
- # Default: ParallelMinion::Minion.enabled?
83
- #
84
147
  # :on_timeout [Exception]
85
148
  # The class to raise on the minion when the minion times out.
86
149
  # By raising the exception on the running thread it ensures that the thread
@@ -92,21 +155,30 @@ module ParallelMinion
92
155
  #
93
156
  # Note: :on_timeout has no effect if not #enabled?
94
157
  #
95
- # *args
96
- # Any number of arguments can be supplied that are passed into the block
97
- # in the order they are listed
98
- # It is recommended to duplicate and/or freeze objects passed as arguments
99
- # so that they are not modified at the same time by multiple threads
100
- # These arguments are accessible while and after the minion is running
101
- # by calling #arguments
158
+ # :metric [String]
159
+ # Name of the metric to forward to Semantic Logger when measuring the minion execution time.
160
+ # Example: inquiry/address_cleansing
102
161
  #
103
- # Proc / lambda
104
- # A block of code must be supplied that the Minion will execute
105
- # NOTE: This block will be executed within the scope of the created minion
106
- # instance and _not_ within the scope of where the Proc/lambda was
107
- # originally created.
108
- # This is done to force all parameters to be passed in explicitly
109
- # and should be read-only or duplicates of the original data
162
+ # When a metric is supplied the following metrics will also be generated:
163
+ # - wait
164
+ # Duration waiting for a minion to complete.
165
+ #
166
+ # The additional metrics are added to the supplied metric name. For example:
167
+ # - inquiry/address_cleansing/wait
168
+ #
169
+ # :log_exception [Symbol]
170
+ # Control whether or how an exception thrown in the block is
171
+ # reported by Semantic Logger. Values:
172
+ # :full
173
+ # Log the exception class, message, and backtrace
174
+ # :partial
175
+ # Log the exception class and message. The backtrace will not be logged
176
+ # :off
177
+ # Any unhandled exception raised in the block will not be logged
178
+ # Default: :partial
179
+ #
180
+ # :enabled [Boolean]
181
+ # Override the global setting: `ParallelMinion::Minion.enabled?` for this minion instance.
110
182
  #
111
183
  # The overhead for moving the task to a Minion (separate thread) vs running it
112
184
  # sequentially is about 0.3 ms if performing other tasks in-between starting
@@ -116,86 +188,32 @@ module ParallelMinion
116
188
  # code in-line:
117
189
  # ParallelMinion::Minion.new(description: 'Count', timeout: 5) { 1 }.result
118
190
  #
119
- # NOTE:
120
- # On JRuby it is very important to add the following setting to .jrubyrc
121
- # thread.pool.enabled=true
191
+ # Note:
192
+ # On JRuby it is recommended to add the following setting to .jrubyrc
193
+ # thread.pool.enabled=true
122
194
  #
123
195
  # Example:
124
196
  # ParallelMinion::Minion.new(10.days.ago, description: 'Doing something else in parallel', timeout: 1000) do |date|
125
197
  # MyTable.where('created_at <= ?', date).count
126
198
  # end
127
- def initialize(*args, &block)
128
- raise "Missing mandatory block that Minion must perform" unless block
199
+ def initialize(*arguments, description: 'Minion', metric: nil, log_exception: nil, enabled: self.class.enabled?, timeout: INFINITE, on_timeout: nil, wait_metric: nil, &block)
200
+ raise 'Missing mandatory block that Minion must perform' unless block
129
201
  @start_time = Time.now
130
202
  @exception = nil
131
- @arguments = args.dup
132
- options = self.class.extract_options!(@arguments)
133
- @timeout = options.delete(:timeout).to_f
134
- @description = (options.delete(:description) || 'Minion').to_s
135
- @metric = options.delete(:metric)
136
- @log_exception = options.delete(:log_exception)
137
- @enabled = options.delete(:enabled)
138
- @enabled = self.class.enabled? if @enabled.nil?
139
- @on_timeout = options.delete(:on_timeout)
140
-
141
- # Warn about any unknown options.
142
- options.each_pair do | key, val |
143
- logger.warn "Ignoring unknown option: #{key.inspect} => #{val.inspect}"
144
- warn "ParallelMinion::Minion Ignoring unknown option: #{key.inspect} => #{val.inspect}"
145
- end
203
+ @arguments = arguments
204
+ @timeout = timeout.to_f
205
+ @description = description.to_s
206
+ @metric = metric
207
+ @log_exception = log_exception
208
+ @enabled = enabled
209
+ @on_timeout = on_timeout
146
210
 
147
- # Run the supplied block of code in the current thread for testing or
148
- # debugging purposes
149
- if @enabled == false
150
- begin
151
- logger.info("Started in the current thread: #{@description}")
152
- logger.benchmark_info("Completed in the current thread: #{@description}", log_exception: @log_exception, metric: @metric) do
153
- @result = instance_exec(*@arguments, &block)
154
- end
155
- rescue Exception => exc
156
- @exception = exc
157
- ensure
158
- @duration = Time.now - @start_time
159
- end
160
- return
161
- end
211
+ @wait_metric = (wait_metric || "#{metric}/wait") if @metric
162
212
 
163
- tags = (logger.tags || []).dup
164
-
165
- # Copy current scopes for new thread. Only applicable for AR models
166
- scopes = self.class.current_scopes if defined?(ActiveRecord::Base)
167
-
168
- @thread = Thread.new(*@arguments) do
169
- # Copy logging tags from parent thread
170
- logger.tagged(*tags) do
171
- # Set the current thread name to the description for this Minion
172
- # so that all log entries in this thread use this thread name
173
- Thread.current.name = "#{@description}-#{Thread.current.object_id}"
174
- logger.info("Started #{@description}")
175
-
176
- begin
177
- logger.benchmark_info("Completed #{@description}", log_exception: @log_exception, metric: @metric) do
178
- # Use the current scope for the duration of the task execution
179
- if scopes.nil? || (scopes.size == 0)
180
- @result = instance_exec(*@arguments, &block)
181
- else
182
- # Each Class to scope requires passing a block to .scoping
183
- proc = Proc.new { instance_exec(*@arguments, &block) }
184
- first = scopes.shift
185
- scopes.each {|scope| proc = Proc.new { scope.scoping(&proc) } }
186
- @result = first.scoping(&proc)
187
- end
188
- end
189
- rescue Exception => exc
190
- @exception = exc
191
- nil
192
- ensure
193
- # Return any database connections used by this thread back to the pool
194
- ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base)
195
- @duration = Time.now - @start_time
196
- end
197
- end
198
- end
213
+ # When minion is disabled it is obvious in the logs since the name will now be 'Inline' instead of 'Minion'
214
+ self.logger = SemanticLogger['Inline'] unless @enabled
215
+
216
+ @enabled ? run(&block) : run_inline(&block)
199
217
  end
200
218
 
201
219
  # Returns the result when the thread completes
@@ -207,10 +225,10 @@ module ParallelMinion
207
225
  # Return nil if Minion is still working and has time left to finish
208
226
  if working?
209
227
  ms = time_left
210
- logger.benchmark_info("Waited for Minion to complete: #{@description}", min_duration: 0.01) do
211
- if @thread.join(ms.nil? ? nil: ms / 1000).nil?
212
- @thread.raise(@on_timeout.new("Minion: #{@description} timed out")) if @on_timeout
213
- logger.warn("Timed out waiting for result from Minion: #{@description}")
228
+ logger.measure(self.class.completed_log_level, "Waited for Minion to complete: #{description}", min_duration: 0.01, metric: wait_metric) do
229
+ if @thread.join(ms.nil? ? nil : ms / 1000).nil?
230
+ @thread.raise(@on_timeout.new("Minion: #{description} timed out")) if @on_timeout
231
+ logger.warn("Timed out waiting for: #{description}")
214
232
  return
215
233
  end
216
234
  end
@@ -239,8 +257,8 @@ module ParallelMinion
239
257
  # Returns 0 if no time is left
240
258
  # Returns nil if their is no time limit. I.e. :timeout was set to Minion::INFINITE (infinite time left)
241
259
  def time_left
242
- return nil if (@timeout == 0) || (@timeout == -1)
243
- duration = @timeout - (Time.now - @start_time) * 1000
260
+ return nil if (timeout == 0) || (timeout == -1)
261
+ duration = timeout - (Time.now - start_time) * 1000
244
262
  duration <= 0 ? 0 : duration
245
263
  end
246
264
 
@@ -252,25 +270,78 @@ module ParallelMinion
252
270
  # Returns the current scopes for each of the models for which scopes will be
253
271
  # copied to the Minions
254
272
  if defined?(ActiveRecord)
255
- if ActiveRecord::VERSION::MAJOR >= 4
273
+ if ActiveRecord::VERSION::MAJOR >= 4
256
274
  def self.current_scopes
257
- @@scoped_classes.collect {|klass| klass.all}
275
+ scoped_classes.collect { |klass| klass.all }
258
276
  end
259
277
  else
260
278
  def self.current_scopes
261
- @@scoped_classes.collect {|klass| klass.scoped}
279
+ scoped_classes.collect { |klass| klass.scoped }
262
280
  end
263
281
  end
264
282
  end
265
283
 
266
- protected
284
+ private
267
285
 
268
- @@enabled = true
269
- @@scoped_classes = []
286
+ # Run the supplied block of code in the current thread.
287
+ # Useful for debugging, testing, and when running in batch environments.
288
+ def run_inline(&block)
289
+ begin
290
+ logger.public_send(self.class.started_log_level, "Started #{@description}")
291
+ logger.measure(self.class.completed_log_level, "Completed #{@description}", log_exception: @log_exception, metric: metric) do
292
+ @result = instance_exec(*arguments, &block)
293
+ end
294
+ rescue Exception => exc
295
+ @exception = exc
296
+ ensure
297
+ @duration = Time.now - start_time
298
+ end
299
+ end
300
+
301
+ def run(&block)
302
+ # Capture tags from current thread
303
+ tags = SemanticLogger.tags
304
+ tags = tags.nil? || tags.empty? ? nil : tags.dup
305
+
306
+ named_tags = SemanticLogger.named_tags
307
+ named_tags = named_tags.nil? || named_tags.empty? ? nil : named_tags.dup
308
+
309
+ # Captures scopes from current thread. Only applicable for AR models
310
+ scopes = self.class.current_scopes if defined?(ActiveRecord::Base)
311
+
312
+ @thread = Thread.new(*arguments) do
313
+ Thread.current.name = "#{description}-#{Thread.current.object_id}"
314
+
315
+ # Copy logging tags from parent thread, if any
316
+ proc = Proc.new { run_in_scope(scopes, &block) }
317
+ proc2 = tags ? Proc.new { SemanticLogger.tagged(*tags, &proc) } : proc
318
+ proc3 = named_tags ? Proc.new { SemanticLogger.named_tagged(named_tags, &proc2) } : proc2
319
+
320
+ logger.public_send(self.class.started_log_level, "Started #{description}")
321
+ begin
322
+ logger.measure(self.class.completed_log_level, "Completed #{description}", log_exception: log_exception, metric: metric, &proc3)
323
+ rescue Exception => exc
324
+ @exception = exc
325
+ nil
326
+ ensure
327
+ @duration = Time.now - start_time
328
+ # Return any database connections used by this thread back to the pool
329
+ ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base)
330
+ end
331
+ end
332
+ end
270
333
 
271
- # Extract options from a hash.
272
- def self.extract_options!(args)
273
- args.last.is_a?(Hash) ? args.pop : {}
334
+ def run_in_scope(scopes, &block)
335
+ if scopes.nil? || scopes.empty?
336
+ @result = instance_exec(*@arguments, &block)
337
+ else
338
+ # Use the captured scope when running the block.
339
+ # Each Class to scope requires passing a block to .scoping.
340
+ proc = Proc.new { instance_exec(*@arguments, &block) }
341
+ first = scopes.shift
342
+ scopes.each { |scope| proc = Proc.new { scope.scoping(&proc) } }
343
+ @result = first.scoping(&proc)
344
+ end
274
345
  end
275
346
 
276
347
  end
@@ -11,6 +11,14 @@ module ParallelMinion #:nodoc:
11
11
  # # Run Minions in the current thread to make debugging easier
12
12
  # config.parallel_minion.enabled = false
13
13
  #
14
+ # # Change the log level for the started log messages to :debug,
15
+ # # so that they do not show up in production when log level is :info.
16
+ # config.parallel_minion.started_log_level = :debug
17
+ #
18
+ # # Change the log level for the completed log messages to :debug,
19
+ # # so that they do not show up in production when log level is :info.
20
+ # config.parallel_minion.completed_log_level = :debug
21
+ #
14
22
  # # Add a model so that its current scope is copied to the Minion
15
23
  # config.after_initialize do
16
24
  # # Perform in an after_initialize so that the model has been loaded
@@ -1,3 +1,3 @@
1
1
  module ParallelMinion #:nodoc
2
- VERSION = "1.1.0"
2
+ VERSION = '1.2.0'
3
3
  end
@@ -1,18 +1,10 @@
1
- require File.join(File.dirname(__FILE__), 'test_helper')
2
-
3
- require 'rubygems'
1
+ require_relative './test_helper'
4
2
  require 'erb'
5
- #require 'test/unit'
6
- # Since we want both the AR and Mongoid extensions loaded we need to require them first
7
3
  require 'active_record'
8
- require 'active_record/relation'
9
- # Should redefines Proc#bind so must include after Rails
10
- #require 'shoulda'
11
- #require 'parallel_minion'
12
4
 
13
- ActiveRecord::Base.logger = SemanticLogger[ActiveRecord]
5
+ ActiveRecord::Base.logger = SemanticLogger[ActiveRecord]
14
6
  ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read('test/config/database.yml')).result)
15
- ActiveRecord::Base.establish_connection('test')
7
+ ActiveRecord::Base.establish_connection(:test)
16
8
 
17
9
  ActiveRecord::Schema.define :version => 0 do
18
10
  create_table :people, :force => true do |t|
@@ -26,15 +18,14 @@ class Person < ActiveRecord::Base
26
18
  end
27
19
 
28
20
  class MinionScopeTest < Minitest::Test
29
-
30
- context ParallelMinion::Minion do
21
+ describe ParallelMinion::Minion do
31
22
  [false, true].each do |enabled|
32
- context ".new with enabled: #{enabled.inspect}" do
33
- setup do
23
+ describe ".new with enabled: #{enabled.inspect}" do
24
+ before do
34
25
  Person.create(name: 'Jack', state: 'FL', zip_code: 38729)
35
26
  Person.create(name: 'John', state: 'FL', zip_code: 35363)
36
27
  Person.create(name: 'Jill', state: 'FL', zip_code: 73534)
37
- Person.create(name: 'Joe', state: 'NY', zip_code: 45325)
28
+ Person.create(name: 'Joe', state: 'NY', zip_code: 45325)
38
29
  Person.create(name: 'Jane', state: 'NY', zip_code: 45325)
39
30
  Person.create(name: 'James', state: 'CA', zip_code: 123123)
40
31
  # Instruct Minions to adhere to any dynamic scopes for Person model
@@ -42,13 +33,13 @@ class MinionScopeTest < Minitest::Test
42
33
  ParallelMinion::Minion.enabled = enabled
43
34
  end
44
35
 
45
- teardown do
36
+ after do
46
37
  Person.destroy_all
47
38
  ParallelMinion::Minion.scoped_classes.clear
48
39
  SemanticLogger.flush
49
40
  end
50
41
 
51
- should 'copy across model scope' do
42
+ it 'copy across model scope' do
52
43
  assert_equal 6, Person.count
53
44
 
54
45
  Person.unscoped.where(state: 'FL').scoping { Person.count }
@@ -65,4 +56,4 @@ class MinionScopeTest < Minitest::Test
65
56
  end
66
57
  end
67
58
  end
68
- end
59
+ end
@@ -1,47 +1,53 @@
1
- require File.join(File.dirname(__FILE__), 'test_helper')
1
+ require_relative './test_helper'
2
2
 
3
3
  # Test ParallelMinion standalone without Rails
4
4
  # Run this test standalone to verify it has no Rails dependencies
5
5
  class MinionTest < Minitest::Test
6
6
  include SemanticLogger::Loggable
7
7
 
8
- context ParallelMinion::Minion do
8
+ describe ParallelMinion::Minion do
9
9
 
10
10
  [false, true].each do |enabled|
11
- context ".new with enabled: #{enabled.inspect}" do
12
- setup do
11
+ describe enabled ? 'enabled' : 'disabled' do
12
+ before do
13
13
  ParallelMinion::Minion.enabled = enabled
14
- $log_struct = nil
14
+ $log_structs = []
15
15
  end
16
16
 
17
- should 'without parameters' do
17
+ it 'without parameters' do
18
18
  minion = ParallelMinion::Minion.new { 196 }
19
19
  assert_equal 196, minion.result
20
20
  end
21
21
 
22
- should 'with a description' do
22
+ it 'with a description' do
23
23
  minion = ParallelMinion::Minion.new(description: 'Test') { 197 }
24
24
  assert_equal 197, minion.result
25
25
  end
26
26
 
27
- should 'with an argument' do
28
- p1 = { name: 198 }
27
+ it 'with an argument' do
28
+ p1 = {name: 198}
29
29
  minion = ParallelMinion::Minion.new(p1, description: 'Test') do |v|
30
30
  v[:name]
31
31
  end
32
32
  assert_equal 198, minion.result
33
33
  end
34
34
 
35
- should 'raise exception' do
35
+ it 'raise exception' do
36
36
  minion = ParallelMinion::Minion.new(description: 'Test') { raise "An exception" }
37
37
  assert_raises RuntimeError do
38
38
  minion.result
39
39
  end
40
40
  end
41
41
 
42
+ it 'has correct logger name' do
43
+ minion = ParallelMinion::Minion.new { 196 }
44
+ name = enabled ? 'Minion' : 'Inline'
45
+ assert_equal name, minion.logger.name
46
+ end
47
+
42
48
  # TODO Blocks still have access to their original scope if variables cannot be
43
49
  # resolved first by the parameters, then by the values in Minion itself
44
- # should 'not have access to local variables' do
50
+ # it 'not have access to local variables' do
45
51
  # name = 'Jack'
46
52
  # minion = ParallelMinion::Minion.new(description: 'Test') { puts name }
47
53
  # assert_raises NameError do
@@ -49,11 +55,11 @@ class MinionTest < Minitest::Test
49
55
  # end
50
56
  # end
51
57
 
52
- should 'run minion' do
53
- hash = { value: 23 }
54
- value = 47
58
+ it 'run minion' do
59
+ hash = {value: 23}
60
+ value = 47
55
61
  minion = ParallelMinion::Minion.new(hash, description: 'Test') do |h|
56
- value = 321
62
+ value = 321
57
63
  h[:value] = 123
58
64
  456
59
65
  end
@@ -62,10 +68,10 @@ class MinionTest < Minitest::Test
62
68
  assert_equal 321, value
63
69
  end
64
70
 
65
- should 'copy across logging tags' do
71
+ it 'copy across logging tags' do
66
72
  minion = nil
67
- logger.tagged('TAG') do
68
- assert_equal 'TAG', logger.tags.last
73
+ SemanticLogger.tagged('TAG') do
74
+ assert_equal 'TAG', SemanticLogger.tags.last
69
75
  minion = ParallelMinion::Minion.new(description: 'Tag Test') do
70
76
  logger.tags.last
71
77
  end
@@ -73,27 +79,38 @@ class MinionTest < Minitest::Test
73
79
  assert_equal 'TAG', minion.result
74
80
  end
75
81
 
76
- should 'include metric' do
77
- metric_name = '/Custom/metric'
78
- hash = { value: 23 }
79
- value = 47
80
- minion = ParallelMinion::Minion.new(hash, description: 'Test', metric: metric_name) do |h|
81
- value = 321
82
+ it 'include metric' do
83
+ metric_name = 'model/method'
84
+ hash = {value: 23}
85
+ value = 47
86
+ minion = ParallelMinion::Minion.new(hash, description: 'Test', metric: metric_name) do |h|
87
+ value = 321
82
88
  h[:value] = 123
89
+ sleep 1
83
90
  456
84
91
  end
85
92
  assert_equal 456, minion.result
86
93
  assert_equal 123, hash[:value]
87
94
  assert_equal 321, value
88
95
  SemanticLogger.flush
89
- assert_equal metric_name, $log_struct.metric
96
+ assert log = $log_structs.first, -> { $log_structs.ai }
97
+ if enabled
98
+ # Completed log message
99
+ assert_equal metric_name, log.metric, -> { $log_structs.ai }
100
+ # Wait log message
101
+ assert log = $log_structs.last, -> { $log_structs.ai }
102
+ assert_equal "#{metric_name}/wait", log.metric, -> { $log_structs.ai }
103
+ else
104
+ # Timeout and wait has no effect when run inline
105
+ assert_equal metric_name, log.metric, -> { $log_structs.ai }
106
+ end
90
107
  end
91
108
 
92
- should 'handle multiple minions concurrently' do
109
+ it 'handle multiple minions concurrently' do
93
110
  # Start 10 minions
94
111
  minions = 10.times.collect do |i|
95
112
  # Each Minion returns its index in the collection
96
- ParallelMinion::Minion.new(i, description: "Minion:#{i}") {|counter| counter }
113
+ ParallelMinion::Minion.new(i, description: "Minion:#{i}") { |counter| counter }
97
114
  end
98
115
  assert_equal 10, minions.count
99
116
  # Fetch the result from each Minion
@@ -102,58 +119,58 @@ class MinionTest < Minitest::Test
102
119
  end
103
120
  end
104
121
 
105
- should 'timeout' do
106
- minion = ParallelMinion::Minion.new(description: 'Test', timeout: 100) { sleep 1 }
122
+ it 'timeout' do
107
123
  if enabled
108
- assert_equal nil, minion.result
124
+ minion = ParallelMinion::Minion.new(description: 'Test', timeout: 100) { sleep 1 }
125
+ assert_nil minion.result
109
126
  end
110
127
  end
111
128
 
112
- should 'timeout and terminate thread with Exception' do
113
- minion = ParallelMinion::Minion.new(description: 'Test', timeout: 100, on_timeout: Timeout::Error) { sleep 1 }
129
+ it 'timeout and terminate thread with Exception' do
114
130
  if enabled
115
- assert_equal nil, minion.result
131
+ minion = ParallelMinion::Minion.new(description: 'Test', timeout: 100, on_timeout: Timeout::Error) { sleep 1 }
132
+ assert_nil minion.result
116
133
  # Give time for thread to terminate
117
134
  sleep 0.1
118
135
  assert_equal Timeout::Error, minion.exception.class
119
136
  assert_equal false, minion.working?
120
- assert_equal true, minion.completed?
121
- assert_equal true, minion.failed?
122
- assert_equal 0, minion.time_left
137
+ assert_equal true, minion.completed?
138
+ assert_equal true, minion.failed?
139
+ assert_equal 0, minion.time_left
123
140
  end
124
141
  end
125
142
 
126
- should 'make description instance variable available' do
143
+ it 'make description instance variable available' do
127
144
  minion = ParallelMinion::Minion.new(description: 'Test') do
128
145
  description
129
146
  end
130
147
  assert_equal 'Test', minion.result
131
148
  end
132
149
 
133
- should 'make timeout instance variable available' do
134
- minion = ParallelMinion::Minion.new(description: 'Test', timeout: 1000 ) do
150
+ it 'make timeout instance variable available' do
151
+ minion = ParallelMinion::Minion.new(description: 'Test', timeout: 1000) do
135
152
  timeout
136
153
  end
137
154
  assert_equal 1000, minion.result
138
155
  end
139
156
 
140
- should 'make enabled? method available' do
157
+ it 'make enabled? method available' do
141
158
  minion = ParallelMinion::Minion.new(description: 'Test') do
142
159
  enabled?
143
160
  end
144
161
  assert_equal enabled, minion.result
145
162
  end
146
163
 
147
- should 'keep the original arguments' do
148
- minion = ParallelMinion::Minion.new(1, 'data', 14.1, description: 'Test') do | num, str, float |
164
+ it 'keep the original arguments' do
165
+ minion = ParallelMinion::Minion.new(1, 'data', 14.1, description: 'Test') do |num, str, float|
149
166
  num + float
150
167
  end
151
168
  assert_equal 15.1, minion.result
152
- assert_equal [ 1, 'data', 14.1 ], minion.arguments
169
+ assert_equal [1, 'data', 14.1], minion.arguments
153
170
  end
154
171
  end
155
172
 
156
173
  end
157
174
 
158
175
  end
159
- end
176
+ end
Binary file
@@ -1,19 +1,13 @@
1
1
  $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
2
 
3
3
  require 'minitest/autorun'
4
- require 'minitest/reporters'
5
- require 'minitest/stub_any_instance'
6
- require 'shoulda/context'
7
4
  require 'parallel_minion'
8
5
  require 'semantic_logger'
9
6
 
10
- Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
11
-
12
- # Register an appender if one is not already registered
13
7
  SemanticLogger.default_level = :trace
14
- SemanticLogger.add_appender('test.log', &SemanticLogger::Appender::Base.colorized_formatter) if SemanticLogger.appenders.size == 0
8
+ SemanticLogger.add_appender(file_name: 'test.log', formatter: :color)
15
9
 
16
10
  # Setup global callback for metric so that it can be tested below
17
11
  SemanticLogger.on_metric do |log_struct|
18
- $log_struct = log_struct.dup
12
+ ($log_structs ||= []) << log_struct.dup
19
13
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallel_minion
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-01 00:00:00.000000000 Z
11
+ date: 2017-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: semantic_logger
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '4.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '4.0'
27
27
  description: Parallel Minion allows you to take existing blocks of code and wrap them
28
28
  in a minion so that they can run asynchronously in a separate thread
29
29
  email:
@@ -56,7 +56,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
56
56
  requirements:
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
- version: '0'
59
+ version: '2.1'
60
60
  required_rubygems_version: !ruby/object:Gem::Requirement
61
61
  requirements:
62
62
  - - ">="
@@ -64,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
64
  version: '0'
65
65
  requirements: []
66
66
  rubyforge_project:
67
- rubygems_version: 2.4.5
67
+ rubygems_version: 2.6.11
68
68
  signing_key:
69
69
  specification_version: 4
70
70
  summary: Pragmatic approach to parallel and asynchronous processing in Ruby