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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93a17f70f0e768222876b22465e7b62d60deb9dbe9761eed9611f31638a7f7cb
4
- data.tar.gz: 9137ab50e74ea4e3e46d74496718ae2830af4bb3c1a51e4b0ac2ba53d1691617
3
+ metadata.gz: 690eaebf0ec2f7621284bc0ce56d6613001bdffc6297651b9d4f6da7a439971b
4
+ data.tar.gz: 8d3104699becc543cbe5aaa6f2b6537d931d34d0e931d720b254262d5b2889a9
5
5
  SHA512:
6
- metadata.gz: cbcbb39d8d270fee9fed3a8befdbee10d7461e082ecd3e6bab0d24562dc00f53e7eb7bc2b8b75065cf2424456ecb1f9364d74658f28a7b49ffb2c2670ff5442b
7
- data.tar.gz: d6eb28d18cdf3aecde015c0da5b39ea529e6118249857e1ea312f4708efb98e3ce7e8a25fae9cc3948870e7afacfafa3e55db46106ae53dd62af8cd261ca1928
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("SPECWERK_SRV_GROUP_BY", "timings"), desc: "How examples will be grouped for workers; fallback to file if no timings are found. Overrides SPECWERK_SRV_GROUP_BY"
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 = Process.fork do
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
- @seed_pid = Process.fork do
392
- require "specwrk/seed_loop"
393
-
394
- ENV["SPECWRK_FORKED"] = "1"
395
- ENV["SPECWRK_SEED"] = "1"
396
-
397
- Specwrk::SeedLoop.loop!(ipc)
398
- end
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
- def initialize
6
- @parent_pid = Process.pid
5
+ attr_reader :parent_socket, :child_socket
7
6
 
8
- @parent_socket, @child_socket = UNIXSocket.pair
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, :parent_socket, :child_socket
36
+ attr_reader :parent_pid
27
37
 
28
38
  def socket
29
39
  child? ? child_socket : parent_socket
@@ -11,6 +11,14 @@ module Specwrk
11
11
  def with_lock(_uri, _key)
12
12
  yield
13
13
  end
14
+
15
+ def serializer
16
+ @serializer ||= Serializer.resolve
17
+ end
18
+
19
+ def reset_serializer!
20
+ @serializer = nil
21
+ end
14
22
  end
15
23
 
16
24
  def initialize(uri, scope)
@@ -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
- JSON.parse(content, symbolize_names: true)
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, JSON.generate(value))
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] = JSON.parse(result.last, symbolize_names: true)
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 = JSON.generate(value)
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.read(filename)
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
- ) + EXT
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(EXT)
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? EXT
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Specwrk
4
- VERSION = "0.17.1"
4
+ VERSION = "0.19.0"
5
5
  end
@@ -9,13 +9,14 @@ module Specwrk
9
9
  EXAMPLE_STATUSES = %w[passed failed pending]
10
10
 
11
11
  def with_response
12
- completed.merge!(completed_examples)
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
- with_lock { pending.push_examples(expired_examples.values) }
21
- processing.delete(*expired_examples.keys.map(&:to_s))
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.17.1
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