chaotic_job 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +11 -11
- data/lib/chaotic_job/journal.rb +6 -2
- data/lib/chaotic_job/performer.rb +12 -2
- data/lib/chaotic_job/scenario.rb +4 -4
- data/lib/chaotic_job/version.rb +1 -1
- data/lib/chaotic_job.rb +38 -9
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8241f1235f37ed46c1da18d53e18c6fce43fb5758273bb51dba8db332dddb5b6
|
4
|
+
data.tar.gz: fc86ee73cf18b16d6142f7111e5d1cfa446b46bfea2e1959eb7aa222a5bf42dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d32f1ab9748ea544d13c15692934634a6c3bcc01d602903e5e554abd229b7623a6a9aff111357031f474ec701fcd96131994604a121f9e55672eaed2616107b
|
7
|
+
data.tar.gz: c8ef73d8d07a091f59ebd1347ecc3f11fbd6ba6abc3eee218e251600ff156f748ec2a911d8108834cad96f9ce7ef59d1b3989606a27a827f1bb7bd93bfd9b062
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.2.0] - 2024-11-06
|
4
|
+
|
5
|
+
- Update the `perform_all` helper method to `perform_all_jobs`
|
6
|
+
- Update the `perform_all_before` helper method to `perform_all_jobs_before`
|
7
|
+
- Update the `perform_all_after` helper method to `perform_all_jobs_after`
|
8
|
+
- Update the `perform_all_within` helper method to `perform_all_jobs_within`
|
9
|
+
|
10
|
+
## [0.1.1] - 2024-11-06
|
11
|
+
|
12
|
+
- Update `Journal` interface
|
13
|
+
- Add top-level `ChaoticJob` methods to work with the journal
|
14
|
+
- Fix bug with job sorting in the `Performer`
|
15
|
+
- Fix bug with resolving time cutoffs in the `Performer`
|
16
|
+
- Fix bug with using the `run_scenario` helper with a block
|
17
|
+
|
3
18
|
## [0.1.0] - 2024-11-06
|
4
19
|
|
5
20
|
- Added `Journal` to log activity for tests
|
data/README.md
CHANGED
@@ -10,7 +10,7 @@
|
|
10
10
|
> [!TIP]
|
11
11
|
> This gem helps you test that your Active Jobs are reliable and resilient to failures. If you want to more easily *build* reliable and resilient Active Jobs, check out the companion [Acidic Job](https://github.com/fractaledmind/acidic_job/tree/alpha-1.0) gem.
|
12
12
|
|
13
|
-
`ChaoticJob` provides a set of tools to help you test the reliability and resilience of your Active Jobs. It does this by allowing you to simulate various types of failures and glitches that can occur in a production environment.
|
13
|
+
`ChaoticJob` provides a set of tools to help you test the reliability and resilience of your Active Jobs. It does this by allowing you to simulate various types of failures and glitches that can occur in a production environment, inspired by the principles of [chaos testing](https://principlesofchaos.org) and [deterministic simulation testing](https://blog.resonatehq.io/deterministic-simulation-testing)
|
14
14
|
|
15
15
|
## Installation
|
16
16
|
|
@@ -59,29 +59,29 @@ end
|
|
59
59
|
|
60
60
|
But, this method does not behave as you would expect. Functionally, it overwrites the `enqueue` method to immediately perform the job, which means that instead of your job being performed in waves, the retry is performed _within_ the execution of the original job. This both confuses the logs and means the behavior in your tests are not representative of the behavior in production.
|
61
61
|
|
62
|
-
In order to properly test job retries, you should use the `
|
62
|
+
In order to properly test job retries, you should use the `perform_all_jobs` method provided by `ChaoticJob::Helpers`:
|
63
63
|
|
64
64
|
```ruby
|
65
65
|
Job.perform_later
|
66
|
-
|
66
|
+
perform_all_jobs
|
67
67
|
```
|
68
68
|
|
69
69
|
This helper will perform the job and all of its retries in the proper way, in waves, just like it would in production.
|
70
70
|
|
71
|
-
If you need more control over which batches of jobs are performed, you can use the `
|
71
|
+
If you need more control over which batches of jobs are performed, you can use the `perform_all_jobs_before` and `perform_all_jobs_after` methods. These are particularly useful if you need to test the behavior of a job that schedules another job. You can use these methods to perform only the original job and its retries, assert the state of the system, and then perform the scheduled job and its retries.
|
72
72
|
|
73
73
|
```ruby
|
74
74
|
JobThatSchedules.perform_later
|
75
|
-
|
75
|
+
perform_all_jobs_before(4.seconds)
|
76
76
|
assert_equal 1, enqueued_jobs.size
|
77
77
|
assert_equal 2, performed_jobs.size
|
78
78
|
|
79
|
-
|
79
|
+
perform_all_jobs_after(1.day)
|
80
80
|
assert_equal 0, enqueued_jobs.size
|
81
81
|
assert_equal 3, performed_jobs.size
|
82
82
|
```
|
83
83
|
|
84
|
-
You can pass either a `Time` object or an `ActiveSupport::Duration` object to these methods. And, to make the code as readable as possible, the `
|
84
|
+
You can pass either a `Time` object or an `ActiveSupport::Duration` object to these methods. And, to make the code as readable as possible, the `perform_all_jobs_before` is also aliased as the `perform_all_jobs_within` method. This allows you to write the example above as `perform_all_jobs_within(4.seconds)`.
|
85
85
|
|
86
86
|
### Simulating Failures
|
87
87
|
|
@@ -115,10 +115,10 @@ end
|
|
115
115
|
> |---|---|
|
116
116
|
> | `Journal.log` | log simply that something happened within the default scope |
|
117
117
|
> | `Journal.log(thing, scope: :special)` | log a particular value within a particular scope |
|
118
|
-
> | `Journal.
|
119
|
-
> | `Journal.
|
120
|
-
> | `Journal.
|
121
|
-
> | `Journal.
|
118
|
+
> | `Journal.size` | get the total number of logs under the default scope |
|
119
|
+
> | `Journal.size(scope: :special)` | get the total number of logs under a particular scope |
|
120
|
+
> | `Journal.entries` | get all of the logged values under the default scope |
|
121
|
+
> | `Journal.entries(scope: :special)` | get all of the logged values under a particular scope |
|
122
122
|
|
123
123
|
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 location of the glitch, which can be either *before* or *after*. The second element is the location of the code that will be affected by the glitch, defined by its file path and line number. What this example scenario does is inject a glitch before the `step_3` method is called, here:
|
124
124
|
|
data/lib/chaotic_job/journal.rb
CHANGED
@@ -20,12 +20,16 @@ module ChaoticJob
|
|
20
20
|
@logs[scope] << item
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
23
|
+
def size(scope: :default)
|
24
24
|
@logs[scope]&.size || 0
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
27
|
+
def entries(scope: :default)
|
28
28
|
@logs[scope]
|
29
29
|
end
|
30
|
+
|
31
|
+
def top(scope: :default)
|
32
|
+
entries&.first
|
33
|
+
end
|
30
34
|
end
|
31
35
|
end
|
@@ -40,7 +40,17 @@ module ChaoticJob
|
|
40
40
|
|
41
41
|
def enqueued_jobs_where(before: nil, after: nil)
|
42
42
|
enqueued_jobs
|
43
|
-
.
|
43
|
+
.sort do |ljob, rjob|
|
44
|
+
lat = ljob[:at]
|
45
|
+
rat = rjob[:at]
|
46
|
+
|
47
|
+
# sort by scheduled time, with nil values first
|
48
|
+
if lat && rat
|
49
|
+
lat <=> rat
|
50
|
+
else
|
51
|
+
lat ? 1 : -1
|
52
|
+
end
|
53
|
+
end
|
44
54
|
.select do |job|
|
45
55
|
scheduled_at = job[:at]
|
46
56
|
|
@@ -67,7 +77,7 @@ module ChaoticJob
|
|
67
77
|
in Time
|
68
78
|
cutoff
|
69
79
|
end
|
70
|
-
delta = (Time.now - time).abs
|
80
|
+
delta = (Time.now - time).abs.floor
|
71
81
|
changeset = case delta
|
72
82
|
when 0..59 # seconds
|
73
83
|
{usec: 0}
|
data/lib/chaotic_job/scenario.rb
CHANGED
@@ -16,15 +16,15 @@ module ChaoticJob
|
|
16
16
|
@events = []
|
17
17
|
end
|
18
18
|
|
19
|
-
def run
|
19
|
+
def run(&block)
|
20
20
|
@job.class.retry_on RetryableError, attempts: 10, wait: 1, jitter: 0
|
21
21
|
|
22
22
|
ActiveSupport::Notifications.subscribed(->(event) { @events << event.dup }, @capture) do
|
23
23
|
glitch.inject! do
|
24
|
-
|
25
|
-
|
24
|
+
@job.enqueue
|
25
|
+
if block
|
26
|
+
block.call
|
26
27
|
else
|
27
|
-
@job.enqueue
|
28
28
|
Performer.perform_all
|
29
29
|
end
|
30
30
|
end
|
data/lib/chaotic_job/version.rb
CHANGED
data/lib/chaotic_job.rb
CHANGED
@@ -11,20 +11,45 @@ module ChaoticJob
|
|
11
11
|
class RetryableError < StandardError
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
def self.log_to_journal!(item = nil, scope: nil)
|
15
|
+
if item && scope
|
16
|
+
Journal.log(item, scope: scope)
|
17
|
+
elsif item
|
18
|
+
Journal.log(item)
|
19
|
+
elsif scope
|
20
|
+
Journal.log(scope: scope)
|
21
|
+
else
|
22
|
+
Journal.log
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.journal_size(scope: nil)
|
27
|
+
if scope
|
28
|
+
Journal.size(scope: scope)
|
29
|
+
else
|
30
|
+
Journal.size
|
17
31
|
end
|
32
|
+
end
|
18
33
|
|
19
|
-
|
20
|
-
|
34
|
+
def self.top_journal_entry(scope: nil)
|
35
|
+
if scope
|
36
|
+
Journal.top(scope: scope)
|
37
|
+
else
|
38
|
+
Journal.top
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module Helpers
|
43
|
+
def perform_all_jobs
|
44
|
+
Performer.perform_all
|
21
45
|
end
|
22
46
|
|
23
|
-
def
|
47
|
+
def perform_all_jobs_before(time)
|
24
48
|
Performer.perform_all_before(time)
|
25
49
|
end
|
50
|
+
alias_method :perform_all_jobs_within, :perform_all_jobs_before
|
26
51
|
|
27
|
-
def
|
52
|
+
def perform_all_jobs_after(time)
|
28
53
|
Performer.perform_all_after(time)
|
29
54
|
end
|
30
55
|
|
@@ -36,11 +61,15 @@ module ChaoticJob
|
|
36
61
|
Simulation.new(job, **kwargs).run(&block)
|
37
62
|
end
|
38
63
|
|
39
|
-
def run_scenario(job, glitch: nil, glitches: nil, raise: nil, capture: nil)
|
64
|
+
def run_scenario(job, glitch: nil, glitches: nil, raise: nil, capture: nil, &block)
|
40
65
|
kwargs = {glitches: glitches || [glitch]}
|
41
66
|
kwargs[:raise] = raise if raise
|
42
67
|
kwargs[:capture] = capture if capture
|
43
|
-
|
68
|
+
if block
|
69
|
+
Scenario.new(job, **kwargs).run(&block)
|
70
|
+
else
|
71
|
+
Scenario.new(job, **kwargs).run
|
72
|
+
end
|
44
73
|
end
|
45
74
|
end
|
46
75
|
end
|