specwrk 0.2.0 → 0.4.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/lib/specwrk/cli.rb +16 -27
- data/lib/specwrk/client.rb +14 -5
- data/lib/specwrk/version.rb +1 -1
- data/lib/specwrk/web/endpoints.rb +3 -1
- data/lib/specwrk/worker/completion_formatter.rb +10 -5
- data/lib/specwrk/worker/executor.rb +7 -0
- data/lib/specwrk/worker.rb +23 -0
- data/lib/specwrk.rb +23 -2
- 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: e3916d92756b30810112db210d2a7ab6627ed250dbddfdc64ba8b0f24248d22c
|
4
|
+
data.tar.gz: eaa7c44ddacb99dde93337de842a420ebe8b12157b9dd90b5a158d75be8bebbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 138e0a9358bcdd82472f3ab03c607ca647393c6d79c818d2c470fbd13670e045853e1355d3ca53634e0e8447058d5cc50676ea8692fb3286afa015346765970a
|
7
|
+
data.tar.gz: 2480031a9d3a080c3ddda3f4a6b5e736c3331356415da510c77ec8525114c65469c34fd0530a60fed49ccb15cef2ca03b438672920118872cc45fa8393edd8a5
|
data/lib/specwrk/cli.rb
CHANGED
@@ -16,7 +16,7 @@ module Specwrk
|
|
16
16
|
extend Hookable
|
17
17
|
|
18
18
|
on_included do |base|
|
19
|
-
base.unique_option :uri, type: :string, default: ENV.fetch("SPECWRK_SRV_URI", "
|
19
|
+
base.unique_option :uri, type: :string, default: ENV.fetch("SPECWRK_SRV_URI", "http://localhost:#{ENV.fetch("SPECWRK_SRV_PORT", "5138")}"), desc: "HTTP URI of the server to pull jobs from. Overrides SPECWRK_SRV_PORT. Default 5138."
|
20
20
|
base.unique_option :key, type: :string, default: ENV.fetch("SPECWRK_SRV_KEY", ""), aliases: ["-k"], desc: "Authentication key clients must use for access. Overrides SPECWRK_SRV_KEY. Default ''."
|
21
21
|
base.unique_option :run, type: :string, default: ENV.fetch("SPECWRK_RUN", "main"), aliases: ["-r"], desc: "The run identifier for this job execution. Overrides SPECWRK_RUN. Default main."
|
22
22
|
base.unique_option :timeout, type: :integer, default: ENV.fetch("SPECWRK_TIMEOUT", "5"), aliases: ["-t"], desc: "The amount of time to wait for the server to respond. Overrides SPECWRK_TIMEOUT. Default 5."
|
@@ -54,7 +54,8 @@ module Specwrk
|
|
54
54
|
|
55
55
|
require "specwrk/worker"
|
56
56
|
|
57
|
-
Specwrk::Worker.run!
|
57
|
+
status = Specwrk::Worker.run!
|
58
|
+
exit(status)
|
58
59
|
end
|
59
60
|
end
|
60
61
|
end
|
@@ -133,13 +134,21 @@ module Specwrk
|
|
133
134
|
self.class.setup(**args)
|
134
135
|
|
135
136
|
start_workers
|
136
|
-
|
137
|
+
wait_for_workers_exit
|
137
138
|
|
138
139
|
require "specwrk/cli_reporter"
|
139
|
-
|
140
|
+
Specwrk::CLIReporter.new.report
|
140
141
|
|
141
142
|
exit(status)
|
142
143
|
end
|
144
|
+
|
145
|
+
def wait_for_workers_exit
|
146
|
+
@exited_pids = Specwrk.wait_for_pids_exit(@worker_pids)
|
147
|
+
end
|
148
|
+
|
149
|
+
def status
|
150
|
+
@exited_pids.value?(1) ? 1 : 0
|
151
|
+
end
|
143
152
|
end
|
144
153
|
|
145
154
|
class Serve < Dry::CLI::Command
|
@@ -194,44 +203,24 @@ module Specwrk
|
|
194
203
|
status "Samples seeded ✓"
|
195
204
|
end
|
196
205
|
|
197
|
-
wait_for_pids_exit([seed_pid])
|
206
|
+
Specwrk.wait_for_pids_exit([seed_pid])
|
198
207
|
|
199
208
|
return if Specwrk.force_quit
|
200
209
|
status "Starting #{worker_count} workers..."
|
201
210
|
start_workers
|
202
211
|
|
203
212
|
status "#{worker_count} workers started ✓\n"
|
204
|
-
wait_for_pids_exit(@worker_pids)
|
213
|
+
Specwrk.wait_for_pids_exit(@worker_pids)
|
205
214
|
|
206
215
|
return if Specwrk.force_quit
|
207
216
|
|
208
217
|
require "specwrk/cli_reporter"
|
209
218
|
status = Specwrk::CLIReporter.new.report
|
210
219
|
|
211
|
-
wait_for_pids_exit([web_pid, seed_pid] + @worker_pids)
|
220
|
+
Specwrk.wait_for_pids_exit([web_pid, seed_pid] + @worker_pids)
|
212
221
|
exit(status)
|
213
222
|
end
|
214
223
|
|
215
|
-
def wait_for_pids_exit(pids)
|
216
|
-
exited_pids = {}
|
217
|
-
|
218
|
-
loop do
|
219
|
-
pids.each do |pid|
|
220
|
-
next if exited_pids.key? pid
|
221
|
-
|
222
|
-
_, status = Process.waitpid2(pid, Process::WNOHANG)
|
223
|
-
exited_pids[pid] = status.exitstatus if status&.exitstatus
|
224
|
-
rescue Errno::ECHILD
|
225
|
-
exited_pids[pid] = 0
|
226
|
-
end
|
227
|
-
|
228
|
-
break if exited_pids.keys.length == pids.length
|
229
|
-
sleep 0.1
|
230
|
-
end
|
231
|
-
|
232
|
-
exited_pids
|
233
|
-
end
|
234
|
-
|
235
224
|
def status(msg)
|
236
225
|
print "\e[2K\r#{msg}"
|
237
226
|
$stdout.flush
|
data/lib/specwrk/client.rb
CHANGED
@@ -4,6 +4,12 @@ require "uri"
|
|
4
4
|
require "net/http"
|
5
5
|
require "json"
|
6
6
|
|
7
|
+
require "specwrk"
|
8
|
+
|
9
|
+
# Some rspec setups might use webmock, which intercepts specwrk server calls
|
10
|
+
# Let's capture the OG HTTP before that happens
|
11
|
+
Specwrk.net_http = Net::HTTP
|
12
|
+
|
7
13
|
module Specwrk
|
8
14
|
class Client
|
9
15
|
def self.connect?
|
@@ -18,7 +24,8 @@ module Specwrk
|
|
18
24
|
|
19
25
|
def self.build_http
|
20
26
|
uri = URI(ENV.fetch("SPECWRK_SRV_URI", "http://localhost:5138"))
|
21
|
-
|
27
|
+
Specwrk.net_http.new(uri.host, uri.port).tap do |http|
|
28
|
+
http.use_ssl = uri.scheme == "https"
|
22
29
|
http.open_timeout = ENV.fetch("SPECWRK_TIMEOUT", "5").to_i
|
23
30
|
http.read_timeout = ENV.fetch("SPECWRK_TIMEOUT", "5").to_i
|
24
31
|
http.keep_alive_timeout = 300
|
@@ -81,6 +88,8 @@ module Specwrk
|
|
81
88
|
case response.code
|
82
89
|
when "200"
|
83
90
|
JSON.parse(response.body, symbolize_names: true)
|
91
|
+
when "204"
|
92
|
+
raise WaitingForSeedError
|
84
93
|
when "404"
|
85
94
|
raise NoMoreExamplesError
|
86
95
|
when "410"
|
@@ -105,28 +114,28 @@ module Specwrk
|
|
105
114
|
private
|
106
115
|
|
107
116
|
def get(path, headers: default_headers, body: nil)
|
108
|
-
request =
|
117
|
+
request = Specwrk.net_http::Get.new(path, headers)
|
109
118
|
request.body = body if body
|
110
119
|
|
111
120
|
make_request(request)
|
112
121
|
end
|
113
122
|
|
114
123
|
def post(path, headers: default_headers, body: nil)
|
115
|
-
request =
|
124
|
+
request = Specwrk.net_http::Post.new(path, headers)
|
116
125
|
request.body = body if body
|
117
126
|
|
118
127
|
make_request(request)
|
119
128
|
end
|
120
129
|
|
121
130
|
def put(path, headers: default_headers, body: nil)
|
122
|
-
request =
|
131
|
+
request = Specwrk.net_http::Put.new(path, headers)
|
123
132
|
request.body = body if body
|
124
133
|
|
125
134
|
make_request(request)
|
126
135
|
end
|
127
136
|
|
128
137
|
def delete(path, headers: default_headers, body: nil)
|
129
|
-
request =
|
138
|
+
request = Specwrk.net_http::Delete.new(path, headers)
|
130
139
|
request.body = body if body
|
131
140
|
|
132
141
|
make_request(request)
|
data/lib/specwrk/version.rb
CHANGED
@@ -94,7 +94,9 @@ module Specwrk
|
|
94
94
|
|
95
95
|
if @examples.length.positive?
|
96
96
|
[200, {"Content-Type" => "application/json"}, [JSON.generate(@examples)]]
|
97
|
-
elsif processing_queue.length.zero?
|
97
|
+
elsif pending_queue.length.zero? && processing_queue.length.zero? && completed_queue.length.zero?
|
98
|
+
[204, {"Content-Type" => "text/plain"}, ["Waiting for sample to be seeded."]]
|
99
|
+
elsif completed_queue.length.positive? && processing_queue.length.zero?
|
98
100
|
[410, {"Content-Type" => "text/plain"}, ["That's a good lad. Run along now and go home."]]
|
99
101
|
else
|
100
102
|
not_found
|
@@ -5,23 +5,28 @@ module Specwrk
|
|
5
5
|
class CompletionFormatter
|
6
6
|
RSpec::Core::Formatters.register self, :stop
|
7
7
|
|
8
|
-
attr_reader :examples
|
8
|
+
attr_reader :examples, :failure
|
9
9
|
|
10
10
|
def initialize
|
11
11
|
@examples = []
|
12
|
+
@failure = false
|
12
13
|
end
|
13
14
|
|
14
15
|
def stop(group_notification)
|
15
16
|
group_notification.notifications.map do |notification|
|
17
|
+
unless failure
|
18
|
+
@failure = notification.example.execution_result.status == :failed
|
19
|
+
end
|
20
|
+
|
16
21
|
examples << {
|
17
22
|
id: notification.example.id,
|
18
23
|
full_description: notification.example.full_description,
|
19
|
-
status: notification.example.
|
24
|
+
status: notification.example.execution_result.status,
|
20
25
|
file_path: notification.example.metadata[:file_path],
|
21
26
|
line_number: notification.example.metadata[:line_number],
|
22
|
-
started_at: notification.example.
|
23
|
-
finished_at: notification.example.
|
24
|
-
run_time: notification.example.
|
27
|
+
started_at: notification.example.execution_result.started_at.iso8601(6),
|
28
|
+
finished_at: notification.example.execution_result.finished_at.iso8601(6),
|
29
|
+
run_time: notification.example.execution_result.run_time
|
25
30
|
}
|
26
31
|
end
|
27
32
|
end
|
@@ -11,6 +11,12 @@ require "specwrk/worker/null_formatter"
|
|
11
11
|
module Specwrk
|
12
12
|
class Worker
|
13
13
|
class Executor
|
14
|
+
attr_reader :example_processed
|
15
|
+
|
16
|
+
def failure
|
17
|
+
completion_formatter.failure
|
18
|
+
end
|
19
|
+
|
14
20
|
def examples
|
15
21
|
completion_formatter.examples
|
16
22
|
end
|
@@ -23,6 +29,7 @@ module Specwrk
|
|
23
29
|
reset!
|
24
30
|
|
25
31
|
example_ids = examples.map { |example| example[:id] }
|
32
|
+
@example_processed ||= true unless example_ids.size.zero? # only ever toggle from nil => true
|
26
33
|
|
27
34
|
options = RSpec::Core::ConfigurationOptions.new rspec_options + example_ids
|
28
35
|
RSpec::Core::Runner.new(options).run($stderr, $stdout)
|
data/lib/specwrk/worker.rb
CHANGED
@@ -36,16 +36,31 @@ module Specwrk
|
|
36
36
|
# This will cause workers to 'hang' until all work has been completed
|
37
37
|
# TODO: break here if all the other worker processes on this host are done executing examples
|
38
38
|
sleep 0.5
|
39
|
+
rescue WaitingForSeedError
|
40
|
+
@seed_wait_count ||= 0
|
41
|
+
@seed_wait_count += 1
|
42
|
+
|
43
|
+
if @seed_wait_count <= 10
|
44
|
+
warn "No examples seeded yet, waiting..."
|
45
|
+
sleep 1
|
46
|
+
else
|
47
|
+
warn "No examples seeded, giving up!"
|
48
|
+
break
|
49
|
+
end
|
39
50
|
end
|
40
51
|
|
41
52
|
executor.final_output.tap(&:rewind).each_line { |line| $stdout.write line }
|
42
53
|
|
43
54
|
@heartbeat_thread.kill
|
44
55
|
client.close
|
56
|
+
|
57
|
+
status
|
45
58
|
rescue Errno::ECONNREFUSED
|
46
59
|
warn "\nServer at #{ENV.fetch("SPECWRK_SRV_URI", "http://localhost:5138")} is refusing connections, exiting..."
|
60
|
+
1
|
47
61
|
rescue Errno::ECONNRESET
|
48
62
|
warn "\nServer at #{ENV.fetch("SPECWRK_SRV_URI", "http://localhost:5138")} stopped responding to connections, exiting..."
|
63
|
+
1
|
49
64
|
end
|
50
65
|
|
51
66
|
def execute
|
@@ -83,6 +98,14 @@ module Specwrk
|
|
83
98
|
|
84
99
|
attr_reader :running, :client, :executor
|
85
100
|
|
101
|
+
def status
|
102
|
+
return 1 unless executor.example_processed
|
103
|
+
return 1 if executor.failure
|
104
|
+
return 1 if Specwrk.force_quit
|
105
|
+
|
106
|
+
0
|
107
|
+
end
|
108
|
+
|
86
109
|
def warn(msg)
|
87
110
|
super("#{ENV.fetch("SPECWRK_ID", "specwrk-worker")}: #{msg}")
|
88
111
|
end
|
data/lib/specwrk.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "specwrk/version"
|
4
4
|
|
5
5
|
module Specwrk
|
6
6
|
Error = Class.new(StandardError)
|
@@ -8,6 +8,7 @@ module Specwrk
|
|
8
8
|
# HTTP Client Errors
|
9
9
|
ClientError = Class.new(Error)
|
10
10
|
UnhandledResponseError = Class.new(ClientError)
|
11
|
+
WaitingForSeedError = Class.new(ClientError)
|
11
12
|
NoMoreExamplesError = Class.new(ClientError)
|
12
13
|
CompletedAllExamplesError = Class.new(ClientError)
|
13
14
|
|
@@ -15,7 +16,27 @@ module Specwrk
|
|
15
16
|
@starting_pid = Process.pid
|
16
17
|
|
17
18
|
class << self
|
18
|
-
attr_accessor :force_quit
|
19
|
+
attr_accessor :force_quit, :net_http
|
19
20
|
attr_reader :starting_pid
|
21
|
+
|
22
|
+
def wait_for_pids_exit(pids)
|
23
|
+
exited_pids = {}
|
24
|
+
|
25
|
+
loop do
|
26
|
+
pids.each do |pid|
|
27
|
+
next if exited_pids.key? pid
|
28
|
+
|
29
|
+
_, status = Process.waitpid2(pid, Process::WNOHANG)
|
30
|
+
exited_pids[pid] = status.exitstatus if status&.exitstatus
|
31
|
+
rescue Errno::ECHILD
|
32
|
+
exited_pids[pid] = 1
|
33
|
+
end
|
34
|
+
|
35
|
+
break if exited_pids.keys.length == pids.length
|
36
|
+
sleep 0.1
|
37
|
+
end
|
38
|
+
|
39
|
+
exited_pids
|
40
|
+
end
|
20
41
|
end
|
21
42
|
end
|