specwrk 0.14.1 → 0.15.1
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/.circleci/config.yml +22 -1
- data/README.md +57 -0
- data/Specwrk.watchfile.rb +25 -0
- data/lib/specwrk/cli.rb +153 -1
- data/lib/specwrk/cli_reporter.rb +46 -1
- data/lib/specwrk/ipc.rb +36 -0
- data/lib/specwrk/list_examples.rb +25 -0
- data/lib/specwrk/seed_loop.rb +27 -0
- data/lib/specwrk/version.rb +1 -1
- data/lib/specwrk/watcher.rb +87 -0
- data/lib/specwrk/web/endpoints.rb +6 -4
- data/lib/specwrk/worker/completion_formatter.rb +11 -1
- data/lib/specwrk/worker/executor.rb +18 -5
- metadata +33 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3de4bb5f29a1c817db2c766d49fcd20d2153e6bf9708fc7b5ece2e1101fbb839
|
4
|
+
data.tar.gz: 3fb0dc079f9d85461ec245e2b1019d03e94ba7f3403813f2c7558504140b2c37
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3bfbec41f429426bcd85e81c2d879c1e3171ce2f0dba908a420890b5fa37373e803540ef92d4bfada74f4e465e4babc558c3e18cf8fa941376eb6e2d0d08cea2
|
7
|
+
data.tar.gz: 9096f33b527c0ae8233ae22a01d7e6b53e446b6684b63417120ba9181f7780e2590a4ff59ee5777a06d750341c5f3781f2f3f705c65874b69ae422db812a1ed2
|
data/.circleci/config.yml
CHANGED
@@ -83,6 +83,17 @@ jobs:
|
|
83
83
|
- .specwrk/report.json
|
84
84
|
key: specwrk-{{ .Branch }}
|
85
85
|
## /SPECWRK STEP ##
|
86
|
+
|
87
|
+
- run:
|
88
|
+
name: Echo the workers output for giggles
|
89
|
+
command: |
|
90
|
+
shopt -s nullglob
|
91
|
+
for file in /tmp/specwrk/main/*.ndjson; do
|
92
|
+
echo "===== $file ====="
|
93
|
+
cat "$file"
|
94
|
+
echo
|
95
|
+
done
|
96
|
+
|
86
97
|
|
87
98
|
specwrk-multi-node-prepare:
|
88
99
|
docker:
|
@@ -116,7 +127,6 @@ jobs:
|
|
116
127
|
--key "$SPECWRK_KEY" \
|
117
128
|
--run "$CIRCLE_WORKFLOW_ID" \
|
118
129
|
spec/
|
119
|
-
|
120
130
|
## /SPECWRK STEP ##
|
121
131
|
|
122
132
|
specwrk-multi-node:
|
@@ -154,3 +164,14 @@ jobs:
|
|
154
164
|
--run "$CIRCLE_WORKFLOW_ID" \
|
155
165
|
--count 2
|
156
166
|
## /SPECWRK STEP ##
|
167
|
+
|
168
|
+
- run:
|
169
|
+
name: Echo the workers output for giggles
|
170
|
+
command: |
|
171
|
+
shopt -s nullglob
|
172
|
+
for file in /tmp/specwrk/main/*.ndjson; do
|
173
|
+
echo "===== $file ====="
|
174
|
+
cat "$file"
|
175
|
+
echo
|
176
|
+
done
|
177
|
+
|
data/README.md
CHANGED
@@ -142,6 +142,26 @@ Options:
|
|
142
142
|
--help, -h # Print this help
|
143
143
|
```
|
144
144
|
|
145
|
+
### `specwrk watch -c 8`
|
146
|
+
Starts `8` worker processes in watch mode for the current directory. Watched spec files will be distributed across the processes. By default, only looks at `_spec.rb` files. Configure a [watchfile](#create-a-watchfile-for-the-watch-command) to map file changes to spec files (i.e. modification of `app/models/user.rb` should run `spec/models/user_spec.rb` and `spec/system/users_spec.rb`).
|
147
|
+
|
148
|
+
```sh
|
149
|
+
$ specwrk watch --help
|
150
|
+
Command:
|
151
|
+
specwrk watch
|
152
|
+
|
153
|
+
Usage:
|
154
|
+
specwrk watch
|
155
|
+
|
156
|
+
Description:
|
157
|
+
Start a server and workers, watch for file changes in the current directory, and execute specs
|
158
|
+
|
159
|
+
Options:
|
160
|
+
--watchfile=VALUE # Path to watchfile configuration, default: "Specwrk.watchfile.rb"
|
161
|
+
--count=VALUE, -c VALUE # The number of worker processes you want to start, default: 1
|
162
|
+
--help, -h # Print this help
|
163
|
+
```
|
164
|
+
|
145
165
|
## Configuring your test environment
|
146
166
|
If you test suite tracks state, starts servers, etc. and you plan on running many processes on the same node, you'll need to make
|
147
167
|
adjustments to avoid conflicting port usage or database/state mutations.
|
@@ -204,6 +224,43 @@ Start a persistent Queue Server given one of the following methods
|
|
204
224
|
|
205
225
|
See [specwrk serve --help](#specwrk-serve) for all possible configuration options.
|
206
226
|
|
227
|
+
### Create a watchfile for the `watch` command
|
228
|
+
Watch file (default path is `Specwrk.watchfile.rb` in the current directory) is a ruby file that will be instance eval'd to configure the watcher. There are two commands available:
|
229
|
+
|
230
|
+
1. `ignore(Regexp)` to define files that should never trigger a run
|
231
|
+
2. `map(Regexp, &blk)` to map a file change to the spec files that should be run to that file change
|
232
|
+
|
233
|
+
By default, files without a `.rb` extension will be ignored and files ending with `_spec.rb` will be run. Presence of a watchfile will override these defaults.
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
# Specwrk.watchfile.rb
|
237
|
+
# Ignore all files which don't have an .rb extension
|
238
|
+
ignore(/^(?!.*\.rb$).+/)
|
239
|
+
|
240
|
+
# When a _spec.rb file changes, it should be run
|
241
|
+
map(/_spec\.rb$/) do |spec_path|
|
242
|
+
spec_path
|
243
|
+
end
|
244
|
+
|
245
|
+
# If a file in lib changes, map it to the spec folder for it's spec file
|
246
|
+
map(/lib\/.*\.rb$/) do |path|
|
247
|
+
path.gsub(/lib\/(.+)\.rb/, "spec/\\1_spec.rb")
|
248
|
+
end
|
249
|
+
|
250
|
+
# If a model file changes (assuming rails app structure), run the model's spec file
|
251
|
+
# map(/app\/models\/.*.rb$/) do |path|
|
252
|
+
# path.gsub(/app\/models\/(.+)\.rb/, "spec/models/\\1_spec.rb")
|
253
|
+
# end
|
254
|
+
#
|
255
|
+
# If a controlelr file changes (assuming rails app structure), run the controller and system specs file
|
256
|
+
# map(/app\/controllers\/.*.rb$/) do |path|
|
257
|
+
# [
|
258
|
+
# path.gsub(/app\/controllers\/(.+)\.rb/, "spec/controllers/\\1_spec.rb"),
|
259
|
+
# path.gsub(/app\/controllers\/(.+)\.rb/, "spec/system/\\1_spec.rb")
|
260
|
+
# ]
|
261
|
+
# end
|
262
|
+
```
|
263
|
+
|
207
264
|
## Contributing
|
208
265
|
|
209
266
|
Bug reports and pull requests are welcome on GitHub at https://github.com/dwestendorf/specwrk.
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Ignore all files which don't have an .rb extension
|
2
|
+
ignore(/^(?!.*\.rb$).+/)
|
3
|
+
|
4
|
+
# When a _spec.rb file changes, it should be run
|
5
|
+
map(/_spec\.rb$/) do |spec_path|
|
6
|
+
spec_path
|
7
|
+
end
|
8
|
+
|
9
|
+
# If a file in lib changes, map it to the spec folder for it's spec file
|
10
|
+
map(/lib\/.*\.rb$/) do |path|
|
11
|
+
path.gsub(/lib\/(.+)\.rb/, "spec/\\1_spec.rb")
|
12
|
+
end
|
13
|
+
|
14
|
+
# If a model file changes (assuming rails app structure), run the model's spec file
|
15
|
+
# map(/app\/models\/.*.rb$/) do |path|
|
16
|
+
# path.gsub(/app\/models\/(.+)\.rb/, "spec/models/\\1_spec.rb")
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# If a controlelr file changes (assuming rails app structure), run the controller and system specs file
|
20
|
+
# map(/app\/controllers\/.*.rb$/) do |path|
|
21
|
+
# [
|
22
|
+
# path.gsub(/app\/controllers\/(.+)\.rb/, "spec/controllers/\\1_spec.rb"),
|
23
|
+
# path.gsub(/app\/controllers\/(.+)\.rb/, "spec/system/\\1_spec.rb")
|
24
|
+
# ]
|
25
|
+
# end
|
data/lib/specwrk/cli.rb
CHANGED
@@ -74,6 +74,7 @@ module Specwrk
|
|
74
74
|
def drain_outputs
|
75
75
|
@final_outputs.each do |reader|
|
76
76
|
reader.each_line { |line| $stdout.print line }
|
77
|
+
reader.close
|
77
78
|
end
|
78
79
|
end
|
79
80
|
|
@@ -252,7 +253,7 @@ module Specwrk
|
|
252
253
|
require "specwrk/cli_reporter"
|
253
254
|
status = Specwrk::CLIReporter.new.report
|
254
255
|
|
255
|
-
Specwrk.wait_for_pids_exit([web_pid, seed_pid]
|
256
|
+
Specwrk.wait_for_pids_exit([web_pid, seed_pid])
|
256
257
|
exit(status)
|
257
258
|
end
|
258
259
|
|
@@ -262,10 +263,161 @@ module Specwrk
|
|
262
263
|
end
|
263
264
|
end
|
264
265
|
|
266
|
+
class Watch < Dry::CLI::Command
|
267
|
+
desc "Start a server and workers, watch for file changes in the current directory, and execute specs"
|
268
|
+
option :watchfile, type: :string, default: "Specwrk.watchfile.rb", desc: "Path to watchfile configuration"
|
269
|
+
option :count, type: :integer, default: 1, aliases: ["-c"], desc: "The number of worker processes you want to start"
|
270
|
+
|
271
|
+
def call(count:, watchfile:, **args)
|
272
|
+
$stdout.sync = true
|
273
|
+
|
274
|
+
# nil this env var if it exists to prevent never-ending workers
|
275
|
+
ENV["SPECWRK_SRV_URI"] = nil
|
276
|
+
ENV["SPECWRK_SEED_WAITS"] = "0"
|
277
|
+
ENV["SPECWRK_MAX_BUCKET_SIZE"] = "1"
|
278
|
+
ENV["SPECWRK_COUNT"] = count.to_s
|
279
|
+
ENV["SPECWRK_RUN"] = "watch"
|
280
|
+
|
281
|
+
web_pid
|
282
|
+
|
283
|
+
return if Specwrk.force_quit
|
284
|
+
|
285
|
+
seed_pid
|
286
|
+
|
287
|
+
start_watcher(watchfile)
|
288
|
+
|
289
|
+
require "specwrk/cli_reporter"
|
290
|
+
|
291
|
+
loop do
|
292
|
+
status "👀 Watching for file changes..."
|
293
|
+
|
294
|
+
@worker_pids = nil
|
295
|
+
Thread.pass until file_queue.length.positive? || Specwrk.force_quit
|
296
|
+
|
297
|
+
break if Specwrk.force_quit
|
298
|
+
|
299
|
+
files = []
|
300
|
+
files.push(file_queue.pop) until file_queue.length.zero?
|
301
|
+
status "Running specs for #{files.join(" ")}..."
|
302
|
+
ipc.write(files.join(" "))
|
303
|
+
|
304
|
+
example_count = ipc.read.to_i
|
305
|
+
if example_count.positive?
|
306
|
+
puts "\n🌱 Seeded #{example_count} examples for execution\n"
|
307
|
+
else
|
308
|
+
puts "\n🙅 No examples to seed for execution\n"
|
309
|
+
end
|
310
|
+
|
311
|
+
next if example_count.zero?
|
312
|
+
|
313
|
+
return if Specwrk.force_quit
|
314
|
+
start_workers
|
315
|
+
|
316
|
+
Specwrk.wait_for_pids_exit(@worker_pids)
|
317
|
+
|
318
|
+
drain_outputs
|
319
|
+
return if Specwrk.force_quit
|
320
|
+
|
321
|
+
Specwrk::CLIReporter.new.report
|
322
|
+
puts
|
323
|
+
$stdout.flush
|
324
|
+
end
|
325
|
+
|
326
|
+
ipc.write "INT" # wakes the socket
|
327
|
+
Specwrk.wait_for_pids_exit([web_pid, seed_pid])
|
328
|
+
end
|
329
|
+
|
330
|
+
private
|
331
|
+
|
332
|
+
def web_pid
|
333
|
+
@web_pid ||= Process.fork do
|
334
|
+
require "specwrk/web"
|
335
|
+
require "specwrk/web/app"
|
336
|
+
|
337
|
+
ENV["SPECWRK_FORKED"] = "1"
|
338
|
+
status "Starting queue server..."
|
339
|
+
Specwrk::Web::App.run!
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def seed_pid
|
344
|
+
@seed_pid ||= begin
|
345
|
+
ipc # must be initialized in the parent process
|
346
|
+
|
347
|
+
@seed_pid = Process.fork do
|
348
|
+
require "specwrk/seed_loop"
|
349
|
+
|
350
|
+
ENV["SPECWRK_FORKED"] = "1"
|
351
|
+
ENV["SPECWRK_SEED"] = "1"
|
352
|
+
|
353
|
+
Specwrk::SeedLoop.loop!(ipc)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def ipc
|
359
|
+
@ipc ||= begin
|
360
|
+
require "specwrk/ipc"
|
361
|
+
|
362
|
+
Specwrk::IPC.new
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def start_watcher(watchfile)
|
367
|
+
require "specwrk/watcher"
|
368
|
+
|
369
|
+
Specwrk::Watcher.watch(Dir.pwd, file_queue, watchfile)
|
370
|
+
end
|
371
|
+
|
372
|
+
def file_queue
|
373
|
+
@file_queue ||= Queue.new
|
374
|
+
end
|
375
|
+
|
376
|
+
def status(msg)
|
377
|
+
print "\e[2K\r#{msg}"
|
378
|
+
$stdout.flush
|
379
|
+
end
|
380
|
+
|
381
|
+
def start_workers
|
382
|
+
@final_outputs = []
|
383
|
+
@worker_pids = worker_count.times.map do |i|
|
384
|
+
reader, writer = IO.pipe
|
385
|
+
@final_outputs << reader
|
386
|
+
|
387
|
+
Process.fork do
|
388
|
+
ENV["TEST_ENV_NUMBER"] = ENV["SPECWRK_FORKED"] = (i + 1).to_s
|
389
|
+
ENV["SPECWRK_ID"] = "specwrk-worker-#{i + 1}"
|
390
|
+
|
391
|
+
$final_output = writer # standard:disable Style/GlobalVars
|
392
|
+
$final_output.sync = true # standard:disable Style/GlobalVars
|
393
|
+
reader.close
|
394
|
+
|
395
|
+
require "specwrk/worker"
|
396
|
+
|
397
|
+
status = Specwrk::Worker.run!
|
398
|
+
$final_output.close # standard:disable Style/GlobalVars
|
399
|
+
exit(status)
|
400
|
+
end.tap { writer.close }
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def drain_outputs
|
405
|
+
@final_outputs.each do |reader|
|
406
|
+
reader.each_line { |line| $stdout.print line }
|
407
|
+
reader.close
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def worker_count
|
412
|
+
@worker_count ||= [1, ENV["SPECWRK_COUNT"].to_i].max
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
265
416
|
register "version", Version, aliases: ["v", "-v", "--version"]
|
266
417
|
register "work", Work, aliases: ["wrk", "twerk", "w"]
|
267
418
|
register "serve", Serve, aliases: ["srv", "s"]
|
268
419
|
register "seed", Seed
|
269
420
|
register "start", Start
|
421
|
+
register "watch", Watch, aliases: ["w", "👀"]
|
270
422
|
end
|
271
423
|
end
|
data/lib/specwrk/cli_reporter.rb
CHANGED
@@ -19,10 +19,25 @@ module Specwrk
|
|
19
19
|
puts "\nFinished in #{Specwrk.human_readable_duration total_duration} " \
|
20
20
|
"(total execution time of #{Specwrk.human_readable_duration total_run_time})\n"
|
21
21
|
|
22
|
+
if flake_count.positive?
|
23
|
+
puts "\nFlaked examples:\n\n"
|
24
|
+
flake_reruns_lines.each { |(command, description)| print "#{colorizer.wrap(command, :magenta)} #{colorizer.wrap(description, :cyan)}\n" }
|
25
|
+
puts ""
|
26
|
+
end
|
27
|
+
|
22
28
|
client.shutdown
|
23
29
|
|
24
30
|
if failure_count.positive?
|
25
31
|
puts colorizer.wrap(totals_line, :red)
|
32
|
+
|
33
|
+
puts "\nFailed examples:\n\n"
|
34
|
+
failure_reruns_lines.each { |(command, description)| print "#{colorizer.wrap(command, :red)} #{colorizer.wrap(description, :cyan)}\n" }
|
35
|
+
puts ""
|
36
|
+
|
37
|
+
1
|
38
|
+
elsif unexecuted_count.positive?
|
39
|
+
puts colorizer.wrap(totals_line, :red)
|
40
|
+
|
26
41
|
1
|
27
42
|
elsif pending_count.positive?
|
28
43
|
puts colorizer.wrap(totals_line, :yellow)
|
@@ -44,11 +59,29 @@ module Specwrk
|
|
44
59
|
def totals_line
|
45
60
|
summary = RSpec::Core::Formatters::Helpers.pluralize(example_count, "example") +
|
46
61
|
", " + RSpec::Core::Formatters::Helpers.pluralize(failure_count, "failure")
|
47
|
-
summary += ", #{pending_count} pending" if pending_count
|
62
|
+
summary += ", #{pending_count} pending" if pending_count.positive?
|
63
|
+
summary += ", #{RSpec::Core::Formatters::Helpers.pluralize(flake_count, "example")} flaked #{RSpec::Core::Formatters::Helpers.pluralize(total_flakes, "time")}" if flake_count.positive?
|
64
|
+
summary += ". #{RSpec::Core::Formatters::Helpers.pluralize(unexecuted_count, "example")} not executed" if unexecuted_count.positive?
|
48
65
|
|
49
66
|
summary
|
50
67
|
end
|
51
68
|
|
69
|
+
def failure_reruns_lines
|
70
|
+
@failure_reruns_lines ||= report_data.dig(:examples).values.map do |example|
|
71
|
+
next unless example[:status] == "failed"
|
72
|
+
|
73
|
+
["rspec #{example[:file_path]}:#{example[:line_number]}", "# #{example[:full_description]}"]
|
74
|
+
end.compact
|
75
|
+
end
|
76
|
+
|
77
|
+
def flake_reruns_lines
|
78
|
+
@flake_reruns_lines ||= report_data.dig(:flakes).map do |example_id, count|
|
79
|
+
example = report_data.dig(:examples, example_id)
|
80
|
+
|
81
|
+
["rspec #{example[:file_path]}:#{example[:line_number]}", "# #{example[:full_description]}. Failed #{RSpec::Core::Formatters::Helpers.pluralize(count, "time")} before passing."]
|
82
|
+
end.compact
|
83
|
+
end
|
84
|
+
|
52
85
|
def report_data
|
53
86
|
@report_data ||= client.report
|
54
87
|
end
|
@@ -69,10 +102,22 @@ module Specwrk
|
|
69
102
|
report_data.dig(:meta, :pending)
|
70
103
|
end
|
71
104
|
|
105
|
+
def unexecuted_count
|
106
|
+
report_data.dig(:meta, :unexecuted)
|
107
|
+
end
|
108
|
+
|
72
109
|
def example_count
|
73
110
|
report_data.dig(:examples).length
|
74
111
|
end
|
75
112
|
|
113
|
+
def flake_count
|
114
|
+
report_data.dig(:flakes).length
|
115
|
+
end
|
116
|
+
|
117
|
+
def total_flakes
|
118
|
+
@total_flakes ||= report_data.dig(:flakes).values.sum
|
119
|
+
end
|
120
|
+
|
76
121
|
def client
|
77
122
|
@client ||= Client.new
|
78
123
|
end
|
data/lib/specwrk/ipc.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
module Specwrk
|
4
|
+
class IPC
|
5
|
+
def initialize
|
6
|
+
@parent_pid = Process.pid
|
7
|
+
|
8
|
+
@parent_socket, @child_socket = UNIXSocket.pair
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(msg)
|
12
|
+
socket.puts msg.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def read
|
16
|
+
IO.select([socket])
|
17
|
+
|
18
|
+
data = socket.gets&.chomp
|
19
|
+
return if data.nil? || data.length.zero? || data == "INT"
|
20
|
+
|
21
|
+
data
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :parent_pid, :parent_socket, :child_socket
|
27
|
+
|
28
|
+
def socket
|
29
|
+
child? ? child_socket : parent_socket
|
30
|
+
end
|
31
|
+
|
32
|
+
def child?
|
33
|
+
Process.pid != parent_pid
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -11,6 +11,7 @@ module Specwrk
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def examples
|
14
|
+
reset!
|
14
15
|
return @examples if defined?(@examples)
|
15
16
|
|
16
17
|
@examples = []
|
@@ -38,6 +39,30 @@ module Specwrk
|
|
38
39
|
|
39
40
|
private
|
40
41
|
|
42
|
+
def reset!
|
43
|
+
return unless ENV["SPECWRK_SEED"]
|
44
|
+
RSpec.clear_examples
|
45
|
+
|
46
|
+
# see https://github.com/rspec/rspec-core/pull/2723
|
47
|
+
if Gem::Version.new(RSpec::Core::Version::STRING) <= Gem::Version.new("3.9.1")
|
48
|
+
RSpec.world.instance_variable_set(
|
49
|
+
:@example_group_counts_by_spec_file, Hash.new(0)
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
# RSpec.clear_examples does not reset those, which causes issues when
|
54
|
+
# a non-example error occurs (subsequent jobs are not executed)
|
55
|
+
RSpec.world.non_example_failure = false
|
56
|
+
|
57
|
+
# we don't want an error that occured outside of the examples (which
|
58
|
+
# would set this to `true`) to stop the worker
|
59
|
+
RSpec.world.wants_to_quit = Specwrk.force_quit
|
60
|
+
|
61
|
+
RSpec.configuration.silence_filter_announcements = true
|
62
|
+
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
41
66
|
def out
|
42
67
|
@out ||= Tempfile.new.tap do |f|
|
43
68
|
f.define_singleton_method(:tty?) { true }
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "specwrk/list_examples"
|
4
|
+
require "specwrk/client"
|
5
|
+
|
6
|
+
module Specwrk
|
7
|
+
class SeedLoop
|
8
|
+
def self.loop!(ipc)
|
9
|
+
Client.wait_for_server!
|
10
|
+
|
11
|
+
loop do
|
12
|
+
break if Specwrk.force_quit
|
13
|
+
|
14
|
+
files = ipc.read
|
15
|
+
|
16
|
+
next unless files
|
17
|
+
examples = ListExamples.new(files.split(" ")).examples
|
18
|
+
|
19
|
+
client = Client.new
|
20
|
+
client.seed(examples, 0)
|
21
|
+
client.close
|
22
|
+
|
23
|
+
ipc.write examples.length
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/specwrk/version.rb
CHANGED
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "listen"
|
4
|
+
|
5
|
+
module Specwrk
|
6
|
+
class Watcher
|
7
|
+
class Config
|
8
|
+
def self.load(file)
|
9
|
+
if file && File.exist?(file)
|
10
|
+
new(false).tap { |instance| instance.instance_eval(File.read(file), file, 1) }
|
11
|
+
else
|
12
|
+
new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(default_config = true)
|
17
|
+
if default_config
|
18
|
+
@mappings = [
|
19
|
+
[/_spec\.rb$/, proc { |changed_file_path| changed_file_path }]
|
20
|
+
]
|
21
|
+
|
22
|
+
@ignore_patterns = [/^(?!.*\.rb$).+/]
|
23
|
+
else
|
24
|
+
@mappings = []
|
25
|
+
@ignore_patterns = []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def map(pattern, &block)
|
30
|
+
@mappings << [pattern, block]
|
31
|
+
end
|
32
|
+
|
33
|
+
def ignore(*patterns)
|
34
|
+
@ignore_patterns.concat(patterns)
|
35
|
+
end
|
36
|
+
|
37
|
+
def spec_files_for(path)
|
38
|
+
return [] if @ignore_patterns.any? { |pattern| pattern.match? path }
|
39
|
+
|
40
|
+
@mappings.map do |pattern, block|
|
41
|
+
next unless pattern.match? path
|
42
|
+
|
43
|
+
block.call(path)
|
44
|
+
end.flatten.compact.uniq
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.watch(dir, queue, watchfile)
|
49
|
+
instance = new(dir, queue, watchfile)
|
50
|
+
|
51
|
+
instance.start
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(dir, queue, watchfile = "Specwrk.watchfile.rb")
|
55
|
+
@dir = dir
|
56
|
+
@queue = queue
|
57
|
+
@config = Config.load(watchfile)
|
58
|
+
end
|
59
|
+
|
60
|
+
def start
|
61
|
+
listener.start
|
62
|
+
end
|
63
|
+
|
64
|
+
def push(paths)
|
65
|
+
paths.each do |path|
|
66
|
+
relative_path = Pathname.new(path).relative_path_from(Pathname.new(dir)).to_s
|
67
|
+
|
68
|
+
spec_files = config.spec_files_for(relative_path)
|
69
|
+
|
70
|
+
spec_files.each do |spec_file_path|
|
71
|
+
queue.push(spec_file_path) if File.exist?(spec_file_path)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
attr_reader :dir, :queue, :config
|
79
|
+
|
80
|
+
def listener
|
81
|
+
@listener ||= Listen.to(dir) do |modified, added|
|
82
|
+
push(modified)
|
83
|
+
push(added)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -145,6 +145,7 @@ module Specwrk
|
|
145
145
|
|
146
146
|
def with_response
|
147
147
|
pending.clear
|
148
|
+
processing.clear
|
148
149
|
failure_counts.clear
|
149
150
|
|
150
151
|
pending.max_retries = payload.fetch(:max_retries, "0").to_i
|
@@ -331,15 +332,16 @@ module Specwrk
|
|
331
332
|
|
332
333
|
class Report < Base
|
333
334
|
def with_response
|
334
|
-
|
335
|
+
completed_dump = completed.dump
|
336
|
+
completed_dump[:meta][:unexecuted] = pending.length + processing.length
|
337
|
+
completed_dump[:flakes] = failure_counts.to_h.reject { |id, _count| completed_dump.dig(:examples, id, :status) == "failed" }
|
338
|
+
|
339
|
+
[200, {"content-type" => "application/json"}, [JSON.generate(completed_dump)]]
|
335
340
|
end
|
336
341
|
end
|
337
342
|
|
338
343
|
class Shutdown < Base
|
339
344
|
def with_response
|
340
|
-
pending.clear
|
341
|
-
processing.clear
|
342
|
-
|
343
345
|
interupt! if ENV["SPECWRK_SRV_SINGLE_RUN"]
|
344
346
|
|
345
347
|
[200, {"content-type" => "text/plain"}, ["✌️"]]
|
@@ -13,7 +13,7 @@ module Specwrk
|
|
13
13
|
|
14
14
|
def stop(group_notification)
|
15
15
|
group_notification.notifications.map do |notification|
|
16
|
-
|
16
|
+
hash = {
|
17
17
|
id: notification.example.id,
|
18
18
|
full_description: notification.example.full_description,
|
19
19
|
status: notification.example.execution_result.status,
|
@@ -23,6 +23,16 @@ module Specwrk
|
|
23
23
|
finished_at: notification.example.execution_result.finished_at.iso8601(6),
|
24
24
|
run_time: notification.example.execution_result.run_time
|
25
25
|
}
|
26
|
+
|
27
|
+
if (e = notification.example.exception)
|
28
|
+
hash[:exception] = {
|
29
|
+
class: e.class.name,
|
30
|
+
message: e.message,
|
31
|
+
backtrace: notification.formatted_backtrace
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
examples << hash
|
26
36
|
end
|
27
37
|
end
|
28
38
|
end
|
@@ -24,12 +24,13 @@ module Specwrk
|
|
24
24
|
|
25
25
|
example_ids = examples.map { |example| example[:id] }
|
26
26
|
|
27
|
-
options = RSpec::Core::ConfigurationOptions.new
|
27
|
+
options = RSpec::Core::ConfigurationOptions.new ["--format", "Specwrk::Worker::NullFormatter"] + example_ids
|
28
28
|
RSpec::Core::Runner.new(options).run($stderr, $stdout)
|
29
29
|
end
|
30
30
|
|
31
31
|
# https://github.com/skroutz/rspecq/blob/341383ce3ca25f42fad5483cbb6a00ba1c405570/lib/rspecq/worker.rb#L208-L224
|
32
32
|
def reset!
|
33
|
+
flush_log
|
33
34
|
completion_formatter.examples.clear
|
34
35
|
|
35
36
|
RSpec.clear_examples
|
@@ -71,13 +72,25 @@ module Specwrk
|
|
71
72
|
@completion_formatter ||= CompletionFormatter.new
|
72
73
|
end
|
73
74
|
|
74
|
-
def
|
75
|
-
|
76
|
-
|
75
|
+
def flush_log
|
76
|
+
completion_formatter.examples.each { |example| json_log_file.puts example }
|
77
|
+
end
|
78
|
+
|
79
|
+
def json_log_file
|
80
|
+
@json_log_file ||= if json_log_file_path
|
81
|
+
FileUtils.mkdir_p(File.dirname(json_log_file_path))
|
82
|
+
File.truncate(json_log_file_path, 0) if File.exist?(json_log_file_path)
|
83
|
+
File.open(json_log_file_path, "a", sync: true)
|
77
84
|
else
|
78
|
-
|
85
|
+
File.open(File::NULL, "a")
|
79
86
|
end
|
80
87
|
end
|
88
|
+
|
89
|
+
def json_log_file_path
|
90
|
+
return unless ENV["SPECWRK_OUT"]
|
91
|
+
|
92
|
+
@json_log_file_path ||= File.join(ENV["SPECWRK_OUT"], ENV["SPECWRK_RUN"], "#{ENV["SPECWRK_FORKED"]}.ndjson")
|
93
|
+
end
|
81
94
|
end
|
82
95
|
end
|
83
96
|
end
|
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.15.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Westendorf
|
@@ -79,6 +79,34 @@ dependencies:
|
|
79
79
|
- - ">="
|
80
80
|
- !ruby/object:Gem::Version
|
81
81
|
version: '0'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: listen
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: logger
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
82
110
|
- !ruby/object:Gem::Dependency
|
83
111
|
name: rackup
|
84
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -177,6 +205,7 @@ files:
|
|
177
205
|
- LICENSE.txt
|
178
206
|
- README.md
|
179
207
|
- Rakefile
|
208
|
+
- Specwrk.watchfile.rb
|
180
209
|
- config.ru
|
181
210
|
- docker/Dockerfile.server
|
182
211
|
- docker/entrypoint.server.sh
|
@@ -190,12 +219,15 @@ files:
|
|
190
219
|
- lib/specwrk/cli_reporter.rb
|
191
220
|
- lib/specwrk/client.rb
|
192
221
|
- lib/specwrk/hookable.rb
|
222
|
+
- lib/specwrk/ipc.rb
|
193
223
|
- lib/specwrk/list_examples.rb
|
224
|
+
- lib/specwrk/seed_loop.rb
|
194
225
|
- lib/specwrk/store.rb
|
195
226
|
- lib/specwrk/store/base_adapter.rb
|
196
227
|
- lib/specwrk/store/file_adapter.rb
|
197
228
|
- lib/specwrk/store/memory_adapter.rb
|
198
229
|
- lib/specwrk/version.rb
|
230
|
+
- lib/specwrk/watcher.rb
|
199
231
|
- lib/specwrk/web.rb
|
200
232
|
- lib/specwrk/web/app.rb
|
201
233
|
- lib/specwrk/web/auth.rb
|