specwrk 0.19.2 → 0.19.4
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 +17 -1
- data/README.md +2 -0
- data/RELEASING.md +126 -0
- data/lib/specwrk/cli.rb +1 -1
- data/lib/specwrk/client.rb +6 -0
- data/lib/specwrk/store/base.rb +2 -0
- data/lib/specwrk/store/file_adapter.rb +27 -9
- data/lib/specwrk/store/serializer.rb +3 -3
- data/lib/specwrk/version.rb +1 -1
- data/lib/specwrk/web/endpoints/base.rb +10 -0
- data/lib/specwrk/web/endpoints/complete_and_pop.rb +63 -12
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ed4bee81f0d4eef75556d4987bfa36101083bd1ee124770857f76ea2827ad036
|
|
4
|
+
data.tar.gz: e33ddadc340adf10fc4d66bd36598ad3322b27e5cb37dd9e0f32c0d74c65c305
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ce4eeb7ca55eae597425b10eb3a1236ae8b04fa6bae13f3564d761a88eb138671c1ffae85cf89f7944fb64c463f9ff96b261e9b650df3dbf34675237d91df7d
|
|
7
|
+
data.tar.gz: 81b3f08f0870b9d7af23476ad2282793e2891c42fa110d18e5f5ae1b567c843eaa945b125fc16d4af72a1b124f5f3e8d5bd214bd8e7804222f6f7c6a1d613649
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
|
-
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.19.
|
|
4
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.19.4...main)
|
|
5
|
+
|
|
6
|
+
## v0.19.4 — 2026-08-23
|
|
7
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.19.3...v0.19.4)
|
|
8
|
+
- Return HTTP 423 on lock contention and retry clients with jitter — [#189](https://github.com/danielwestendorf/specwrk/pull/189) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
9
|
+
- Document the gem and Docker release process — [#190](https://github.com/danielwestendorf/specwrk/pull/190) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
10
|
+
|
|
11
|
+
## v0.19.3 — 2026-07-26
|
|
12
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.19.2...v0.19.3)
|
|
13
|
+
- Retry transient file store read descriptor errors — [@danielwestendorf](https://github.com/danielwestendorf)
|
|
14
|
+
- Complete omitted worker processing examples as failures — [@danielwestendorf](https://github.com/danielwestendorf), [@benjaminwood](https://github.com/benjaminwood)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## v0.19.2 — 2025-12-09
|
|
18
|
+
[Compare](https://github.com/danielwestendorf/specwrk/compare/v0.19.1...v0.19.2)
|
|
19
|
+
- Optimize msgpack usage by using one packer/unpacker — [#180](https://github.com/danielwestendorf/specwrk/pull/180) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
20
|
+
- Standard style to v3.4 — [#179](https://github.com/danielwestendorf/specwrk/pull/179) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
5
21
|
- Add msgpack to the docker server — [#178](https://github.com/danielwestendorf/specwrk/pull/178) by [@danielwestendorf](https://github.com/danielwestendorf)
|
|
6
22
|
|
|
7
23
|
## v0.19.1 — 2025-12-08
|
data/README.md
CHANGED
|
@@ -292,6 +292,8 @@ specwrk is different because it:
|
|
|
292
292
|
|
|
293
293
|
Bug reports and pull requests are welcome on GitHub at https://github.com/danielwestendorf/specwrk.
|
|
294
294
|
|
|
295
|
+
Maintainers can follow [RELEASING.md](RELEASING.md) to publish the gem and Docker image.
|
|
296
|
+
|
|
295
297
|
## License
|
|
296
298
|
|
|
297
299
|
The gem is available as open source under the terms of the [LGPLv3 License](http://www.gnu.org/licenses/lgpl-3.0.html).
|
data/RELEASING.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Releasing Specwrk
|
|
2
|
+
|
|
3
|
+
Releases are cut from `main` in two stages:
|
|
4
|
+
|
|
5
|
+
1. `gem bump` updates the version and creates the release commit.
|
|
6
|
+
2. Bundler's `rake release` task tags the commit, pushes Git, and publishes the gem.
|
|
7
|
+
|
|
8
|
+
The Docker image is published separately after the gem release.
|
|
9
|
+
|
|
10
|
+
## One-time setup
|
|
11
|
+
|
|
12
|
+
Install `gem-release`, which provides the `gem bump` command. It is intentionally
|
|
13
|
+
not a development dependency because it is only needed by maintainers cutting a
|
|
14
|
+
release.
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
gem install gem-release
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Configure credentials for RubyGems and Docker Hub:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
gem signin
|
|
24
|
+
docker login docker.io
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The Docker release also requires a buildx builder capable of building `linux/arm64`
|
|
28
|
+
and `linux/amd64` images. Check the active builder with:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
docker buildx inspect --bootstrap
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Cut the gem release
|
|
35
|
+
|
|
36
|
+
Start from an up-to-date, clean `main` branch. This is important because
|
|
37
|
+
`rake release` pushes the current branch.
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
git switch main
|
|
41
|
+
git pull --ff-only origin main
|
|
42
|
+
git status --short
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Bump the version without committing. Use `patch`, `minor`, `major`, or an
|
|
46
|
+
explicit version number:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
gem bump --version patch --no-commit
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This updates `lib/specwrk/version.rb` while leaving the release changes
|
|
53
|
+
uncommitted. Update `CHANGELOG.md` using the new version:
|
|
54
|
+
|
|
55
|
+
1. Change the Unreleased comparison link to start at the new version.
|
|
56
|
+
2. Add a dated section for the new version.
|
|
57
|
+
3. Move the Unreleased entries into that section.
|
|
58
|
+
|
|
59
|
+
Stage the version, changelog, and any release documentation, then create the
|
|
60
|
+
version commit:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
git add lib/specwrk/version.rb CHANGELOG.md RELEASING.md
|
|
64
|
+
git commit -m "Bump specwrk to VERSION"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Replace `VERSION` with the new version, for example `0.19.4`. Confirm that the
|
|
68
|
+
commit contains the version and changelog updates:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
git show --stat --oneline HEAD
|
|
72
|
+
git status --short
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Run the full checks:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
bundle exec rake
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Publish the release:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
bundle exec rake release
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The release task requires a clean tracked worktree and then:
|
|
88
|
+
|
|
89
|
+
1. Builds `pkg/specwrk-VERSION.gem`.
|
|
90
|
+
2. Creates the annotated `vVERSION` tag.
|
|
91
|
+
3. Pushes `main` and the tag to `origin`.
|
|
92
|
+
4. Pushes the gem to RubyGems.org.
|
|
93
|
+
|
|
94
|
+
Use `gem bump` only to update the version file; use `bundle exec rake release`,
|
|
95
|
+
not `gem release`, for publishing this project.
|
|
96
|
+
|
|
97
|
+
If the RubyGems push fails after the tag was pushed, fix the authentication or
|
|
98
|
+
network problem and rerun `bundle exec rake release`. The task recognizes the
|
|
99
|
+
existing tag and retries the gem publication.
|
|
100
|
+
|
|
101
|
+
## Build and publish the Docker image
|
|
102
|
+
|
|
103
|
+
After the gem release succeeds, run:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
scripts/build-server
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The script reads the version from `bundle exec exe/specwrk --version`, builds the
|
|
110
|
+
gem from the current checkout, and runs a multi-platform `docker buildx build`
|
|
111
|
+
with `--push`. It publishes both of these tags:
|
|
112
|
+
|
|
113
|
+
- `docker.io/danielwestendorf/specwrk-server:VERSION`
|
|
114
|
+
- `docker.io/danielwestendorf/specwrk-server:latest`
|
|
115
|
+
|
|
116
|
+
It builds images for both `linux/arm64` and `linux/amd64`. The temporary gem in
|
|
117
|
+
the repository root is removed after a successful build.
|
|
118
|
+
|
|
119
|
+
Verify the published manifest with:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
docker buildx imagetools inspect docker.io/danielwestendorf/specwrk-server:VERSION
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Replace `VERSION` with the released version without the `v` prefix, for example
|
|
126
|
+
`0.19.4`.
|
data/lib/specwrk/cli.rb
CHANGED
data/lib/specwrk/client.rb
CHANGED
|
@@ -12,6 +12,8 @@ Specwrk.net_http = Net::HTTP
|
|
|
12
12
|
|
|
13
13
|
module Specwrk
|
|
14
14
|
class Client
|
|
15
|
+
LockedResponseError = Class.new(StandardError)
|
|
16
|
+
|
|
15
17
|
def self.connect?
|
|
16
18
|
http = build_http
|
|
17
19
|
http.start
|
|
@@ -158,10 +160,14 @@ module Specwrk
|
|
|
158
160
|
@last_request_at = Time.now
|
|
159
161
|
@http.request(request).tap do |response|
|
|
160
162
|
@retry_count = 0
|
|
163
|
+
raise LockedResponseError if response.code == "423"
|
|
161
164
|
|
|
162
165
|
@worker_status = response["x-specwrk-status"].to_i if response["x-specwrk-status"]
|
|
163
166
|
end
|
|
164
167
|
end
|
|
168
|
+
rescue LockedResponseError
|
|
169
|
+
sleep rand
|
|
170
|
+
retry
|
|
165
171
|
rescue Net::ReadTimeout, Net::WriteTimeout => e
|
|
166
172
|
@retry_count ||= 0
|
|
167
173
|
|
data/lib/specwrk/store/base.rb
CHANGED
|
@@ -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
|
|
|
@@ -21,11 +24,13 @@ module Specwrk
|
|
|
21
24
|
|
|
22
25
|
lock_file = File.open(lock_file_path, "a")
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
locked = lock_file.flock(File::LOCK_EX | File::LOCK_NB)
|
|
28
|
+
raise LockUnavailableError unless locked
|
|
25
29
|
|
|
26
30
|
yield
|
|
27
31
|
ensure
|
|
28
|
-
lock_file
|
|
32
|
+
lock_file&.flock(File::LOCK_UN) if locked
|
|
33
|
+
lock_file&.close
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
def schedule_work(&blk)
|
|
@@ -91,18 +96,21 @@ module Specwrk
|
|
|
91
96
|
|
|
92
97
|
read_keys.each do |key|
|
|
93
98
|
self.class.schedule_work do
|
|
94
|
-
result_queue.push([key.to_s, read(key)])
|
|
99
|
+
result_queue.push([:ok, key.to_s, read(key)])
|
|
100
|
+
rescue => e
|
|
101
|
+
result_queue.push([:error, key.to_s, e])
|
|
95
102
|
end
|
|
96
103
|
end
|
|
97
104
|
|
|
98
|
-
|
|
105
|
+
raw_results = read_keys.length.times.map { result_queue.pop }
|
|
106
|
+
error = raw_results.find { |status, _key, _result| status == :error }
|
|
107
|
+
raise error.last if error
|
|
99
108
|
|
|
100
109
|
results = {}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
next if result.last.nil?
|
|
110
|
+
raw_results.each do |_status, key, content|
|
|
111
|
+
next if content.nil?
|
|
104
112
|
|
|
105
|
-
results[
|
|
113
|
+
results[key] = self.class.serializer.load(content)
|
|
106
114
|
end
|
|
107
115
|
|
|
108
116
|
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 +148,17 @@ module Specwrk
|
|
|
140
148
|
|
|
141
149
|
def read(key)
|
|
142
150
|
filename = filename_for_key key
|
|
143
|
-
|
|
151
|
+
attempts = 0
|
|
152
|
+
|
|
153
|
+
begin
|
|
154
|
+
attempts += 1
|
|
155
|
+
File.binread(filename)
|
|
156
|
+
rescue Errno::EBADF
|
|
157
|
+
raise if attempts >= READ_ATTEMPTS
|
|
158
|
+
|
|
159
|
+
sleep(READ_RETRY_DELAY * attempts)
|
|
160
|
+
retry
|
|
161
|
+
end
|
|
144
162
|
rescue Errno::ENOENT
|
|
145
163
|
nil
|
|
146
164
|
end
|
|
@@ -36,14 +36,14 @@ module Specwrk
|
|
|
36
36
|
|
|
37
37
|
def dump(value)
|
|
38
38
|
packer.tap(&:clear)
|
|
39
|
-
.tap {
|
|
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 {
|
|
46
|
-
.then {
|
|
45
|
+
.tap { |msgpack| msgpack.feed(payload) }
|
|
46
|
+
.then { |msgpack| msgpack.read }
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def packer
|
data/lib/specwrk/version.rb
CHANGED
|
@@ -30,6 +30,8 @@ module Specwrk
|
|
|
30
30
|
with_response.tap do |response|
|
|
31
31
|
response[1]["x-specwrk-status"] = worker_status.to_s
|
|
32
32
|
end
|
|
33
|
+
rescue Store::LockUnavailableError
|
|
34
|
+
locked
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
def with_response
|
|
@@ -56,6 +58,14 @@ module Specwrk
|
|
|
56
58
|
end
|
|
57
59
|
end
|
|
58
60
|
|
|
61
|
+
def locked
|
|
62
|
+
if request.head?
|
|
63
|
+
[423, {}, []]
|
|
64
|
+
else
|
|
65
|
+
[423, {"content-type" => "text/plain"}, ["Locked. Try again later."]]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
59
69
|
def payload
|
|
60
70
|
return unless request.content_type&.start_with?("application/json")
|
|
61
71
|
return unless request.post? || request.put? || request.delete?
|
|
@@ -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.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Westendorf
|
|
@@ -204,6 +204,7 @@ files:
|
|
|
204
204
|
- CHANGELOG.md
|
|
205
205
|
- LICENSE
|
|
206
206
|
- README.md
|
|
207
|
+
- RELEASING.md
|
|
207
208
|
- Rakefile
|
|
208
209
|
- Specwrk.watchfile.rb
|
|
209
210
|
- config.ru
|
|
@@ -274,7 +275,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
274
275
|
- !ruby/object:Gem::Version
|
|
275
276
|
version: '0'
|
|
276
277
|
requirements: []
|
|
277
|
-
rubygems_version:
|
|
278
|
+
rubygems_version: 4.0.10
|
|
278
279
|
specification_version: 4
|
|
279
280
|
summary: Parallel rspec test runner from a queue of pending jobs.
|
|
280
281
|
test_files: []
|