specwrk 0.4.9 → 0.5.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/CHANGELOG.md +28 -0
- data/README.md +1 -1
- data/config.ru +1 -0
- data/docker/Dockerfile.server +1 -1
- data/docker/entrypoint.server.sh +5 -1
- data/lib/specwrk/cli.rb +9 -10
- data/lib/specwrk/client.rb +2 -0
- data/lib/specwrk/version.rb +1 -1
- data/lib/specwrk/web/app.rb +49 -5
- data/lib/specwrk/web/endpoints.rb +12 -19
- data/lib/specwrk/web.rb +8 -1
- data/lib/specwrk/worker.rb +5 -3
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 331e0982599a516c3c862c4a5c33a825c71256f5f95e6d1656c0ad410541df06
|
4
|
+
data.tar.gz: c7d93bf3add6e6de08cad22f8036ef89b01752b0e6dfe42615035d9e301ecff5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9daa901f8a7b7f453079f766d09e47fb621cf85e1cdb6111303e1f8b17cc3091312b2128ca141328f1c8a28216151b991f431cbb4550445ce137349dcb30f3c5
|
7
|
+
data.tar.gz: f0ae841735759d62e9ad6607065ca7bc8747a9d85e8906dea19019af7c63183df26b82cce51bc2a8c453cf4e1bb7d3d0b0c900c917cf689730cdac6028efaae5
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,33 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## v0.4.11
|
4
|
+
|
5
|
+
- Move logic out of CLI methods ([#64](https://github.com/danielwestendorf/specwrk/issues/64)) by @danielwestendorf
|
6
|
+
- Add thruster in front of puma ([#65](https://github.com/danielwestendorf/specwrk/issues/65)) by @danielwestendorf
|
7
|
+
- Revise heartbeats logic ([#66](https://github.com/danielwestendorf/specwrk/issues/66)) by @danielwestendorf
|
8
|
+
|
9
|
+
## v0.4.9
|
10
|
+
|
11
|
+
- Remove single-run env var (it was not what I wanted) by @danielwestendorf
|
12
|
+
|
13
|
+
## v0.4.8
|
14
|
+
|
15
|
+
- Fix `config.ru` by @danielwestendorf
|
16
|
+
|
17
|
+
## v0.4.7
|
18
|
+
|
19
|
+
- Switch to Puma for the Docker image (#59) by @danielwestendorf
|
20
|
+
|
21
|
+
## v0.4.6
|
22
|
+
|
23
|
+
- Nil env var that will prevent start command from completing ([#56](https://github.com/danielwestendorf/specwrk/issues/56)) by @danielwestendorf
|
24
|
+
- Better handling of seed failing ([#57](https://github.com/danielwestendorf/specwrk/issues/57)) by @danielwestendorf
|
25
|
+
- Silence health logging ([#58](https://github.com/danielwestendorf/specwrk/issues/58)) by @danielwestendorf
|
26
|
+
- Add missing CCI caching of `report.json` ([#55](https://github.com/danielwestendorf/specwrk/issues/55)) by @danielwestendorf
|
27
|
+
- Add CircleCI examples ([#50](https://github.com/danielwestendorf/specwrk/issues/50)) by @danielwestendorf
|
28
|
+
- Better GHA Examples ([#49](https://github.com/danielwestendorf/specwrk/issues/49)) by @danielwestendorf
|
29
|
+
- Skip key lookup and rely on the result of `Hash#delete` instead by @danielwestendorf
|
30
|
+
|
3
31
|
## v0.4.5
|
4
32
|
|
5
33
|
- Set ENV var when generating seed examples [#47](https://github.com/danielwestendorf/specwrk/issues/47). by @danielwestendorf
|
data/README.md
CHANGED
@@ -129,7 +129,7 @@ Description:
|
|
129
129
|
Start one or more worker processes
|
130
130
|
|
131
131
|
Options:
|
132
|
-
--id=VALUE # The identifier for this worker.
|
132
|
+
--id=VALUE # The identifier for this worker. Overrides SPECWRK_ID. If none provided one in the format of specwrk-worker-8_RAND_CHARS-COUNT_INDEX will be used
|
133
133
|
--count=VALUE, -c VALUE # The number of worker processes you want to start, default: 1
|
134
134
|
--output=VALUE, -o VALUE # Directory where worker output is stored. Overrides SPECWRK_OUT, default: ".specwrk/"
|
135
135
|
--seed-waits=VALUE, -w VALUE # Number of times the worker will wait for examples to be seeded to the server. 1sec between attempts. Overrides SPECWRK_SEED_WAITS, default: "10"
|
data/config.ru
CHANGED
data/docker/Dockerfile.server
CHANGED
data/docker/entrypoint.server.sh
CHANGED
@@ -1,3 +1,7 @@
|
|
1
1
|
#!/bin/sh
|
2
2
|
|
3
|
-
|
3
|
+
export THRUSTER_HTTP_PORT=${PORT:-5138}
|
4
|
+
export THRUSTER_TARGET_PORT=3000
|
5
|
+
export THRUSTER_HTTP_IDLE_TIMEOUT=${IDLE_TIMEOUT:-305}
|
6
|
+
|
7
|
+
exec thrust puma --workers 0 --bind tcp://127.0.0.1:3000 --threads ${PUMA_THREADS:-1}
|
data/lib/specwrk/cli.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "pathname"
|
4
|
-
require "
|
4
|
+
require "securerandom"
|
5
5
|
|
6
6
|
require "dry/cli"
|
7
7
|
|
@@ -34,18 +34,18 @@ module Specwrk
|
|
34
34
|
extend Hookable
|
35
35
|
|
36
36
|
on_included do |base|
|
37
|
-
base.unique_option :id, type: :string,
|
37
|
+
base.unique_option :id, type: :string, desc: "The identifier for this worker. Overrides SPECWRK_ID. If none provided one in the format of specwrk-worker-8_RAND_CHARS-COUNT_INDEX will be used"
|
38
38
|
base.unique_option :count, type: :integer, default: 1, aliases: ["-c"], desc: "The number of worker processes you want to start"
|
39
39
|
base.unique_option :output, type: :string, default: ENV.fetch("SPECWRK_OUT", ".specwrk/"), aliases: ["-o"], desc: "Directory where worker output is stored. Overrides SPECWRK_OUT"
|
40
40
|
base.unique_option :seed_waits, type: :integer, default: ENV.fetch("SPECWRK_SEED_WAITS", "10"), aliases: ["-w"], desc: "Number of times the worker will wait for examples to be seeded to the server. 1sec between attempts. Overrides SPECWRK_SEED_WAITS"
|
41
41
|
end
|
42
42
|
|
43
|
-
on_setup do |
|
44
|
-
ENV["SPECWRK_ID"]
|
43
|
+
on_setup do |count:, output:, seed_waits:, id: "specwrk-worker-#{SecureRandom.uuid[0, 8]}", **|
|
44
|
+
ENV["SPECWRK_ID"] ||= id # Unique default. Don't override the ENV value here
|
45
|
+
|
45
46
|
ENV["SPECWRK_COUNT"] = count.to_s
|
46
47
|
ENV["SPECWRK_SEED_WAITS"] = seed_waits.to_s
|
47
48
|
ENV["SPECWRK_OUT"] = Pathname.new(output).expand_path(Dir.pwd).to_s
|
48
|
-
FileUtils.mkdir_p(ENV["SPECWRK_OUT"])
|
49
49
|
end
|
50
50
|
|
51
51
|
def start_workers
|
@@ -80,16 +80,13 @@ module Specwrk
|
|
80
80
|
base.unique_option :verbose, type: :boolean, default: false, desc: "Run in verbose mode. Default false."
|
81
81
|
end
|
82
82
|
|
83
|
-
on_setup do |port:, bind:, output:, key:,
|
83
|
+
on_setup do |port:, bind:, output:, key:, group_by:, verbose:, **|
|
84
84
|
ENV["SPECWRK_OUT"] = Pathname.new(output).expand_path(Dir.pwd).to_s
|
85
|
-
|
85
|
+
ENV["SPECWRK_SRV_VERBOSE"] = "1" if verbose
|
86
86
|
|
87
|
-
ENV["SPECWRK_SRV_LOG"] ||= Pathname.new(File.join(ENV["SPECWRK_OUT"], "server.log")).to_s if output && !verbose
|
88
|
-
ENV["SPECWRK_SRV_OUTPUT"] ||= Pathname.new(File.join(ENV["SPECWRK_OUT"], "report.json")).expand_path(Dir.pwd).to_s if output
|
89
87
|
ENV["SPECWRK_SRV_PORT"] = port
|
90
88
|
ENV["SPECWRK_SRV_BIND"] = bind
|
91
89
|
ENV["SPECWRK_SRV_KEY"] = key
|
92
|
-
ENV["SPECWRK_SRV_SINGLE_SEED_PER_RUN"] = "1" if single_seed_per_run
|
93
90
|
ENV["SPECWRK_SRV_GROUP_BY"] = group_by
|
94
91
|
end
|
95
92
|
end
|
@@ -120,6 +117,8 @@ module Specwrk
|
|
120
117
|
|
121
118
|
Client.wait_for_server!
|
122
119
|
Client.new.seed(examples)
|
120
|
+
file_count = examples.group_by { |e| e[:file_path] }.keys.size
|
121
|
+
puts "🌱 Seeded #{examples.size} examples across #{file_count} files"
|
123
122
|
rescue Errno::ECONNREFUSED
|
124
123
|
puts "Server at #{ENV.fetch("SPECWRK_SRV_URI", "http://localhost:5138")} is refusing connections, exiting...#{ENV["SPECWRK_FLUSH_DELIMINATOR"]}"
|
125
124
|
exit 1
|
data/lib/specwrk/client.rb
CHANGED
@@ -150,7 +150,9 @@ module Specwrk
|
|
150
150
|
|
151
151
|
def default_headers
|
152
152
|
@default_headers ||= {}.tap do |h|
|
153
|
+
h["User-Agent"] = "Specwrk/#{VERSION}"
|
153
154
|
h["Authorization"] = "Bearer #{ENV["SPECWRK_SRV_KEY"]}" if ENV["SPECWRK_SRV_KEY"]
|
155
|
+
h["X-Specwrk-Id"] = ENV.fetch("SPECWRK_ID", "specwrk-client")
|
154
156
|
h["X-Specwrk-Run"] = ENV["SPECWRK_RUN"] if ENV["SPECWRK_RUN"]
|
155
157
|
h["Content-Type"] = "application/json"
|
156
158
|
end
|
data/lib/specwrk/version.rb
CHANGED
data/lib/specwrk/web/app.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "pathname"
|
4
|
+
require "fileutils"
|
5
|
+
|
3
6
|
require "webrick"
|
4
7
|
require "rack"
|
5
8
|
|
@@ -10,6 +13,7 @@ rescue LoadError
|
|
10
13
|
require "rack/handler/webrick"
|
11
14
|
end
|
12
15
|
|
16
|
+
require "specwrk/web"
|
13
17
|
require "specwrk/web/logger"
|
14
18
|
require "specwrk/web/auth"
|
15
19
|
require "specwrk/web/endpoints"
|
@@ -17,13 +21,13 @@ require "specwrk/web/endpoints"
|
|
17
21
|
module Specwrk
|
18
22
|
class Web
|
19
23
|
class App
|
24
|
+
REAP_INTERVAL = 330 # HTTP connection timeout + some buffer
|
25
|
+
|
20
26
|
class << self
|
21
27
|
def run!
|
22
28
|
Process.setproctitle "specwrk-server"
|
23
29
|
|
24
|
-
|
25
|
-
$stdout.reopen(ENV["SPECWRK_SRV_LOG"], "w")
|
26
|
-
end
|
30
|
+
setup!
|
27
31
|
|
28
32
|
server_opts = {
|
29
33
|
Port: ENV.fetch("SPECWRK_SRV_PORT", "5138").to_i,
|
@@ -46,16 +50,36 @@ module Specwrk
|
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|
53
|
+
def setup!
|
54
|
+
if ENV["SPECWRK_OUT"]
|
55
|
+
|
56
|
+
FileUtils.mkdir_p(ENV["SPECWRK_OUT"])
|
57
|
+
ENV["SPECWRK_SRV_LOG"] ||= Pathname.new(File.join(ENV["SPECWRK_OUT"], "server.log")).to_s unless ENV["SPECWRK_SRV_VERBOSE"]
|
58
|
+
ENV["SPECWRK_SRV_OUTPUT"] ||= Pathname.new(File.join(ENV["SPECWRK_OUT"], "report.json")).expand_path(Dir.pwd).to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
if ENV["SPECWRK_SRV_LOG"]
|
62
|
+
$stdout.reopen(ENV["SPECWRK_SRV_LOG"], "w")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
49
66
|
def rackup
|
50
67
|
Rack::Builder.new do
|
51
|
-
|
52
|
-
|
68
|
+
if ENV["SPECWRK_SRV_VERBOSE"]
|
69
|
+
use Rack::Runtime
|
70
|
+
use Specwrk::Web::Logger, $stdout, %w[/health]
|
71
|
+
end
|
72
|
+
|
53
73
|
use Specwrk::Web::Auth, %w[/health] # global auth check
|
54
74
|
run Specwrk::Web::App.new # your router
|
55
75
|
end
|
56
76
|
end
|
57
77
|
end
|
58
78
|
|
79
|
+
def initialize
|
80
|
+
@reaper_thread = Thread.new { reaper } unless ENV["SPECWRK_SRV_SINGLE_RUN"]
|
81
|
+
end
|
82
|
+
|
59
83
|
def call(env)
|
60
84
|
env[:request] ||= Rack::Request.new(env)
|
61
85
|
|
@@ -84,6 +108,26 @@ module Specwrk
|
|
84
108
|
Endpoints::NotFound
|
85
109
|
end
|
86
110
|
end
|
111
|
+
|
112
|
+
def reaper
|
113
|
+
until Specwrk.force_quit
|
114
|
+
sleep REAP_INTERVAL
|
115
|
+
|
116
|
+
reap
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def reap
|
121
|
+
Web::WORKERS.each do |run, workers|
|
122
|
+
most_recent_last_seen_at = workers.map { |id, worker| worker[:last_seen_at] }.max
|
123
|
+
next unless most_recent_last_seen_at
|
124
|
+
|
125
|
+
# Don't consider runs which aren't at least REAP_INTERVAL sec stale
|
126
|
+
if most_recent_last_seen_at < Time.now - REAP_INTERVAL
|
127
|
+
Web.clear_run_queues(run)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
87
131
|
end
|
88
132
|
end
|
89
133
|
end
|
@@ -8,6 +8,9 @@ module Specwrk
|
|
8
8
|
class Base
|
9
9
|
def initialize(request)
|
10
10
|
@request = request
|
11
|
+
|
12
|
+
worker[:first_seen_at] ||= Time.now
|
13
|
+
worker[:last_seen_at] = Time.now
|
11
14
|
end
|
12
15
|
|
13
16
|
def response
|
@@ -45,6 +48,14 @@ module Specwrk
|
|
45
48
|
def completed_queue
|
46
49
|
Web::COMPLETED_QUEUES[request.get_header("HTTP_X_SPECWRK_RUN")]
|
47
50
|
end
|
51
|
+
|
52
|
+
def workers
|
53
|
+
Web::WORKERS[request.get_header("HTTP_X_SPECWRK_RUN")]
|
54
|
+
end
|
55
|
+
|
56
|
+
def worker
|
57
|
+
workers[request.get_header("HTTP_X_SPECWRK_ID")]
|
58
|
+
end
|
48
59
|
end
|
49
60
|
|
50
61
|
# Base default response is 404
|
@@ -135,25 +146,7 @@ module Specwrk
|
|
135
146
|
|
136
147
|
class Shutdown < Base
|
137
148
|
def response
|
138
|
-
if ENV["SPECWRK_SRV_SINGLE_RUN"]
|
139
|
-
interupt!
|
140
|
-
elsif processing_queue.length.positive?
|
141
|
-
# Push any processing jobs back into the pending queue
|
142
|
-
processing_queue.synchronize do |processing_queue_hash|
|
143
|
-
pending_queue.synchronize do |pending_queue_hash|
|
144
|
-
processing_queue_hash.each do |id, example|
|
145
|
-
pending_queue_hash[id] = example
|
146
|
-
processing_queue_hash.delete(id)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
elsif processing_queue.length.zero? && pending_queue.length.zero?
|
152
|
-
# All done, we can clear the completed queue
|
153
|
-
completed_queue.clear
|
154
|
-
end
|
155
|
-
|
156
|
-
# TODO: clear any zombie queues
|
149
|
+
interupt! if ENV["SPECWRK_SRV_SINGLE_RUN"]
|
157
150
|
|
158
151
|
[200, {"Content-Type" => "text/plain"}, ["✌️"]]
|
159
152
|
end
|
data/lib/specwrk/web.rb
CHANGED
@@ -7,9 +7,16 @@ module Specwrk
|
|
7
7
|
PENDING_QUEUES = Queue.new { |h, key| h[key] = PendingQueue.new.tap { |q| q.previous_run_times_file = ENV["SPECWRK_SRV_OUTPUT"] } }
|
8
8
|
PROCESSING_QUEUES = Queue.new { |h, key| h[key] = Queue.new }
|
9
9
|
COMPLETED_QUEUES = Queue.new { |h, key| h[key] = CompletedQueue.new }
|
10
|
+
WORKERS = Hash.new { |h, key| h[key] = Hash.new { |h, key| h[key] = {} } }
|
10
11
|
|
11
12
|
def self.clear_queues
|
12
|
-
[PENDING_QUEUES, PROCESSING_QUEUES, COMPLETED_QUEUES].each(&:clear)
|
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)
|
19
|
+
end
|
13
20
|
end
|
14
21
|
end
|
15
22
|
end
|
data/lib/specwrk/worker.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "stringio"
|
4
|
+
require "fileutils"
|
4
5
|
|
5
6
|
require "specwrk/client"
|
6
7
|
require "specwrk/worker/executor"
|
@@ -13,6 +14,7 @@ module Specwrk
|
|
13
14
|
|
14
15
|
def initialize
|
15
16
|
Process.setproctitle ENV.fetch("SPECWRK_ID", "specwrk-worker")
|
17
|
+
FileUtils.mkdir_p(ENV["SPECWRK_OUT"]) if ENV["SPECWRK_OUT"]
|
16
18
|
|
17
19
|
@running = true
|
18
20
|
@client = Client.new
|
@@ -85,13 +87,13 @@ module Specwrk
|
|
85
87
|
|
86
88
|
def thump
|
87
89
|
while running && !Specwrk.force_quit
|
90
|
+
sleep 10
|
91
|
+
|
88
92
|
begin
|
89
|
-
client.heartbeat if client.last_request_at.nil? || client.last_request_at < Time.now -
|
93
|
+
client.heartbeat if client.last_request_at.nil? || client.last_request_at < Time.now - 30
|
90
94
|
rescue
|
91
95
|
warn "Heartbeat failed!"
|
92
96
|
end
|
93
|
-
|
94
|
-
sleep 1
|
95
97
|
end
|
96
98
|
end
|
97
99
|
|