specwrk 0.19.1 → 0.19.3
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/.standard.yml +1 -1
- data/CHANGELOG.md +70 -0
- data/docker/Dockerfile.server +1 -1
- data/lib/specwrk/store/file_adapter.rb +23 -7
- data/lib/specwrk/store/serializer.rb +19 -7
- data/lib/specwrk/version.rb +1 -1
- data/lib/specwrk/web/endpoints/complete_and_pop.rb +63 -12
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2eb07bead54ad609b2d8fb821793a60b783485326daeccc092c14343ae96dc0
|
|
4
|
+
data.tar.gz: c41e008f026a61f2748cc8f5cd9c7dc2c257103518aa89fded4c84c9bd2ca976
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f9a7206df73023a2262d9775ff3b1983f403b4630ce54a04f7c4500ab77c51d30df6262604332b37e37fdf21cb98fe39952fcd0287f8b9ded7811a39ac97afe
|
|
7
|
+
data.tar.gz: ffbf1b2583044b4f3e4bdbc7cadb35278f93eb64274905c5869398f5ece9d2854ddad0c75463ac8172d025d8dcc7a92c8486d5d9ef581ad269c875aca5c22420
|
data/.standard.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.19.3...main)
|
|
5
|
+
|
|
6
|
+
## v0.19.3 — 2026-07-26
|
|
7
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.19.2...v0.19.3)
|
|
8
|
+
- Retry transient file store read descriptor errors — [@danielwestendorf](https://github.com/danielwestendorf)
|
|
9
|
+
- Complete omitted worker processing examples as failures — [@danielwestendorf](https://github.com/danielwestendorf), [@benjaminwood](https://github.com/benjaminwood)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## v0.19.2 — 2025-12-09
|
|
13
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.19.1...v0.19.2)
|
|
14
|
+
- Optimize msgpack usage by using one packer/unpacker — [#180](https://github.com/danielwestendorf/specwrk/pull/180) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
15
|
+
- Standard style to v3.4 — [#179](https://github.com/danielwestendorf/specwrk/pull/179) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
16
|
+
- Add msgpack to the docker server — [#178](https://github.com/danielwestendorf/specwrk/pull/178) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
17
|
+
|
|
18
|
+
## v0.19.1 — 2025-12-08
|
|
19
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.19.0...v0.19.1)
|
|
20
|
+
- Add missing require for serializer in base adapter — [#177](https://github.com/danielwestendorf/specwrk/pull/177) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
21
|
+
|
|
22
|
+
## v0.19.0 — 2025-12-08
|
|
23
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.18.0...v0.19.0)
|
|
24
|
+
- Add support for configured serializer, adding msgpack — [#176](https://github.com/danielwestendorf/specwrk/pull/176) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
25
|
+
|
|
26
|
+
## v0.18.0 — 2025-12-07
|
|
27
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.17.1...v0.18.0)
|
|
28
|
+
- Spawn workers with `spawn` instead of `fork` — [#174](https://github.com/danielwestendorf/specwrk/pull/174) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
29
|
+
- If pending is empty, return before acquiring lock — [#173](https://github.com/danielwestendorf/specwrk/pull/173) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
30
|
+
- Fix typo resulting in wrong group by — [#166](https://github.com/danielwestendorf/specwrk/pull/166) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
31
|
+
|
|
32
|
+
## v0.17.1 — 2025-12-06
|
|
33
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.17.0...v0.17.1)
|
|
34
|
+
- Version bump only (no functional changes) — [@danielwestendorf](https://github.com/danielwestendorf)
|
|
35
|
+
|
|
36
|
+
## v0.17.0 — 2025-12-06
|
|
37
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.16.3...v0.17.0)
|
|
38
|
+
- Add bucket store — [#170](https://github.com/danielwestendorf/specwrk/pull/170) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
39
|
+
- s/Bucket/BucketStore — [#171](https://github.com/danielwestendorf/specwrk/pull/171) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
40
|
+
- Bucket at seed — [#172](https://github.com/danielwestendorf/specwrk/pull/172) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
41
|
+
- Split Store classes into their own files — [#169](https://github.com/danielwestendorf/specwrk/pull/169) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
42
|
+
- Fix worker failing to flush on worker exit — [#167](https://github.com/danielwestendorf/specwrk/pull/167) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
43
|
+
- Typo — [#168](https://github.com/danielwestendorf/specwrk/pull/168) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
44
|
+
|
|
45
|
+
## v0.16.3 — 2025-12-01
|
|
46
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.16.2...v0.16.3)
|
|
47
|
+
- Disable locks on heartbeat and health endpoints — [#163](https://github.com/danielwestendorf/specwrk/pull/163) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
48
|
+
- Add specwrk worker info to logger — [#164](https://github.com/danielwestendorf/specwrk/pull/164) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
49
|
+
- Ignore Agents.md — [#165](https://github.com/danielwestendorf/specwrk/pull/165) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
50
|
+
|
|
51
|
+
## v0.16.2 — 2025-10-23
|
|
52
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.16.1...v0.16.2)
|
|
53
|
+
- exit(1) from seed process when example size is zero — [#158](https://github.com/danielwestendorf/specwrk/pull/158) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
54
|
+
|
|
55
|
+
## v0.16.1 — 2025-10-03
|
|
56
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.16.0...v0.16.1)
|
|
57
|
+
- Ensure next_examples are always cleared after execution — [#156](https://github.com/danielwestendorf/specwrk/pull/156) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
58
|
+
- More frequent heartbeats — [#154](https://github.com/danielwestendorf/specwrk/pull/154) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
59
|
+
- Ensure mutex is held when a lock is expected — [#153](https://github.com/danielwestendorf/specwrk/pull/153) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
60
|
+
|
|
61
|
+
## v0.16.0 — 2025-10-02
|
|
62
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.10...v0.16.0)
|
|
63
|
+
- Support retry of all client requests, configurable count — [#151](https://github.com/danielwestendorf/specwrk/pull/151) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
64
|
+
- Expire workers from worker heartbeats — [#149](https://github.com/danielwestendorf/specwrk/pull/149) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
65
|
+
- Fix a few minor typos in README — [#147](https://github.com/danielwestendorf/specwrk/pull/147) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
66
|
+
- Run flake test if the srv uri is localhost — [#146](https://github.com/danielwestendorf/specwrk/pull/146) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
67
|
+
|
|
68
|
+
## v0.15.10 — 2025-08-28
|
|
69
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.9...v0.15.10)
|
|
70
|
+
- Handle default path for start and seed better — [#144](https://github.com/danielwestendorf/specwrk/pull/144) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
71
|
+
- Mention Redis storage adapter — [@danielwestendorf](https://github.com/danielwestendorf)
|
|
72
|
+
|
|
3
73
|
## v0.15.9 — 2025-08-28
|
|
4
74
|
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.15.8...v0.15.9)
|
|
5
75
|
- Support lock skipping for read-only endpoints — [#139](https://github.com/danielwestendorf/specwrk/pull/139) by [@danielwestendorf](https://github.com/danielwestendorf)
|
data/docker/Dockerfile.server
CHANGED
|
@@ -14,7 +14,7 @@ COPY $GEM_FILE ./
|
|
|
14
14
|
RUN gem install ./$GEM_FILE --no-document
|
|
15
15
|
RUN rm ./$GEM_FILE
|
|
16
16
|
|
|
17
|
-
RUN gem install pitchfork thruster specwrk-store-redis_adapter
|
|
17
|
+
RUN gem install pitchfork thruster specwrk-store-redis_adapter msgpack
|
|
18
18
|
COPY config.ru ./
|
|
19
19
|
COPY docker/pitchfork.conf ./
|
|
20
20
|
|
|
@@ -10,6 +10,9 @@ require "specwrk/store/base_adapter"
|
|
|
10
10
|
module Specwrk
|
|
11
11
|
class Store
|
|
12
12
|
class FileAdapter < BaseAdapter
|
|
13
|
+
READ_ATTEMPTS = 3
|
|
14
|
+
READ_RETRY_DELAY = 0.01
|
|
15
|
+
|
|
13
16
|
@work_queue = Queue.new
|
|
14
17
|
@threads = []
|
|
15
18
|
|
|
@@ -91,18 +94,21 @@ module Specwrk
|
|
|
91
94
|
|
|
92
95
|
read_keys.each do |key|
|
|
93
96
|
self.class.schedule_work do
|
|
94
|
-
result_queue.push([key.to_s, read(key)])
|
|
97
|
+
result_queue.push([:ok, key.to_s, read(key)])
|
|
98
|
+
rescue => e
|
|
99
|
+
result_queue.push([:error, key.to_s, e])
|
|
95
100
|
end
|
|
96
101
|
end
|
|
97
102
|
|
|
98
|
-
|
|
103
|
+
raw_results = read_keys.length.times.map { result_queue.pop }
|
|
104
|
+
error = raw_results.find { |status, _key, _result| status == :error }
|
|
105
|
+
raise error.last if error
|
|
99
106
|
|
|
100
107
|
results = {}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
next if result.last.nil?
|
|
108
|
+
raw_results.each do |_status, key, content|
|
|
109
|
+
next if content.nil?
|
|
104
110
|
|
|
105
|
-
results[
|
|
111
|
+
results[key] = self.class.serializer.load(content)
|
|
106
112
|
end
|
|
107
113
|
|
|
108
114
|
read_keys.map { |key| [key.to_s, results[key.to_s]] if results.key?(key.to_s) }.compact.to_h # respect order requested in the returned hash
|
|
@@ -140,7 +146,17 @@ module Specwrk
|
|
|
140
146
|
|
|
141
147
|
def read(key)
|
|
142
148
|
filename = filename_for_key key
|
|
143
|
-
|
|
149
|
+
attempts = 0
|
|
150
|
+
|
|
151
|
+
begin
|
|
152
|
+
attempts += 1
|
|
153
|
+
File.binread(filename)
|
|
154
|
+
rescue Errno::EBADF
|
|
155
|
+
raise if attempts >= READ_ATTEMPTS
|
|
156
|
+
|
|
157
|
+
sleep(READ_RETRY_DELAY * attempts)
|
|
158
|
+
retry
|
|
159
|
+
end
|
|
144
160
|
rescue Errno::ENOENT
|
|
145
161
|
nil
|
|
146
162
|
end
|
|
@@ -22,31 +22,43 @@ module Specwrk
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
module MessagePack
|
|
25
|
-
module_function
|
|
26
|
-
|
|
27
25
|
def ensure_loaded!
|
|
26
|
+
return if defined?(::MessagePack)
|
|
27
|
+
|
|
28
28
|
require "msgpack"
|
|
29
29
|
rescue LoadError
|
|
30
30
|
raise LoadError, "Unable to use msgpack, gem not found. Add `gem 'msgpack' to your Gemfile and bundle install"
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
module_function
|
|
34
|
-
|
|
35
33
|
def adapter_name
|
|
36
34
|
"msgpack"
|
|
37
35
|
end
|
|
38
36
|
|
|
39
37
|
def dump(value)
|
|
38
|
+
packer.tap(&:clear)
|
|
39
|
+
.tap { |msgpack| msgpack.write(value) }
|
|
40
|
+
.then(&:to_s)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def load(payload)
|
|
44
|
+
unpacker.tap(&:reset)
|
|
45
|
+
.tap { |msgpack| msgpack.feed(payload) }
|
|
46
|
+
.then { |msgpack| msgpack.read }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def packer
|
|
40
50
|
ensure_loaded!
|
|
41
51
|
|
|
42
|
-
::MessagePack.
|
|
52
|
+
Thread.current[:__specwrk_msgpack_packer] ||= ::MessagePack::Packer.new
|
|
43
53
|
end
|
|
44
54
|
|
|
45
|
-
def
|
|
55
|
+
def unpacker
|
|
46
56
|
ensure_loaded!
|
|
47
57
|
|
|
48
|
-
::MessagePack.
|
|
58
|
+
Thread.current[:__specwrk_msgpack_unpacker] ||= ::MessagePack::Unpacker.new(symbolize_keys: true)
|
|
49
59
|
end
|
|
60
|
+
|
|
61
|
+
module_function :ensure_loaded!, :adapter_name, :dump, :load, :packer, :unpacker
|
|
50
62
|
end
|
|
51
63
|
end
|
|
52
64
|
|
data/lib/specwrk/version.rb
CHANGED
|
@@ -7,12 +7,14 @@ module Specwrk
|
|
|
7
7
|
module Endpoints
|
|
8
8
|
class CompleteAndPop < Popable
|
|
9
9
|
EXAMPLE_STATUSES = %w[passed failed pending]
|
|
10
|
+
SYNTHETIC_FAILURE_CLASS = "Specwrk::WorkerExecutionError"
|
|
10
11
|
|
|
11
12
|
def with_response
|
|
12
13
|
retry_examples # pre-calculate before lock
|
|
14
|
+
omitted_processing_examples # pre-calculate before lock
|
|
13
15
|
|
|
14
16
|
with_lock do
|
|
15
|
-
processing.delete(*(completed_examples.keys + retry_examples.keys))
|
|
17
|
+
processing.delete(*(completed_examples.keys + retry_examples.keys).map(&:to_s))
|
|
16
18
|
pending.merge!(retry_examples)
|
|
17
19
|
end
|
|
18
20
|
|
|
@@ -26,24 +28,30 @@ module Specwrk
|
|
|
26
28
|
|
|
27
29
|
private
|
|
28
30
|
|
|
29
|
-
def
|
|
30
|
-
@
|
|
31
|
+
def reported_processing_examples
|
|
32
|
+
@reported_processing_examples ||= payload.map do |example|
|
|
33
|
+
[example[:id], example] if processing_examples_for_payload[example[:id]]
|
|
34
|
+
end.compact.to_h
|
|
31
35
|
end
|
|
32
36
|
|
|
33
|
-
def
|
|
34
|
-
@
|
|
37
|
+
def processing_examples_for_payload
|
|
38
|
+
@processing_examples_for_payload ||= processing.multi_read(*payload.map { |example| example[:id] })
|
|
35
39
|
end
|
|
36
40
|
|
|
37
41
|
def completed_examples
|
|
38
|
-
@completed_examples ||=
|
|
39
|
-
|
|
42
|
+
@completed_examples ||= begin
|
|
43
|
+
reported_completed_examples = reported_processing_examples.map do |id, example|
|
|
44
|
+
next if retry_example?(example)
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
[id, example]
|
|
47
|
+
end.compact.to_h
|
|
48
|
+
|
|
49
|
+
reported_completed_examples.merge(unreported_completed_examples)
|
|
50
|
+
end
|
|
43
51
|
end
|
|
44
52
|
|
|
45
53
|
def retry_examples
|
|
46
|
-
@retry_examples ||=
|
|
54
|
+
@retry_examples ||= reported_processing_examples.map do |id, example|
|
|
47
55
|
next unless retry_example?(example)
|
|
48
56
|
|
|
49
57
|
[id, example]
|
|
@@ -66,7 +74,48 @@ module Specwrk
|
|
|
66
74
|
end
|
|
67
75
|
|
|
68
76
|
def all_example_failure_counts
|
|
69
|
-
@all_example_failure_counts ||= failure_counts.multi_read(*
|
|
77
|
+
@all_example_failure_counts ||= failure_counts.multi_read(*reported_processing_examples.keys)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def omitted_processing_examples
|
|
81
|
+
@omitted_processing_examples ||= processing.to_h.select do |_id, example|
|
|
82
|
+
example[:worker_id].to_s == worker_id && !payload_example_ids.include?(example[:id].to_s)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def unreported_completed_examples
|
|
87
|
+
@unreported_completed_examples ||= omitted_processing_examples.transform_values { |example| synthetic_failure_for(example) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def payload_example_ids
|
|
91
|
+
@payload_example_ids ||= payload.map { |example| example[:id].to_s }.uniq
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def synthetic_failure_for(example)
|
|
95
|
+
finished_at = Time.now
|
|
96
|
+
|
|
97
|
+
{
|
|
98
|
+
id: example[:id],
|
|
99
|
+
full_description: "Specwrk worker execution failed before completing #{example[:id]}",
|
|
100
|
+
status: "failed",
|
|
101
|
+
file_path: example[:file_path],
|
|
102
|
+
line_number: line_number_for(example),
|
|
103
|
+
started_at: finished_at.iso8601(6),
|
|
104
|
+
finished_at: finished_at.iso8601(6),
|
|
105
|
+
run_time: 0.0,
|
|
106
|
+
exception: {
|
|
107
|
+
class: SYNTHETIC_FAILURE_CLASS,
|
|
108
|
+
message: "Worker #{worker_id} claimed this example but did not submit a completion result. Check worker stderr/stdout for the underlying RSpec output.",
|
|
109
|
+
backtrace: []
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def line_number_for(example)
|
|
115
|
+
return example[:line_number] if example[:line_number]
|
|
116
|
+
|
|
117
|
+
match = example[:id].to_s.match(/:(\d+)\z/)
|
|
118
|
+
match[1].to_i if match
|
|
70
119
|
end
|
|
71
120
|
|
|
72
121
|
def completed_examples_status_counts
|
|
@@ -87,7 +136,9 @@ module Specwrk
|
|
|
87
136
|
end
|
|
88
137
|
|
|
89
138
|
def run_time_data
|
|
90
|
-
@run_time_data ||= payload.map { |example| [example[:id], example[:run_time]] }.to_h
|
|
139
|
+
@run_time_data ||= payload.map { |example| [example[:id], example[:run_time]] }.to_h.merge(
|
|
140
|
+
unreported_completed_examples.transform_values { |example| example[:run_time] }
|
|
141
|
+
)
|
|
91
142
|
end
|
|
92
143
|
end
|
|
93
144
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: specwrk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.19.
|
|
4
|
+
version: 0.19.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Westendorf
|
|
@@ -274,7 +274,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
274
274
|
- !ruby/object:Gem::Version
|
|
275
275
|
version: '0'
|
|
276
276
|
requirements: []
|
|
277
|
-
rubygems_version:
|
|
277
|
+
rubygems_version: 4.0.10
|
|
278
278
|
specification_version: 4
|
|
279
279
|
summary: Parallel rspec test runner from a queue of pending jobs.
|
|
280
280
|
test_files: []
|