chaotic_job 0.4.0 → 0.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/CHANGELOG.md +4 -0
- data/README.md +18 -1
- data/lib/chaotic_job/simulation.rb +9 -17
- data/lib/chaotic_job/tracer.rb +50 -0
- data/lib/chaotic_job/version.rb +1 -1
- data/lib/chaotic_job.rb +7 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c4da24d7fc4aa39030976d4f7249a14c87995ef5d5b05c523ad5e1e83724f37
|
4
|
+
data.tar.gz: 0a7be90ecb5381a5c9e67c6673bb41bc3ad2997c904ca42bfedbb4c701ef6bf0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11a054f050a3e95aa40c392e281b69bf2c43dbba3b91bce3d17473d589f4e2057d26a18af5071d7a5f4cb2b055775bbdd293af6c712792fbe1d042c4c99cca85
|
7
|
+
data.tar.gz: d6750f62adecc71334dd42cfb15e35e33fb36680be62a6c9d0d5f4e2ed29eb598f9f92fac6a09ec1a4636816789f13996bc0241faa5a1ef9eb4acac0577fc06f
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.0] - 2025-06-04
|
4
|
+
|
5
|
+
- Add a Tracer class [#3](https://github.com/fractaledmind/chaotic_job/pull/3)
|
6
|
+
|
3
7
|
## [0.4.0] - 2025-05-27
|
4
8
|
|
5
9
|
- Allow a Glitch to be defined for a method call or method return [#4](https://github.com/fractaledmind/chaotic_job/pull/4)
|
data/README.md
CHANGED
@@ -217,7 +217,24 @@ More specifically, it will create a scenario injecting a glitch before every lin
|
|
217
217
|
|
218
218
|
It generates all possible glitch scenarios by performing your job once with a [`TracePoint`](https://docs.ruby-lang.org/en/master/TracePoint.html) that captures each line executed in your job. It then computes all possible glitch locations to produce a set of scenarios that will be run. The block that you pass to `run_simulation` will be called for each scenario, allowing you to make assertions about the behavior of your job under all scenarios.
|
219
219
|
|
220
|
-
|
220
|
+
If you want to have the simulation run against a larger collection of scenarios, you can capture a custom callstack using the `ChaoticJob::Tracer` class and pass it to the `run_simulation` method as the `callstack` parameter. A `Tracer` is initialized with a block that determines which `TracePoint` events to collect. You then call `capture` with a block that defines the code to be traced. The default `Simulation` tracer collects all events for the passed job and then traces the job execution, essentially like this:
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
job_file_path = YourJob.instance_method(:perform).source_location&.first
|
224
|
+
tracer = Tracer.new { |tp| tp.path == job_file_path || tp.defined_class == YourJob }
|
225
|
+
tracer.capture { YourJob.perform_now }
|
226
|
+
```
|
227
|
+
|
228
|
+
To capture, for example, a custom callstack that includes all events within your application, you can use the `ChaoticJob::Tracer` class as follows:
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
tracer = ChaoticJob::Tracer.new { |tp| tp.path.start_with?(Rails.root.to_s) }
|
232
|
+
tracer.capture { YourJob.perform_now }
|
233
|
+
```
|
234
|
+
|
235
|
+
If you passed this callstack to your simulation, it would test what happens to your job whenever a transient glitch is injected anywhere in your application code called as a part of executing the job under test.
|
236
|
+
|
237
|
+
Remember, in your application tests, you will want to make assertions about the side-effects that your job performs, asserting that they are correctly idempotent (only occur once) and result in the correct state.
|
221
238
|
|
222
239
|
## Development
|
223
240
|
|
@@ -6,13 +6,16 @@
|
|
6
6
|
# Simulation.new(job).scenarios
|
7
7
|
module ChaoticJob
|
8
8
|
class Simulation
|
9
|
-
def initialize(job, depth: 1, variations: 100, test: nil, seed: nil)
|
9
|
+
def initialize(job, callstack: nil, depth: 1, variations: 100, test: nil, seed: nil)
|
10
10
|
@template = job
|
11
|
+
@callstack = callstack || capture_callstack
|
11
12
|
@depth = depth
|
12
13
|
@variations = variations
|
13
14
|
@test = test
|
14
15
|
@seed = seed || Random.new_seed
|
15
16
|
@random = Random.new(@seed)
|
17
|
+
|
18
|
+
raise Error.new("callstack must be a generated via the ChaoticJob::Tracer") unless @callstack.is_a?(Stack)
|
16
19
|
end
|
17
20
|
|
18
21
|
def run(&callback)
|
@@ -27,9 +30,8 @@ module ChaoticJob
|
|
27
30
|
end
|
28
31
|
|
29
32
|
def permutations
|
30
|
-
|
31
|
-
|
32
|
-
[:before_line, "#{path}:#{lineno}"]
|
33
|
+
error_locations = @callstack.map do |event, key|
|
34
|
+
["before_#{event}", key]
|
33
35
|
end
|
34
36
|
error_locations.permutation(@depth)
|
35
37
|
end
|
@@ -52,23 +54,13 @@ module ChaoticJob
|
|
52
54
|
private
|
53
55
|
|
54
56
|
def capture_callstack
|
55
|
-
return @callstack if defined?(@callstack)
|
56
|
-
|
57
|
-
@callstack = Set.new
|
58
57
|
job_class = @template.class
|
59
58
|
job_file_path = job_class.instance_method(:perform).source_location&.first
|
59
|
+
tracer = Tracer.new { |tp| tp.path == job_file_path || tp.defined_class == job_class }
|
60
|
+
callstack = tracer.capture { @template.dup.perform_now }
|
60
61
|
|
61
|
-
trace = TracePoint.new(:line) do |tp|
|
62
|
-
next if tp.defined_class == self.class
|
63
|
-
next unless tp.path == job_file_path ||
|
64
|
-
tp.defined_class == job_class
|
65
|
-
|
66
|
-
@callstack << [tp.path, tp.lineno]
|
67
|
-
end
|
68
|
-
|
69
|
-
trace.enable { @template.dup.perform_now }
|
70
62
|
@template.class.queue_adapter.enqueued_jobs = []
|
71
|
-
|
63
|
+
callstack
|
72
64
|
end
|
73
65
|
|
74
66
|
def run_scenario(scenario, &callback)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Tracer.new { |tp| tp.path.start_with? "foo" }
|
4
|
+
# Tracer.new.capture { code_execution_to_trace_callstack }
|
5
|
+
|
6
|
+
module ChaoticJob
|
7
|
+
class Tracer
|
8
|
+
def initialize(&constraint)
|
9
|
+
@constraint = constraint
|
10
|
+
@callstack = Stack.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def capture(&block)
|
14
|
+
trace = TracePoint.new(:line, :call, :return) do |tp|
|
15
|
+
# :nocov: SimpleCov cannot track code executed _within_ a TracePoint
|
16
|
+
next if tp.defined_class == self.class
|
17
|
+
next unless @constraint.call(tp)
|
18
|
+
|
19
|
+
case tp.event
|
20
|
+
when :line
|
21
|
+
key = line_key(tp)
|
22
|
+
when :call, :return
|
23
|
+
key = call_key(tp)
|
24
|
+
end
|
25
|
+
|
26
|
+
@callstack << [tp.event, key]
|
27
|
+
# :nocov:
|
28
|
+
end
|
29
|
+
|
30
|
+
trace.enable(&block)
|
31
|
+
@callstack
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# :nocov: SimpleCov cannot track code executed _within_ a TracePoint
|
37
|
+
def line_key(event)
|
38
|
+
"#{event.path}:#{event.lineno}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def call_key(event)
|
42
|
+
if Module === event.self
|
43
|
+
"#{event.self}.#{event.method_id}"
|
44
|
+
else
|
45
|
+
"#{event.defined_class}##{event.method_id}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
# :nocov:
|
49
|
+
end
|
50
|
+
end
|
data/lib/chaotic_job/version.rb
CHANGED
data/lib/chaotic_job.rb
CHANGED
@@ -3,13 +3,16 @@
|
|
3
3
|
require_relative "chaotic_job/version"
|
4
4
|
require_relative "chaotic_job/journal"
|
5
5
|
require_relative "chaotic_job/performer"
|
6
|
+
require_relative "chaotic_job/tracer"
|
6
7
|
require_relative "chaotic_job/glitch"
|
7
8
|
require_relative "chaotic_job/scenario"
|
8
9
|
require_relative "chaotic_job/simulation"
|
10
|
+
require "set"
|
9
11
|
|
10
12
|
module ChaoticJob
|
11
|
-
|
12
|
-
|
13
|
+
Error = Class.new(StandardError)
|
14
|
+
RetryableError = Class.new(Error)
|
15
|
+
Stack = Set
|
13
16
|
|
14
17
|
def self.log_to_journal!(item = nil, scope: nil)
|
15
18
|
if item && scope
|
@@ -63,11 +66,12 @@ module ChaoticJob
|
|
63
66
|
Performer.perform_all_after(time)
|
64
67
|
end
|
65
68
|
|
66
|
-
def run_simulation(job, depth: nil, variations: nil, &block)
|
69
|
+
def run_simulation(job, depth: nil, variations: nil, callstack: nil, &block)
|
67
70
|
seed = defined?(RSpec) ? RSpec.configuration.seed : Minitest.seed
|
68
71
|
kwargs = {test: self, seed: seed}
|
69
72
|
kwargs[:depth] = depth if depth
|
70
73
|
kwargs[:variations] = variations if variations
|
74
|
+
kwargs[:callstack] = callstack if callstack
|
71
75
|
self.simulation_scenario = nil
|
72
76
|
Simulation.new(job, **kwargs).run(&block)
|
73
77
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chaotic_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Margheim
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-06-03 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activejob
|
@@ -41,6 +41,7 @@ files:
|
|
41
41
|
- lib/chaotic_job/performer.rb
|
42
42
|
- lib/chaotic_job/scenario.rb
|
43
43
|
- lib/chaotic_job/simulation.rb
|
44
|
+
- lib/chaotic_job/tracer.rb
|
44
45
|
- lib/chaotic_job/version.rb
|
45
46
|
- sig/chaotic_job.rbs
|
46
47
|
homepage: https://github.com/fractaledmind/chaotic_job
|