specwrk 0.6.2 → 0.7.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/docker/Dockerfile.server +11 -7
- data/docker/entrypoint.server.sh +2 -2
- data/examples/circleci/config.yml +1 -0
- data/examples/github/specwrk-multi-node.yml +1 -0
- data/examples/github/specwrk-single-node.yml +1 -0
- data/exe/specwrk +1 -0
- data/lib/specwrk/filestore.rb +64 -0
- data/lib/specwrk/queue.rb +38 -82
- data/lib/specwrk/version.rb +1 -1
- data/lib/specwrk/web/app.rb +0 -24
- data/lib/specwrk/web/auth.rb +1 -1
- data/lib/specwrk/web/endpoints.rb +59 -55
- data/lib/specwrk/web.rb +4 -12
- data/lib/specwrk/worker.rb +3 -1
- metadata +5 -2
- data/docker/pitchfork.conf +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c6ab0d31757bbed9bf9f9ba02172a9bdd0b1ba57c7209f9d694b6471588ddd1
|
4
|
+
data.tar.gz: 44a634acd2023813f35b3eac9bcc331782c4cf624d716bf1d6341db80a07d464
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c29b9f3e52b7b4d88920b6a4d92cba4b24fed583573b9cfa8dd273777a73534ded97f2a89ecc078d58d183e48a0095a1403900de7c3d497954f6c9002b4e962
|
7
|
+
data.tar.gz: ce81b65408313ce7088ba157c4578ffae66c15f9f8d4e053992c8eb1c4c99a9ecbc3cb68e1903e6817d988740b7dd57e282866a06a23146841ca057adb89fb19
|
data/docker/Dockerfile.server
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
FROM ruby:3.4-alpine
|
2
2
|
|
3
|
-
RUN apk add --no-cache
|
3
|
+
RUN apk add --no-cache \
|
4
|
+
build-base \
|
5
|
+
ruby-dev \
|
6
|
+
linux-headers \
|
7
|
+
zlib-dev \
|
8
|
+
libffi-dev
|
4
9
|
|
5
10
|
WORKDIR /app
|
6
11
|
|
@@ -8,15 +13,14 @@ RUN mkdir .specwrk/
|
|
8
13
|
|
9
14
|
ARG SPECWRK_SRV_PORT=5138
|
10
15
|
ARG SPECWRK_VERSION=latest
|
11
|
-
ARG
|
16
|
+
ARG GEMFILE=specwrk-$SPECWRK_VERSION.gem
|
12
17
|
|
13
|
-
COPY $
|
14
|
-
RUN gem install ./$
|
15
|
-
RUN rm ./$
|
18
|
+
COPY $GEMFILE ./
|
19
|
+
RUN gem install ./$GEMFILE --no-document
|
20
|
+
RUN rm ./$GEMFILE
|
16
21
|
|
17
|
-
RUN gem install
|
22
|
+
RUN gem install puma thruster
|
18
23
|
COPY config.ru ./
|
19
|
-
COPY docker/pitchfork.conf ./
|
20
24
|
|
21
25
|
COPY docker/entrypoint.server.sh /usr/local/bin/entrypoint
|
22
26
|
RUN chmod +x /usr/local/bin/entrypoint
|
data/docker/entrypoint.server.sh
CHANGED
@@ -2,6 +2,6 @@
|
|
2
2
|
|
3
3
|
export THRUSTER_HTTP_PORT=${PORT:-5138}
|
4
4
|
export THRUSTER_TARGET_PORT=3000
|
5
|
-
export THRUSTER_HTTP_IDLE_TIMEOUT=${IDLE_TIMEOUT:-
|
5
|
+
export THRUSTER_HTTP_IDLE_TIMEOUT=${IDLE_TIMEOUT:-305}
|
6
6
|
|
7
|
-
exec thrust
|
7
|
+
exec thrust puma --workers 0 --bind tcp://127.0.0.1:3000 --threads ${PUMA_THREADS:-1}
|
@@ -0,0 +1 @@
|
|
1
|
+
.circleci/config.yml
|
@@ -0,0 +1 @@
|
|
1
|
+
.github/workflows/specwrk-multi-node.yml
|
@@ -0,0 +1 @@
|
|
1
|
+
.github/workflows/specwrk-single-node.yml
|
data/exe/specwrk
CHANGED
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module Specwrk
|
6
|
+
class Filestore
|
7
|
+
@mutexes = {}
|
8
|
+
@mutexes_mutex = Mutex.new # 🐢🐢🐢🐢
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def [](path)
|
12
|
+
new(path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def mutex_for(path)
|
16
|
+
@mutexes_mutex.synchronize do
|
17
|
+
@mutexes[path] ||= Mutex.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(path)
|
23
|
+
@path = path
|
24
|
+
@tmpfile_path = @path + ".tmp"
|
25
|
+
@lock_path = @path + ".lock"
|
26
|
+
|
27
|
+
FileUtils.mkdir_p File.dirname(@path)
|
28
|
+
File.open(@path, "a") {} # multi-process and multi-thread safe touch
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_lock
|
32
|
+
self.class.mutex_for(@path).synchronize do
|
33
|
+
lock_file.flock(File::LOCK_EX)
|
34
|
+
|
35
|
+
hash = read
|
36
|
+
result = yield(hash)
|
37
|
+
|
38
|
+
# Will truncate if already exists
|
39
|
+
File.open(@tmpfile_path, "w") do |tmpfile|
|
40
|
+
tmpfile.write(hash.to_json)
|
41
|
+
tmpfile.fsync
|
42
|
+
tmpfile.close
|
43
|
+
end
|
44
|
+
|
45
|
+
File.rename(@tmpfile_path, @path)
|
46
|
+
result
|
47
|
+
ensure
|
48
|
+
lock_file.flock(File::LOCK_UN)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def lock_file
|
55
|
+
@lock_file ||= File.open(@lock_path, "w")
|
56
|
+
end
|
57
|
+
|
58
|
+
def read
|
59
|
+
JSON.parse(File.read(@path), symbolize_names: true)
|
60
|
+
rescue JSON::ParserError
|
61
|
+
{}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/specwrk/queue.rb
CHANGED
@@ -4,44 +4,7 @@ require "time"
|
|
4
4
|
require "json"
|
5
5
|
|
6
6
|
module Specwrk
|
7
|
-
|
8
|
-
class Queue
|
9
|
-
attr_reader :created_at
|
10
|
-
|
11
|
-
def initialize(hash = {})
|
12
|
-
@created_at = Time.now
|
13
|
-
|
14
|
-
if block_given?
|
15
|
-
@mutex = Monitor.new # Reentrant locking is required here
|
16
|
-
# It's possible to enter the proc from two threads, so we need to ||= in case
|
17
|
-
# one thread has set a value prior to the yield.
|
18
|
-
hash.default_proc = proc { |h, key| @mutex.synchronize { yield(h, key) } }
|
19
|
-
end
|
20
|
-
|
21
|
-
@mutex ||= Mutex.new # Monitor is up-to 20% slower than Mutex, so if no block is given, use a mutex
|
22
|
-
@hash = hash
|
23
|
-
end
|
24
|
-
|
25
|
-
def synchronize(&blk)
|
26
|
-
if @mutex.owned?
|
27
|
-
yield(@hash)
|
28
|
-
else
|
29
|
-
@mutex.synchronize { yield(@hash) }
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def method_missing(name, *args, &block)
|
34
|
-
if @hash.respond_to?(name)
|
35
|
-
@mutex.synchronize { @hash.public_send(name, *args, &block) }
|
36
|
-
else
|
37
|
-
super
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def respond_to_missing?(name, include_private = false)
|
42
|
-
@hash.respond_to?(name, include_private) || super
|
43
|
-
end
|
44
|
-
end
|
7
|
+
Queue = Class.new(Hash)
|
45
8
|
|
46
9
|
class PendingQueue < Queue
|
47
10
|
def shift_bucket
|
@@ -81,12 +44,12 @@ module Specwrk
|
|
81
44
|
end
|
82
45
|
|
83
46
|
def merge_with_previous_run_times!(h2)
|
84
|
-
|
85
|
-
h2.each { |_id, example| merge_example(example) }
|
47
|
+
h2.each { |_id, example| merge_example(example) }
|
86
48
|
|
87
|
-
|
88
|
-
|
89
|
-
|
49
|
+
# Sort by exepcted run time, slowest to fastest
|
50
|
+
new_h = sort_by { |_, example| example[:expected_run_time] }.reverse.to_h
|
51
|
+
clear
|
52
|
+
merge!(new_h)
|
90
53
|
end
|
91
54
|
|
92
55
|
private
|
@@ -104,17 +67,15 @@ module Specwrk
|
|
104
67
|
def bucket_by_file
|
105
68
|
bucket = []
|
106
69
|
|
107
|
-
|
108
|
-
|
109
|
-
break if key.nil?
|
70
|
+
key = keys.first
|
71
|
+
return [] if key.nil?
|
110
72
|
|
111
|
-
|
112
|
-
|
113
|
-
|
73
|
+
file_path = self[key][:file_path]
|
74
|
+
each do |id, example|
|
75
|
+
next unless example[:file_path] == file_path
|
114
76
|
|
115
|
-
|
116
|
-
|
117
|
-
end
|
77
|
+
bucket << example
|
78
|
+
delete id
|
118
79
|
end
|
119
80
|
|
120
81
|
bucket
|
@@ -124,30 +85,27 @@ module Specwrk
|
|
124
85
|
def bucket_by_timings
|
125
86
|
bucket = []
|
126
87
|
|
127
|
-
|
128
|
-
estimated_run_time_total = 0
|
88
|
+
estimated_run_time_total = 0
|
129
89
|
|
130
|
-
|
131
|
-
|
132
|
-
|
90
|
+
while estimated_run_time_total < run_time_bucket_threshold
|
91
|
+
key = keys.first
|
92
|
+
break if key.nil?
|
133
93
|
|
134
|
-
|
135
|
-
|
94
|
+
estimated_run_time_total += dig(key, :expected_run_time)
|
95
|
+
break if estimated_run_time_total > run_time_bucket_threshold && bucket.length.positive?
|
136
96
|
|
137
|
-
|
138
|
-
|
139
|
-
end
|
97
|
+
bucket << self[key]
|
98
|
+
delete key
|
140
99
|
end
|
141
100
|
|
142
101
|
bucket
|
143
102
|
end
|
144
103
|
|
145
|
-
# Ensure @mutex is held when calling this method
|
146
104
|
def merge_example(example)
|
147
|
-
return if
|
148
|
-
return if
|
105
|
+
return if key? example[:id]
|
106
|
+
return if key? example[:file_path]
|
149
107
|
|
150
|
-
|
108
|
+
self[example[:id]] = if previous_run_times
|
151
109
|
example.merge!(
|
152
110
|
expected_run_time: previous_run_times.dig(:examples, example[:id].to_sym, :run_time) || 99999.9 # run "unknown" files first
|
153
111
|
)
|
@@ -165,26 +123,24 @@ module Specwrk
|
|
165
123
|
end
|
166
124
|
|
167
125
|
def dump
|
168
|
-
@
|
169
|
-
|
170
|
-
|
171
|
-
@last_finished_at = Time.new(1900, 1, 1, 0, 0, 0)
|
126
|
+
@run_times = []
|
127
|
+
@first_started_at = Time.new(2999, 1, 1, 0, 0, 0) # TODO: Make future proof /s
|
128
|
+
@last_finished_at = Time.new(1900, 1, 1, 0, 0, 0)
|
172
129
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
130
|
+
@output = {
|
131
|
+
file_totals: Hash.new { |h, filename| h[filename] = 0.0 },
|
132
|
+
meta: {failures: 0, passes: 0, pending: 0},
|
133
|
+
examples: {}
|
134
|
+
}
|
178
135
|
|
179
|
-
|
136
|
+
values.each { |example| calculate(example) }
|
180
137
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
138
|
+
@output[:meta][:total_run_time] = @run_times.sum
|
139
|
+
@output[:meta][:average_run_time] = @output[:meta][:total_run_time] / [@run_times.length, 1].max.to_f
|
140
|
+
@output[:meta][:first_started_at] = @first_started_at.iso8601(6)
|
141
|
+
@output[:meta][:last_finished_at] = @last_finished_at.iso8601(6)
|
185
142
|
|
186
|
-
|
187
|
-
end
|
143
|
+
@output
|
188
144
|
end
|
189
145
|
|
190
146
|
private
|
data/lib/specwrk/version.rb
CHANGED
data/lib/specwrk/web/app.rb
CHANGED
@@ -74,10 +74,6 @@ module Specwrk
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
-
def initialize
|
78
|
-
@reaper_thread = Thread.new { reaper } unless ENV["SPECWRK_SRV_SINGLE_RUN"]
|
79
|
-
end
|
80
|
-
|
81
77
|
def call(env)
|
82
78
|
env[:request] ||= Rack::Request.new(env)
|
83
79
|
|
@@ -106,26 +102,6 @@ module Specwrk
|
|
106
102
|
Endpoints::NotFound
|
107
103
|
end
|
108
104
|
end
|
109
|
-
|
110
|
-
def reaper
|
111
|
-
until Specwrk.force_quit
|
112
|
-
sleep REAP_INTERVAL
|
113
|
-
|
114
|
-
reap
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def reap
|
119
|
-
Web::WORKERS.each do |run, workers|
|
120
|
-
most_recent_last_seen_at = workers.map { |id, worker| worker[:last_seen_at] }.max
|
121
|
-
next unless most_recent_last_seen_at
|
122
|
-
|
123
|
-
# Don't consider runs which aren't at least REAP_INTERVAL sec stale
|
124
|
-
if most_recent_last_seen_at < Time.now - REAP_INTERVAL
|
125
|
-
Web.clear_run_queues(run)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
105
|
end
|
130
106
|
end
|
131
107
|
end
|
data/lib/specwrk/web/auth.rb
CHANGED
@@ -6,14 +6,38 @@ module Specwrk
|
|
6
6
|
class Web
|
7
7
|
module Endpoints
|
8
8
|
class Base
|
9
|
+
attr_reader :pending_queue, :processing_queue, :completed_queue, :workers, :started_at
|
10
|
+
|
9
11
|
def initialize(request)
|
10
12
|
@request = request
|
11
|
-
|
12
|
-
worker[:first_seen_at] ||= Time.now
|
13
|
-
worker[:last_seen_at] = Time.now
|
14
13
|
end
|
15
14
|
|
16
15
|
def response
|
16
|
+
datastore.with_lock do |db|
|
17
|
+
@started_at = if db[:started_at]
|
18
|
+
Time.parse(db[:started_at])
|
19
|
+
else
|
20
|
+
db[:started_at] = Time.now
|
21
|
+
end
|
22
|
+
|
23
|
+
@pending_queue = PendingQueue.new.merge!(db[:pending] || {})
|
24
|
+
@processing_queue = Queue.new.merge!(db[:processing] || {})
|
25
|
+
@completed_queue = CompletedQueue.new.merge!(db[:completed] || {})
|
26
|
+
@workers = db[:workers] ||= {}
|
27
|
+
|
28
|
+
worker[:first_seen_at] ||= Time.now
|
29
|
+
worker[:last_seen_at] = Time.now
|
30
|
+
|
31
|
+
with_response.tap do
|
32
|
+
db[:pending] = pending_queue.to_h
|
33
|
+
db[:processing] = processing_queue.to_h
|
34
|
+
db[:completed] = completed_queue.to_h
|
35
|
+
db[:workers] = workers.to_h
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def with_response
|
17
41
|
not_found
|
18
42
|
end
|
19
43
|
|
@@ -22,11 +46,11 @@ module Specwrk
|
|
22
46
|
attr_reader :request
|
23
47
|
|
24
48
|
def not_found
|
25
|
-
[404, {"
|
49
|
+
[404, {"Content-Type" => "text/plain"}, ["This is not the path you're looking for, 'ol chap..."]]
|
26
50
|
end
|
27
51
|
|
28
52
|
def ok
|
29
|
-
[200, {"
|
53
|
+
[200, {"Content-Type" => "text/plain"}, ["OK, 'ol chap"]]
|
30
54
|
end
|
31
55
|
|
32
56
|
def payload
|
@@ -37,24 +61,8 @@ module Specwrk
|
|
37
61
|
@body ||= request.body.read
|
38
62
|
end
|
39
63
|
|
40
|
-
def pending_queue
|
41
|
-
Web::PENDING_QUEUES[run_id]
|
42
|
-
end
|
43
|
-
|
44
|
-
def processing_queue
|
45
|
-
Web::PROCESSING_QUEUES[request.get_header("HTTP_X_SPECWRK_RUN")]
|
46
|
-
end
|
47
|
-
|
48
|
-
def completed_queue
|
49
|
-
Web::COMPLETED_QUEUES[request.get_header("HTTP_X_SPECWRK_RUN")]
|
50
|
-
end
|
51
|
-
|
52
|
-
def workers
|
53
|
-
Web::WORKERS[request.get_header("HTTP_X_SPECWRK_RUN")]
|
54
|
-
end
|
55
|
-
|
56
64
|
def worker
|
57
|
-
workers[request.get_header("HTTP_X_SPECWRK_ID")]
|
65
|
+
workers[request.get_header("HTTP_X_SPECWRK_ID")] ||= {}
|
58
66
|
end
|
59
67
|
|
60
68
|
def run_id
|
@@ -62,7 +70,11 @@ module Specwrk
|
|
62
70
|
end
|
63
71
|
|
64
72
|
def run_report_file_path
|
65
|
-
@run_report_file_path ||= File.join(ENV["SPECWRK_OUT"], "#{
|
73
|
+
@run_report_file_path ||= File.join(ENV["SPECWRK_OUT"], run_id, "#{started_at.strftime("%Y%m%dT%H%M%S")}-report.json").to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
def datastore
|
77
|
+
Web.datastore[File.join(ENV["SPECWRK_OUT"], run_id, "queues.json").to_s]
|
66
78
|
end
|
67
79
|
end
|
68
80
|
|
@@ -70,27 +82,22 @@ module Specwrk
|
|
70
82
|
NotFound = Class.new(Base)
|
71
83
|
|
72
84
|
class Health < Base
|
73
|
-
def
|
85
|
+
def with_response
|
74
86
|
[200, {}, []]
|
75
87
|
end
|
76
88
|
end
|
77
89
|
|
78
90
|
class Heartbeat < Base
|
79
|
-
def
|
91
|
+
def with_response
|
80
92
|
ok
|
81
93
|
end
|
82
94
|
end
|
83
95
|
|
84
96
|
class Seed < Base
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
pending_queue.merge_with_previous_run_times!(examples)
|
91
|
-
|
92
|
-
ok
|
93
|
-
end
|
97
|
+
def with_response
|
98
|
+
if ENV["SPECWRK_SRV_SINGLE_SEED_PER_RUN"].nil? || pending_queue.length.zero?
|
99
|
+
examples = payload.map { |hash| [hash[:id], hash] }.to_h
|
100
|
+
pending_queue.merge_with_previous_run_times!(examples)
|
94
101
|
end
|
95
102
|
|
96
103
|
ok
|
@@ -98,16 +105,15 @@ module Specwrk
|
|
98
105
|
end
|
99
106
|
|
100
107
|
class Complete < Base
|
101
|
-
def
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
completed_queue[example[:id]] = example
|
106
|
-
end
|
108
|
+
def with_response
|
109
|
+
payload.each do |example|
|
110
|
+
next unless processing_queue.delete(example[:id].to_sym)
|
111
|
+
completed_queue[example[:id].to_sym] = example
|
107
112
|
end
|
108
113
|
|
109
114
|
if pending_queue.length.zero? && processing_queue.length.zero? && completed_queue.length.positive? && ENV["SPECWRK_OUT"]
|
110
115
|
completed_queue.dump_and_write(run_report_file_path)
|
116
|
+
FileUtils.ln_sf(run_report_file_path, File.join(ENV["SPECWRK_OUT"], "report.json"))
|
111
117
|
end
|
112
118
|
|
113
119
|
ok
|
@@ -115,21 +121,19 @@ module Specwrk
|
|
115
121
|
end
|
116
122
|
|
117
123
|
class Pop < Base
|
118
|
-
def
|
119
|
-
|
120
|
-
@examples = pending_queue.shift_bucket
|
124
|
+
def with_response
|
125
|
+
@examples = pending_queue.shift_bucket
|
121
126
|
|
122
|
-
|
123
|
-
|
124
|
-
end
|
127
|
+
@examples.each do |example|
|
128
|
+
processing_queue[example[:id]] = example
|
125
129
|
end
|
126
130
|
|
127
131
|
if @examples.length.positive?
|
128
|
-
[200, {"
|
132
|
+
[200, {"Content-Type" => "application/json"}, [JSON.generate(@examples)]]
|
129
133
|
elsif pending_queue.length.zero? && processing_queue.length.zero? && completed_queue.length.zero?
|
130
|
-
[204, {"
|
134
|
+
[204, {"Content-Type" => "text/plain"}, ["Waiting for sample to be seeded."]]
|
131
135
|
elsif completed_queue.length.positive? && processing_queue.length.zero?
|
132
|
-
[410, {"
|
136
|
+
[410, {"Content-Type" => "text/plain"}, ["That's a good lad. Run along now and go home."]]
|
133
137
|
else
|
134
138
|
not_found
|
135
139
|
end
|
@@ -137,11 +141,11 @@ module Specwrk
|
|
137
141
|
end
|
138
142
|
|
139
143
|
class Report < Base
|
140
|
-
def
|
144
|
+
def with_response
|
141
145
|
if data
|
142
|
-
[200, {"
|
146
|
+
[200, {"Content-Type" => "application/json"}, [data]]
|
143
147
|
else
|
144
|
-
[404, {"
|
148
|
+
[404, {"Content-Type" => "text/plain"}, ["Unable to report on run #{run_id}; no file matching #{"*-report-#{run_id}.json"}"]]
|
145
149
|
end
|
146
150
|
end
|
147
151
|
|
@@ -160,15 +164,15 @@ module Specwrk
|
|
160
164
|
end
|
161
165
|
|
162
166
|
def most_recent_run_report_file
|
163
|
-
@most_recent_run_report_file ||= Dir.glob(File.join(ENV["SPECWRK_OUT"], "*-report
|
167
|
+
@most_recent_run_report_file ||= Dir.glob(File.join(ENV["SPECWRK_OUT"], run_id, "*-report.json")).last
|
164
168
|
end
|
165
169
|
end
|
166
170
|
|
167
171
|
class Shutdown < Base
|
168
|
-
def
|
172
|
+
def with_response
|
169
173
|
interupt! if ENV["SPECWRK_SRV_SINGLE_RUN"]
|
170
174
|
|
171
|
-
[200, {"
|
175
|
+
[200, {"Content-Type" => "text/plain"}, ["✌️"]]
|
172
176
|
end
|
173
177
|
|
174
178
|
def interupt!
|
data/lib/specwrk/web.rb
CHANGED
@@ -1,21 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "specwrk/queue"
|
4
|
+
require "specwrk/filestore"
|
4
5
|
|
5
6
|
module Specwrk
|
6
7
|
class Web
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
WORKERS = Hash.new { |h, key| h[key] = Hash.new { |h, key| h[key] = {} } }
|
11
|
-
|
12
|
-
def self.clear_queues
|
13
|
-
[PENDING_QUEUES, PROCESSING_QUEUES, COMPLETED_QUEUES, WORKERS].each(&:clear)
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.clear_run_queues(run)
|
17
|
-
[PENDING_QUEUES, PROCESSING_QUEUES, COMPLETED_QUEUES, WORKERS].each do |queue|
|
18
|
-
queue.delete(run)
|
8
|
+
class << self
|
9
|
+
def datastore
|
10
|
+
Filestore
|
19
11
|
end
|
20
12
|
end
|
21
13
|
end
|
data/lib/specwrk/worker.rb
CHANGED
@@ -19,6 +19,7 @@ module Specwrk
|
|
19
19
|
@running = true
|
20
20
|
@client = Client.new
|
21
21
|
@executor = Executor.new
|
22
|
+
@all_examples_completed = false
|
22
23
|
@seed_waits = ENV.fetch("SPECWRK_SEED_WAITS", "10").to_i
|
23
24
|
@heartbeat_thread ||= Thread.new do
|
24
25
|
thump
|
@@ -33,6 +34,7 @@ module Specwrk
|
|
33
34
|
|
34
35
|
execute
|
35
36
|
rescue CompletedAllExamplesError
|
37
|
+
@all_examples_completed = true
|
36
38
|
break
|
37
39
|
rescue NoMoreExamplesError
|
38
40
|
# Wait for the other processes (workers) on the same host to finish
|
@@ -102,7 +104,7 @@ module Specwrk
|
|
102
104
|
attr_reader :running, :client, :executor
|
103
105
|
|
104
106
|
def status
|
105
|
-
return 1
|
107
|
+
return 1 if !executor.example_processed && !@all_examples_completed
|
106
108
|
return 1 if executor.failure
|
107
109
|
return 1 if Specwrk.force_quit
|
108
110
|
|
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.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Westendorf
|
@@ -152,12 +152,15 @@ files:
|
|
152
152
|
- config.ru
|
153
153
|
- docker/Dockerfile.server
|
154
154
|
- docker/entrypoint.server.sh
|
155
|
-
-
|
155
|
+
- examples/circleci/config.yml
|
156
|
+
- examples/github/specwrk-multi-node.yml
|
157
|
+
- examples/github/specwrk-single-node.yml
|
156
158
|
- exe/specwrk
|
157
159
|
- lib/specwrk.rb
|
158
160
|
- lib/specwrk/cli.rb
|
159
161
|
- lib/specwrk/cli_reporter.rb
|
160
162
|
- lib/specwrk/client.rb
|
163
|
+
- lib/specwrk/filestore.rb
|
161
164
|
- lib/specwrk/hookable.rb
|
162
165
|
- lib/specwrk/list_examples.rb
|
163
166
|
- lib/specwrk/queue.rb
|