specwrk 0.4.9 → 0.5.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: 5dc867c1250644f28442d434b2932e0798b65cb13398067d95fde60436f97b43
4
- data.tar.gz: 824d06bdb7b4af68263bd02e366cd8fe4d728b85cf5df6a810f191d4a7b2c92a
3
+ metadata.gz: 331e0982599a516c3c862c4a5c33a825c71256f5f95e6d1656c0ad410541df06
4
+ data.tar.gz: c7d93bf3add6e6de08cad22f8036ef89b01752b0e6dfe42615035d9e301ecff5
5
5
  SHA512:
6
- metadata.gz: 505174406cb0174bdabe365c5c2a64bebaf98b77ddfe3d5ccad14c922287eb21fdada54c0ab044407f1039e4c01ccdd0487a7120a0c1751d8ce902cdd1efd60c
7
- data.tar.gz: f5c49b22baac3d0416b0927db2e2dafc2e4c89a8de88e2fada96daff2e039a4b5a0955dc8489afcf485a2f09ed0164523170c39efeb954a7962d2bddc12d4a85
6
+ metadata.gz: 9daa901f8a7b7f453079f766d09e47fb621cf85e1cdb6111303e1f8b17cc3091312b2128ca141328f1c8a28216151b991f431cbb4550445ce137349dcb30f3c5
7
+ data.tar.gz: f0ae841735759d62e9ad6607065ca7bc8747a9d85e8906dea19019af7c63183df26b82cce51bc2a8c453cf4e1bb7d3d0b0c900c917cf689730cdac6028efaae5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## v0.4.11
4
+
5
+ - Move logic out of CLI methods ([#64](https://github.com/danielwestendorf/specwrk/issues/64)) by @danielwestendorf
6
+ - Add thruster in front of puma ([#65](https://github.com/danielwestendorf/specwrk/issues/65)) by @danielwestendorf
7
+ - Revise heartbeats logic ([#66](https://github.com/danielwestendorf/specwrk/issues/66)) by @danielwestendorf
8
+
9
+ ## v0.4.9
10
+
11
+ - Remove single-run env var (it was not what I wanted) by @danielwestendorf
12
+
13
+ ## v0.4.8
14
+
15
+ - Fix `config.ru` by @danielwestendorf
16
+
17
+ ## v0.4.7
18
+
19
+ - Switch to Puma for the Docker image (#59) by @danielwestendorf
20
+
21
+ ## v0.4.6
22
+
23
+ - Nil env var that will prevent start command from completing ([#56](https://github.com/danielwestendorf/specwrk/issues/56)) by @danielwestendorf
24
+ - Better handling of seed failing ([#57](https://github.com/danielwestendorf/specwrk/issues/57)) by @danielwestendorf
25
+ - Silence health logging ([#58](https://github.com/danielwestendorf/specwrk/issues/58)) by @danielwestendorf
26
+ - Add missing CCI caching of `report.json` ([#55](https://github.com/danielwestendorf/specwrk/issues/55)) by @danielwestendorf
27
+ - Add CircleCI examples ([#50](https://github.com/danielwestendorf/specwrk/issues/50)) by @danielwestendorf
28
+ - Better GHA Examples ([#49](https://github.com/danielwestendorf/specwrk/issues/49)) by @danielwestendorf
29
+ - Skip key lookup and rely on the result of `Hash#delete` instead by @danielwestendorf
30
+
3
31
  ## v0.4.5
4
32
 
5
33
  - Set ENV var when generating seed examples [#47](https://github.com/danielwestendorf/specwrk/issues/47). by @danielwestendorf
data/README.md CHANGED
@@ -129,7 +129,7 @@ Description:
129
129
  Start one or more worker processes
130
130
 
131
131
  Options:
132
- --id=VALUE # The identifier for this worker. Default specwrk-worker(-COUNT_INDEX), default: "specwrk-worker"
132
+ --id=VALUE # 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
133
133
  --count=VALUE, -c VALUE # The number of worker processes you want to start, default: 1
134
134
  --output=VALUE, -o VALUE # Directory where worker output is stored. Overrides SPECWRK_OUT, default: ".specwrk/"
135
135
  --seed-waits=VALUE, -w VALUE # Number of times the worker will wait for examples to be seeded to the server. 1sec between attempts. Overrides SPECWRK_SEED_WAITS, default: "10"
data/config.ru CHANGED
@@ -2,4 +2,5 @@ require "specwrk"
2
2
  require "specwrk/web"
3
3
  require "specwrk/web/app"
4
4
 
5
+ Specwrk::Web::App.setup!
5
6
  run Specwrk::Web::App.rackup
@@ -19,7 +19,7 @@ COPY $GEMFILE ./
19
19
  RUN gem install ./$GEMFILE --no-document
20
20
  RUN rm ./$GEMFILE
21
21
 
22
- RUN gem install puma
22
+ RUN gem install puma thruster
23
23
  COPY config.ru ./
24
24
 
25
25
  COPY docker/entrypoint.server.sh /usr/local/bin/entrypoint
@@ -1,3 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
- exec puma --port ${PORT:-$SPECWRK_SRV_PORT} --bind tcp://0.0.0.0 --threads 1 --workers 0 --silent
3
+ export THRUSTER_HTTP_PORT=${PORT:-5138}
4
+ export THRUSTER_TARGET_PORT=3000
5
+ export THRUSTER_HTTP_IDLE_TIMEOUT=${IDLE_TIMEOUT:-305}
6
+
7
+ exec thrust puma --workers 0 --bind tcp://127.0.0.1:3000 --threads ${PUMA_THREADS:-1}
data/lib/specwrk/cli.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "pathname"
4
- require "fileutils"
4
+ require "securerandom"
5
5
 
6
6
  require "dry/cli"
7
7
 
@@ -34,18 +34,18 @@ module Specwrk
34
34
  extend Hookable
35
35
 
36
36
  on_included do |base|
37
- base.unique_option :id, type: :string, default: "specwrk-worker", desc: "The identifier for this worker. Default specwrk-worker(-COUNT_INDEX)"
37
+ 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"
38
38
  base.unique_option :count, type: :integer, default: 1, aliases: ["-c"], desc: "The number of worker processes you want to start"
39
39
  base.unique_option :output, type: :string, default: ENV.fetch("SPECWRK_OUT", ".specwrk/"), aliases: ["-o"], desc: "Directory where worker output is stored. Overrides SPECWRK_OUT"
40
40
  base.unique_option :seed_waits, type: :integer, default: ENV.fetch("SPECWRK_SEED_WAITS", "10"), aliases: ["-w"], desc: "Number of times the worker will wait for examples to be seeded to the server. 1sec between attempts. Overrides SPECWRK_SEED_WAITS"
41
41
  end
42
42
 
43
- on_setup do |id:, count:, output:, seed_waits:, **|
44
- ENV["SPECWRK_ID"] = id
43
+ on_setup do |count:, output:, seed_waits:, id: "specwrk-worker-#{SecureRandom.uuid[0, 8]}", **|
44
+ ENV["SPECWRK_ID"] ||= id # Unique default. Don't override the ENV value here
45
+
45
46
  ENV["SPECWRK_COUNT"] = count.to_s
46
47
  ENV["SPECWRK_SEED_WAITS"] = seed_waits.to_s
47
48
  ENV["SPECWRK_OUT"] = Pathname.new(output).expand_path(Dir.pwd).to_s
48
- FileUtils.mkdir_p(ENV["SPECWRK_OUT"])
49
49
  end
50
50
 
51
51
  def start_workers
@@ -80,16 +80,13 @@ module Specwrk
80
80
  base.unique_option :verbose, type: :boolean, default: false, desc: "Run in verbose mode. Default false."
81
81
  end
82
82
 
83
- on_setup do |port:, bind:, output:, key:, single_seed_per_run:, group_by:, verbose:, **|
83
+ on_setup do |port:, bind:, output:, key:, group_by:, verbose:, **|
84
84
  ENV["SPECWRK_OUT"] = Pathname.new(output).expand_path(Dir.pwd).to_s
85
- FileUtils.mkdir_p(ENV["SPECWRK_OUT"])
85
+ ENV["SPECWRK_SRV_VERBOSE"] = "1" if verbose
86
86
 
87
- ENV["SPECWRK_SRV_LOG"] ||= Pathname.new(File.join(ENV["SPECWRK_OUT"], "server.log")).to_s if output && !verbose
88
- ENV["SPECWRK_SRV_OUTPUT"] ||= Pathname.new(File.join(ENV["SPECWRK_OUT"], "report.json")).expand_path(Dir.pwd).to_s if output
89
87
  ENV["SPECWRK_SRV_PORT"] = port
90
88
  ENV["SPECWRK_SRV_BIND"] = bind
91
89
  ENV["SPECWRK_SRV_KEY"] = key
92
- ENV["SPECWRK_SRV_SINGLE_SEED_PER_RUN"] = "1" if single_seed_per_run
93
90
  ENV["SPECWRK_SRV_GROUP_BY"] = group_by
94
91
  end
95
92
  end
@@ -120,6 +117,8 @@ module Specwrk
120
117
 
121
118
  Client.wait_for_server!
122
119
  Client.new.seed(examples)
120
+ file_count = examples.group_by { |e| e[:file_path] }.keys.size
121
+ puts "🌱 Seeded #{examples.size} examples across #{file_count} files"
123
122
  rescue Errno::ECONNREFUSED
124
123
  puts "Server at #{ENV.fetch("SPECWRK_SRV_URI", "http://localhost:5138")} is refusing connections, exiting...#{ENV["SPECWRK_FLUSH_DELIMINATOR"]}"
125
124
  exit 1
@@ -150,7 +150,9 @@ module Specwrk
150
150
 
151
151
  def default_headers
152
152
  @default_headers ||= {}.tap do |h|
153
+ h["User-Agent"] = "Specwrk/#{VERSION}"
153
154
  h["Authorization"] = "Bearer #{ENV["SPECWRK_SRV_KEY"]}" if ENV["SPECWRK_SRV_KEY"]
155
+ h["X-Specwrk-Id"] = ENV.fetch("SPECWRK_ID", "specwrk-client")
154
156
  h["X-Specwrk-Run"] = ENV["SPECWRK_RUN"] if ENV["SPECWRK_RUN"]
155
157
  h["Content-Type"] = "application/json"
156
158
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Specwrk
4
- VERSION = "0.4.9"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pathname"
4
+ require "fileutils"
5
+
3
6
  require "webrick"
4
7
  require "rack"
5
8
 
@@ -10,6 +13,7 @@ rescue LoadError
10
13
  require "rack/handler/webrick"
11
14
  end
12
15
 
16
+ require "specwrk/web"
13
17
  require "specwrk/web/logger"
14
18
  require "specwrk/web/auth"
15
19
  require "specwrk/web/endpoints"
@@ -17,13 +21,13 @@ require "specwrk/web/endpoints"
17
21
  module Specwrk
18
22
  class Web
19
23
  class App
24
+ REAP_INTERVAL = 330 # HTTP connection timeout + some buffer
25
+
20
26
  class << self
21
27
  def run!
22
28
  Process.setproctitle "specwrk-server"
23
29
 
24
- if ENV["SPECWRK_SRV_LOG"]
25
- $stdout.reopen(ENV["SPECWRK_SRV_LOG"], "w")
26
- end
30
+ setup!
27
31
 
28
32
  server_opts = {
29
33
  Port: ENV.fetch("SPECWRK_SRV_PORT", "5138").to_i,
@@ -46,16 +50,36 @@ module Specwrk
46
50
  end
47
51
  end
48
52
 
53
+ def setup!
54
+ if ENV["SPECWRK_OUT"]
55
+
56
+ FileUtils.mkdir_p(ENV["SPECWRK_OUT"])
57
+ ENV["SPECWRK_SRV_LOG"] ||= Pathname.new(File.join(ENV["SPECWRK_OUT"], "server.log")).to_s unless ENV["SPECWRK_SRV_VERBOSE"]
58
+ ENV["SPECWRK_SRV_OUTPUT"] ||= Pathname.new(File.join(ENV["SPECWRK_OUT"], "report.json")).expand_path(Dir.pwd).to_s
59
+ end
60
+
61
+ if ENV["SPECWRK_SRV_LOG"]
62
+ $stdout.reopen(ENV["SPECWRK_SRV_LOG"], "w")
63
+ end
64
+ end
65
+
49
66
  def rackup
50
67
  Rack::Builder.new do
51
- use Rack::Runtime
52
- use Specwrk::Web::Logger, $stdout, %w[/health]
68
+ if ENV["SPECWRK_SRV_VERBOSE"]
69
+ use Rack::Runtime
70
+ use Specwrk::Web::Logger, $stdout, %w[/health]
71
+ end
72
+
53
73
  use Specwrk::Web::Auth, %w[/health] # global auth check
54
74
  run Specwrk::Web::App.new # your router
55
75
  end
56
76
  end
57
77
  end
58
78
 
79
+ def initialize
80
+ @reaper_thread = Thread.new { reaper } unless ENV["SPECWRK_SRV_SINGLE_RUN"]
81
+ end
82
+
59
83
  def call(env)
60
84
  env[:request] ||= Rack::Request.new(env)
61
85
 
@@ -84,6 +108,26 @@ module Specwrk
84
108
  Endpoints::NotFound
85
109
  end
86
110
  end
111
+
112
+ def reaper
113
+ until Specwrk.force_quit
114
+ sleep REAP_INTERVAL
115
+
116
+ reap
117
+ end
118
+ end
119
+
120
+ def reap
121
+ Web::WORKERS.each do |run, workers|
122
+ most_recent_last_seen_at = workers.map { |id, worker| worker[:last_seen_at] }.max
123
+ next unless most_recent_last_seen_at
124
+
125
+ # Don't consider runs which aren't at least REAP_INTERVAL sec stale
126
+ if most_recent_last_seen_at < Time.now - REAP_INTERVAL
127
+ Web.clear_run_queues(run)
128
+ end
129
+ end
130
+ end
87
131
  end
88
132
  end
89
133
  end
@@ -8,6 +8,9 @@ module Specwrk
8
8
  class Base
9
9
  def initialize(request)
10
10
  @request = request
11
+
12
+ worker[:first_seen_at] ||= Time.now
13
+ worker[:last_seen_at] = Time.now
11
14
  end
12
15
 
13
16
  def response
@@ -45,6 +48,14 @@ module Specwrk
45
48
  def completed_queue
46
49
  Web::COMPLETED_QUEUES[request.get_header("HTTP_X_SPECWRK_RUN")]
47
50
  end
51
+
52
+ def workers
53
+ Web::WORKERS[request.get_header("HTTP_X_SPECWRK_RUN")]
54
+ end
55
+
56
+ def worker
57
+ workers[request.get_header("HTTP_X_SPECWRK_ID")]
58
+ end
48
59
  end
49
60
 
50
61
  # Base default response is 404
@@ -135,25 +146,7 @@ module Specwrk
135
146
 
136
147
  class Shutdown < Base
137
148
  def response
138
- if ENV["SPECWRK_SRV_SINGLE_RUN"]
139
- interupt!
140
- elsif processing_queue.length.positive?
141
- # Push any processing jobs back into the pending queue
142
- processing_queue.synchronize do |processing_queue_hash|
143
- pending_queue.synchronize do |pending_queue_hash|
144
- processing_queue_hash.each do |id, example|
145
- pending_queue_hash[id] = example
146
- processing_queue_hash.delete(id)
147
- end
148
- end
149
- end
150
-
151
- elsif processing_queue.length.zero? && pending_queue.length.zero?
152
- # All done, we can clear the completed queue
153
- completed_queue.clear
154
- end
155
-
156
- # TODO: clear any zombie queues
149
+ interupt! if ENV["SPECWRK_SRV_SINGLE_RUN"]
157
150
 
158
151
  [200, {"Content-Type" => "text/plain"}, ["✌️"]]
159
152
  end
data/lib/specwrk/web.rb CHANGED
@@ -7,9 +7,16 @@ module Specwrk
7
7
  PENDING_QUEUES = Queue.new { |h, key| h[key] = PendingQueue.new.tap { |q| q.previous_run_times_file = ENV["SPECWRK_SRV_OUTPUT"] } }
8
8
  PROCESSING_QUEUES = Queue.new { |h, key| h[key] = Queue.new }
9
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] = {} } }
10
11
 
11
12
  def self.clear_queues
12
- [PENDING_QUEUES, PROCESSING_QUEUES, COMPLETED_QUEUES].each(&:clear)
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)
19
+ end
13
20
  end
14
21
  end
15
22
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "stringio"
4
+ require "fileutils"
4
5
 
5
6
  require "specwrk/client"
6
7
  require "specwrk/worker/executor"
@@ -13,6 +14,7 @@ module Specwrk
13
14
 
14
15
  def initialize
15
16
  Process.setproctitle ENV.fetch("SPECWRK_ID", "specwrk-worker")
17
+ FileUtils.mkdir_p(ENV["SPECWRK_OUT"]) if ENV["SPECWRK_OUT"]
16
18
 
17
19
  @running = true
18
20
  @client = Client.new
@@ -85,13 +87,13 @@ module Specwrk
85
87
 
86
88
  def thump
87
89
  while running && !Specwrk.force_quit
90
+ sleep 10
91
+
88
92
  begin
89
- client.heartbeat if client.last_request_at.nil? || client.last_request_at < Time.now - 10
93
+ client.heartbeat if client.last_request_at.nil? || client.last_request_at < Time.now - 30
90
94
  rescue
91
95
  warn "Heartbeat failed!"
92
96
  end
93
-
94
- sleep 1
95
97
  end
96
98
  end
97
99
 
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.9
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Westendorf