specwrk 0.11.0 → 0.13.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_reporter.rb +2 -2
- data/lib/specwrk/client.rb +7 -8
- data/lib/specwrk/store/file_adapter.rb +9 -29
- data/lib/specwrk/store.rb +39 -1
- data/lib/specwrk/version.rb +1 -1
- data/lib/specwrk/web/app.rb +0 -2
- data/lib/specwrk/web/auth.rb +6 -2
- data/lib/specwrk/web/endpoints.rb +24 -30
- data/lib/specwrk/worker/completion_formatter.rb +1 -6
- data/lib/specwrk/worker/executor.rb +1 -7
- data/lib/specwrk/worker.rb +2 -3
- data/lib/specwrk.rb +16 -0
- 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: c7b5abc9ec00673b175314cce03016affa2e47b7ca9f445235ea93fdadb7c79a
|
4
|
+
data.tar.gz: 7f30c131e7bbce5c787c527af0e1b58acb16345cc85c5cf22671be1281e8106e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75d4427606c9491a4344d95713f2062df258e7aea660e520ee126dc2233b8b2bd2b24ca9f56c15d696b0b35b9c0a6211ac05c2b4e63af950df5723e0c625816e
|
7
|
+
data.tar.gz: '0885f9962b4cc30f84d72d8e0a99dbb3541588f6aefc292657d95f52576c80251c2cfc4d04af2f35f74df74ca09ebb5385493d55f67ff905cd7cb1522c99f1b4'
|
data/lib/specwrk/cli_reporter.rb
CHANGED
@@ -16,8 +16,8 @@ module Specwrk
|
|
16
16
|
return 1
|
17
17
|
end
|
18
18
|
|
19
|
-
puts "\nFinished in #{total_duration} " \
|
20
|
-
"(total execution time of #{total_run_time})\n"
|
19
|
+
puts "\nFinished in #{Specwrk.human_readable_duration total_duration} " \
|
20
|
+
"(total execution time of #{Specwrk.human_readable_duration total_run_time})\n"
|
21
21
|
|
22
22
|
client.shutdown
|
23
23
|
|
data/lib/specwrk/client.rb
CHANGED
@@ -44,12 +44,13 @@ module Specwrk
|
|
44
44
|
raise Errno::ECONNREFUSED unless connected
|
45
45
|
end
|
46
46
|
|
47
|
-
attr_reader :last_request_at, :retry_count
|
47
|
+
attr_reader :last_request_at, :retry_count, :worker_status
|
48
48
|
|
49
49
|
def initialize
|
50
50
|
@mutex = Mutex.new
|
51
51
|
@http = self.class.build_http
|
52
52
|
@http.start
|
53
|
+
@worker_status = 1
|
53
54
|
end
|
54
55
|
|
55
56
|
def close
|
@@ -99,12 +100,6 @@ module Specwrk
|
|
99
100
|
end
|
100
101
|
end
|
101
102
|
|
102
|
-
def complete_examples(examples)
|
103
|
-
response = post "/complete", body: examples.to_json
|
104
|
-
|
105
|
-
(response.code == "200") ? true : raise(UnhandledResponseError.new("#{response.code}: #{response.body}"))
|
106
|
-
end
|
107
|
-
|
108
103
|
def complete_and_fetch_examples(examples)
|
109
104
|
response = post "/complete_and_pop", body: examples.to_json
|
110
105
|
|
@@ -171,7 +166,11 @@ module Specwrk
|
|
171
166
|
def make_request(request)
|
172
167
|
@mutex.synchronize do
|
173
168
|
@last_request_at = Time.now
|
174
|
-
@http.request(request).tap
|
169
|
+
@http.request(request).tap do |response|
|
170
|
+
@retry_count = 0
|
171
|
+
|
172
|
+
@worker_status = response["x-specwrk-status"].to_i if response["x-specwrk-status"]
|
173
|
+
end
|
175
174
|
end
|
176
175
|
end
|
177
176
|
|
@@ -60,7 +60,6 @@ module Specwrk
|
|
60
60
|
else
|
61
61
|
filename = filename_for_key(key_string)
|
62
62
|
write(filename, JSON.generate(value))
|
63
|
-
known_key_pairs[key_string] = filename
|
64
63
|
end
|
65
64
|
end
|
66
65
|
|
@@ -71,16 +70,12 @@ module Specwrk
|
|
71
70
|
def clear
|
72
71
|
FileUtils.rm_rf(path)
|
73
72
|
FileUtils.mkdir_p(path)
|
74
|
-
|
75
|
-
@known_key_pairs = nil
|
76
73
|
end
|
77
74
|
|
78
75
|
def delete(*keys)
|
79
|
-
filenames = keys.map { |key|
|
76
|
+
filenames = keys.map { |key| filename_for_key key }.compact
|
80
77
|
|
81
78
|
FileUtils.rm_f(filenames)
|
82
|
-
|
83
|
-
keys.each { |key| known_key_pairs.delete(key) }
|
84
79
|
end
|
85
80
|
|
86
81
|
def merge!(h2)
|
@@ -88,8 +83,6 @@ module Specwrk
|
|
88
83
|
end
|
89
84
|
|
90
85
|
def multi_read(*read_keys)
|
91
|
-
known_key_pairs # precache before each thread tries to look them up
|
92
|
-
|
93
86
|
result_queue = Queue.new
|
94
87
|
|
95
88
|
read_keys.each do |key|
|
@@ -112,8 +105,6 @@ module Specwrk
|
|
112
105
|
end
|
113
106
|
|
114
107
|
def multi_write(hash)
|
115
|
-
known_key_pairs # precache before each thread tries to look them up
|
116
|
-
|
117
108
|
result_queue = Queue.new
|
118
109
|
|
119
110
|
hash_with_filenames = hash.map { |key, value| [key.to_s, [filename_for_key(key.to_s), value]] }.to_h
|
@@ -126,7 +117,6 @@ module Specwrk
|
|
126
117
|
end
|
127
118
|
|
128
119
|
Thread.pass until result_queue.length == hash.length
|
129
|
-
hash_with_filenames.each { |key, (filename, _value)| known_key_pairs[key] = filename }
|
130
120
|
end
|
131
121
|
|
132
122
|
def empty?
|
@@ -136,7 +126,7 @@ module Specwrk
|
|
136
126
|
private
|
137
127
|
|
138
128
|
def write(filename, content)
|
139
|
-
tmp_filename = [filename, "tmp"].join(".")
|
129
|
+
tmp_filename = [filename, SecureRandom.uuid, "tmp"].join(".")
|
140
130
|
|
141
131
|
File.binwrite(tmp_filename, content)
|
142
132
|
|
@@ -145,29 +135,19 @@ module Specwrk
|
|
145
135
|
end
|
146
136
|
|
147
137
|
def read(key)
|
148
|
-
|
138
|
+
filename = filename_for_key key
|
139
|
+
File.read(filename)
|
140
|
+
rescue Errno::ENOENT
|
141
|
+
nil
|
149
142
|
end
|
150
143
|
|
151
144
|
def filename_for_key(key)
|
152
145
|
File.join(
|
153
146
|
path,
|
154
|
-
|
155
|
-
counter_prefix(key),
|
156
|
-
encode_key(key)
|
157
|
-
].join("_")
|
147
|
+
encode_key(key)
|
158
148
|
) + EXT
|
159
149
|
end
|
160
150
|
|
161
|
-
def counter_prefix(key)
|
162
|
-
count = keys.index(key) || counter.tap { @counter += 1 }
|
163
|
-
|
164
|
-
"%012d" % count
|
165
|
-
end
|
166
|
-
|
167
|
-
def counter
|
168
|
-
@counter ||= keys.length
|
169
|
-
end
|
170
|
-
|
171
151
|
def path
|
172
152
|
@path ||= File.join(uri.path, scope).tap do |full_path|
|
173
153
|
FileUtils.mkdir_p(full_path)
|
@@ -179,14 +159,14 @@ module Specwrk
|
|
179
159
|
end
|
180
160
|
|
181
161
|
def decode_key(key)
|
182
|
-
encoded_key_part = File.basename(key).delete_suffix(EXT)
|
162
|
+
encoded_key_part = File.basename(key).delete_suffix(EXT)
|
183
163
|
padding_count = (4 - encoded_key_part.length % 4) % 4
|
184
164
|
|
185
165
|
Base64.urlsafe_decode64(encoded_key_part + ("=" * padding_count))
|
186
166
|
end
|
187
167
|
|
188
168
|
def known_key_pairs
|
189
|
-
|
169
|
+
Dir.entries(path).sort.map do |filename|
|
190
170
|
next if filename.start_with? "."
|
191
171
|
next unless filename.end_with? EXT
|
192
172
|
|
data/lib/specwrk/store.rb
CHANGED
@@ -72,7 +72,7 @@ module Specwrk
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def to_h
|
75
|
-
|
75
|
+
multi_read(*keys).transform_keys!(&:to_sym)
|
76
76
|
end
|
77
77
|
|
78
78
|
def inspect
|
@@ -98,6 +98,7 @@ module Specwrk
|
|
98
98
|
|
99
99
|
class PendingStore < Store
|
100
100
|
RUN_TIME_BUCKET_MAXIMUM_KEY = :____run_time_bucket_maximum
|
101
|
+
ORDER_KEY = :____order
|
101
102
|
|
102
103
|
def run_time_bucket_maximum=(val)
|
103
104
|
@run_time_bucket_maximum = self[RUN_TIME_BUCKET_MAXIMUM_KEY] = val
|
@@ -107,6 +108,41 @@ module Specwrk
|
|
107
108
|
@run_time_bucket_maximum ||= self[RUN_TIME_BUCKET_MAXIMUM_KEY]
|
108
109
|
end
|
109
110
|
|
111
|
+
def order=(val)
|
112
|
+
@order = nil
|
113
|
+
|
114
|
+
self[ORDER_KEY] = if val.nil? || val.length.zero?
|
115
|
+
nil
|
116
|
+
else
|
117
|
+
val
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def order
|
122
|
+
@order ||= self[ORDER_KEY] || []
|
123
|
+
end
|
124
|
+
|
125
|
+
def keys
|
126
|
+
return super if order.length.zero?
|
127
|
+
|
128
|
+
order
|
129
|
+
end
|
130
|
+
|
131
|
+
def merge!(hash)
|
132
|
+
super
|
133
|
+
self.order = hash.keys
|
134
|
+
end
|
135
|
+
|
136
|
+
def clear
|
137
|
+
@order = nil
|
138
|
+
super
|
139
|
+
end
|
140
|
+
|
141
|
+
def reload
|
142
|
+
@order = nil
|
143
|
+
super
|
144
|
+
end
|
145
|
+
|
110
146
|
def shift_bucket
|
111
147
|
return bucket_by_file unless run_time_bucket_maximum&.positive?
|
112
148
|
|
@@ -146,6 +182,7 @@ module Specwrk
|
|
146
182
|
end
|
147
183
|
|
148
184
|
delete(*consumed_keys)
|
185
|
+
self.order = order - consumed_keys
|
149
186
|
bucket
|
150
187
|
end
|
151
188
|
|
@@ -171,6 +208,7 @@ module Specwrk
|
|
171
208
|
end
|
172
209
|
|
173
210
|
delete(*consumed_keys)
|
211
|
+
self.order = order - consumed_keys
|
174
212
|
bucket
|
175
213
|
end
|
176
214
|
end
|
data/lib/specwrk/version.rb
CHANGED
data/lib/specwrk/web/app.rb
CHANGED
data/lib/specwrk/web/auth.rb
CHANGED
@@ -11,7 +11,7 @@ module Specwrk
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def call(env)
|
14
|
-
env[:request] ||= Rack::Request.new(env)
|
14
|
+
@request = env[:request] ||= Rack::Request.new(env)
|
15
15
|
|
16
16
|
return @app.call(env) if [nil, ""].include? ENV["SPECWRK_SRV_KEY"]
|
17
17
|
return @app.call(env) if @excluded_paths.include? env[:request].path_info
|
@@ -28,7 +28,11 @@ module Specwrk
|
|
28
28
|
private
|
29
29
|
|
30
30
|
def unauthorized
|
31
|
-
|
31
|
+
if @request.head?
|
32
|
+
[401, {}, []]
|
33
|
+
else
|
34
|
+
[401, {"content-type" => "application/json"}, ["Unauthorized"]]
|
35
|
+
end
|
32
36
|
end
|
33
37
|
end
|
34
38
|
end
|
@@ -33,6 +33,8 @@ module Specwrk
|
|
33
33
|
|
34
34
|
after_lock
|
35
35
|
|
36
|
+
final_response[1]["x-specwrk-status"] = worker_status.to_s
|
37
|
+
|
36
38
|
final_response
|
37
39
|
end
|
38
40
|
|
@@ -102,6 +104,12 @@ module Specwrk
|
|
102
104
|
@worker ||= Store.new(ENV.fetch("SPECWRK_SRV_STORE_URI", "memory:///"), File.join(run_id, "workers", request.get_header("HTTP_X_SPECWRK_ID").to_s))
|
103
105
|
end
|
104
106
|
|
107
|
+
def worker_status
|
108
|
+
return 0 if worker[:failed].nil? && completed.any? # worker starts after run has completed
|
109
|
+
|
110
|
+
worker[:failed] || 1
|
111
|
+
end
|
112
|
+
|
105
113
|
def run_id
|
106
114
|
request.get_header("HTTP_X_SPECWRK_RUN")
|
107
115
|
end
|
@@ -190,29 +198,6 @@ module Specwrk
|
|
190
198
|
end
|
191
199
|
end
|
192
200
|
|
193
|
-
class Complete < Base
|
194
|
-
def with_response
|
195
|
-
warn "[DEPRECATED] This endpoint will be retired in favor of CompleteAndPop. Upgrade your clients."
|
196
|
-
completed.merge!(completed_examples)
|
197
|
-
processing.delete(*completed_examples.keys)
|
198
|
-
|
199
|
-
ok
|
200
|
-
end
|
201
|
-
|
202
|
-
private
|
203
|
-
|
204
|
-
def completed_examples
|
205
|
-
@completed_data ||= payload.map { |example| [example[:id], example] if processing[example[:id]] }.compact.to_h
|
206
|
-
end
|
207
|
-
|
208
|
-
# We don't care about exact values here, just approximate run times are fine
|
209
|
-
# So if we overwrite run times from another process it is nbd
|
210
|
-
def after_lock
|
211
|
-
run_time_data = payload.map { |example| [example[:id], example[:run_time]] }.to_h
|
212
|
-
run_times.merge! run_time_data
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
201
|
class Popable < Base
|
217
202
|
private
|
218
203
|
|
@@ -261,6 +246,8 @@ module Specwrk
|
|
261
246
|
end
|
262
247
|
|
263
248
|
class CompleteAndPop < Popable
|
249
|
+
EXAMPLE_STATUSES = %w[passed failed pending]
|
250
|
+
|
264
251
|
def with_response
|
265
252
|
completed.merge!(completed_examples)
|
266
253
|
processing.delete(*completed_examples.keys)
|
@@ -270,19 +257,26 @@ module Specwrk
|
|
270
257
|
|
271
258
|
private
|
272
259
|
|
273
|
-
def before_lock
|
274
|
-
completed_examples
|
275
|
-
run_time_data
|
276
|
-
end
|
277
|
-
|
278
260
|
def completed_examples
|
279
261
|
@completed_data ||= payload.map { |example| [example[:id], example] if processing[example[:id]] }.compact.to_h
|
280
262
|
end
|
281
263
|
|
282
|
-
|
283
|
-
|
264
|
+
def completed_examples_status_counts
|
265
|
+
@completed_examples_status_counts ||= completed_examples.values.map { |example| example[:status] }.tally
|
266
|
+
end
|
267
|
+
|
284
268
|
def after_lock
|
269
|
+
# We don't care about exact values here, just approximate run times are fine
|
270
|
+
# So if we overwrite run times from another process it is nbd
|
285
271
|
run_times.merge! run_time_data
|
272
|
+
|
273
|
+
# workers are single proces, single-threaded, so safe to do this work without the lock
|
274
|
+
existing_status_counts = worker.multi_read(*EXAMPLE_STATUSES)
|
275
|
+
new_status_counts = EXAMPLE_STATUSES.map do |status|
|
276
|
+
[status, existing_status_counts.fetch(status, 0) + completed_examples_status_counts.fetch(status, 0)]
|
277
|
+
end.to_h
|
278
|
+
|
279
|
+
worker.merge!(new_status_counts)
|
286
280
|
end
|
287
281
|
|
288
282
|
def run_time_data
|
@@ -5,19 +5,14 @@ module Specwrk
|
|
5
5
|
class CompletionFormatter
|
6
6
|
RSpec::Core::Formatters.register self, :stop
|
7
7
|
|
8
|
-
attr_reader :examples
|
8
|
+
attr_reader :examples
|
9
9
|
|
10
10
|
def initialize
|
11
11
|
@examples = []
|
12
|
-
@failure = false
|
13
12
|
end
|
14
13
|
|
15
14
|
def stop(group_notification)
|
16
15
|
group_notification.notifications.map do |notification|
|
17
|
-
unless failure
|
18
|
-
@failure = notification.example.execution_result.status == :failed
|
19
|
-
end
|
20
|
-
|
21
16
|
examples << {
|
22
17
|
id: notification.example.id,
|
23
18
|
full_description: notification.example.full_description,
|
@@ -11,12 +11,6 @@ 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
|
-
|
20
14
|
def examples
|
21
15
|
completion_formatter.examples
|
22
16
|
end
|
@@ -29,7 +23,6 @@ module Specwrk
|
|
29
23
|
reset!
|
30
24
|
|
31
25
|
example_ids = examples.map { |example| example[:id] }
|
32
|
-
@example_processed ||= true unless example_ids.size.zero? # only ever toggle from nil => true
|
33
26
|
|
34
27
|
options = RSpec::Core::ConfigurationOptions.new rspec_options + example_ids
|
35
28
|
RSpec::Core::Runner.new(options).run($stderr, $stdout)
|
@@ -40,6 +33,7 @@ module Specwrk
|
|
40
33
|
completion_formatter.examples.clear
|
41
34
|
|
42
35
|
RSpec.clear_examples
|
36
|
+
RSpec.configuration.backtrace_formatter.filter_gem "specwrk"
|
43
37
|
|
44
38
|
# see https://github.com/rspec/rspec-core/pull/2723
|
45
39
|
if Gem::Version.new(RSpec::Core::Version::STRING) <= Gem::Version.new("3.9.1")
|
data/lib/specwrk/worker.rb
CHANGED
@@ -110,11 +110,10 @@ module Specwrk
|
|
110
110
|
attr_reader :running, :client, :executor
|
111
111
|
|
112
112
|
def status
|
113
|
-
return
|
114
|
-
return 1 if executor.failure
|
113
|
+
return 0 if @all_examples_completed && client.worker_status.zero?
|
115
114
|
return 1 if Specwrk.force_quit
|
116
115
|
|
117
|
-
|
116
|
+
client.worker_status
|
118
117
|
end
|
119
118
|
|
120
119
|
def warn(msg)
|
data/lib/specwrk.rb
CHANGED
@@ -38,5 +38,21 @@ module Specwrk
|
|
38
38
|
|
39
39
|
exited_pids
|
40
40
|
end
|
41
|
+
|
42
|
+
def human_readable_duration(total_seconds, precision: 2)
|
43
|
+
secs = total_seconds.to_f
|
44
|
+
hours = (secs / 3600).to_i
|
45
|
+
mins = ((secs % 3600) / 60).to_i
|
46
|
+
seconds = secs % 60
|
47
|
+
|
48
|
+
parts = []
|
49
|
+
parts << "#{hours}h" if hours.positive?
|
50
|
+
parts << "#{mins}m" if mins.positive?
|
51
|
+
if seconds.positive?
|
52
|
+
sec_str = format("%0.#{precision}f", seconds).sub(/\.?0+$/, "")
|
53
|
+
parts << "#{sec_str}s"
|
54
|
+
end
|
55
|
+
parts.join(" ")
|
56
|
+
end
|
41
57
|
end
|
42
58
|
end
|