specwrk 0.17.1 → 0.19.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 +146 -115
- data/lib/specwrk/ipc.rb +14 -4
- data/lib/specwrk/store/base_adapter.rb +8 -0
- data/lib/specwrk/store/file_adapter.rb +14 -10
- data/lib/specwrk/store/serializer.rb +70 -0
- data/lib/specwrk/version.rb +1 -1
- data/lib/specwrk/web/endpoints/complete_and_pop.rb +3 -2
- data/lib/specwrk/web/endpoints/popable.rb +7 -2
- metadata +16 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 690eaebf0ec2f7621284bc0ce56d6613001bdffc6297651b9d4f6da7a439971b
|
|
4
|
+
data.tar.gz: 8d3104699becc543cbe5aaa6f2b6537d931d34d0e931d720b254262d5b2889a9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 52210c132192267d027e6756e9b34e7871544a968e57627f433d7f5bcef5f8211cd1a548721f6903269397a07c547f65bbd89816a0c4e576e3f9e22a958e777f
|
|
7
|
+
data.tar.gz: 43132d8f86f99fa6932f479e3bcb0a1c9d224dcd62a2f6b59f18910c9a2ef6d24560d1ad48d35d1c5ce4c7df483ed0d42ebe05663c4d6a94b3899bb0cdb9cda2
|
data/lib/specwrk/cli.rb
CHANGED
|
@@ -2,16 +2,90 @@
|
|
|
2
2
|
|
|
3
3
|
require "pathname"
|
|
4
4
|
require "securerandom"
|
|
5
|
+
require "json"
|
|
5
6
|
|
|
6
7
|
require "dry/cli"
|
|
7
8
|
|
|
8
9
|
require "specwrk"
|
|
9
10
|
require "specwrk/hookable"
|
|
11
|
+
require "specwrk/store"
|
|
10
12
|
|
|
11
13
|
module Specwrk
|
|
12
14
|
module CLI
|
|
13
15
|
extend Dry::CLI::Registry
|
|
14
16
|
|
|
17
|
+
module WorkerProcesses
|
|
18
|
+
WORKER_INIT_SCRIPT = <<~RUBY
|
|
19
|
+
writer = IO.for_fd(Integer(ENV.fetch("SPECWRK_FINAL_FD")))
|
|
20
|
+
$final_output = writer # standard:disable Style/GlobalVars
|
|
21
|
+
$final_output.sync = true # standard:disable Style/GlobalVars
|
|
22
|
+
$stdout.sync = true
|
|
23
|
+
$stderr.sync = true
|
|
24
|
+
|
|
25
|
+
require "specwrk/worker"
|
|
26
|
+
|
|
27
|
+
trap("INT") do
|
|
28
|
+
RSpec.world.wants_to_quit = true if defined?(RSpec)
|
|
29
|
+
exit(1) if Specwrk.force_quit
|
|
30
|
+
Specwrk.force_quit = true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
status = Specwrk::Worker.run!
|
|
34
|
+
$final_output.close # standard:disable Style/GlobalVars
|
|
35
|
+
exit(status)
|
|
36
|
+
RUBY
|
|
37
|
+
|
|
38
|
+
def start_workers
|
|
39
|
+
@final_outputs = []
|
|
40
|
+
@worker_pids = worker_count.times.map do |i|
|
|
41
|
+
reader, writer = IO.pipe
|
|
42
|
+
@final_outputs << reader
|
|
43
|
+
|
|
44
|
+
env = worker_env_for(i + 1).merge(
|
|
45
|
+
"SPECWRK_FINAL_FD" => writer.fileno.to_s
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
Process.spawn(
|
|
49
|
+
env, RbConfig.ruby, "-e", WORKER_INIT_SCRIPT,
|
|
50
|
+
writer.fileno => writer,
|
|
51
|
+
:in => :close,
|
|
52
|
+
:close_others => false
|
|
53
|
+
).tap { writer.close }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def drain_outputs
|
|
58
|
+
@final_outputs.each do |reader|
|
|
59
|
+
reader.each_line { |line| $stdout.print line }
|
|
60
|
+
reader.close
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def worker_count
|
|
65
|
+
@worker_count ||= [1, ENV["SPECWRK_COUNT"].to_i].max
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def worker_env_for(idx)
|
|
69
|
+
{
|
|
70
|
+
"TEST_ENV_NUMBER" => idx.to_s,
|
|
71
|
+
"SPECWRK_FORKED" => idx.to_s,
|
|
72
|
+
"SPECWRK_ID" => "#{ENV.fetch("SPECWRK_ID", "specwrk-worker")}-#{idx}"
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
module PortDiscoverable
|
|
78
|
+
def find_open_port
|
|
79
|
+
require "socket"
|
|
80
|
+
|
|
81
|
+
server = TCPServer.new("127.0.0.1", 0)
|
|
82
|
+
port = server.addr[1]
|
|
83
|
+
server.close
|
|
84
|
+
|
|
85
|
+
port
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
15
89
|
module Clientable
|
|
16
90
|
extend Hookable
|
|
17
91
|
|
|
@@ -34,6 +108,7 @@ module Specwrk
|
|
|
34
108
|
|
|
35
109
|
module Workable
|
|
36
110
|
extend Hookable
|
|
111
|
+
include WorkerProcesses
|
|
37
112
|
|
|
38
113
|
on_included do |base|
|
|
39
114
|
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"
|
|
@@ -49,44 +124,11 @@ module Specwrk
|
|
|
49
124
|
ENV["SPECWRK_SEED_WAITS"] = seed_waits.to_s
|
|
50
125
|
ENV["SPECWRK_OUT"] = Pathname.new(output).expand_path(Dir.pwd).to_s
|
|
51
126
|
end
|
|
52
|
-
|
|
53
|
-
def start_workers
|
|
54
|
-
@final_outputs = []
|
|
55
|
-
@worker_pids = worker_count.times.map do |i|
|
|
56
|
-
reader, writer = IO.pipe
|
|
57
|
-
@final_outputs << reader
|
|
58
|
-
|
|
59
|
-
Process.fork do
|
|
60
|
-
ENV["TEST_ENV_NUMBER"] = ENV["SPECWRK_FORKED"] = (i + 1).to_s
|
|
61
|
-
ENV["SPECWRK_ID"] = ENV["SPECWRK_ID"] + "-#{i + 1}"
|
|
62
|
-
|
|
63
|
-
$final_output = writer # standard:disable Style/GlobalVars
|
|
64
|
-
$final_output.sync = true # standard:disable Style/GlobalVars
|
|
65
|
-
reader.close
|
|
66
|
-
|
|
67
|
-
require "specwrk/worker"
|
|
68
|
-
|
|
69
|
-
status = Specwrk::Worker.run!
|
|
70
|
-
$final_output.close # standard:disable Style/GlobalVars
|
|
71
|
-
exit(status)
|
|
72
|
-
end.tap { writer.close }
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def drain_outputs
|
|
77
|
-
@final_outputs.each do |reader|
|
|
78
|
-
reader.each_line { |line| $stdout.print line }
|
|
79
|
-
reader.close
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def worker_count
|
|
84
|
-
@worker_count ||= [1, ENV["SPECWRK_COUNT"].to_i].max
|
|
85
|
-
end
|
|
86
127
|
end
|
|
87
128
|
|
|
88
129
|
module Servable
|
|
89
130
|
extend Hookable
|
|
131
|
+
include PortDiscoverable
|
|
90
132
|
|
|
91
133
|
on_included do |base|
|
|
92
134
|
base.unique_option :port, type: :integer, default: ENV.fetch("SPECWRK_SRV_PORT", "5138"), aliases: ["-p"], desc: "Server port. Overrides SPECWRK_SRV_PORT"
|
|
@@ -94,30 +136,22 @@ module Specwrk
|
|
|
94
136
|
base.unique_option :key, type: :string, aliases: ["-k"], default: ENV.fetch("SPECWRK_SRV_KEY", ""), desc: "Authentication key clients must use for access. Overrides SPECWRK_SRV_KEY"
|
|
95
137
|
base.unique_option :output, type: :string, default: ENV.fetch("SPECWRK_OUT", ".specwrk/"), aliases: ["-o"], desc: "Directory where worker or server output is stored. Overrides SPECWRK_OUT"
|
|
96
138
|
base.unique_option :store_uri, type: :string, desc: "Directory where server state is stored. Required for multi-node or multi-process servers."
|
|
97
|
-
base.unique_option :group_by, values: %w[file timings], default: ENV.fetch("
|
|
139
|
+
base.unique_option :group_by, values: %w[file timings], default: ENV.fetch("SPECWRK_SRV_GROUP_BY", "timings"), desc: "How examples will be grouped for workers; fallback to file if no timings are found. Overrides SPECWRK_SRV_GROUP_BY"
|
|
140
|
+
base.unique_option :store_serializer, values: %w[json msgpack], default: ENV.fetch("SPECWRK_STORE_SERIALIZER", "json"), desc: "Serializer to use for store payloads. Overrides SPECWRK_STORE_SERIALIZER"
|
|
98
141
|
base.unique_option :verbose, type: :boolean, default: false, desc: "Run in verbose mode"
|
|
99
142
|
end
|
|
100
143
|
|
|
101
|
-
on_setup do |port:, bind:, output:, key:, group_by:, verbose:, **opts|
|
|
144
|
+
on_setup do |port:, bind:, output:, key:, group_by:, verbose:, store_serializer:, **opts|
|
|
102
145
|
ENV["SPECWRK_OUT"] = Pathname.new(output).expand_path(Dir.pwd).to_s
|
|
103
146
|
ENV["SPECWRK_SRV_STORE_URI"] = opts[:store_uri] if opts.key? :store_uri
|
|
104
147
|
ENV["SPECWRK_SRV_VERBOSE"] = "1" if verbose
|
|
148
|
+
ENV["SPECWRK_STORE_SERIALIZER"] = store_serializer
|
|
105
149
|
|
|
106
150
|
ENV["SPECWRK_SRV_PORT"] = port
|
|
107
151
|
ENV["SPECWRK_SRV_BIND"] = bind
|
|
108
152
|
ENV["SPECWRK_SRV_KEY"] = key
|
|
109
153
|
ENV["SPECWRK_SRV_GROUP_BY"] = group_by
|
|
110
154
|
end
|
|
111
|
-
|
|
112
|
-
def find_open_port
|
|
113
|
-
require "socket"
|
|
114
|
-
|
|
115
|
-
server = TCPServer.new("127.0.0.1", 0)
|
|
116
|
-
port = server.addr[1]
|
|
117
|
-
server.close
|
|
118
|
-
|
|
119
|
-
port
|
|
120
|
-
end
|
|
121
155
|
end
|
|
122
156
|
|
|
123
157
|
class Version < Dry::CLI::Command
|
|
@@ -210,6 +244,31 @@ module Specwrk
|
|
|
210
244
|
include Workable
|
|
211
245
|
include Servable
|
|
212
246
|
|
|
247
|
+
SEED_INIT_SCRIPT = <<~'RUBY'
|
|
248
|
+
require "json"
|
|
249
|
+
require "specwrk/list_examples"
|
|
250
|
+
require "specwrk/client"
|
|
251
|
+
|
|
252
|
+
def status(msg)
|
|
253
|
+
print "\e[2K\r#{msg}"
|
|
254
|
+
$stdout.flush
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
dir = JSON.parse(ENV.fetch("SPECWRK_SEED_DIRS"))
|
|
258
|
+
max_retries = Integer(ENV.fetch("SPECWRK_MAX_RETRIES", "0"))
|
|
259
|
+
|
|
260
|
+
examples = Specwrk::ListExamples.new(dir).examples
|
|
261
|
+
|
|
262
|
+
status "Waiting for server to respond..."
|
|
263
|
+
Specwrk::Client.wait_for_server!
|
|
264
|
+
status "Server responding ✓"
|
|
265
|
+
status "Seeding #{examples.length} examples..."
|
|
266
|
+
Specwrk::Client.new.seed(examples, max_retries)
|
|
267
|
+
file_count = examples.group_by { |e| e[:file_path] }.keys.size
|
|
268
|
+
status "🌱 Seeded #{examples.size} examples across #{file_count} files"
|
|
269
|
+
exit(1) if examples.size.zero?
|
|
270
|
+
RUBY
|
|
271
|
+
|
|
213
272
|
desc "Start a server and workers, monitor until complete"
|
|
214
273
|
option :max_retries, default: 0, desc: "Number of times an example will be re-run should it fail"
|
|
215
274
|
argument :dir, type: :array, required: false, desc: "Relative spec directory to run against, default: spec/"
|
|
@@ -238,23 +297,7 @@ module Specwrk
|
|
|
238
297
|
end
|
|
239
298
|
|
|
240
299
|
return if Specwrk.force_quit
|
|
241
|
-
seed_pid =
|
|
242
|
-
require "specwrk/list_examples"
|
|
243
|
-
require "specwrk/client"
|
|
244
|
-
|
|
245
|
-
ENV["SPECWRK_FORKED"] = "1"
|
|
246
|
-
ENV["SPECWRK_SEED"] = "1"
|
|
247
|
-
examples = ListExamples.new(dir).examples
|
|
248
|
-
|
|
249
|
-
status "Waiting for server to respond..."
|
|
250
|
-
Client.wait_for_server!
|
|
251
|
-
status "Server responding ✓"
|
|
252
|
-
status "Seeding #{examples.length} examples..."
|
|
253
|
-
Client.new.seed(examples, max_retries)
|
|
254
|
-
file_count = examples.group_by { |e| e[:file_path] }.keys.size
|
|
255
|
-
status "🌱 Seeded #{examples.size} examples across #{file_count} files"
|
|
256
|
-
exit(1) if examples.size.zero?
|
|
257
|
-
end
|
|
300
|
+
seed_pid = spawn_seed_process(dir, max_retries)
|
|
258
301
|
|
|
259
302
|
if Specwrk.wait_for_pids_exit([seed_pid]).value?(1)
|
|
260
303
|
status "Seeding examples failed, exiting."
|
|
@@ -279,6 +322,19 @@ module Specwrk
|
|
|
279
322
|
exit(status)
|
|
280
323
|
end
|
|
281
324
|
|
|
325
|
+
def spawn_seed_process(dir, max_retries)
|
|
326
|
+
Process.spawn(
|
|
327
|
+
{
|
|
328
|
+
"SPECWRK_FORKED" => "1",
|
|
329
|
+
"SPECWRK_SEED" => "1",
|
|
330
|
+
"SPECWRK_SEED_DIRS" => JSON.dump(dir),
|
|
331
|
+
"SPECWRK_MAX_RETRIES" => max_retries.to_s
|
|
332
|
+
},
|
|
333
|
+
RbConfig.ruby, "-e", SEED_INIT_SCRIPT,
|
|
334
|
+
close_others: false
|
|
335
|
+
)
|
|
336
|
+
end
|
|
337
|
+
|
|
282
338
|
def status(msg)
|
|
283
339
|
print "\e[2K\r#{msg}"
|
|
284
340
|
$stdout.flush
|
|
@@ -286,6 +342,20 @@ module Specwrk
|
|
|
286
342
|
end
|
|
287
343
|
|
|
288
344
|
class Watch < Dry::CLI::Command
|
|
345
|
+
include WorkerProcesses
|
|
346
|
+
include PortDiscoverable
|
|
347
|
+
|
|
348
|
+
SEED_LOOP_INIT_SCRIPT = <<~RUBY
|
|
349
|
+
require "specwrk/ipc"
|
|
350
|
+
require "specwrk/seed_loop"
|
|
351
|
+
|
|
352
|
+
parent_pid = Integer(ENV.fetch("SPECWRK_IPC_PARENT_PID"))
|
|
353
|
+
fd = Integer(ENV.fetch("SPECWRK_IPC_FD"))
|
|
354
|
+
ipc = Specwrk::IPC.from_child_fd(fd, parent_pid: parent_pid)
|
|
355
|
+
|
|
356
|
+
Specwrk::SeedLoop.loop!(ipc)
|
|
357
|
+
RUBY
|
|
358
|
+
|
|
289
359
|
desc "Start a server and workers, watch for file changes in the current directory, and execute specs"
|
|
290
360
|
option :watchfile, type: :string, default: "Specwrk.watchfile.rb", desc: "Path to watchfile configuration"
|
|
291
361
|
option :count, type: :integer, default: 1, aliases: ["-c"], desc: "The number of worker processes you want to start"
|
|
@@ -388,14 +458,19 @@ module Specwrk
|
|
|
388
458
|
@seed_pid ||= begin
|
|
389
459
|
ipc # must be initialized in the parent process
|
|
390
460
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
461
|
+
ipc.child_socket.close_on_exec = false
|
|
462
|
+
|
|
463
|
+
Process.spawn(
|
|
464
|
+
{
|
|
465
|
+
"SPECWRK_FORKED" => "1",
|
|
466
|
+
"SPECWRK_SEED" => "1",
|
|
467
|
+
"SPECWRK_IPC_FD" => ipc.child_socket.fileno.to_s,
|
|
468
|
+
"SPECWRK_IPC_PARENT_PID" => Process.pid.to_s
|
|
469
|
+
},
|
|
470
|
+
RbConfig.ruby, "-e", SEED_LOOP_INIT_SCRIPT,
|
|
471
|
+
ipc.child_socket => ipc.child_socket,
|
|
472
|
+
:close_others => false
|
|
473
|
+
)
|
|
399
474
|
end
|
|
400
475
|
end
|
|
401
476
|
|
|
@@ -421,50 +496,6 @@ module Specwrk
|
|
|
421
496
|
print "\e[2K\r#{msg}"
|
|
422
497
|
$stdout.flush
|
|
423
498
|
end
|
|
424
|
-
|
|
425
|
-
def start_workers
|
|
426
|
-
@final_outputs = []
|
|
427
|
-
@worker_pids = worker_count.times.map do |i|
|
|
428
|
-
reader, writer = IO.pipe
|
|
429
|
-
@final_outputs << reader
|
|
430
|
-
|
|
431
|
-
Process.fork do
|
|
432
|
-
ENV["TEST_ENV_NUMBER"] = ENV["SPECWRK_FORKED"] = (i + 1).to_s
|
|
433
|
-
ENV["SPECWRK_ID"] = "specwrk-worker-#{i + 1}"
|
|
434
|
-
|
|
435
|
-
$final_output = writer # standard:disable Style/GlobalVars
|
|
436
|
-
$final_output.sync = true # standard:disable Style/GlobalVars
|
|
437
|
-
reader.close
|
|
438
|
-
|
|
439
|
-
require "specwrk/worker"
|
|
440
|
-
|
|
441
|
-
status = Specwrk::Worker.run!
|
|
442
|
-
$final_output.close # standard:disable Style/GlobalVars
|
|
443
|
-
exit(status)
|
|
444
|
-
end.tap { writer.close }
|
|
445
|
-
end
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
def drain_outputs
|
|
449
|
-
@final_outputs.each do |reader|
|
|
450
|
-
reader.each_line { |line| $stdout.print line }
|
|
451
|
-
reader.close
|
|
452
|
-
end
|
|
453
|
-
end
|
|
454
|
-
|
|
455
|
-
def worker_count
|
|
456
|
-
@worker_count ||= [1, ENV["SPECWRK_COUNT"].to_i].max
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
def find_open_port
|
|
460
|
-
require "socket"
|
|
461
|
-
|
|
462
|
-
server = TCPServer.new("127.0.0.1", 0)
|
|
463
|
-
port = server.addr[1]
|
|
464
|
-
server.close
|
|
465
|
-
|
|
466
|
-
port
|
|
467
|
-
end
|
|
468
499
|
end
|
|
469
500
|
|
|
470
501
|
register "version", Version, aliases: ["v", "-v", "--version"]
|
data/lib/specwrk/ipc.rb
CHANGED
|
@@ -2,10 +2,20 @@ require "socket"
|
|
|
2
2
|
|
|
3
3
|
module Specwrk
|
|
4
4
|
class IPC
|
|
5
|
-
|
|
6
|
-
@parent_pid = Process.pid
|
|
5
|
+
attr_reader :parent_socket, :child_socket
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
def self.from_child_fd(fd, parent_pid:)
|
|
8
|
+
new(
|
|
9
|
+
parent_pid: parent_pid,
|
|
10
|
+
child_socket: UNIXSocket.for_fd(fd)
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(parent_pid: Process.pid, parent_socket: nil, child_socket: nil)
|
|
15
|
+
@parent_pid = parent_pid
|
|
16
|
+
|
|
17
|
+
@parent_socket, @child_socket = parent_socket, child_socket
|
|
18
|
+
@parent_socket, @child_socket = UNIXSocket.pair if @parent_socket.nil? && @child_socket.nil?
|
|
9
19
|
end
|
|
10
20
|
|
|
11
21
|
def write(msg)
|
|
@@ -23,7 +33,7 @@ module Specwrk
|
|
|
23
33
|
|
|
24
34
|
private
|
|
25
35
|
|
|
26
|
-
attr_reader :parent_pid
|
|
36
|
+
attr_reader :parent_pid
|
|
27
37
|
|
|
28
38
|
def socket
|
|
29
39
|
child? ? child_socket : parent_socket
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "base64"
|
|
5
5
|
require "securerandom"
|
|
6
|
+
require "fileutils"
|
|
6
7
|
|
|
7
8
|
require "specwrk/store/base_adapter"
|
|
9
|
+
require "specwrk/store/serializer"
|
|
8
10
|
|
|
9
11
|
module Specwrk
|
|
10
12
|
class Store
|
|
11
13
|
class FileAdapter < BaseAdapter
|
|
12
|
-
EXT = ".wrk.json"
|
|
13
|
-
|
|
14
14
|
@work_queue = Queue.new
|
|
15
15
|
@threads = []
|
|
16
16
|
|
|
@@ -45,13 +45,17 @@ module Specwrk
|
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
|
+
|
|
49
|
+
def ext
|
|
50
|
+
".wrk.#{serializer.adapter_name}"
|
|
51
|
+
end
|
|
48
52
|
end
|
|
49
53
|
|
|
50
54
|
def [](key)
|
|
51
55
|
content = read(key.to_s)
|
|
52
56
|
return unless content
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
self.class.serializer.load(content)
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
def []=(key, value)
|
|
@@ -60,7 +64,7 @@ module Specwrk
|
|
|
60
64
|
delete(key_string)
|
|
61
65
|
else
|
|
62
66
|
filename = filename_for_key(key_string)
|
|
63
|
-
write(filename,
|
|
67
|
+
write(filename, self.class.serializer.dump(value))
|
|
64
68
|
end
|
|
65
69
|
end
|
|
66
70
|
|
|
@@ -99,7 +103,7 @@ module Specwrk
|
|
|
99
103
|
result = result_queue.pop
|
|
100
104
|
next if result.last.nil?
|
|
101
105
|
|
|
102
|
-
results[result.first] =
|
|
106
|
+
results[result.first] = self.class.serializer.load(result.last)
|
|
103
107
|
end
|
|
104
108
|
|
|
105
109
|
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
|
|
@@ -110,7 +114,7 @@ module Specwrk
|
|
|
110
114
|
|
|
111
115
|
hash_with_filenames = hash.map { |key, value| [key.to_s, [filename_for_key(key.to_s), value]] }.to_h
|
|
112
116
|
hash_with_filenames.each do |key, (filename, value)|
|
|
113
|
-
content =
|
|
117
|
+
content = self.class.serializer.dump(value)
|
|
114
118
|
|
|
115
119
|
self.class.schedule_work do
|
|
116
120
|
result_queue << write(filename, content)
|
|
@@ -137,7 +141,7 @@ module Specwrk
|
|
|
137
141
|
|
|
138
142
|
def read(key)
|
|
139
143
|
filename = filename_for_key key
|
|
140
|
-
File.
|
|
144
|
+
File.binread(filename)
|
|
141
145
|
rescue Errno::ENOENT
|
|
142
146
|
nil
|
|
143
147
|
end
|
|
@@ -146,7 +150,7 @@ module Specwrk
|
|
|
146
150
|
File.join(
|
|
147
151
|
path,
|
|
148
152
|
encode_key(key)
|
|
149
|
-
) +
|
|
153
|
+
) + self.class.ext
|
|
150
154
|
end
|
|
151
155
|
|
|
152
156
|
def path
|
|
@@ -160,7 +164,7 @@ module Specwrk
|
|
|
160
164
|
end
|
|
161
165
|
|
|
162
166
|
def decode_key(key)
|
|
163
|
-
encoded_key_part = File.basename(key).delete_suffix(
|
|
167
|
+
encoded_key_part = File.basename(key).delete_suffix(self.class.ext)
|
|
164
168
|
padding_count = (4 - encoded_key_part.length % 4) % 4
|
|
165
169
|
|
|
166
170
|
Base64.urlsafe_decode64(encoded_key_part + ("=" * padding_count))
|
|
@@ -169,7 +173,7 @@ module Specwrk
|
|
|
169
173
|
def known_key_pairs
|
|
170
174
|
Dir.entries(path).sort.map do |filename|
|
|
171
175
|
next if filename.start_with? "."
|
|
172
|
-
next unless filename.end_with?
|
|
176
|
+
next unless filename.end_with? self.class.ext
|
|
173
177
|
|
|
174
178
|
file_path = File.join(path, filename)
|
|
175
179
|
[decode_key(filename), file_path]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Specwrk
|
|
6
|
+
class Store
|
|
7
|
+
module Serializers
|
|
8
|
+
module JSON
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def adapter_name
|
|
12
|
+
"json"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def dump(value)
|
|
16
|
+
::JSON.generate(value)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def load(payload)
|
|
20
|
+
::JSON.parse(payload, symbolize_names: true)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module MessagePack
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
def ensure_loaded!
|
|
28
|
+
require "msgpack"
|
|
29
|
+
rescue LoadError
|
|
30
|
+
raise LoadError, "Unable to use msgpack, gem not found. Add `gem 'msgpack' to your Gemfile and bundle install"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
module_function
|
|
34
|
+
|
|
35
|
+
def adapter_name
|
|
36
|
+
"msgpack"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def dump(value)
|
|
40
|
+
ensure_loaded!
|
|
41
|
+
|
|
42
|
+
::MessagePack.dump(value)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def load(payload)
|
|
46
|
+
ensure_loaded!
|
|
47
|
+
|
|
48
|
+
::MessagePack.load(payload, symbolize_keys: true)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
module Serializer
|
|
54
|
+
module_function
|
|
55
|
+
|
|
56
|
+
def resolve(name = ENV.fetch("SPECWRK_STORE_SERIALIZER", "json"))
|
|
57
|
+
return name if name.respond_to?(:dump) && name.respond_to?(:load)
|
|
58
|
+
|
|
59
|
+
case name.to_s.downcase
|
|
60
|
+
when "", "json"
|
|
61
|
+
Serializers::JSON
|
|
62
|
+
when "msgpack", "messagepack"
|
|
63
|
+
Serializers::MessagePack.tap(&:ensure_loaded!)
|
|
64
|
+
else
|
|
65
|
+
raise ArgumentError, "Unsupported serializer #{name.inspect}. Choose json or msgpack."
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/specwrk/version.rb
CHANGED
|
@@ -9,13 +9,14 @@ module Specwrk
|
|
|
9
9
|
EXAMPLE_STATUSES = %w[passed failed pending]
|
|
10
10
|
|
|
11
11
|
def with_response
|
|
12
|
-
|
|
13
|
-
processing.delete(*(completed_examples.keys + retry_examples.keys))
|
|
12
|
+
retry_examples # pre-calculate before lock
|
|
14
13
|
|
|
15
14
|
with_lock do
|
|
15
|
+
processing.delete(*(completed_examples.keys + retry_examples.keys))
|
|
16
16
|
pending.merge!(retry_examples)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
completed.merge!(completed_examples)
|
|
19
20
|
failure_counts.merge!(retry_examples_new_failure_counts)
|
|
20
21
|
|
|
21
22
|
update_run_times
|
|
@@ -17,8 +17,12 @@ module Specwrk
|
|
|
17
17
|
[410, {"content-type" => "text/plain"}, ["That's a good lad. Run along now and go home."]]
|
|
18
18
|
elsif expired_examples.length.positive?
|
|
19
19
|
expired_examples.each { |_id, example| example[:worker_id] = worker_id }
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
|
|
21
|
+
with_lock do
|
|
22
|
+
pending.push_examples(expired_examples.values)
|
|
23
|
+
processing.delete(*expired_examples.keys.map(&:to_s))
|
|
24
|
+
end
|
|
25
|
+
|
|
22
26
|
@examples = nil
|
|
23
27
|
|
|
24
28
|
[200, {"content-type" => "application/json"}, [JSON.generate(examples)]]
|
|
@@ -29,6 +33,7 @@ module Specwrk
|
|
|
29
33
|
|
|
30
34
|
def examples
|
|
31
35
|
@examples ||= begin
|
|
36
|
+
return [] if pending.empty?
|
|
32
37
|
bucket_id = with_lock { pending.shift_bucket }
|
|
33
38
|
return [] if bucket_id.nil?
|
|
34
39
|
|
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.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Westendorf
|
|
@@ -177,6 +177,20 @@ dependencies:
|
|
|
177
177
|
- - ">="
|
|
178
178
|
- !ruby/object:Gem::Version
|
|
179
179
|
version: '0'
|
|
180
|
+
- !ruby/object:Gem::Dependency
|
|
181
|
+
name: msgpack
|
|
182
|
+
requirement: !ruby/object:Gem::Requirement
|
|
183
|
+
requirements:
|
|
184
|
+
- - ">="
|
|
185
|
+
- !ruby/object:Gem::Version
|
|
186
|
+
version: '0'
|
|
187
|
+
type: :development
|
|
188
|
+
prerelease: false
|
|
189
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
190
|
+
requirements:
|
|
191
|
+
- - ">="
|
|
192
|
+
- !ruby/object:Gem::Version
|
|
193
|
+
version: '0'
|
|
180
194
|
email:
|
|
181
195
|
- daniel@prowestech.com
|
|
182
196
|
executables:
|
|
@@ -217,6 +231,7 @@ files:
|
|
|
217
231
|
- lib/specwrk/store/memory_adapter.rb
|
|
218
232
|
- lib/specwrk/store/pending_store.rb
|
|
219
233
|
- lib/specwrk/store/processing_store.rb
|
|
234
|
+
- lib/specwrk/store/serializer.rb
|
|
220
235
|
- lib/specwrk/store/worker_store.rb
|
|
221
236
|
- lib/specwrk/version.rb
|
|
222
237
|
- lib/specwrk/watcher.rb
|