skn_utils 5.4.1 → 5.5.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 +4 -4
- data/.rspec +2 -0
- data/README.md +173 -54
- data/bin/concurrent_test_block +54 -0
- data/bin/concurrent_test_grouped +45 -0
- data/bin/concurrent_test_procs +45 -0
- data/bin/concurrent_test_wrapped +49 -0
- data/lib/skn_utils.rb +10 -2
- data/lib/skn_utils/concurrent_jobs.rb +96 -0
- data/lib/skn_utils/http_processor.rb +34 -0
- data/lib/skn_utils/job_commands.rb +201 -0
- data/lib/skn_utils/version.rb +2 -2
- data/skn_utils.gemspec +3 -0
- data/spec/lib/skn_utils/concurrent_jobs_spec.rb +277 -0
- data/spec/spec_helper.rb +4 -0
- metadata +39 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a78df2df5ed11a61cea3d00b0f3543295e3ce5b
|
4
|
+
data.tar.gz: eb021f9d1f707e8e08411b542b05959061322d25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ee33852c9675b13b64a8beb22fdd26fa6afd09994ee4809e4d2fc84bbc46201f469f9b715f4a7ba2291abadd3f5de36f6d98d257c3cc1f085b29f4f7c316374
|
7
|
+
data.tar.gz: d7ff622a8c1dc13ab5a0438c08b5f9f0e5fc04f32c989bcc8ab32d8d08243cae69c1cd6ce985c0a7871b7784f56a181d6ed6ab8879282e9c139588cbd4112f95
|
data/.rspec
CHANGED
data/README.md
CHANGED
@@ -2,68 +2,81 @@
|
|
2
2
|
[](http://badge.fury.io/rb/skn_utils)
|
3
3
|
|
4
4
|
# SknUtils
|
5
|
-
## SknUtils::NestedResult class; dynamic key/value container
|
6
|
-
A Ruby Gem containing a Ruby PORO (Plain Old Ruby Object) that can be instantiated at runtime with an input hash. This library creates
|
7
|
-
an Object with Dot and Hash notational accessors to each key's value. Additional key/value pairs can be added post-create
|
8
|
-
by simply assigning it; `obj.my_new_var = "some value"`
|
9
5
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
## Available Classes
|
44
|
-
* SknSuccess
|
45
|
-
* SknFailure
|
6
|
+
`SknUtils` is a collection of pure-ruby utility classes and modules, with limited
|
7
|
+
dependencies, to augment the development of Ruby applications. Examples of these
|
8
|
+
utilities in action can be found in my related projects `SknServices`, `SknWebApp`,
|
9
|
+
and `SknBase`.
|
10
|
+
|
11
|
+
* The exchange or handoff of values between objects is addressed via the `NestedResults`
|
12
|
+
class which implements dot-notation and nesting over a concurrent hash: A ruby Hash can
|
13
|
+
use any valid ruby object as a key or value.
|
14
|
+
* `NestedResults` is later sub-classed as `Configuration` to provide application level
|
15
|
+
settings using YAML files with a API simular to the RbConfig gem.
|
16
|
+
* Object or method return values can be enclosed in `SknSuccess` or `SknFailure` classes
|
17
|
+
to prevent or minimize nil returns.
|
18
|
+
* Precise microsecond `duration`s, Number to `as_human_size`, and a `catch_exceptions`
|
19
|
+
retry-able feature are implemented on the SknUtils class directly for ease of use.
|
20
|
+
* `Configurable` module extends any class or module with configurable attribute method
|
21
|
+
as needed, with a set of defaults methods which emulate Rails.env, Rails.logger, and
|
22
|
+
Rails.root functionality.
|
23
|
+
* `CoreObjectExtensions` simular to ActiveSupport's, `#present?` and `#blank?` are
|
24
|
+
automatically applied, unless already present, when SknUtils gem is loaded.
|
25
|
+
* `SknRegistry` class is an advanced feature which allows you to manually register
|
26
|
+
classes, procs, or any value with a user-defined `label`. Initialization and dependency injection
|
27
|
+
requirements of service-like classes can be included in this registration process allowing
|
28
|
+
them to be centrally maintained. The `label` can be any valid ruby value; like a
|
29
|
+
classname, symbol, or string as needed
|
30
|
+
* `NullObject`, `NotifierBase`, and `Wrappable` are interesting classes which you might want to explore further.
|
31
|
+
* `ConcurrentJobs` is a feature implemented to allow concurrent/multi-threaded execution of jobs. The included
|
32
|
+
companion classes focus on HTTP GET,PUT,POST, and DELETE jobs as an example of how to use the `ConcurrentJobs` feature.
|
33
|
+
|
34
|
+
All classes and modules have RSpec test coverage (90+) of their originally intended use-cases.
|
35
|
+
|
36
|
+
|
37
|
+
### Available Classes
|
46
38
|
* SknSettings
|
47
39
|
* SknUtils::Configuration
|
48
40
|
* SknUtils::EnvStringHandler
|
49
|
-
* SknRegistry
|
50
|
-
* SknContainer
|
51
41
|
* SknHash
|
52
42
|
* SknUtils::ResultBean
|
53
43
|
* SknUtils::PageControls
|
54
44
|
* SknUtils::NestedResult
|
55
|
-
*
|
56
|
-
*
|
45
|
+
* SknRegistry
|
46
|
+
* SknContainer
|
47
|
+
* SknSuccess
|
48
|
+
* SknFailure
|
57
49
|
* SknUtils::Configurable
|
58
|
-
|
59
|
-
|
50
|
+
* SknUtils::CoreObjectExtensions
|
51
|
+
* SknUtils::NullObject
|
52
|
+
* SknUtils::NotifierBase
|
53
|
+
* SknUtils::Wrappable
|
54
|
+
* SknUtils::ConcurrentJobs
|
55
|
+
* SknUtils::JobWrapper (via ConcurrentJobs)
|
56
|
+
* SknUtils::CommandJSONPost (via JobCommands)
|
57
|
+
* SknUtils::CommandFORMPost
|
58
|
+
* SknUtils::CommandJSONGet
|
59
|
+
* SknUtils::CommandJSONPut
|
60
|
+
* SknUtils::CommandFORMDelete
|
61
|
+
* SknUtils::HttpProcessor
|
62
|
+
|
63
|
+
### Available Class.Methods
|
60
64
|
* SknUtils.catch_exceptions()
|
61
65
|
* SknUtils.as_human_size()
|
62
66
|
* SknUtils.duration(start_time=nil)
|
63
67
|
|
64
68
|
|
65
|
-
|
66
69
|
## History
|
70
|
+
2/24/2019 V5.5.0
|
71
|
+
Added
|
72
|
+
* ConcurrentJobs feature set
|
73
|
+
- Executes (HTTP/any) jobs in parallel
|
74
|
+
|
75
|
+
1/2/2019 V5.4.1
|
76
|
+
Added
|
77
|
+
- Wrappable module for evaluation.
|
78
|
+
- Ruby comment # frozen_string_literal, everywhere
|
79
|
+
|
67
80
|
12/16/2018 V5.4.0
|
68
81
|
Added :duration() utils to SknUtils module:
|
69
82
|
#duration() #=> returns start_time value
|
@@ -154,6 +167,43 @@ There are many more use cases for Ruby's Hash that this gem just makes easier to
|
|
154
167
|
06/2015 V1.5.1 commit #67ef656
|
155
168
|
Last Version to depend on Rails (ActiveModel) for #to_json and #to_xml serialization
|
156
169
|
|
170
|
+
### NestedResult class; dynamic key/value container
|
171
|
+
A class implementing a Ruby PORO (Plain Old Ruby Object) that can be instantiated at runtime with a hash. Creates
|
172
|
+
a nested object with Dot and Hash notational accessors to each key's value. Additional key/value pairs can be added post-create
|
173
|
+
by simply assigning it; `obj.my_new_var = "some value"`
|
174
|
+
|
175
|
+
* Transforms the initialing hash into accessible object instance values, with their keys as method names.
|
176
|
+
* If the key's value is also a hash, it too will become an Object.
|
177
|
+
* if the key's value is a Array of Hashes, or Array of Arrays of Hashes, each hash element of the Arrays will become an Object.
|
178
|
+
* The current key/value (including nested) pairs are returned via #to_hash or #to_json when and if needed.
|
179
|
+
* Best described as dot notation wrapper over a Ruby (Concurrent-Ruby) Hash.
|
180
|
+
|
181
|
+
Ruby's Hash object is already extremely flexible, even more so with the addition of dot-notation. As I work more with Ruby outside of Rails, I'm finding more use cases for the capabilities of this gem. Here are a few examples:
|
182
|
+
|
183
|
+
1. Application settings containers, SknSettings. Loads Yaml file based on `ENV['RACK_ENV']` value, or specified file-key.
|
184
|
+
- Replaces Config and/or RBConfig Gems for yaml based settings
|
185
|
+
1. Substitute for Rails.root, via a little ERB/YAML/Marshal statement in settings.yml file, and a helper class
|
186
|
+
- settings.yml (YAML)
|
187
|
+
- `root: <%= Dir.pwd %>`
|
188
|
+
- enables `SknSettings.root`
|
189
|
+
- `env: !ruby/string:SknUtils::EnvStringHandler <%= ENV.fetch('RACK_ENV', 'development') %>`
|
190
|
+
- enables `SknSettings.env.production?` ...
|
191
|
+
1. Since SknSettings is by necessity a global constant, it can serve as Session Storage to keep system objects; like a ROM-RB instance.
|
192
|
+
1. In-Memory Key-Store, use it to cache active user objects, or active Integration passwords, and/or objects that are not serializable.
|
193
|
+
1. Command registries used to dispatch command requests to proper command handler. see example app [SknBase](https://github.com/skoona/skn_base/blob/master/strategy/services/content/command_handler.rb)
|
194
|
+
```ruby
|
195
|
+
SknSettings.command_handler = {
|
196
|
+
Commands::RetrieveAvailableResources => method(:resources_metadata_service),
|
197
|
+
Commands::RetrieveResourceContent => method(:resource_content_service)
|
198
|
+
}
|
199
|
+
...
|
200
|
+
SknSettings.command_handler[ cmd.class ].call( cmd )
|
201
|
+
-- or --
|
202
|
+
SknSettings.command_handler.key?( cmd.class ) &&
|
203
|
+
cmd.valid? ? SknSettings.command_handler[ cmd.class ].call( cmd ) : command_not_found_action()
|
204
|
+
```
|
205
|
+
There are many more use cases for Ruby's Hash that this gem just makes easier to implement.
|
206
|
+
|
157
207
|
|
158
208
|
## Public Components
|
159
209
|
SknUtils::NestedResult # Primary Key/Value Container with Dot/Hash notiation support.
|
@@ -171,6 +221,14 @@ There are many more use cases for Ruby's Hash that this gem just makes easier to
|
|
171
221
|
SknSuccess # Three attribute value containers for return codes -- #value, #message, #success
|
172
222
|
- Extra #payload method returns value as NestResult if value is_a Hash
|
173
223
|
SknFailure # Three attribute value containers for return codes -- #value, #message, #success
|
224
|
+
SknUtils::ConcurrentJobs # Async/Sync Job executor pool with HTTP support
|
225
|
+
SknUtils::CommandJSONGet # HTTP Get Command class expecting `json` return, located inside `job_commands`
|
226
|
+
SknUtils::CommandJSONPut # HTTP Put Command class expecting `json` return, located inside `job_commands`
|
227
|
+
SknUtils::CommandJSONPost # HTTP Post Command class expecting `json` return, located inside `job_commands`
|
228
|
+
SknUtils::CommandFORMPost # HTTP Post Command class expecting `form` data return, located inside `job_commands`
|
229
|
+
SknUtils::CommandFORMDelete # HTTP Delete Command class expecting `form` data return, located inside `job_commands`
|
230
|
+
SknUtils::HttpProcessor # Command driven HTTP processing object supporting GET,PUT,POST, and DELETE
|
231
|
+
SknUtils::JobWrapper # Outer wrapper class to capture catastrophic exceptions (syntax normally)
|
174
232
|
|
175
233
|
|
176
234
|
## Public Methods: SknUtils::Configurable module
|
@@ -239,8 +297,8 @@ There are many more use cases for Ruby's Hash that this gem just makes easier to
|
|
239
297
|
```
|
240
298
|
|
241
299
|
|
242
|
-
## Public Methods:
|
243
|
-
SknContainer is global constant assigned to an instantiated instance of SknRegistry
|
300
|
+
## Public Methods: SknRegistry ONLY
|
301
|
+
`SknContainer` is global constant assigned to an instantiated instance of `SknRegistry`.
|
244
302
|
Returns the keyed value as the original instance/value or if provided a proc the result of calling that proc.
|
245
303
|
To register a class or object for global retrieval, use the following API. Also review the RSpecs for additional useage info.
|
246
304
|
|
@@ -266,9 +324,69 @@ There are many more use cases for Ruby's Hash that this gem just makes easier to
|
|
266
324
|
same_instance = SknContainer.resolve(:the_instance)
|
267
325
|
|
268
326
|
|
327
|
+
## Public Methods: SknUtils::ConcurrentJobs ONLY
|
328
|
+
`ConcurrentJobs` behaves as a concurrent thread pool by using Concurrent::Promise from the `concurrent-ruby` gem.
|
329
|
+
Enables the definition of Procs, or any callable class, which will be executed in parrallel the available jobs
|
330
|
+
loaded into ConcurrentJobs. Meant to reduce user-sensitive response times when multiple APIs must be invoked.
|
331
|
+
Also review the RSpecs for additional useage info.
|
332
|
+
|
333
|
+
SknUtils::ConcurrentJobs
|
334
|
+
#call(async: true) - Instantiate ConcurrentJobs with Async Workers, false for Sync Workers
|
335
|
+
#register_jobs(cmds, callable) - Array of Command to be executed by single callable
|
336
|
+
#register_job(&block) - Adds callable block to internal worker queue
|
337
|
+
#render_jobs - Collect results from all jobs into a Result object
|
338
|
+
#elapsed_time_string - "0.012 seconds" string showing duration of last #render_jobs call
|
339
|
+
|
340
|
+
SknUtils::Result - Contains individual results from each job executed
|
341
|
+
#success? - Determines if any job failed
|
342
|
+
#messages - Retrieves messages from job results, assumed present when job fails
|
343
|
+
#values - Returns an array of individual results from job executions
|
344
|
+
|
345
|
+
Commands and HttpProcessors are included to demonstrate Job creating patterns. ConcurrentJobs is not restricted
|
346
|
+
to Http calls or the command to command handler pattern. Using the #register_job method you can pass callable BLOCK
|
347
|
+
and it will be executed when #render_jobs is invoked. HttpProcessor is what I needed and triggered me to add this feature.
|
348
|
+
|
349
|
+
Example here:
|
350
|
+
```ruby
|
351
|
+
begin
|
352
|
+
# CommandJSONPost, CommandFORMGet, CommandJSONGet,
|
353
|
+
# CommandJSONPut, CommandFORMDelete
|
354
|
+
commands = [
|
355
|
+
SknUtils::CommandJSONGet.call(full_url: "http://jsonplaceholder.typicode.com/posts"),
|
356
|
+
SknUtils::CommandJSONGet.call(full_url: "https://jsonplaceholder.typicode.com/comments"),
|
357
|
+
SknUtils::CommandJSONGet.call(full_url: "https://jsonplaceholder.typicode.com/todos/1"),
|
358
|
+
SknUtils::CommandJSONGet.call(full_url: "http://jsonplaceholder.typicode.com/users")
|
359
|
+
]
|
360
|
+
|
361
|
+
# Initialize the queue with Async Workers by default
|
362
|
+
provider = SknUtils::ConcurrentJobs.call
|
363
|
+
|
364
|
+
# Populate WorkQueue
|
365
|
+
provider.register_jobs(commands, SknUtils::HttpProcessor) # mis-spelling these params result in an immediate exception (line 43 below)
|
366
|
+
|
367
|
+
# Execute WorkQueue
|
368
|
+
result = provider.render_jobs
|
369
|
+
|
370
|
+
if result.success?
|
371
|
+
puts "Success: true"
|
372
|
+
puts "Values: #{result.values}"
|
373
|
+
puts "Messages: #{result.messages}"
|
374
|
+
else
|
375
|
+
puts "Success: false - errors: #{result.messages.join(', ')}"
|
376
|
+
puts "Values: #{result.values}"
|
377
|
+
end
|
378
|
+
|
379
|
+
# result.values
|
380
|
+
rescue => e
|
381
|
+
$stderr.puts e.message, e.backtrace
|
382
|
+
end
|
383
|
+
|
384
|
+
```
|
385
|
+
|
386
|
+
|
269
387
|
|
270
388
|
## Public Methods: SknSettings ONLY
|
271
|
-
SknSettings is global constant containing an initialized Object of SknUtils::Configuration using defaults
|
389
|
+
SknSettings is a global constant containing an initialized Object of SknUtils::Configuration using defaults
|
272
390
|
To change the 'development'.yml default please use the following method early or in the case of Rails in 'application.rb
|
273
391
|
#load_config_basename!(config_name) -- Where config_name is the name of yml files stored in the `./config/settings` directory
|
274
392
|
#config_path!(path) -- Where path format is './<dirs>/', default is: './config/'
|
@@ -420,10 +538,11 @@ There are many more use cases for Ruby's Hash that this gem just makes easier to
|
|
420
538
|
## Installation
|
421
539
|
|
422
540
|
runtime prereqs:
|
423
|
-
*
|
424
|
-
*
|
425
|
-
*
|
426
|
-
*
|
541
|
+
* V5+ None
|
542
|
+
* V4+ None
|
543
|
+
* V3+ None
|
544
|
+
* V2+ None
|
545
|
+
* V1+ gem 'active_model', '~> 3.0'
|
427
546
|
|
428
547
|
|
429
548
|
Add this line to your application's Gemfile:
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# file: concurrent_test_block
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'bundler/setup'
|
7
|
+
require 'skn_utils'
|
8
|
+
|
9
|
+
|
10
|
+
# ##
|
11
|
+
# MainLine
|
12
|
+
# ##
|
13
|
+
#
|
14
|
+
begin
|
15
|
+
# CommandJSONPost, CommandFORMGet, CommandJSONGet,
|
16
|
+
# CommandJSONPut, CommandFORMDelete
|
17
|
+
commands = [
|
18
|
+
SknUtils::CommandJSONGet.call(full_url: "http://jsonplaceholder.typicode.com/posts"),
|
19
|
+
SknUtils::CommandJSONGet.call(full_url: "https://jsonplaceholder.typicode.com/comments"),
|
20
|
+
SknUtils::CommandJSONGet.call(full_url: "https://jsonplaceholder.typicode.com/todos/1"),
|
21
|
+
SknUtils::CommandJSONGet.call(full_url: "http://jsonplaceholder.typicode.com/users")
|
22
|
+
]
|
23
|
+
|
24
|
+
# Initialize the queue with Async Workers by default
|
25
|
+
provider = SknUtils::ConcurrentJobs.call
|
26
|
+
|
27
|
+
# Populate WorkQueue
|
28
|
+
commands.each do |command|
|
29
|
+
provider.register_job do
|
30
|
+
begin
|
31
|
+
SknUtils::HttpProcessor.call(command) # mis-spelling these params result in [SknFailure, SknFailure, ...] results
|
32
|
+
rescue => ex
|
33
|
+
$stderr.puts ex
|
34
|
+
SknFailure.(ex.class.name, "#{ex.message}; #{ex.backtrace[0]}")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Execute WorkQueue
|
40
|
+
result = provider.render_jobs
|
41
|
+
|
42
|
+
if result.success?
|
43
|
+
puts "Success: true"
|
44
|
+
puts "Values: #{result.values}"
|
45
|
+
puts "Messages: #{result.messages}"
|
46
|
+
else
|
47
|
+
puts "Success: false - errors: #{result.messages.join(', ')}"
|
48
|
+
puts "Values: #{result.values}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# result.values
|
52
|
+
rescue => e
|
53
|
+
$stderr.puts e.message, e.backtrace
|
54
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# file: concurrent_test_grouped
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'bundler/setup'
|
7
|
+
require 'skn_utils'
|
8
|
+
|
9
|
+
|
10
|
+
# ##
|
11
|
+
# MainLine
|
12
|
+
# ##
|
13
|
+
#
|
14
|
+
begin
|
15
|
+
# CommandJSONPost, CommandFORMGet, CommandJSONGet,
|
16
|
+
# CommandJSONPut, CommandFORMDelete
|
17
|
+
commands = [
|
18
|
+
SknUtils::CommandJSONGet.call(full_url: "http://jsonplaceholder.typicode.com/posts"),
|
19
|
+
SknUtils::CommandJSONGet.call(full_url: "https://jsonplaceholder.typicode.com/comments"),
|
20
|
+
SknUtils::CommandJSONGet.call(full_url: "https://jsonplaceholder.typicode.com/todos/1"),
|
21
|
+
SknUtils::CommandJSONGet.call(full_url: "http://jsonplaceholder.typicode.com/users")
|
22
|
+
]
|
23
|
+
|
24
|
+
# Initialize the queue with Async Workers by default
|
25
|
+
provider = SknUtils::ConcurrentJobs.call
|
26
|
+
|
27
|
+
# Populate WorkQueue
|
28
|
+
provider.register_jobs(commands, SknUtils::HttpProcessor) # mis-spelling these params result in an immediate exception (line 43 below)
|
29
|
+
|
30
|
+
# Execute WorkQueue
|
31
|
+
result = provider.render_jobs
|
32
|
+
|
33
|
+
if result.success?
|
34
|
+
puts "Success: true"
|
35
|
+
puts "Values: #{result.values}"
|
36
|
+
puts "Messages: #{result.messages}"
|
37
|
+
else
|
38
|
+
puts "Success: false - errors: #{result.messages.join(', ')}"
|
39
|
+
puts "Values: #{result.values}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# result.values
|
43
|
+
rescue => e
|
44
|
+
$stderr.puts e.message, e.backtrace
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# file: concurrent_test_procs
|
4
|
+
#
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'skn_utils'
|
7
|
+
|
8
|
+
|
9
|
+
# ##
|
10
|
+
# MainLine
|
11
|
+
# ##
|
12
|
+
#
|
13
|
+
begin
|
14
|
+
# CommandJSONPost, CommandFORMGet, CommandJSONGet,
|
15
|
+
# CommandJSONPut, CommandFORMDelete
|
16
|
+
commands = [
|
17
|
+
SknUtils::CommandJSONGet.call(full_url: "http://jsonplaceholder.typicode.com/posts"),
|
18
|
+
SknUtils::CommandJSONGet.call(full_url: "https://jsonplaceholder.typicode.com/comments"),
|
19
|
+
SknUtils::CommandJSONGet.call(full_url: "https://jsonplaceholder.typicode.com/todos/1"),
|
20
|
+
SknUtils::CommandJSONGet.call(full_url: "http://jsonplaceholder.typicode.com/users")
|
21
|
+
]
|
22
|
+
|
23
|
+
# Initialize the queue with Async Workers by default
|
24
|
+
provider = SknUtils::ConcurrentJobs.call
|
25
|
+
|
26
|
+
# Populate WorkQueue
|
27
|
+
work_proc = ->(cmd) { SknSuccess.(cmd.uri.request_uri, "Ok") } # mis-spelling these params result in [SknFailure, SknFailure, ...] results
|
28
|
+
provider.register_jobs(commands, work_proc)
|
29
|
+
|
30
|
+
# Execute WorkQueue
|
31
|
+
result = provider.render_jobs
|
32
|
+
|
33
|
+
if result.success?
|
34
|
+
puts "Success: true"
|
35
|
+
puts "Values: #{result.values}"
|
36
|
+
puts "Messages: #{result.messages}"
|
37
|
+
else
|
38
|
+
puts "Success: false - errors: #{result.messages.join(', ')}"
|
39
|
+
puts "Values: #{result.values}"
|
40
|
+
end
|
41
|
+
|
42
|
+
# result.values
|
43
|
+
rescue => e
|
44
|
+
$stderr.puts e.message, e.backtrace
|
45
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# file: concurrent_test_wrapped
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'bundler/setup'
|
7
|
+
require 'skn_utils'
|
8
|
+
|
9
|
+
|
10
|
+
# ##
|
11
|
+
# MainLine
|
12
|
+
# ##
|
13
|
+
#
|
14
|
+
begin
|
15
|
+
# CommandJSONPost, CommandFORMGet, CommandJSONGet,
|
16
|
+
# CommandJSONPut, CommandFORMDelete
|
17
|
+
commands = [
|
18
|
+
SknUtils::CommandJSONGet.call(full_url: "http://jsonplaceholder.typicode.com/posts"),
|
19
|
+
SknUtils::CommandJSONGet.call(full_url: "https://jsonplaceholder.typicode.com/comments"),
|
20
|
+
SknUtils::CommandJSONGet.call(full_url: "https://jsonplaceholder.typicode.com/todos/1"),
|
21
|
+
SknUtils::CommandJSONGet.call(full_url: "http://jsonplaceholder.typicode.com/users")
|
22
|
+
]
|
23
|
+
|
24
|
+
# Initialize the queue with Async Workers by default
|
25
|
+
provider = SknUtils::ConcurrentJobs.call
|
26
|
+
|
27
|
+
# Populate WorkQueue
|
28
|
+
commands.each do |command|
|
29
|
+
provider.register_job do
|
30
|
+
SknUtils::JobWrapper.call(command, SknUtils::HttpProcessor) # mis-spelling these params result in [nil, nil, ...] results
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Execute WorkQueue
|
35
|
+
result = provider.render_jobs
|
36
|
+
|
37
|
+
if result.success?
|
38
|
+
puts "Success: true"
|
39
|
+
puts "Values: #{result.values}"
|
40
|
+
puts "Messages: #{result.messages}"
|
41
|
+
else
|
42
|
+
puts "Success: false - errors: #{result.messages.join(', ')}"
|
43
|
+
puts "Values: #{result.values}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# result.values
|
47
|
+
rescue => e
|
48
|
+
$stderr.puts e.message, e.backtrace
|
49
|
+
end
|
data/lib/skn_utils.rb
CHANGED
@@ -10,9 +10,12 @@ require 'time'
|
|
10
10
|
require 'concurrent'
|
11
11
|
unless defined?(Rails)
|
12
12
|
begin
|
13
|
+
require "uri"
|
14
|
+
require "net/http"
|
15
|
+
require 'net/https'
|
13
16
|
require 'deep_merge'
|
14
17
|
rescue LoadError => e
|
15
|
-
puts e.message
|
18
|
+
$stderr.puts e.message, e.backtrace
|
16
19
|
end
|
17
20
|
end
|
18
21
|
require 'skn_utils/core_extensions'
|
@@ -29,6 +32,10 @@ require 'skn_utils/configuration'
|
|
29
32
|
require 'skn_utils/configurable'
|
30
33
|
require 'skn_utils/wrappable'
|
31
34
|
|
35
|
+
require "skn_utils/job_commands"
|
36
|
+
require "skn_utils/http_processor"
|
37
|
+
require "skn_utils/concurrent_jobs"
|
38
|
+
|
32
39
|
require 'skn_hash'
|
33
40
|
require 'skn_registry'
|
34
41
|
require 'skn_container'
|
@@ -51,7 +58,7 @@ module SknUtils
|
|
51
58
|
[SknFailure, SknFailure].any? {|o| res.kind_of?(o) } ? res : SknSuccess.( res )
|
52
59
|
|
53
60
|
rescue StandardError, ScriptError => error
|
54
|
-
puts "#{retry_count} - #{error.class.name}:#{error.message}"
|
61
|
+
$stderr.puts "#{retry_count} - #{error.class.name}:#{error.message}"
|
55
62
|
if retry_count <= attempts
|
56
63
|
retry_count+= 1
|
57
64
|
sleep(pause_between)
|
@@ -89,3 +96,4 @@ module SknUtils
|
|
89
96
|
end
|
90
97
|
|
91
98
|
end
|
99
|
+
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# ##
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# See JobCommands, HttpProcessor, ...
|
6
|
+
# See ./bin/par_test_[block|grouped|wrapped] examples
|
7
|
+
#
|
8
|
+
|
9
|
+
module SknUtils
|
10
|
+
|
11
|
+
class SyncWorker
|
12
|
+
def initialize(&blk)
|
13
|
+
@blk = blk
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
@blk.call
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class AsyncWorker
|
22
|
+
def initialize(&blk)
|
23
|
+
@blk = Concurrent::Promise.execute(&blk)
|
24
|
+
end
|
25
|
+
|
26
|
+
def call
|
27
|
+
@blk.value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Result
|
32
|
+
def initialize(merged)
|
33
|
+
@merged = merged
|
34
|
+
end
|
35
|
+
|
36
|
+
def success?
|
37
|
+
@merged.all?(&:success) rescue false
|
38
|
+
end
|
39
|
+
|
40
|
+
def messages
|
41
|
+
@merged.map(&:message)&.compact rescue []
|
42
|
+
end
|
43
|
+
|
44
|
+
def values
|
45
|
+
@merged
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class JobWrapper
|
50
|
+
def self.call(command, callable)
|
51
|
+
callable.call(command)
|
52
|
+
rescue => ex
|
53
|
+
SknFailure.(ex.class.name, "#{ex.message}; #{ex.backtrace[0]}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class ConcurrentJobs
|
58
|
+
attr_reader :elapsed_time_string
|
59
|
+
|
60
|
+
def self.call(async: true)
|
61
|
+
worker = async ? AsyncWorker : SyncWorker
|
62
|
+
new(worker: worker)
|
63
|
+
end
|
64
|
+
|
65
|
+
def initialize(worker:)
|
66
|
+
@worker = worker
|
67
|
+
@workers = []
|
68
|
+
end
|
69
|
+
|
70
|
+
# commands: array of command objects related to callable
|
71
|
+
# callable: callable class or proc, ex:SknUtils::HttpProcessor
|
72
|
+
# callable must return SknSuccess || SknFailure
|
73
|
+
def register_jobs(commands, callable)
|
74
|
+
commands.each do |command|
|
75
|
+
register_job do
|
76
|
+
JobWrapper.call(command,callable)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def register_job(&blk)
|
82
|
+
@workers << @worker.new(&blk)
|
83
|
+
end
|
84
|
+
|
85
|
+
def render_jobs
|
86
|
+
stime = SknUtils.duration
|
87
|
+
merged = @workers.each_with_object([]) do |worker, acc|
|
88
|
+
acc.push( worker.call )
|
89
|
+
end
|
90
|
+
@elapsed_time_string = SknUtils.duration(stime)
|
91
|
+
Result.new(merged)
|
92
|
+
rescue => e
|
93
|
+
Result.new(merged || [])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# ##
|
2
|
+
#
|
3
|
+
#
|
4
|
+
module SknUtils
|
5
|
+
|
6
|
+
class HttpProcessor
|
7
|
+
|
8
|
+
def self.call(command)
|
9
|
+
completion = false
|
10
|
+
|
11
|
+
response = Net::HTTP.start( command.uri.host,command.uri.port,
|
12
|
+
use_ssl: command.uri.scheme.eql?("https")
|
13
|
+
) do |http|
|
14
|
+
http.open_timeout = 5 # in seconds, for internal http timeouts
|
15
|
+
http.read_timeout = 15 # in seconds
|
16
|
+
if command.uri.scheme.eql?("https")
|
17
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
18
|
+
end
|
19
|
+
http.request(command.request)
|
20
|
+
end
|
21
|
+
|
22
|
+
if ( response.kind_of?(Net::HTTPClientError) or response.kind_of?(Net::HTTPServerError) )
|
23
|
+
completion = SknFailure.call(response.code, response.message)
|
24
|
+
else
|
25
|
+
payload = command.json? ? JSON.load(response.body) : response.body
|
26
|
+
completion = SknSuccess.call(payload, response.class.name)
|
27
|
+
end
|
28
|
+
|
29
|
+
completion
|
30
|
+
rescue => exception
|
31
|
+
SknFailure.call(command.uri.request_uri, "#{exception.message}; #{exception.backtrace[0]}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# ##
|
2
|
+
#
|
3
|
+
# Ref: https://yukimotopress.github.io/http
|
4
|
+
|
5
|
+
module SknUtils
|
6
|
+
|
7
|
+
# #################################################
|
8
|
+
#
|
9
|
+
class CommandJSONPost
|
10
|
+
def self.call(options) # {full_url:,username:,userpass:,payload:}
|
11
|
+
new(options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def json?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def uri
|
19
|
+
@_uri
|
20
|
+
end
|
21
|
+
|
22
|
+
def request
|
23
|
+
req = Net::HTTP::Post.new(uri.path) # Generate HTTPRequest object
|
24
|
+
req.basic_auth(@_username, @_userpass) if credentials?
|
25
|
+
req.content_type = 'application/json'
|
26
|
+
req.body = formatted_data
|
27
|
+
req
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def initialize(opts={})
|
33
|
+
@_username = opts[:username]
|
34
|
+
@_userpass = opts[:userpass]
|
35
|
+
@_uri = URI.parse( opts[:full_url])
|
36
|
+
@_data = opts[:payload]
|
37
|
+
end
|
38
|
+
|
39
|
+
def formatted_data
|
40
|
+
@_data.respond_to?(:to_json) ? @_data.to_json : @_data
|
41
|
+
end
|
42
|
+
|
43
|
+
def credentials?
|
44
|
+
!(@_username.nil? || @_userpass.nil?)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# #################################################
|
50
|
+
#
|
51
|
+
class CommandFORMPost
|
52
|
+
def self.call(options) # {full_url:,username:,userpass:,payload:}
|
53
|
+
new(options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def json?
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
def uri
|
61
|
+
@_uri
|
62
|
+
end
|
63
|
+
|
64
|
+
def request
|
65
|
+
req = Net::HTTP::Post.new(uri.path) # Generate HTTPRequest object
|
66
|
+
req.basic_auth(@_username, @_userpass) if credentials?
|
67
|
+
req.content_type = 'application/x-www-form-urlencoded'
|
68
|
+
req.set_form_data(formatted_data)
|
69
|
+
req
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def initialize(opts={})
|
75
|
+
@_username = opts[:username]
|
76
|
+
@_userpass = opts[:userpass]
|
77
|
+
@_uri = URI.parse( opts[:full_url])
|
78
|
+
@_data = opts[:payload]
|
79
|
+
end
|
80
|
+
|
81
|
+
def formatted_data
|
82
|
+
@_data
|
83
|
+
end
|
84
|
+
|
85
|
+
def credentials?
|
86
|
+
!(@_username.nil? || @_userpass.nil?)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
# #################################################
|
92
|
+
#
|
93
|
+
class CommandJSONGet
|
94
|
+
def self.call(options) # {full_url:,username:,userpass:}
|
95
|
+
new(options)
|
96
|
+
end
|
97
|
+
|
98
|
+
def json?
|
99
|
+
true
|
100
|
+
end
|
101
|
+
|
102
|
+
def uri
|
103
|
+
@_uri
|
104
|
+
end
|
105
|
+
|
106
|
+
def request
|
107
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
108
|
+
req.basic_auth(@_username, @_userpass) if credentials?
|
109
|
+
req
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def initialize(opts={})
|
115
|
+
@_username = opts[:username]
|
116
|
+
@_userpass = opts[:userpass]
|
117
|
+
@_uri = URI.parse( opts[:full_url])
|
118
|
+
end
|
119
|
+
|
120
|
+
def credentials?
|
121
|
+
!(@_username.nil? || @_userpass.nil?)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
# #################################################
|
127
|
+
#
|
128
|
+
class CommandJSONPut
|
129
|
+
def self.call(options) # {full_url:,username:,userpass:,payload:}
|
130
|
+
new(options)
|
131
|
+
end
|
132
|
+
|
133
|
+
def json?
|
134
|
+
true
|
135
|
+
end
|
136
|
+
|
137
|
+
def uri
|
138
|
+
@_uri
|
139
|
+
end
|
140
|
+
|
141
|
+
def request
|
142
|
+
req = Net::HTTP::Put.new(uri.path) # Generate HTTPRequest object
|
143
|
+
req.basic_auth(@_username, @_userpass) if credentials?
|
144
|
+
req.content_type = 'application/json'
|
145
|
+
req.body = formatted_data
|
146
|
+
req
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def initialize(opts={})
|
152
|
+
@_username = opts[:username]
|
153
|
+
@_userpass = opts[:userpass]
|
154
|
+
@_uri = URI.parse( opts[:full_url])
|
155
|
+
@_data = opts[:payload]
|
156
|
+
end
|
157
|
+
|
158
|
+
def formatted_data
|
159
|
+
@_data.respond_to?(:to_json) ? @_data.to_json : @_data
|
160
|
+
end
|
161
|
+
|
162
|
+
def credentials?
|
163
|
+
!(@_username.nil? || @_userpass.nil?)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
# #################################################
|
169
|
+
#
|
170
|
+
class CommandFORMDelete
|
171
|
+
def self.call(options) # {full_url:,username:,userpass:}
|
172
|
+
new(options)
|
173
|
+
end
|
174
|
+
|
175
|
+
def json?
|
176
|
+
false
|
177
|
+
end
|
178
|
+
|
179
|
+
def uri
|
180
|
+
@_uri
|
181
|
+
end
|
182
|
+
|
183
|
+
def request
|
184
|
+
req = Net::HTTP::Delete.new(uri.request_uri)
|
185
|
+
req.basic_auth(@_username, @_userpass) if credentials?
|
186
|
+
req
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
def initialize(opts={})
|
192
|
+
@_username = opts[:username]
|
193
|
+
@_userpass = opts[:userpass]
|
194
|
+
@_uri = URI.parse( opts[:full_url])
|
195
|
+
end
|
196
|
+
|
197
|
+
def credentials?
|
198
|
+
!(@_username.nil? || @_userpass.nil?)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
data/lib/skn_utils/version.rb
CHANGED
data/skn_utils.gemspec
CHANGED
@@ -39,7 +39,10 @@ EOF
|
|
39
39
|
spec.add_development_dependency "rake", "~> 10"
|
40
40
|
spec.add_development_dependency "rspec", '~> 3'
|
41
41
|
spec.add_development_dependency "pry", "~> 0"
|
42
|
+
spec.add_development_dependency "pry-coolline"
|
42
43
|
spec.add_development_dependency "simplecov", "~> 0"
|
43
44
|
spec.add_development_dependency 'benchmark-ips', '~> 2'
|
45
|
+
spec.add_development_dependency 'webmock'
|
46
|
+
|
44
47
|
|
45
48
|
end
|
@@ -0,0 +1,277 @@
|
|
1
|
+
##
|
2
|
+
# spec/lib/skn_utils/as_human_size_spec.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
|
6
|
+
describe SknUtils::ConcurrentJobs, 'Run Multiple Jobs' do
|
7
|
+
|
8
|
+
let(:commands) {
|
9
|
+
[
|
10
|
+
SknUtils::CommandJSONPost.call(full_url: "http://example.com/posts", payload: {one: 1}),
|
11
|
+
SknUtils::CommandFORMPost.call(full_url: "http://example.com/posts", payload: {one: 1}),
|
12
|
+
SknUtils::CommandJSONGet.call(full_url: "http://example.com/posts/1"),
|
13
|
+
SknUtils::CommandJSONPut.call(full_url: "http://example.com/posts", payload: {one: 1}),
|
14
|
+
SknUtils::CommandFORMDelete.call(full_url: "http://example.com/posts/1")
|
15
|
+
]
|
16
|
+
}
|
17
|
+
|
18
|
+
let(:test_proc) {
|
19
|
+
->(cmd) { SknSuccess.(cmd.uri.request_uri, "Ok") }
|
20
|
+
}
|
21
|
+
|
22
|
+
let(:inline_failure_proc) {
|
23
|
+
->(cmd) { SknFailure.(cmd.uri.request_uri, "Failure") }
|
24
|
+
}
|
25
|
+
|
26
|
+
let(:catastrophic_proc) {
|
27
|
+
->(cmd) { SomeUnkownThing.(cmd.uri.request_uri, "Catastrophic") }
|
28
|
+
}
|
29
|
+
|
30
|
+
context "HTTP Requests " do
|
31
|
+
it "Job Commands will provide a valid http request object" do
|
32
|
+
expect(commands.any?(&:request)).to be true
|
33
|
+
end
|
34
|
+
|
35
|
+
it "Performs Http Post Requests" do
|
36
|
+
test_url = "http://jsonplaceholder.typicode.com/users"
|
37
|
+
stub_request(:post, test_url).
|
38
|
+
to_return(status: 200, body: "{\"message\":\"me\"}", headers: {})
|
39
|
+
|
40
|
+
cmd = SknUtils::CommandJSONPost.call(full_url: test_url, payload: {"one" => 1})
|
41
|
+
|
42
|
+
provider = SknUtils::ConcurrentJobs.call
|
43
|
+
provider.register_job do
|
44
|
+
SknUtils::JobWrapper.call(cmd, SknUtils::HttpProcessor)
|
45
|
+
end
|
46
|
+
result = provider.render_jobs
|
47
|
+
|
48
|
+
expect(result).to be_a(SknUtils::Result)
|
49
|
+
expect(result.success?).to be true
|
50
|
+
expect(result.values.size).to eq(1)
|
51
|
+
expect(result.values[0]).to be_a(SknSuccess)
|
52
|
+
expect(result.values[0].value).to be_a(Hash)
|
53
|
+
expect(result.values[0].value["message"]).to eq("me")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "Performs Http Form Post Requests" do
|
57
|
+
test_url = "http://jsonplaceholder.typicode.com/users"
|
58
|
+
stub_request(:post, test_url).
|
59
|
+
to_return(status: 200, body: "message=me", headers: {})
|
60
|
+
|
61
|
+
cmd = SknUtils::CommandFORMPost.call(full_url: test_url, payload: {"one" => 1})
|
62
|
+
|
63
|
+
provider = SknUtils::ConcurrentJobs.call
|
64
|
+
provider.register_job do
|
65
|
+
SknUtils::JobWrapper.call(cmd, SknUtils::HttpProcessor)
|
66
|
+
end
|
67
|
+
result = provider.render_jobs
|
68
|
+
|
69
|
+
expect(result).to be_a(SknUtils::Result)
|
70
|
+
expect(result.success?).to be true
|
71
|
+
expect(result.values.size).to eq(1)
|
72
|
+
expect(result.values[0]).to be_a(SknSuccess)
|
73
|
+
expect(result.values[0].value).to be_a(String)
|
74
|
+
expect(result.values[0].value).to eq("message=me")
|
75
|
+
end
|
76
|
+
|
77
|
+
it "Performs Http Get Requests" do
|
78
|
+
test_url = "http://jsonplaceholder.typicode.com/users"
|
79
|
+
stub_request(:get, test_url).
|
80
|
+
to_return(status: 200, body: "{\"message\":\"me\"}", headers: {})
|
81
|
+
|
82
|
+
cmd = SknUtils::CommandJSONGet.call(full_url: test_url)
|
83
|
+
|
84
|
+
provider = SknUtils::ConcurrentJobs.call
|
85
|
+
provider.register_job do
|
86
|
+
SknUtils::JobWrapper.call(cmd, SknUtils::HttpProcessor)
|
87
|
+
end
|
88
|
+
result = provider.render_jobs
|
89
|
+
|
90
|
+
expect(result).to be_a(SknUtils::Result)
|
91
|
+
expect(result.success?).to be true
|
92
|
+
expect(result.values.size).to eq(1)
|
93
|
+
expect(result.values[0]).to be_a(SknSuccess)
|
94
|
+
expect(result.values[0].value).to be_a(Hash)
|
95
|
+
expect(result.values[0].value["message"]).to eq("me")
|
96
|
+
end
|
97
|
+
|
98
|
+
it "Performs Http Put Requests" do
|
99
|
+
test_url = "http://jsonplaceholder.typicode.com/users"
|
100
|
+
stub_request(:put, test_url).
|
101
|
+
to_return(status: 200, body: "{\"message\":\"me\"}", headers: {})
|
102
|
+
|
103
|
+
cmd = SknUtils::CommandJSONPut.call(full_url: test_url, payload: {"one" => 1})
|
104
|
+
|
105
|
+
provider = SknUtils::ConcurrentJobs.call
|
106
|
+
provider.register_job do
|
107
|
+
SknUtils::JobWrapper.call(cmd, SknUtils::HttpProcessor)
|
108
|
+
end
|
109
|
+
result = provider.render_jobs
|
110
|
+
|
111
|
+
expect(result).to be_a(SknUtils::Result)
|
112
|
+
expect(result.success?).to be true
|
113
|
+
expect(result.values.size).to eq(1)
|
114
|
+
expect(result.values[0]).to be_a(SknSuccess)
|
115
|
+
expect(result.values[0].value).to be_a(Hash)
|
116
|
+
expect(result.values[0].value["message"]).to eq("me")
|
117
|
+
end
|
118
|
+
|
119
|
+
it "Performs Http Form Delete Requests" do
|
120
|
+
test_url = "http://jsonplaceholder.typicode.com/users"
|
121
|
+
stub_request(:delete, test_url).
|
122
|
+
to_return(status: 200, body: "message=me", headers: {})
|
123
|
+
|
124
|
+
cmd = SknUtils::CommandFORMDelete.call(full_url: test_url, payload: {"one" => 1})
|
125
|
+
|
126
|
+
provider = SknUtils::ConcurrentJobs.call
|
127
|
+
provider.register_job do
|
128
|
+
SknUtils::JobWrapper.call(cmd, SknUtils::HttpProcessor)
|
129
|
+
end
|
130
|
+
result = provider.render_jobs
|
131
|
+
|
132
|
+
expect(result).to be_a(SknUtils::Result)
|
133
|
+
expect(result.success?).to be true
|
134
|
+
expect(result.values.size).to eq(1)
|
135
|
+
expect(result.values[0]).to be_a(SknSuccess)
|
136
|
+
expect(result.values[0].value).to be_a(String)
|
137
|
+
expect(result.values[0].value).to eq("message=me")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context "Asynchronous" do
|
142
|
+
it "Runs Jobs" do
|
143
|
+
provider = SknUtils::ConcurrentJobs.call
|
144
|
+
provider.register_jobs(commands, test_proc)
|
145
|
+
result = provider.render_jobs
|
146
|
+
|
147
|
+
expect(result).to be_a(SknUtils::Result)
|
148
|
+
expect(result.success?).to be true
|
149
|
+
expect(result.values.size).to eq(commands.size)
|
150
|
+
expect(result.values[0]).to be_a(SknSuccess)
|
151
|
+
end
|
152
|
+
it "Runs Jobs and handles inline failures" do
|
153
|
+
provider = SknUtils::ConcurrentJobs.call
|
154
|
+
|
155
|
+
provider.register_job do
|
156
|
+
SknUtils::JobWrapper.call(commands[0], test_proc)
|
157
|
+
end
|
158
|
+
provider.register_job do
|
159
|
+
SknUtils::JobWrapper.call(commands[1], inline_failure_proc)
|
160
|
+
end
|
161
|
+
provider.register_job do
|
162
|
+
SknUtils::JobWrapper.call(commands[2], inline_failure_proc)
|
163
|
+
end
|
164
|
+
provider.register_job do
|
165
|
+
SknUtils::JobWrapper.call(commands[3], inline_failure_proc)
|
166
|
+
end
|
167
|
+
provider.register_job do
|
168
|
+
SknUtils::JobWrapper.call(commands[4], test_proc)
|
169
|
+
end
|
170
|
+
|
171
|
+
result = provider.render_jobs
|
172
|
+
|
173
|
+
expect(result).to be_a(SknUtils::Result)
|
174
|
+
expect(result.success?).to be false
|
175
|
+
expect(result.values.size).to eq(commands.size)
|
176
|
+
expect(result.values[1]).to be_a(SknFailure)
|
177
|
+
expect(result.values[1].message).to eq("Failure")
|
178
|
+
end
|
179
|
+
it "Runs Jobs and handles catastrophic failures" do
|
180
|
+
provider = SknUtils::ConcurrentJobs.call
|
181
|
+
provider.register_job do
|
182
|
+
SknUtils::JobWrapper.call(command[0], test_proc)
|
183
|
+
end
|
184
|
+
provider.register_job do # notice `command` vs `commands`
|
185
|
+
SknUtils::JobWrapper.call(command[1], inline_failure_proc)
|
186
|
+
end
|
187
|
+
provider.register_job do
|
188
|
+
SknUtils::JobWrapper.call(command[2], catastrophic_proc)
|
189
|
+
end
|
190
|
+
provider.register_job do
|
191
|
+
SknUtils::JobWrapper.call(commands[3], catastrophic_proc)
|
192
|
+
end
|
193
|
+
provider.register_job do
|
194
|
+
SknUtils::JobWrapper.call(commands[4], test_proc)
|
195
|
+
end
|
196
|
+
|
197
|
+
result = provider.render_jobs
|
198
|
+
|
199
|
+
expect(result).to be_a(SknUtils::Result)
|
200
|
+
expect(result.success?).to be false
|
201
|
+
expect(result.values.size).to eq(commands.size)
|
202
|
+
expect(result.values[3].value).to eq("NameError")
|
203
|
+
expect(result.values[3]).to be_a(SknFailure)
|
204
|
+
expect(result.values[3]).to be_a(SknFailure)
|
205
|
+
expect(result.values[0]).to be nil
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context "Synchronous" do
|
210
|
+
it "Runs Jobs" do
|
211
|
+
provider = SknUtils::ConcurrentJobs.call(async: false)
|
212
|
+
provider.register_jobs(commands, test_proc)
|
213
|
+
result = provider.render_jobs
|
214
|
+
|
215
|
+
expect(result).to be_a(SknUtils::Result)
|
216
|
+
expect(result.success?).to be true
|
217
|
+
expect(result.values.size).to eq(commands.size)
|
218
|
+
expect(result.values[0]).to be_a(SknSuccess)
|
219
|
+
end
|
220
|
+
it "Runs Jobs and handles inline failures" do
|
221
|
+
provider = SknUtils::ConcurrentJobs.call
|
222
|
+
|
223
|
+
provider.register_job do
|
224
|
+
SknUtils::JobWrapper.call(commands[0], test_proc)
|
225
|
+
end
|
226
|
+
provider.register_job do
|
227
|
+
SknUtils::JobWrapper.call(commands[1], inline_failure_proc)
|
228
|
+
end
|
229
|
+
provider.register_job do
|
230
|
+
SknUtils::JobWrapper.call(commands[2], inline_failure_proc)
|
231
|
+
end
|
232
|
+
provider.register_job do
|
233
|
+
SknUtils::JobWrapper.call(commands[3], inline_failure_proc)
|
234
|
+
end
|
235
|
+
provider.register_job do
|
236
|
+
SknUtils::JobWrapper.call(commands[4], test_proc)
|
237
|
+
end
|
238
|
+
|
239
|
+
result = provider.render_jobs
|
240
|
+
|
241
|
+
expect(result).to be_a(SknUtils::Result)
|
242
|
+
expect(result.success?).to be false
|
243
|
+
expect(result.values.size).to eq(commands.size)
|
244
|
+
expect(result.values[1]).to be_a(SknFailure)
|
245
|
+
expect(result.values[1].message).to eq("Failure")
|
246
|
+
end
|
247
|
+
it "Runs Jobs and handles catastrophic failures" do
|
248
|
+
provider = SknUtils::ConcurrentJobs.call
|
249
|
+
provider.register_job do
|
250
|
+
SknUtils::JobWrapper.call(commands[0], test_proc)
|
251
|
+
end
|
252
|
+
provider.register_job do
|
253
|
+
SknUtils::JobWrapper.call(commands[1], inline_failure_proc)
|
254
|
+
end
|
255
|
+
provider.register_job do
|
256
|
+
SknUtils::JobWrapper.call(commands[2], catastrophic_proc)
|
257
|
+
end
|
258
|
+
provider.register_job do
|
259
|
+
SknUtils::JobWrapper.call(commands[3], catastrophic_proc)
|
260
|
+
end
|
261
|
+
provider.register_job do
|
262
|
+
SknUtils::JobWrapper.call(commands[4], test_proc)
|
263
|
+
end
|
264
|
+
|
265
|
+
result = provider.render_jobs
|
266
|
+
|
267
|
+
expect(result).to be_a(SknUtils::Result)
|
268
|
+
expect(result.success?).to be false
|
269
|
+
expect(result.values.size).to eq(commands.size)
|
270
|
+
expect(result.values[3].value).to eq("NameError")
|
271
|
+
expect(result.values[2]).to be_a(SknFailure)
|
272
|
+
expect(result.values[1]).to be_a(SknFailure)
|
273
|
+
expect(result.values[0]).to be_a(SknSuccess)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -15,6 +15,8 @@ end
|
|
15
15
|
require 'skn_utils'
|
16
16
|
require 'rspec'
|
17
17
|
|
18
|
+
require 'webmock/rspec'
|
19
|
+
|
18
20
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
19
21
|
RSpec.configure do |config|
|
20
22
|
Kernel.srand config.seed
|
@@ -30,6 +32,8 @@ RSpec.configure do |config|
|
|
30
32
|
# config.disable_monkey_patching! # -- breaks rspec runtime
|
31
33
|
config.warnings = true
|
32
34
|
|
35
|
+
config.include WebMock::API
|
36
|
+
|
33
37
|
if config.files_to_run.one?
|
34
38
|
config.formatter = :documentation
|
35
39
|
else
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: skn_utils
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Scott Jr
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deep_merge
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry-coolline
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: simplecov
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,6 +150,20 @@ dependencies:
|
|
136
150
|
- - "~>"
|
137
151
|
- !ruby/object:Gem::Version
|
138
152
|
version: '2'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: webmock
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
139
167
|
description: "The intent of the NestedResult class is to be a container for data values
|
140
168
|
composed of key/value pairs, \nwith easy access to its contents, and on-demand transformation
|
141
169
|
back to the hash (#to_hash).\n\nReview the RSpec tests, and or review the README
|
@@ -156,6 +184,10 @@ files:
|
|
156
184
|
- Rakefile
|
157
185
|
- _config.yml
|
158
186
|
- bin/bench_nested_result.rb
|
187
|
+
- bin/concurrent_test_block
|
188
|
+
- bin/concurrent_test_grouped
|
189
|
+
- bin/concurrent_test_procs
|
190
|
+
- bin/concurrent_test_wrapped
|
159
191
|
- bin/configs/settings.yml
|
160
192
|
- bin/configs/settings/development.yml
|
161
193
|
- bin/configs/settings/production.yml
|
@@ -172,11 +204,14 @@ files:
|
|
172
204
|
- lib/skn_settings.rb
|
173
205
|
- lib/skn_success.rb
|
174
206
|
- lib/skn_utils.rb
|
207
|
+
- lib/skn_utils/concurrent_jobs.rb
|
175
208
|
- lib/skn_utils/configurable.rb
|
176
209
|
- lib/skn_utils/configuration.rb
|
177
210
|
- lib/skn_utils/core_extensions.rb
|
178
211
|
- lib/skn_utils/dotted_hash.rb
|
179
212
|
- lib/skn_utils/env_string_handler.rb
|
213
|
+
- lib/skn_utils/http_processor.rb
|
214
|
+
- lib/skn_utils/job_commands.rb
|
180
215
|
- lib/skn_utils/nested_result.rb
|
181
216
|
- lib/skn_utils/notifier_base.rb
|
182
217
|
- lib/skn_utils/null_object.rb
|
@@ -196,6 +231,7 @@ files:
|
|
196
231
|
- spec/lib/skn_settings_spec.rb
|
197
232
|
- spec/lib/skn_utils/as_human_size_spec.rb
|
198
233
|
- spec/lib/skn_utils/catch_exceptions_spec.rb
|
234
|
+
- spec/lib/skn_utils/concurrent_jobs_spec.rb
|
199
235
|
- spec/lib/skn_utils/configurable_spec.rb
|
200
236
|
- spec/lib/skn_utils/container_spec.rb
|
201
237
|
- spec/lib/skn_utils/nested_result_spec.rb
|
@@ -246,6 +282,7 @@ test_files:
|
|
246
282
|
- spec/lib/skn_settings_spec.rb
|
247
283
|
- spec/lib/skn_utils/as_human_size_spec.rb
|
248
284
|
- spec/lib/skn_utils/catch_exceptions_spec.rb
|
285
|
+
- spec/lib/skn_utils/concurrent_jobs_spec.rb
|
249
286
|
- spec/lib/skn_utils/configurable_spec.rb
|
250
287
|
- spec/lib/skn_utils/container_spec.rb
|
251
288
|
- spec/lib/skn_utils/nested_result_spec.rb
|