specwrk 0.15.9 → 0.16.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 +52 -17
- data/README.md +19 -9
- data/lib/specwrk/cli.rb +9 -3
- data/lib/specwrk/client.rb +10 -10
- data/lib/specwrk/store.rb +41 -16
- data/lib/specwrk/version.rb +1 -1
- data/lib/specwrk/web/endpoints/base.rb +11 -3
- data/lib/specwrk/web/endpoints/popable.rb +25 -7
- 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: 1736573f9c3fc84992518a7a2ea486c03eefe4885b8b857a79b15551533deec0
|
|
4
|
+
data.tar.gz: 7106e3b4c856bfd9ff89e5fe562ba3da45ad5981356b295e24b55579aa48609f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 067222d93a38d847886d40b5a5121e24d68f633115ecfddccdd7c199adf598fd780cb8ca5fd1110028679828436ac3bc8a4335ca54c7a481aeaa7dc85938d0a4
|
|
7
|
+
data.tar.gz: 5d10c755bb71d7ace592e33c5fa8b2c4a2d9a349805870914ca0c9ac3410306765324211820c1283c8c2d8846ddbbcf5d1c88f89f2d5b89899f554a6e5c187dd
|
data/CHANGELOG.md
CHANGED
|
@@ -1,22 +1,57 @@
|
|
|
1
|
-
## [Unreleased]
|
|
2
|
-
|
|
3
1
|
# Changelog
|
|
4
2
|
|
|
5
|
-
## v0.15.
|
|
6
|
-
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
[
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
3
|
+
## v0.15.9 — 2025-08-28
|
|
4
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.8...v0.15.9)
|
|
5
|
+
- Support lock skipping for read-only endpoints — [#139](https://github.com/danielwestendorf/specwrk/pull/139) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
6
|
+
- Fix typo in README — [#137](https://github.com/danielwestendorf/specwrk/pull/137) by [@willnet](https://github.com/willnet)
|
|
7
|
+
- Support an array of arguments when seeding/starting — [#141](https://github.com/danielwestendorf/specwrk/pull/141) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
8
|
+
- Load examples from processing before global lock — [#142](https://github.com/danielwestendorf/specwrk/pull/142) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
9
|
+
|
|
10
|
+
## v0.15.8 — 2025-08-27
|
|
11
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.7...v0.15.8)
|
|
12
|
+
- Friendly helper message when Redis adapter is not available — [@danielwestendorf](https://github.com/danielwestendorf)
|
|
13
|
+
|
|
14
|
+
## v0.15.7 — 2025-08-26
|
|
15
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.6...v0.15.7)
|
|
16
|
+
- Change request locking from global to per-`run_id` — [#135](https://github.com/danielwestendorf/specwrk/pull/135) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
17
|
+
- `rspec-core` is a runtime dependency (not `rspec`) — [#128](https://github.com/danielwestendorf/specwrk/pull/128) by [@bquorning](https://github.com/bquorning)
|
|
18
|
+
- README typo fix — [#126](https://github.com/danielwestendorf/specwrk/pull/126) by [@brett-anderson](https://github.com/brett-anderson)
|
|
19
|
+
|
|
20
|
+
## v0.15.6 — 2025-08-26
|
|
21
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.5...v0.15.6)
|
|
22
|
+
- Test `Store.adapter_klass` — [@danielwestendorf](https://github.com/danielwestendorf)
|
|
23
|
+
|
|
24
|
+
## v0.15.5 — 2025-08-26
|
|
25
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.4...v0.15.5)
|
|
26
|
+
- Add support for Redis store adapters — [#133](https://github.com/danielwestendorf/specwrk/pull/133) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
27
|
+
- Remove support for Ruby 3.1.0 — [@danielwestendorf](https://github.com/danielwestendorf)
|
|
28
|
+
- Remove `gem-release` from dev dependencies — [@danielwestendorf](https://github.com/danielwestendorf)
|
|
29
|
+
|
|
30
|
+
## v0.15.4 — 2025-08-23
|
|
31
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.3...v0.15.4)
|
|
32
|
+
- Split web endpoints into separate files for easier comprehension — [#129](https://github.com/danielwestendorf/specwrk/pull/129) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
33
|
+
- Write the title sequence as watch loops run — [#131](https://github.com/danielwestendorf/specwrk/pull/131) by [@danielwestendorf](https://github.com/danielwestendorf) (fixes [#130](https://github.com/danielwestendorf/specwrk/issues/130))
|
|
34
|
+
|
|
35
|
+
## v0.15.3 — 2025-08-22
|
|
36
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.2...v0.15.3)
|
|
37
|
+
- Clear filter-manager inclusions/exclusions when listing examples — [#122](https://github.com/danielwestendorf/specwrk/pull/122) by [@danielwestendorf](https://github.com/danielwestendorf) (fixes [#121](https://github.com/danielwestendorf/specwrk/issues/121))
|
|
38
|
+
- Smarter completion-threshold calculation; make runs resumable — [#124](https://github.com/danielwestendorf/specwrk/pull/124) by [@danielwestendorf](https://github.com/danielwestendorf) (fixes [#121](https://github.com/danielwestendorf/specwrk/issues/121))
|
|
39
|
+
|
|
40
|
+
## v0.15.2 — 2025-08-15
|
|
41
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.1...v0.15.2)
|
|
42
|
+
- Fix bug where Ruby objects were being written to NDJSON files instead of JSON — [#119](https://github.com/danielwestendorf/specwrk/pull/119) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
43
|
+
|
|
44
|
+
## v0.15.1 — 2025-08-14
|
|
45
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.0...v0.15.1)
|
|
46
|
+
- Add `watch` command to split spec files across processes as they change — [#117](https://github.com/danielwestendorf/specwrk/pull/117) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
47
|
+
- README formatting tweaks — [@danielwestendorf](https://github.com/danielwestendorf)
|
|
48
|
+
|
|
49
|
+
## v0.15.0 — 2025-08-08
|
|
50
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.14.1...v0.15.0)
|
|
51
|
+
- Per-worker NDJSON output when `--output` is specified — [#113](https://github.com/danielwestendorf/specwrk/pull/113) by [@danielwestendorf](https://github.com/danielwestendorf) (fixes [#10](https://github.com/danielwestendorf/specwrk/issues/10))
|
|
52
|
+
- Print re-run commands for failures — [#114](https://github.com/danielwestendorf/specwrk/pull/114) by [@danielwestendorf](https://github.com/danielwestendorf) (fixes [#7](https://github.com/danielwestendorf/specwrk/issues/7))
|
|
53
|
+
- Show number of examples that did not execute; make runs resumable — [#115](https://github.com/danielwestendorf/specwrk/pull/115) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
54
|
+
- Report flake counts and re-run commands — [#116](https://github.com/danielwestendorf/specwrk/pull/116) by [@danielwestendorf](https://github.com/danielwestendorf) (fixes [#112](https://github.com/danielwestendorf/specwrk/issues/112))
|
|
20
55
|
|
|
21
56
|
## v0.14.1 — 2025-08-07
|
|
22
57
|
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.14.0...v0.14.1)
|
data/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Commands:
|
|
|
34
34
|
Intended for quick ad-hoc local host development or single-node CI runs. This command starts a queue server, seeds it with examples from the `spec/` directory, and starts `8` worker processes. It will report the ultimate success or failure.
|
|
35
35
|
|
|
36
36
|
```sh
|
|
37
|
-
$ start --help
|
|
37
|
+
$ specwrk start --help
|
|
38
38
|
Command:
|
|
39
39
|
specwrk start
|
|
40
40
|
|
|
@@ -52,6 +52,7 @@ Options:
|
|
|
52
52
|
--key=VALUE, -k VALUE # Authentication key clients must use for access. Overrides SPECWRK_SRV_KEY, default: ""
|
|
53
53
|
--run=VALUE, -r VALUE # The run identifier for this job execution. Overrides SPECWRK_RUN, default: "main"
|
|
54
54
|
--timeout=VALUE, -t VALUE # The amount of time to wait for the server to respond. Overrides SPECWRK_TIMEOUT, default: "5"
|
|
55
|
+
--network-retries=VALUE # The number of times to retry in the event of a network failure. Overrides SPECWRK_NETWORK_RETRIES, default: "1"
|
|
55
56
|
--id=VALUE # The identifier for this worker. Overrides SPECWRK_ID. If none provided one in the format of specwrk-worker-8_RAND_CHARS-COUNT_INDEX will be used
|
|
56
57
|
--count=VALUE, -c VALUE # The number of worker processes you want to start, default: 1
|
|
57
58
|
--output=VALUE, -o VALUE # Directory where worker output is stored. Overrides SPECWRK_OUT, default: ".specwrk/"
|
|
@@ -113,6 +114,7 @@ Options:
|
|
|
113
114
|
--key=VALUE, -k VALUE # Authentication key clients must use for access. Overrides SPECWRK_SRV_KEY, default: ""
|
|
114
115
|
--run=VALUE, -r VALUE # The run identifier for this job execution. Overrides SPECWRK_RUN, default: "main"
|
|
115
116
|
--timeout=VALUE, -t VALUE # The amount of time to wait for the server to respond. Overrides SPECWRK_TIMEOUT, default: "5"
|
|
117
|
+
--network-retries=VALUE # The number of times to retry in the event of a network failure. Overrides SPECWRK_NETWORK_RETRIES, default: "1"
|
|
116
118
|
--max-retries=VALUE # Number of times an example will be re-run should it fail, default: 0
|
|
117
119
|
--help, -h # Print this help
|
|
118
120
|
```
|
|
@@ -140,6 +142,7 @@ Options:
|
|
|
140
142
|
--key=VALUE, -k VALUE # Authentication key clients must use for access. Overrides SPECWRK_SRV_KEY, default: ""
|
|
141
143
|
--run=VALUE, -r VALUE # The run identifier for this job execution. Overrides SPECWRK_RUN, default: "main"
|
|
142
144
|
--timeout=VALUE, -t VALUE # The amount of time to wait for the server to respond. Overrides SPECWRK_TIMEOUT, default: "5"
|
|
145
|
+
--network-retries=VALUE # The number of times to retry in the event of a network failure. Overrides SPECWRK_NETWORK_RETRIES, default: "1"
|
|
143
146
|
--help, -h # Print this help
|
|
144
147
|
```
|
|
145
148
|
|
|
@@ -164,13 +167,13 @@ Options:
|
|
|
164
167
|
```
|
|
165
168
|
|
|
166
169
|
## Configuring your test environment
|
|
167
|
-
If
|
|
170
|
+
If your test suite tracks state, starts servers, etc. and you plan on running many processes on the same node, you'll need to make
|
|
168
171
|
adjustments to avoid conflicting port usage or database/state mutations.
|
|
169
172
|
|
|
170
173
|
`specwrk` workers will have `TEST_ENV_NUMBER={i}` set to help you configure approriately.
|
|
171
174
|
|
|
172
175
|
### Rails
|
|
173
|
-
Rails has had easy multi-process test setup for a while now by creating unique test databases per process. For my rails v7.2 app which uses PostgreSQL and
|
|
176
|
+
Rails has had easy multi-process test setup for a while now by creating unique test databases per process. For my rails v7.2 app which uses PostgreSQL and Capybara, I made these changes to my `spec/rails_helper.rb`:
|
|
174
177
|
|
|
175
178
|
```diff
|
|
176
179
|
++ if ENV["TEST_ENV_NUMBER"]
|
|
@@ -200,7 +203,7 @@ Make sure to persist `$SPECWRK_OUT/report.json` between runs so that subsequent
|
|
|
200
203
|
[CircleCI Example](https://github.com/danielwestendorf/specwrk/blob/main/.circleci/config.yml) (specwrk-single-node job)
|
|
201
204
|
|
|
202
205
|
### Multi-node, multi-process
|
|
203
|
-
Multi-node, multi-process works best when have many nodes running tests. This distributes the test execution across the nodes until the queue is for the run is empty, optimizing for slowest specs first. This distributes test execution across all nodes evenly(-ish).
|
|
206
|
+
Multi-node, multi-process works best when you have many nodes running tests. This distributes the test execution across the nodes until the queue is for the run is empty, optimizing for slowest specs first. This distributes test execution across all nodes evenly(-ish).
|
|
204
207
|
|
|
205
208
|
To accomplish this, a central queue server is required, examples must be explicitly seeded, and workers explicitly started.
|
|
206
209
|
|
|
@@ -221,7 +224,9 @@ Start a persistent Queue Server given one of the following methods
|
|
|
221
224
|
|
|
222
225
|
### Configuring your Queue Server
|
|
223
226
|
- Secure your server with a key either with the `SPECWRK_SRV_KEY` environment variable or `--key` CLI option
|
|
224
|
-
- Configure the server output to be a persisted volume so your timings survive between system restarts with the `SPECWRK_SRV_STORE_URI` environment variable or `--store-uri` CLI option. By default, `memory:///` will be used for the run's data stores (so run data will
|
|
227
|
+
- Configure the server output to be a persisted volume so your timings survive between system restarts with the `SPECWRK_SRV_STORE_URI` environment variable or `--store-uri` CLI option. By default, `memory:///` will be used for the run's data stores (so run data will not survive server restarts) while `file://#{Dir.tmpdir}` will be used for run timings. Pass `--store-uri file:///whatever/absolute/path` to store all data on disk (required for multiple server processes).
|
|
228
|
+
|
|
229
|
+
See [specwrk-store-redis_adapter](https://github.com/danielwestendorf/specwrk-store-redis_adapter) for Redis-compatible backed storage.
|
|
225
230
|
|
|
226
231
|
See [specwrk serve --help](#specwrk-serve) for all possible configuration options.
|
|
227
232
|
|
|
@@ -243,7 +248,7 @@ map(/_spec\.rb$/) do |spec_path|
|
|
|
243
248
|
spec_path
|
|
244
249
|
end
|
|
245
250
|
|
|
246
|
-
# If a file in lib changes, map it to the spec folder for
|
|
251
|
+
# If a file in lib changes, map it to the spec folder for its spec file
|
|
247
252
|
map(/lib\/.*\.rb$/) do |path|
|
|
248
253
|
path.gsub(/lib\/(.+)\.rb/, "spec/\\1_spec.rb")
|
|
249
254
|
end
|
|
@@ -253,7 +258,7 @@ end
|
|
|
253
258
|
# path.gsub(/app\/models\/(.+)\.rb/, "spec/models/\\1_spec.rb")
|
|
254
259
|
# end
|
|
255
260
|
#
|
|
256
|
-
# If a
|
|
261
|
+
# If a controller file changes (assuming rails app structure), run the controller and system specs file
|
|
257
262
|
# map(/app\/controllers\/.*.rb$/) do |path|
|
|
258
263
|
# [
|
|
259
264
|
# path.gsub(/app\/controllers\/(.+)\.rb/, "spec/controllers/\\1_spec.rb"),
|
|
@@ -263,7 +268,7 @@ end
|
|
|
263
268
|
```
|
|
264
269
|
|
|
265
270
|
## Prior/other works
|
|
266
|
-
There are many prior works for running rspec tests across multiple processes. Most of them combine process output making failures hard to grok. Some are good at running tests locally, but not on CI, while others are inversely true. Others are
|
|
271
|
+
There are many prior works for running rspec tests across multiple processes. Most of them combine process output making failures hard to grok. Some are good at running tests locally, but not on CI, while others are inversely true. Others are commercial or impractical without making a purchase.
|
|
267
272
|
|
|
268
273
|
specwrk is different because it:
|
|
269
274
|
1. Puts your developer experience first. Easy execution. No messy outputs. Retries built in. Easy(er) debugging of flaky tests.
|
|
@@ -273,14 +278,19 @@ specwrk is different because it:
|
|
|
273
278
|
5. Is free.
|
|
274
279
|
|
|
275
280
|
[parallel_rspec](https://github.com/willbryant/parallel_rspec)
|
|
281
|
+
|
|
276
282
|
[Knapsack](https://github.com/KnapsackPro/knapsack)
|
|
283
|
+
|
|
277
284
|
[parallel_tests](https://github.com/grosser/parallel_tests)
|
|
285
|
+
|
|
278
286
|
[rspecq](https://github.com/skroutz/rspecq)
|
|
287
|
+
|
|
279
288
|
[RSpec ABQ](https://github.com/rwx-research/rspec-abq)
|
|
280
289
|
|
|
290
|
+
|
|
281
291
|
## Contributing
|
|
282
292
|
|
|
283
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
293
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/danielwestendorf/specwrk.
|
|
284
294
|
|
|
285
295
|
## License
|
|
286
296
|
|
data/lib/specwrk/cli.rb
CHANGED
|
@@ -20,13 +20,15 @@ module Specwrk
|
|
|
20
20
|
base.unique_option :key, type: :string, default: ENV.fetch("SPECWRK_SRV_KEY", ""), aliases: ["-k"], desc: "Authentication key clients must use for access. Overrides SPECWRK_SRV_KEY"
|
|
21
21
|
base.unique_option :run, type: :string, default: ENV.fetch("SPECWRK_RUN", "main"), aliases: ["-r"], desc: "The run identifier for this job execution. Overrides SPECWRK_RUN"
|
|
22
22
|
base.unique_option :timeout, type: :integer, default: ENV.fetch("SPECWRK_TIMEOUT", "5"), aliases: ["-t"], desc: "The amount of time to wait for the server to respond. Overrides SPECWRK_TIMEOUT"
|
|
23
|
+
base.unique_option :network_retries, type: :integer, default: ENV.fetch("SPECWRK_NETWORK_RETRIES", "1"), desc: "The number of times to retry in the event of a network failure. Overrides SPECWRK_NETWORK_RETRIES"
|
|
23
24
|
end
|
|
24
25
|
|
|
25
|
-
on_setup do |uri:, key:, run:, timeout:, **|
|
|
26
|
+
on_setup do |uri:, key:, run:, timeout:, network_retries:, **|
|
|
26
27
|
ENV["SPECWRK_SRV_URI"] = uri
|
|
27
28
|
ENV["SPECWRK_SRV_KEY"] = key
|
|
28
29
|
ENV["SPECWRK_RUN"] = run
|
|
29
30
|
ENV["SPECWRK_TIMEOUT"] = timeout
|
|
31
|
+
ENV["SPECWRK_NETWORK_RETRIES"] = network_retries
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
|
|
@@ -131,9 +133,11 @@ module Specwrk
|
|
|
131
133
|
|
|
132
134
|
desc "Seed the server with a list of specs for the run"
|
|
133
135
|
option :max_retries, default: 0, desc: "Number of times an example will be re-run should it fail"
|
|
134
|
-
argument :dir, type: :array, required: false,
|
|
136
|
+
argument :dir, type: :array, required: false, desc: "Relative spec directory to run against, default: spec/"
|
|
135
137
|
|
|
136
138
|
def call(max_retries:, dir:, **args)
|
|
139
|
+
dir = ["spec"] if dir.length.zero?
|
|
140
|
+
|
|
137
141
|
self.class.setup(**args)
|
|
138
142
|
|
|
139
143
|
require "specwrk/list_examples"
|
|
@@ -208,9 +212,11 @@ module Specwrk
|
|
|
208
212
|
|
|
209
213
|
desc "Start a server and workers, monitor until complete"
|
|
210
214
|
option :max_retries, default: 0, desc: "Number of times an example will be re-run should it fail"
|
|
211
|
-
argument :dir,
|
|
215
|
+
argument :dir, type: :array, required: false, desc: "Relative spec directory to run against, default: spec/"
|
|
212
216
|
|
|
213
217
|
def call(max_retries:, dir:, **args)
|
|
218
|
+
dir = ["spec"] if dir.length.zero?
|
|
219
|
+
|
|
214
220
|
self.class.setup(**args)
|
|
215
221
|
$stdout.sync = true
|
|
216
222
|
|
data/lib/specwrk/client.rb
CHANGED
|
@@ -115,16 +115,6 @@ module Specwrk
|
|
|
115
115
|
else
|
|
116
116
|
raise UnhandledResponseError.new("#{response.code}: #{response.body}")
|
|
117
117
|
end
|
|
118
|
-
rescue Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout => e
|
|
119
|
-
@retry_count ||= 0
|
|
120
|
-
@retry_count += 1
|
|
121
|
-
|
|
122
|
-
raise e if @retry_count == 5
|
|
123
|
-
|
|
124
|
-
warn e
|
|
125
|
-
sleep @retry_count
|
|
126
|
-
|
|
127
|
-
retry
|
|
128
118
|
end
|
|
129
119
|
|
|
130
120
|
def seed(examples, max_retries)
|
|
@@ -172,6 +162,16 @@ module Specwrk
|
|
|
172
162
|
@worker_status = response["x-specwrk-status"].to_i if response["x-specwrk-status"]
|
|
173
163
|
end
|
|
174
164
|
end
|
|
165
|
+
rescue Net::ReadTimeout, Net::WriteTimeout => e
|
|
166
|
+
@retry_count ||= 0
|
|
167
|
+
|
|
168
|
+
raise e if @retry_count == ENV["SPECWRK_NETWORK_RETRIES"].to_i
|
|
169
|
+
@retry_count += 1
|
|
170
|
+
|
|
171
|
+
warn e
|
|
172
|
+
sleep @retry_count
|
|
173
|
+
|
|
174
|
+
retry
|
|
175
175
|
end
|
|
176
176
|
|
|
177
177
|
def default_headers
|
data/lib/specwrk/store.rb
CHANGED
|
@@ -105,6 +105,47 @@ module Specwrk
|
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
+
class WorkerStore < Store
|
|
109
|
+
FIRST_SEEN_AT_KEY = :____first_seen_at_key
|
|
110
|
+
LAST_SEEN_AT_KEY = :____last_seen_at_key
|
|
111
|
+
|
|
112
|
+
def first_seen_at=(val)
|
|
113
|
+
@first_seen_at = nil
|
|
114
|
+
|
|
115
|
+
self[FIRST_SEEN_AT_KEY] = val.to_i
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def first_seen_at
|
|
119
|
+
@first_seen_at ||= begin
|
|
120
|
+
value = self[FIRST_SEEN_AT_KEY]
|
|
121
|
+
return @first_seen_at = value unless value
|
|
122
|
+
|
|
123
|
+
@first_seen_at = Time.at(value.to_i)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def last_seen_at=(val)
|
|
128
|
+
@last_seen_at = nil
|
|
129
|
+
|
|
130
|
+
self[LAST_SEEN_AT_KEY] = val.to_i
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def last_seen_at
|
|
134
|
+
@last_seen_at ||= begin
|
|
135
|
+
value = self[LAST_SEEN_AT_KEY]
|
|
136
|
+
return @last_seen_at = value unless value
|
|
137
|
+
|
|
138
|
+
@last_seen_at = Time.at(value.to_i)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def reload
|
|
143
|
+
@last_seen_at = nil
|
|
144
|
+
@first_seen_at = nil
|
|
145
|
+
super
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
108
149
|
class PendingStore < Store
|
|
109
150
|
RUN_TIME_BUCKET_MAXIMUM_KEY = :____run_time_bucket_maximum
|
|
110
151
|
ORDER_KEY = :____order
|
|
@@ -234,22 +275,6 @@ module Specwrk
|
|
|
234
275
|
end
|
|
235
276
|
|
|
236
277
|
class ProcessingStore < Store
|
|
237
|
-
def expired
|
|
238
|
-
@expired ||= begin
|
|
239
|
-
bucket = []
|
|
240
|
-
|
|
241
|
-
keys.each_slice(24).each do |key_group|
|
|
242
|
-
examples = multi_read(*key_group)
|
|
243
|
-
examples.each do |id, example|
|
|
244
|
-
next if example[:completion_threshold].nil?
|
|
245
|
-
|
|
246
|
-
bucket << [id, example] if example[:completion_threshold] < Time.now.to_i
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
bucket.to_h
|
|
251
|
-
end
|
|
252
|
-
end
|
|
253
278
|
end
|
|
254
279
|
|
|
255
280
|
class CompletedStore < Store
|
data/lib/specwrk/version.rb
CHANGED
|
@@ -21,8 +21,8 @@ module Specwrk
|
|
|
21
21
|
|
|
22
22
|
before_lock
|
|
23
23
|
|
|
24
|
-
worker
|
|
25
|
-
worker
|
|
24
|
+
worker.first_seen_at ||= Time.now
|
|
25
|
+
worker.last_seen_at = Time.now
|
|
26
26
|
|
|
27
27
|
final_response = with_lock do
|
|
28
28
|
started_at = metadata[:started_at] ||= Time.now.iso8601
|
|
@@ -109,7 +109,11 @@ module Specwrk
|
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
def worker
|
|
112
|
-
@worker ||=
|
|
112
|
+
@worker ||= worker_store_for(worker_id)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def worker_id
|
|
116
|
+
request.get_header("HTTP_X_SPECWRK_ID").to_s
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
def worker_status
|
|
@@ -118,6 +122,10 @@ module Specwrk
|
|
|
118
122
|
worker[:failed] || 1
|
|
119
123
|
end
|
|
120
124
|
|
|
125
|
+
def worker_store_for(id)
|
|
126
|
+
WorkerStore.new(ENV.fetch("SPECWRK_SRV_STORE_URI", "memory:///"), File.join(run_id, "workers", id))
|
|
127
|
+
end
|
|
128
|
+
|
|
121
129
|
def run_id
|
|
122
130
|
request.get_header("HTTP_X_SPECWRK_RUN")
|
|
123
131
|
end
|
|
@@ -15,9 +15,9 @@ module Specwrk
|
|
|
15
15
|
[204, {"content-type" => "text/plain"}, ["Waiting for sample to be seeded."]]
|
|
16
16
|
elsif completed.any? && processing.empty?
|
|
17
17
|
[410, {"content-type" => "text/plain"}, ["That's a good lad. Run along now and go home."]]
|
|
18
|
-
elsif
|
|
19
|
-
pending.merge!(
|
|
20
|
-
processing.delete(*
|
|
18
|
+
elsif expired_examples.length.positive?
|
|
19
|
+
pending.merge!(expired_examples.each { |_id, example| example[:worker_id] = worker_id })
|
|
20
|
+
processing.delete(*expired_examples.keys)
|
|
21
21
|
@examples = nil
|
|
22
22
|
|
|
23
23
|
[200, {"content-type" => "application/json"}, [JSON.generate(examples)]]
|
|
@@ -29,13 +29,10 @@ module Specwrk
|
|
|
29
29
|
def examples
|
|
30
30
|
@examples ||= begin
|
|
31
31
|
examples = pending.shift_bucket
|
|
32
|
-
bucket_run_time_total = examples.map { |example| example.fetch(:expected_run_time, 10.0) }.compact.sum * 2
|
|
33
|
-
maximum_completion_threshold = (pending.run_time_bucket_maximum || 30.0) * 2
|
|
34
|
-
completion_threshold = Time.now + [bucket_run_time_total, maximum_completion_threshold, 20.0].max
|
|
35
32
|
|
|
36
33
|
processing_data = examples.map do |example|
|
|
37
34
|
[
|
|
38
|
-
example[:id], example.merge(
|
|
35
|
+
example[:id], example.merge(worker_id: worker_id, processing_started_at: Time.now.to_i)
|
|
39
36
|
]
|
|
40
37
|
end
|
|
41
38
|
|
|
@@ -44,6 +41,27 @@ module Specwrk
|
|
|
44
41
|
examples
|
|
45
42
|
end
|
|
46
43
|
end
|
|
44
|
+
|
|
45
|
+
def expired_examples
|
|
46
|
+
return unless processing.any?
|
|
47
|
+
|
|
48
|
+
@expired_examples ||= processing.to_h.select { |_id, example| expired?(example) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Has the worker missed two heartbeat check-ins?
|
|
52
|
+
def expired?(example)
|
|
53
|
+
return false unless example[:worker_id]
|
|
54
|
+
return false unless example[:processing_started_at]
|
|
55
|
+
return false unless example[:processing_started_at] < (Time.now - 20).to_i
|
|
56
|
+
|
|
57
|
+
workers_last_heartbeats[example[:worker_id]] < Time.now - 20
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def workers_last_heartbeats
|
|
61
|
+
@workers_last_heartbeats ||= Hash.new do |h, k|
|
|
62
|
+
h[k] = worker_store_for(k).last_seen_at || Time.at(0)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
47
65
|
end
|
|
48
66
|
end
|
|
49
67
|
end
|