specwrk 0.19.2 → 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: befb2589fe879bbe901360402374acb906359ff133c877a9169bcb0f928d1833
4
- data.tar.gz: 30c49a123a026d5a4af2da4fadf01cfc92637b095328c83c660d5a3ee7c26d43
3
+ metadata.gz: b2eb07bead54ad609b2d8fb821793a60b783485326daeccc092c14343ae96dc0
4
+ data.tar.gz: c41e008f026a61f2748cc8f5cd9c7dc2c257103518aa89fded4c84c9bd2ca976
5
5
  SHA512:
6
- metadata.gz: 9e997305868b8cca5ce07ea2285462ed26b01079ccebe09617749ae76c42380787b68ed693af0df4aa6cf1e196b941c5449ae7034cf21614b16650c73de4fc6b
7
- data.tar.gz: 21bb6a0af32806ed771f0e49ac0295e87ac6f57afb6259b5d74e09971adc5be23bdb1a5bcfb6762578f3f85b0387478d23767f1068e1fc53dd0dd2a63e84bbd3
6
+ metadata.gz: 1f9a7206df73023a2262d9775ff3b1983f403b4630ce54a04f7c4500ab77c51d30df6262604332b37e37fdf21cb98fe39952fcd0287f8b9ded7811a39ac97afe
7
+ data.tar.gz: ffbf1b2583044b4f3e4bdbc7cadb35278f93eb64274905c5869398f5ece9d2854ddad0c75463ac8172d025d8dcc7a92c8486d5d9ef581ad269c875aca5c22420
data/CHANGELOG.md CHANGED
@@ -1,7 +1,18 @@
1
1
  # Changelog
2
2
 
3
3
  ## Unreleased
4
- [Compare](https://github.com/danielwestendorf/specwrk/compare/v0.19.1...main)
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)
5
16
  - Add msgpack to the docker server — [#178](https://github.com/danielwestendorf/specwrk/pull/178) by [@danielwestendorf](https://github.com/danielwestendorf)
6
17
 
7
18
  ## v0.19.1 — 2025-12-08
@@ -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
@@ -36,14 +36,14 @@ module Specwrk
36
36
 
37
37
  def dump(value)
38
38
  packer.tap(&:clear)
39
- .tap { it.write(value) }
39
+ .tap { |msgpack| msgpack.write(value) }
40
40
  .then(&:to_s)
41
41
  end
42
42
 
43
43
  def load(payload)
44
44
  unpacker.tap(&:reset)
45
- .tap { it.feed(payload) }
46
- .then { it.read }
45
+ .tap { |msgpack| msgpack.feed(payload) }
46
+ .then { |msgpack| msgpack.read }
47
47
  end
48
48
 
49
49
  def packer
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Specwrk
4
- VERSION = "0.19.2"
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.2
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: []