chaotic_job 0.5.0 โ†’ 0.7.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
  SHA256:
3
- metadata.gz: 0c4da24d7fc4aa39030976d4f7249a14c87995ef5d5b05c523ad5e1e83724f37
4
- data.tar.gz: 0a7be90ecb5381a5c9e67c6673bb41bc3ad2997c904ca42bfedbb4c701ef6bf0
3
+ metadata.gz: 94de9ce766a9042a925882e70f29a8022fbd3b22fbd175ed3254260e04962cba
4
+ data.tar.gz: e05d2546038d72cd9181fef3eff223d7adec84225b0fa1267d89d0b3880351ef
5
5
  SHA512:
6
- metadata.gz: 11a054f050a3e95aa40c392e281b69bf2c43dbba3b91bce3d17473d589f4e2057d26a18af5071d7a5f4cb2b055775bbdd293af6c712792fbe1d042c4c99cca85
7
- data.tar.gz: d6750f62adecc71334dd42cfb15e35e33fb36680be62a6c9d0d5f4e2ed29eb598f9f92fac6a09ec1a4636816789f13996bc0241faa5a1ef9eb4acac0577fc06f
6
+ metadata.gz: 29fedcda5ca8ec4c98fa68d4f1e0661194c558a72a7a70b6b19a445de0e4bb0741e873c1365911315a8be73461248ccbca3005dd604e0a1f8229cf75aa4f7f69
7
+ data.tar.gz: 93423ffd252f3faf4b3c0e0c0e7097fc9e14468d884935fed12421ca653cc99a47819ab80833d4068e0c8ebf23752e45385923384c56e4072e08554d7fe872f2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.7.0] - 2025-06-09
4
+
5
+ - Glitch only works with singular event + key definition [#6](https://github.com/fractaledmind/chaotic_job/pull/6)
6
+ - Scenarios assert the glitch was executed [#7](https://github.com/fractaledmind/chaotic_job/pull/7)
7
+ - Add helper methods to create a Glitch of the various kinds [#8](https://github.com/fractaledmind/chaotic_job/pull/8)
8
+ - Improve test coverage [#9](https://github.com/fractaledmind/chaotic_job/pull/9)
9
+
10
+ ## [0.6.0] - 2025-06-08
11
+
12
+ - `run_scenario` requires a Glitch instance [#5](https://github.com/fractaledmind/chaotic_job/pull/5)
13
+
3
14
  ## [0.5.0] - 2025-06-04
4
15
 
5
16
  - Add a Tracer class [#3](https://github.com/fractaledmind/chaotic_job/pull/3)
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/chaotic_job.svg)](https://rubygems.org/gems/chaotic_job)
4
4
  [![Gem Downloads](https://img.shields.io/gem/dt/chaotic_job)](https://rubygems.org/gems/chaotic_job)
5
5
  ![Tests](https://github.com/fractaledmind/chaotic_job/actions/workflows/main.yml/badge.svg)
6
- ![Coverage](https://img.shields.io/badge/code%20coverage-92%25-success)
6
+ ![Coverage](https://img.shields.io/badge/code%20coverage-98%25-success)
7
7
  [![Sponsors](https://img.shields.io/github/sponsors/fractaledmind?color=eb4aaa&logo=GitHub%20Sponsors)](https://github.com/sponsors/fractaledmind)
8
8
  [![Twitter Follow](https://img.shields.io/twitter/url?label=%40fractaledmind&style=social&url=https%3A%2F%2Ftwitter.com%2Ffractaledmind)](https://twitter.com/fractaledmind)
9
9
 
@@ -107,7 +107,7 @@ test "scenario of a simple job" do
107
107
  def step_3; ChaoticJob::Journal.log; end
108
108
  end
109
109
 
110
- run_scenario(Job.new, glitch: [:before_call, "Job#step_3"])
110
+ run_scenario(Job.new, glitch: ChaoticJob::Glitch.before_call("Job#step_3"))
111
111
 
112
112
  assert_equal 5, ChaoticJob::Journal.total
113
113
  end
@@ -124,14 +124,16 @@ end
124
124
  > | `Journal.entries` | get all of the logged values under the default scope |
125
125
  > | `Journal.entries(scope: :special)` | get all of the logged values under a particular scope |
126
126
 
127
- In this example, the job being tested is defined within the test case. You can, of course, also test jobs defined in your application. The key detail is the `glitch` keyword argument. A "glitch" is simply a tuple that describes precisely where you would like the failure to occur. The first element of the tuple is the _kind_ of glitch, which can be either `:before_line`, `:before_call`, or `:before_return`. These refer to the three kinds of `TracePoint` events that the gem hooks into. The second element is the _key_ for the code that will be affected by the glitch. This _key_ is a specially formatted string that defines the specific bit of code that the glitch should be inserted before. The different kinds of glitches are identified by different kinds of keys:
127
+ In this example, the job being tested is defined within the test case. You can, of course, also test jobs defined in your application. The key detail is the `glitch` keyword argument.
128
+
129
+ A "glitch" is describes precisely where you would like the failure to occur. The description is composed first of the _kind_ of glitch, which can be either `before_line`, `before_call`, or `before_return`. These refer to the three kinds of `TracePoint` events that the gem hooks into. The second element is the _key_ for the code that will be affected by the glitch. This _key_ is a specially formatted string that defines the specific bit of code that the glitch should be inserted before. The different kinds of glitches are identified by different kinds of keys:
128
130
  |kind|key format|key example|
129
131
  |---|---|---|
130
- |`:before_line`|`"#{file_path}:#{line_number}"`|`"/Users/you/path/to/file.rb:123"`|
131
- |`:before_call`|`"#{YourClass.name}(.|#)#{method_name}"`|`"YourClass.some_class_method"`|
132
- |`:before_return`|`"#{YourClass.name}(.|#)#{method_name}"`|`"YourClass#some_instance_method"`|
132
+ |`before_line`|`"#{file_path}:#{line_number}"`|`"/Users/you/path/to/file.rb:123"`|
133
+ |`before_call`|`"#{YourClass.name}(.|#)#{method_name}"`|`"YourClass.some_class_method"`|
134
+ |`before_return`|`"#{YourClass.name}(.|#)#{method_name}"`|`"YourClass#some_instance_method"`|
133
135
 
134
- As you can see, the `:before_call` and `:before_return` keys are formatted the same, and can identify any instance (`#`) or class (`.`) method.
136
+ As you can see, the `before_call` and `before_return` keys are formatted the same, and can identify any instance (`#`) or class (`.`) method.
135
137
 
136
138
  What the example scenario above does is inject a glitch before the `step_3` method is called, here:
137
139
 
@@ -144,10 +146,10 @@ def perform
144
146
  end
145
147
  ```
146
148
 
147
- If we wanted to inject a glitch right before the `step_3` method finishes, we could define the glitch as a `:before_return`, like this:
149
+ If we wanted to inject a glitch right before the `step_3` method finishes, we could define the glitch as a `before_return`, like this:
148
150
 
149
151
  ```ruby
150
- run_scenario(Job.new, glitch: [:before_return, "Job#step_3"])
152
+ run_scenario(Job.new, glitch: ChaoticJob::Glitch.before_return("Job#step_3"))
151
153
  ```
152
154
 
153
155
  and it would inject the transient error right here:
@@ -159,19 +161,10 @@ def step_3
159
161
  end
160
162
  ```
161
163
 
162
- Finally, if you need to inject a glitch right before a particular line of code is executed that is neither a method call nor a method return, you can use the `:before_line` key, like this:
164
+ Finally, if you need to inject a glitch right before a particular line of code is executed that is neither a method call nor a method return, you can use the `before_line` key, like this:
163
165
 
164
166
  ```ruby
165
- run_scenario(Job.new, glitch: [:before_line, "#{__FILE__}:6"])
166
- ```
167
-
168
- If you want to simulate multiple glitches affecting a job run, you can use the plural `glitches` keyword argument instead and pass an array of tuples:
169
-
170
- ```ruby
171
- run_scenario(Job.new, glitches: [
172
- [:before_call, "Job#step_1"],
173
- [:before_return, "Job#step_1"]
174
- ])
167
+ run_scenario(Job.new, glitch: ChaoticJob::Glitch.before_line("#{__FILE__}:6"))
175
168
  ```
176
169
 
177
170
  Scenario testing is useful to test the behavior of a job under a specific set of conditions. But, if you want to test the behavior of a job under a variety of conditions, you can use the `run_simulation` method. Instead of running a single scenario, a simulation will run the full set of possible error scenarios for your job.
@@ -199,23 +192,21 @@ end
199
192
  More specifically, it will create a scenario injecting a glitch before every line of code executed in your job. So, in this example, the simulation will run 12 scenarios:
200
193
 
201
194
  ```ruby
202
- [
203
- [[:before_line, "test_chaotic_job.rb:69"]],
204
- [[:before_line, "test_chaotic_job.rb:75"]],
205
- [[:before_line, "test_chaotic_job.rb:74"]],
206
- [[:before_line, "test_chaotic_job.rb:74"]],
207
- [[:before_line, "test_chaotic_job.rb:68"]],
208
- [[:before_line, "test_chaotic_job.rb:70"]],
209
- [[:before_line, "test_chaotic_job.rb:68"]],
210
- [[:before_line, "test_chaotic_job.rb:73"]],
211
- [[:before_line, "test_chaotic_job.rb:75"]],
212
- [[:before_line, "test_chaotic_job.rb:69"]],
213
- [[:before_line, "test_chaotic_job.rb:70"]],
214
- [[:before_line, "test_chaotic_job.rb:73"]]
215
- ]
216
- ```
217
-
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.
195
+ #<Set:
196
+ {[:call, "Job#perform"],
197
+ [:line, "file.rb:3"],
198
+ [:call, "Job#step_1"],
199
+ [:return, "Job#step_1"],
200
+ [:line, "file.rb:4"],
201
+ [:call, "Job#step_2"],
202
+ [:return, "Job#step_2"],
203
+ [:line, "file.rb:5"],
204
+ [:call, "Job#step_3"],
205
+ [:return, "Job#step_3"],
206
+ [:return, "Job#perform"]}>
207
+ ```
208
+
209
+ 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 every event executed as a part of your job running. 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
210
 
220
211
  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
212
 
@@ -1,73 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Glitch.new.before_line("job_crucible.rb:10") { do_anything }
4
- # Glitch.new.before_call("Model#method", String, name: "Joel") { do_anything }
5
- # Glitch.new.before_return("Model#method", String, name: "Joel") { do_anything }
6
- # Glitch.new.inject! { execute code to glitch }
3
+ # Glitch.before_line("job_crucible.rb:10") { do_anything }
4
+ # Glitch.before_call("Model#method", String, name: "Joel") { do_anything }
5
+ # Glitch.before_return("Model#method", String, name: "Joel") { do_anything }
6
+ # Glitch.inject! { execute code to glitch }
7
7
 
8
8
  module ChaoticJob
9
9
  class Glitch
10
- def initialize
11
- @breakpoints = {}
10
+ def self.before_line(key, &block)
11
+ new(key, :line, &block)
12
12
  end
13
13
 
14
- def before_line(key, &block)
15
- set_breakpoint(key, :line, &block)
16
- self
14
+ def self.before_call(key, ...)
15
+ new(key, :call, ...)
17
16
  end
18
17
 
19
- def before_call(key, ...)
20
- set_breakpoint(key, :call, ...)
21
- self
18
+ def self.before_return(key, return_type = nil, &block)
19
+ new(key, :return, retval: return_type, &block)
22
20
  end
23
21
 
24
- def before_return(key, return_type = nil, &block)
25
- set_breakpoint(key, :return, retval: return_type, &block)
26
- self
22
+ def initialize(key, event, *args, retval: nil, **kwargs, &block)
23
+ @event = event
24
+ @key = key
25
+ @args = args
26
+ @retval = retval
27
+ @kwargs = kwargs
28
+ @block = block
29
+ @executed = false
27
30
  end
28
31
 
29
- def inject!(&block)
30
- breakpoints = @breakpoints
32
+ def set_action(force: false, &block)
33
+ @block = block if @block.nil? || force
34
+ end
31
35
 
32
- trace = TracePoint.new(:line, :call, :return) do |tp|
36
+ def inject!(&block)
37
+ trace = TracePoint.new(@event) do |tp|
33
38
  # :nocov: SimpleCov cannot track code executed _within_ a TracePoint
34
39
  key = derive_key(tp)
35
- matchers = derive_matchers(tp)
40
+ next unless @key == key
36
41
 
37
- next unless (defn = breakpoints.dig(key, tp.event))
38
- next unless matches?(defn, matchers)
42
+ matchers = derive_matchers(tp)
43
+ next unless matches?(matchers)
39
44
 
40
- execute_block(defn)
45
+ execute_block
41
46
  # :nocov:
42
47
  end
43
48
 
44
49
  trace.enable(&block)
45
50
  end
46
51
 
47
- def all_executed?
48
- @breakpoints.all? do |_location, handlers|
49
- handlers.all? { |_position, handler| handler[:executed] }
50
- end
52
+ def executed?
53
+ @executed
51
54
  end
52
55
 
53
- # def inspect
54
- # @breakpoints.flat_map do |location, configs|
55
- # configs.keys.map { |position| "#{position}-#{location}" }
56
- # end.join("|>")
57
- # end
58
-
59
56
  private
60
57
 
61
- def set_breakpoint(key, event, *args, retval: nil, **kwargs, &block)
62
- @breakpoints[key] ||= {}
63
- @breakpoints[key][event] = {args: args, kwargs: kwargs, retval: retval, block: block, executed: false}
64
- end
65
-
66
58
  # :nocov: SimpleCov cannot track code executed _within_ a TracePoint
67
- def matches?(defn, matchers)
68
- return true if defn.nil?
59
+ def matches?(matchers)
69
60
  return true if matchers.nil?
70
- return true if defn[:args].empty? && defn[:kwargs].empty? && defn[:retval].nil?
61
+ return true if @args.empty? && @kwargs.empty? && @retval.nil?
71
62
 
72
63
  args = []
73
64
  kwargs = {}
@@ -92,25 +83,24 @@ module ChaoticJob
92
83
  end
93
84
  end
94
85
 
95
- defn[:args].each_with_index do |type, index|
86
+ @args.each_with_index do |type, index|
96
87
  return false unless type === args[index]
97
88
  end
98
89
 
99
- defn[:kwargs].each do |key, type|
90
+ @kwargs.each do |key, type|
100
91
  return false unless type === kwargs[key]
101
92
  end
102
93
 
103
- return false unless defn[:retval] === retval
94
+ return false unless @retval === retval
104
95
 
105
96
  true
106
97
  end
107
98
 
108
- def execute_block(handler)
109
- return unless handler
110
- return if handler[:executed]
99
+ def execute_block
100
+ return if @executed
111
101
 
112
- handler[:executed] = true
113
- handler[:block].call
102
+ @executed = true
103
+ @block.call
114
104
  end
115
105
 
116
106
  def derive_key(trace)
@@ -76,8 +76,11 @@ module ChaoticJob
76
76
  cutoff.from_now
77
77
  in Time
78
78
  cutoff
79
+ else
80
+ raise Error.new("cutoff must be Time or ActiveSupport::Duration, but got #{cutoff.inspect}")
79
81
  end
80
82
  delta = (Time.now - time).abs.floor
83
+
81
84
  changeset = case delta
82
85
  when 0..59 # seconds
83
86
  {usec: 0}
@@ -85,7 +88,7 @@ module ChaoticJob
85
88
  {sec: 0, usec: 0}
86
89
  when 3600..86_399 # hours
87
90
  {min: 0, sec: 0, usec: 0}
88
- when 86_400..Float::INFINITY # days+
91
+ else # days+
89
92
  {hour: 0, min: 0, sec: 0, usec: 0}
90
93
  end
91
94
  time.change(**changeset)
@@ -5,22 +5,22 @@
5
5
 
6
6
  module ChaoticJob
7
7
  class Scenario
8
- attr_reader :events
8
+ attr_reader :events, :glitch, :job
9
9
 
10
- def initialize(job, glitches:, raise: RetryableError, capture: /active_job/)
10
+ def initialize(job, glitch:, raise: RetryableError, capture: /active_job/)
11
11
  @job = job
12
- @glitches = glitches
12
+ @glitch = (Glitch === glitch) ? glitch : (raise Error.new("glitch: must be a Glitch instance, but got #{glitch.inspect}"))
13
13
  @raise = binding.local_variable_get(:raise)
14
14
  @capture = capture
15
- @glitch = nil
16
15
  @events = []
17
16
  end
18
17
 
19
18
  def run(&block)
20
19
  @job.class.retry_on RetryableError, attempts: 10, wait: 1, jitter: 0
20
+ @glitch.set_action { raise @raise }
21
21
 
22
22
  ActiveSupport::Notifications.subscribed(->(event) { @events << event.dup }, @capture) do
23
- glitch.inject! do
23
+ @glitch.inject! do
24
24
  @job.enqueue
25
25
  if block
26
26
  block.call
@@ -30,25 +30,11 @@ module ChaoticJob
30
30
  end
31
31
  end
32
32
 
33
- # TODO: assert that all glitches ran
33
+ self
34
34
  end
35
35
 
36
- def to_s
37
- @glitches.map { |position, location| "#{position}-#{location}" }.join("|>")
38
- end
39
-
40
- def all_glitched?
41
- @glitch.all_executed?
42
- end
43
-
44
- private
45
-
46
- def glitch
47
- @glitch ||= Glitch.new.tap do |glitch|
48
- @glitches.each do |kind, location, _description|
49
- glitch.public_send(kind, location) { raise @raise }
50
- end
51
- end
36
+ def glitched?
37
+ @glitch.executed?
52
38
  end
53
39
  end
54
40
  end
@@ -1,53 +1,47 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Simulation.new(job).run { |scenario| ... }
4
- # Simulation.new(job).permutations
5
4
  # Simulation.new(job).variants
6
5
  # Simulation.new(job).scenarios
7
6
  module ChaoticJob
8
7
  class Simulation
9
- def initialize(job, callstack: nil, depth: 1, variations: 100, test: nil, seed: nil)
8
+ def initialize(job, callstack: nil, variations: nil, test: nil, seed: nil)
10
9
  @template = job
11
10
  @callstack = callstack || capture_callstack
12
- @depth = depth
13
11
  @variations = variations
14
12
  @test = test
15
13
  @seed = seed || Random.new_seed
16
14
  @random = Random.new(@seed)
17
15
 
18
- raise Error.new("callstack must be a generated via the ChaoticJob::Tracer") unless @callstack.is_a?(Stack)
16
+ raise Error.new("callstack must be a generated via ChaoticJob::Tracer") unless @callstack.is_a?(Stack)
19
17
  end
20
18
 
21
- def run(&callback)
22
- @template.class.retry_on RetryableError, attempts: @depth + 2, wait: 1, jitter: 0
19
+ def run(&assertions)
20
+ @template.class.retry_on RetryableError, attempts: 3, wait: 1, jitter: 0
23
21
 
24
- debug "๐Ÿ‘พ Running #{variants.size} simulations of the total #{permutations.size} possibilities..."
22
+ debug "๐Ÿ‘พ Running #{@variations || "all"} simulations of the total #{variants.size} possibilities..."
25
23
 
26
24
  scenarios.map do |scenario|
27
- run_scenario(scenario, &callback)
25
+ run_scenario(scenario, &assertions)
28
26
  print "ยท"
29
27
  end
30
28
  end
31
29
 
32
- def permutations
30
+ def variants
33
31
  error_locations = @callstack.map do |event, key|
34
32
  ["before_#{event}", key]
35
33
  end
36
- error_locations.permutation(@depth)
37
- end
38
34
 
39
- def variants
40
- return permutations if @variations.nil?
35
+ return error_locations if @variations.nil?
41
36
 
42
- permutations.to_a.sample(@variations, random: @random)
37
+ error_locations.sample(@variations, random: @random)
43
38
  end
44
39
 
45
40
  def scenarios
46
- variants.map do |glitches|
41
+ variants.map do |(event, key)|
47
42
  job = clone_job_template
48
- scenario = Scenario.new(job, glitches: glitches)
49
- job.job_id = scenario.to_s
50
- scenario
43
+ glitch = Glitch.public_send(event, key)
44
+ Scenario.new(job, glitch: glitch)
51
45
  end
52
46
  end
53
47
 
@@ -63,13 +57,14 @@ module ChaoticJob
63
57
  callstack
64
58
  end
65
59
 
66
- def run_scenario(scenario, &callback)
60
+ def run_scenario(scenario, &assertions)
67
61
  debug "๐Ÿ‘พ Running simulation with scenario: #{scenario}"
68
62
  @test.before_setup
69
- @test.simulation_scenario = scenario.to_s
63
+ @test.simulation_scenario = scenario
70
64
  scenario.run
71
65
  @test.after_teardown
72
- callback.call(scenario)
66
+ @test.assert scenario.glitched?, "Scenario did not execute glitch: #{scenario.glitch}"
67
+ assertions.call(scenario)
73
68
  ensure
74
69
  @test.simulation_scenario = nil
75
70
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ChaoticJob
4
- VERSION = "0.5.0"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/chaotic_job.rb CHANGED
@@ -66,20 +66,22 @@ module ChaoticJob
66
66
  Performer.perform_all_after(time)
67
67
  end
68
68
 
69
- def run_simulation(job, depth: nil, variations: nil, callstack: nil, &block)
69
+ def run_simulation(job, variations: nil, callstack: nil, &block)
70
70
  seed = defined?(RSpec) ? RSpec.configuration.seed : Minitest.seed
71
71
  kwargs = {test: self, seed: seed}
72
- kwargs[:depth] = depth if depth
73
72
  kwargs[:variations] = variations if variations
74
73
  kwargs[:callstack] = callstack if callstack
75
74
  self.simulation_scenario = nil
76
75
  Simulation.new(job, **kwargs).run(&block)
77
76
  end
78
77
 
79
- def run_scenario(job, glitch: nil, glitches: nil, raise: nil, capture: nil, &block)
80
- kwargs = {glitches: glitches || [glitch]}
78
+ def run_scenario(job, glitch:, raise: nil, capture: nil, &block)
79
+ kwargs = {}
80
+
81
+ kwargs[:glitch] = glitch
81
82
  kwargs[:raise] = binding.local_variable_get(:raise) if binding.local_variable_get(:raise)
82
83
  kwargs[:capture] = capture if capture
84
+
83
85
  if block
84
86
  Scenario.new(job, **kwargs).run(&block)
85
87
  else
@@ -87,6 +89,18 @@ module ChaoticJob
87
89
  end
88
90
  end
89
91
 
92
+ def glitch_before_line(key, &block)
93
+ Glitch.before_line(key, &block)
94
+ end
95
+
96
+ def glitch_before_call(key, ...)
97
+ Glitch.before_call(key, ...)
98
+ end
99
+
100
+ def glitch_before_return(key, return_type = nil, &block)
101
+ Glitch.before_return(key, return_type, &block)
102
+ end
103
+
90
104
  def assert(test, msg = nil)
91
105
  return super unless @simulation_scenario
92
106
 
@@ -95,7 +109,8 @@ module ChaoticJob
95
109
  default_msg = "Expected #{mu_pp test} to be truthy."
96
110
  custom_msg = msg.is_a?(Proc) ? msg.call : msg
97
111
  full_msg = custom_msg || default_msg
98
- " #{@simulation_scenario}\n#{full_msg}"
112
+ indented_scenario = @simulation_scenario.to_s.split("\n").join("\n ")
113
+ " #{indented_scenario}\n#{full_msg}"
99
114
  end
100
115
 
101
116
  super(test, contextual_msg)
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.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Margheim
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-03 00:00:00.000000000 Z
10
+ date: 2025-06-09 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activejob