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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7467aa52809672311415ce6338aa0baa26b13cbb4fd4a2bf12854726f500517e
4
- data.tar.gz: 1226cb23262a885106c39ec68c97d903c410976b12b4e857e739009284b42da2
3
+ metadata.gz: 4c6ab0d31757bbed9bf9f9ba02172a9bdd0b1ba57c7209f9d694b6471588ddd1
4
+ data.tar.gz: 44a634acd2023813f35b3eac9bcc331782c4cf624d716bf1d6341db80a07d464
5
5
  SHA512:
6
- metadata.gz: c5ecdc6493127599390e7a95e13148cef715165c8ec4c3d897fc643eca7895e283dc21cb20f99b2f4d961dec8a4110c6e3ec3d077de0a609da6d2c58ee5a13a4
7
- data.tar.gz: 39ec42792a73bd68b693b81b379e54bb959fe21a78bfe811f645135e11e28793e5daf23b1f39a6d36fe81b56ae46963b4cdf95df632230adcc09c71460fae92f
6
+ metadata.gz: 6c29b9f3e52b7b4d88920b6a4d92cba4b24fed583573b9cfa8dd273777a73534ded97f2a89ecc078d58d183e48a0095a1403900de7c3d497954f6c9002b4e962
7
+ data.tar.gz: ce81b65408313ce7088ba157c4578ffae66c15f9f8d4e053992c8eb1c4c99a9ecbc3cb68e1903e6817d988740b7dd57e282866a06a23146841ca057adb89fb19
@@ -1,6 +1,11 @@
1
1
  FROM ruby:3.4-alpine
2
2
 
3
- RUN apk add --no-cache build-base
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 GEM_FILE=specwrk-$SPECWRK_VERSION.gem
16
+ ARG GEMFILE=specwrk-$SPECWRK_VERSION.gem
12
17
 
13
- COPY $GEM_FILE ./
14
- RUN gem install ./$GEM_FILE --no-document
15
- RUN rm ./$GEM_FILE
18
+ COPY $GEMFILE ./
19
+ RUN gem install ./$GEMFILE --no-document
20
+ RUN rm ./$GEMFILE
16
21
 
17
- RUN gem install pitchfork thruster
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
@@ -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:-300}
5
+ export THRUSTER_HTTP_IDLE_TIMEOUT=${IDLE_TIMEOUT:-305}
6
6
 
7
- exec thrust pitchfork -c pitchfork.conf
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
@@ -9,6 +9,7 @@ trap("INT") do
9
9
 
10
10
  Specwrk.force_quit = true
11
11
  elsif Specwrk.starting_pid != Process.pid
12
+ RSpec.world.wants_to_quit = true if defined?(RSpec)
12
13
  exit(1) if Specwrk.force_quit
13
14
  Specwrk.force_quit = true
14
15
  end
@@ -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
- # Thread-safe Hash access
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
- synchronize do
85
- h2.each { |_id, example| merge_example(example) }
47
+ h2.each { |_id, example| merge_example(example) }
86
48
 
87
- # Sort by exepcted run time, slowest to fastest
88
- @hash = @hash.sort_by { |_, example| example[:expected_run_time] }.reverse.to_h
89
- end
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
- @mutex.synchronize do
108
- key = @hash.keys.first
109
- break if key.nil?
70
+ key = keys.first
71
+ return [] if key.nil?
110
72
 
111
- file_path = @hash[key][:file_path]
112
- @hash.each do |id, example|
113
- next unless example[:file_path] == file_path
73
+ file_path = self[key][:file_path]
74
+ each do |id, example|
75
+ next unless example[:file_path] == file_path
114
76
 
115
- bucket << example
116
- @hash.delete id
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
- @mutex.synchronize do
128
- estimated_run_time_total = 0
88
+ estimated_run_time_total = 0
129
89
 
130
- while estimated_run_time_total < run_time_bucket_threshold
131
- key = @hash.keys.first
132
- break if key.nil?
90
+ while estimated_run_time_total < run_time_bucket_threshold
91
+ key = keys.first
92
+ break if key.nil?
133
93
 
134
- estimated_run_time_total += @hash.dig(key, :expected_run_time)
135
- break if estimated_run_time_total > run_time_bucket_threshold && bucket.length.positive?
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
- bucket << @hash[key]
138
- @hash.delete key
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 @hash.key? example[:id]
148
- return if @hash.key? example[:file_path]
105
+ return if key? example[:id]
106
+ return if key? example[:file_path]
149
107
 
150
- @hash[example[:id]] = if previous_run_times
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
- @mutex.synchronize do
169
- @run_times = []
170
- @first_started_at = Time.new(2999, 1, 1, 0, 0, 0) # TODO: Make future proof /s
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
- @output = {
174
- file_totals: Hash.new { |h, filename| h[filename] = 0.0 },
175
- meta: {failures: 0, passes: 0, pending: 0},
176
- examples: {}
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
- @hash.values.each { |example| calculate(example) }
136
+ values.each { |example| calculate(example) }
180
137
 
181
- @output[:meta][:total_run_time] = @run_times.sum
182
- @output[:meta][:average_run_time] = @output[:meta][:total_run_time] / [@run_times.length, 1].max.to_f
183
- @output[:meta][:first_started_at] = @first_started_at.iso8601(6)
184
- @output[:meta][:last_finished_at] = @last_finished_at.iso8601(6)
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
- @output
187
- end
143
+ @output
188
144
  end
189
145
 
190
146
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Specwrk
4
- VERSION = "0.6.2"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -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
@@ -28,7 +28,7 @@ module Specwrk
28
28
  private
29
29
 
30
30
  def unauthorized
31
- [401, {"content-type" => "application/json"}, ["Unauthorized"]]
31
+ [401, {"Content-Type" => "application/json"}, ["Unauthorized"]]
32
32
  end
33
33
  end
34
34
  end
@@ -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, {"content-type" => "text/plain"}, ["This is not the path you're looking for, 'ol chap..."]]
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, {"content-type" => "text/plain"}, ["OK, 'ol chap"]]
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"], "#{completed_queue.created_at.strftime("%Y%m%dT%H%M%S")}-report-#{run_id}.json").to_s
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 response
85
+ def with_response
74
86
  [200, {}, []]
75
87
  end
76
88
  end
77
89
 
78
90
  class Heartbeat < Base
79
- def response
91
+ def with_response
80
92
  ok
81
93
  end
82
94
  end
83
95
 
84
96
  class Seed < Base
85
- def response
86
- pending_queue.synchronize do |pending_queue_hash|
87
- unless ENV["SPECWRK_SRV_SINGLE_SEED_PER_RUN"] && pending_queue_hash.length.positive?
88
- examples = payload.map { |hash| [hash[:id], hash] }.to_h
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 response
102
- processing_queue.synchronize do |processing_queue_hash|
103
- payload.each do |example|
104
- next unless processing_queue_hash.delete(example[:id])
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 response
119
- processing_queue.synchronize do |processing_queue_hash|
120
- @examples = pending_queue.shift_bucket
124
+ def with_response
125
+ @examples = pending_queue.shift_bucket
121
126
 
122
- @examples.each do |example|
123
- processing_queue_hash[example[:id]] = example
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, {"content-type" => "application/json"}, [JSON.generate(@examples)]]
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, {"content-type" => "text/plain"}, ["Waiting for sample to be seeded."]]
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, {"content-type" => "text/plain"}, ["That's a good lad. Run along now and go home."]]
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 response
144
+ def with_response
141
145
  if data
142
- [200, {"content-type" => "application/json"}, [data]]
146
+ [200, {"Content-Type" => "application/json"}, [data]]
143
147
  else
144
- [404, {"content-type" => "text/plain"}, ["Unable to report on run #{run_id}; no file matching #{"*-report-#{run_id}.json"}"]]
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-#{run_id}.json")).last
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 response
172
+ def with_response
169
173
  interupt! if ENV["SPECWRK_SRV_SINGLE_RUN"]
170
174
 
171
- [200, {"content-type" => "text/plain"}, ["✌️"]]
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
- PENDING_QUEUES = Queue.new { |h, key| h[key] = PendingQueue.new }
8
- PROCESSING_QUEUES = Queue.new { |h, key| h[key] = Queue.new }
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] = {} } }
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
@@ -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 unless executor.example_processed
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.6.2
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
- - docker/pitchfork.conf
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
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- worker_processes 1
4
- listen "localhost:3000", backlog: 2048
5
- timeout 301