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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86a495d990068d402539acc8e3e06ba3380d49f2c7f647c4ddb9359e8c6644f3
4
- data.tar.gz: 723d7ade3f6fde1fef61dd523611f73b5791885fbfec529f79f1113fdccb6802
3
+ metadata.gz: b2eb07bead54ad609b2d8fb821793a60b783485326daeccc092c14343ae96dc0
4
+ data.tar.gz: c41e008f026a61f2748cc8f5cd9c7dc2c257103518aa89fded4c84c9bd2ca976
5
5
  SHA512:
6
- metadata.gz: f892c6d698d85db59d06b3372d7f7a41ce8011f38c3343e9ce41ee41e5592c0ea034686242c94a928433eddb878211509a72231bd407e8f346498b95ec31ac19
7
- data.tar.gz: d9af25e6db969725369118561c2d3e1ec37c86a3cb26c054b6abba3a45b2617076afdd3b66feb9ed668b988a0f8ed888e007cb5ec24eee35318b50084c1987a5
6
+ metadata.gz: 1f9a7206df73023a2262d9775ff3b1983f403b4630ce54a04f7c4500ab77c51d30df6262604332b37e37fdf21cb98fe39952fcd0287f8b9ded7811a39ac97afe
7
+ data.tar.gz: ffbf1b2583044b4f3e4bdbc7cadb35278f93eb64274905c5869398f5ece9d2854ddad0c75463ac8172d025d8dcc7a92c8486d5d9ef581ad269c875aca5c22420
data/.standard.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  # For available configuration options, see:
2
2
  # https://github.com/standardrb/standard
3
- ruby_version: 3.1
3
+ ruby_version: 3.4
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)
@@ -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
- Thread.pass until result_queue.length == read_keys.length
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
- until result_queue.empty?
102
- result = result_queue.pop
103
- next if result.last.nil?
108
+ raw_results.each do |_status, key, content|
109
+ next if content.nil?
104
110
 
105
- results[result.first] = self.class.serializer.load(result.last)
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
- File.binread(filename)
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.dump(value)
52
+ Thread.current[:__specwrk_msgpack_packer] ||= ::MessagePack::Packer.new
43
53
  end
44
54
 
45
- def load(payload)
55
+ def unpacker
46
56
  ensure_loaded!
47
57
 
48
- ::MessagePack.load(payload, symbolize_keys: true)
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Specwrk
4
- VERSION = "0.19.1"
4
+ VERSION = "0.19.3"
5
5
  end
@@ -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 all_examples
30
- @all_examples ||= payload.map { |example| [example[:id], example] if processing_examples[example[:id]] }.compact.to_h
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 processing_examples
34
- @processing_examples ||= processing.multi_read(*payload.map { |example| example[:id] })
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 ||= all_examples.map do |id, example|
39
- next if retry_example?(example)
42
+ @completed_examples ||= begin
43
+ reported_completed_examples = reported_processing_examples.map do |id, example|
44
+ next if retry_example?(example)
40
45
 
41
- [id, example]
42
- end.compact.to_h
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 ||= all_examples.map do |id, example|
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(*all_examples.keys)
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.1
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: 3.6.9
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: []