knapsack_pro 9.2.2 → 10.0.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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/exe/knapsack_pro +5 -0
  3. data/lib/knapsack_pro/adapters/rspec_adapter.rb +7 -3
  4. data/lib/knapsack_pro/build_distribution_fetcher.rb +1 -1
  5. data/lib/knapsack_pro/client/api/action.rb +1 -9
  6. data/lib/knapsack_pro/client/api/v1/queues.rb +0 -53
  7. data/lib/knapsack_pro/client/api/v2/queues.rb +111 -0
  8. data/lib/knapsack_pro/client/connection.rb +15 -15
  9. data/lib/knapsack_pro/commands/retry_failed_tests.rb +49 -0
  10. data/lib/knapsack_pro/commands.rb +74 -0
  11. data/lib/knapsack_pro/config/ci/base.rb +3 -0
  12. data/lib/knapsack_pro/config/ci/buildkite.rb +4 -0
  13. data/lib/knapsack_pro/config/ci/circle.rb +6 -0
  14. data/lib/knapsack_pro/config/ci/github_actions.rb +4 -0
  15. data/lib/knapsack_pro/config/ci/gitlab_ci.rb +4 -0
  16. data/lib/knapsack_pro/config/env.rb +30 -0
  17. data/lib/knapsack_pro/crypto/encryptor.rb +8 -0
  18. data/lib/knapsack_pro/formatters/time_tracker.rb +32 -10
  19. data/lib/knapsack_pro/formatters/time_tracker_fetcher.rb +2 -2
  20. data/lib/knapsack_pro/queue_allocator.rb +86 -20
  21. data/lib/knapsack_pro/queue_allocator_builder.rb +1 -2
  22. data/lib/knapsack_pro/report.rb +1 -2
  23. data/lib/knapsack_pro/repository_adapters/git_adapter.rb +31 -10
  24. data/lib/knapsack_pro/rspec/test_queue_initializer.rb +2 -2
  25. data/lib/knapsack_pro/runners/queue/base_runner.rb +2 -1
  26. data/lib/knapsack_pro/runners/queue/rspec_runner.rb +8 -8
  27. data/lib/knapsack_pro/test_suite.rb +1 -1
  28. data/lib/knapsack_pro/urls.rb +3 -1
  29. data/lib/knapsack_pro/version.rb +1 -1
  30. data/lib/knapsack_pro.rb +14 -1
  31. data/lib/tasks/queue/rspec.rake +1 -0
  32. metadata +22 -18
  33. data/bin/knapsack_pro +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53d7f0f50c7114a8e5cefd993a8ef8cb5fa05a6d36dd78cf8f9784884a6ac5d9
4
- data.tar.gz: 56366ca74436f87208e2a297941d00ec403589e0e6d0ed3759862d059b8b16bb
3
+ metadata.gz: 142cab6d40b7b31270887dd59d36daa249813769e01ee5a83aba4b78dc4cea1d
4
+ data.tar.gz: ee60df331664ef4bac5e3aa7906cdf41f4a5c9c69a5ebada6c542a6dc2780313
5
5
  SHA512:
6
- metadata.gz: 315e0df100e135cdd2646b546ce331291514c24d40f0ff88ea0a69f0d60842623cc35cb8f390a07b725c6b89ecbbebe61cf587a067728c2d2c59c99f531df4f0
7
- data.tar.gz: 8d51be76e65fce53d2cdae3c45359ad5ce1dcdfab0a48c6ccacef0fe4d85636879c2586d1f937853f7d3d7b4652846e182af4fa2a548d9e2405399a538695599
6
+ metadata.gz: 5591eb67604230bd3f33cbc82f0662b8dd0a289834166760262ca44df22ae83651599f9294faa3a31e31c25cdc743a1b3870829bbac429c15364bbfdb28349dc
7
+ data.tar.gz: 3a248092779d5459d8776b94963b745f798cd55d57137764aa54b9a58ba9688ca75f349c35e0c165c6e2c8452803caa13b6cb0f5f73527e76d58354a1f364d2d
data/exe/knapsack_pro ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "knapsack_pro"
4
+ require "knapsack_pro/commands"
5
+ KnapsackPro::Commands.start(ARGV)
@@ -81,11 +81,15 @@ module KnapsackPro
81
81
  !id.nil?
82
82
  end
83
83
 
84
+ def self.concat_test_files(test_files, id_paths)
85
+ paths = concat_paths(test_files, id_paths)
86
+ KnapsackPro::TestFilePresenter.test_files(paths)
87
+ end
88
+
84
89
  def self.concat_paths(test_files, id_paths)
85
90
  paths = KnapsackPro::TestFilePresenter.paths(test_files)
86
91
  file_paths = id_paths.map { |id_path| parse_file_path(id_path) }
87
- acc = paths + id_paths - file_paths
88
- KnapsackPro::TestFilePresenter.test_files(acc)
92
+ paths + id_paths - file_paths
89
93
  end
90
94
 
91
95
  def self.rails_helper_exists?(test_dir)
@@ -138,7 +142,7 @@ module KnapsackPro
138
142
  ::RSpec.configure do |config|
139
143
  config.append_before(:suite) do
140
144
  time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
141
- time_tracker.scheduled_paths = KnapsackPro::Adapters::RSpecAdapter.scheduled_paths
145
+ time_tracker.schedule(KnapsackPro::Adapters::RSpecAdapter.scheduled_paths)
142
146
  end
143
147
  end
144
148
  end
@@ -59,7 +59,7 @@ module KnapsackPro
59
59
 
60
60
  def additional_params
61
61
  {
62
- node_build_id: KnapsackPro::Config::Env.ci_node_build_id,
62
+ node_build_id: KnapsackPro::Config::Env.test_queue_id,
63
63
  none_if_queue_initialized: true
64
64
  }
65
65
  end
@@ -3,15 +3,7 @@
3
3
  module KnapsackPro
4
4
  module Client
5
5
  module API
6
- class Action
7
- attr_reader :endpoint_path, :http_method, :request_hash
8
-
9
- def initialize(args)
10
- @endpoint_path = args.fetch(:endpoint_path)
11
- @http_method = args.fetch(:http_method)
12
- @request_hash = args.fetch(:request_hash)
13
- end
14
- end
6
+ Action = Struct.new(:endpoint_path, :http_method, :request_hash, keyword_init: true)
15
7
  end
16
8
  end
17
9
  end
@@ -36,59 +36,6 @@ module KnapsackPro
36
36
  request_hash: request_hash
37
37
  )
38
38
  end
39
-
40
- def initialize(paths)
41
- git_adapter = KnapsackPro::RepositoryAdapters::GitAdapter.new
42
- repository_adapter = KnapsackPro::RepositoryAdapterInitiator.call
43
-
44
- request_hash = {
45
- attempt_connect_to_queue: false,
46
- branch: KnapsackPro::Crypto::BranchEncryptor.call(repository_adapter.branch),
47
- build_author: git_adapter.build_author,
48
- can_initialize_queue: true,
49
- commit_authors: git_adapter.commit_authors,
50
- commit_hash: repository_adapter.commit_hash,
51
- fixed_queue_split: KnapsackPro::Config::Env.fixed_queue_split,
52
- node_build_id: KnapsackPro::Config::Env.ci_node_build_id,
53
- node_index: KnapsackPro::Config::Env.ci_node_index,
54
- node_total: KnapsackPro::Config::Env.ci_node_total,
55
- skip_pull: true,
56
- test_files: KnapsackPro::Crypto::Encryptor.call(paths),
57
- user_seat: KnapsackPro::Config::Env.masked_user_seat,
58
- }
59
-
60
- action_class.new(
61
- endpoint_path: '/v1/queues/queue',
62
- http_method: :post,
63
- request_hash: request_hash
64
- )
65
- end
66
-
67
- def connect
68
- git_adapter = KnapsackPro::RepositoryAdapters::GitAdapter.new
69
- repository_adapter = KnapsackPro::RepositoryAdapterInitiator.call
70
-
71
- request_hash = {
72
- attempt_connect_to_queue: true,
73
- branch: KnapsackPro::Crypto::BranchEncryptor.call(repository_adapter.branch),
74
- build_author: git_adapter.build_author,
75
- can_initialize_queue: true,
76
- commit_authors: git_adapter.commit_authors,
77
- commit_hash: repository_adapter.commit_hash,
78
- fixed_queue_split: KnapsackPro::Config::Env.fixed_queue_split,
79
- node_build_id: KnapsackPro::Config::Env.ci_node_build_id,
80
- node_index: KnapsackPro::Config::Env.ci_node_index,
81
- node_total: KnapsackPro::Config::Env.ci_node_total,
82
- skip_pull: true,
83
- user_seat: KnapsackPro::Config::Env.masked_user_seat,
84
- }
85
-
86
- action_class.new(
87
- endpoint_path: '/v1/queues/queue',
88
- http_method: :post,
89
- request_hash: request_hash
90
- )
91
- end
92
39
  end
93
40
  end
94
41
  end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KnapsackPro
4
+ module Client
5
+ module API
6
+ module V2
7
+ class Queues < KnapsackPro::Client::API::V1::Base
8
+ CODE_ATTEMPT_CONNECT_TO_QUEUE_FAILED = 'ATTEMPT_CONNECT_TO_QUEUE_FAILED'
9
+
10
+ class << self
11
+ def queue(args)
12
+ request_hash = {
13
+ attempt_connect_to_queue: args.fetch(:attempt_connect_to_queue),
14
+ batch_index: args.fetch(:batch_index),
15
+ branch: args.fetch(:branch),
16
+ can_initialize_queue: args.fetch(:can_initialize_queue),
17
+ commit_hash: args.fetch(:commit_hash),
18
+ fixed_queue_split: KnapsackPro::Config::Env.fixed_queue_split_?,
19
+ node_index: args.fetch(:node_index),
20
+ node_total: args.fetch(:node_total),
21
+ node_uuid: KnapsackPro::Config::Env.node_uuid,
22
+ test_queue_id: KnapsackPro::Config::Env.test_queue_id,
23
+ user_seat: KnapsackPro::Config::Env.masked_user_seat
24
+ }
25
+
26
+ if request_hash[:can_initialize_queue] && !request_hash[:attempt_connect_to_queue]
27
+ git_adapter = KnapsackPro::RepositoryAdapters::GitAdapter.new
28
+
29
+ request_hash.merge!(
30
+ build_author: git_adapter.build_author,
31
+ commit_authors: git_adapter.commit_authors,
32
+ paths: args.fetch(:paths)
33
+ )
34
+ end
35
+
36
+ if !request_hash[:can_initialize_queue] && !request_hash[:attempt_connect_to_queue]
37
+ request_hash.merge!(
38
+ failed_paths: args.fetch(:failed_paths),
39
+ batch_id: args.fetch(:batch_id)
40
+ )
41
+ end
42
+
43
+ action_class.new(
44
+ endpoint_path: '/v2/queues/queue',
45
+ http_method: :post,
46
+ request_hash: request_hash
47
+ )
48
+ end
49
+
50
+ def connect
51
+ git_adapter = KnapsackPro::RepositoryAdapters::GitAdapter.new
52
+ repository_adapter = KnapsackPro::RepositoryAdapterInitiator.call
53
+
54
+ request_hash = {
55
+ attempt_connect_to_queue: true,
56
+ batch_index: 0,
57
+ branch: KnapsackPro::Crypto::BranchEncryptor.call(repository_adapter.branch),
58
+ build_author: git_adapter.build_author,
59
+ can_initialize_queue: true,
60
+ commit_authors: git_adapter.commit_authors,
61
+ commit_hash: repository_adapter.commit_hash,
62
+ fixed_queue_split: KnapsackPro::Config::Env.fixed_queue_split_?,
63
+ node_index: KnapsackPro::Config::Env.ci_node_index,
64
+ node_total: KnapsackPro::Config::Env.ci_node_total,
65
+ node_uuid: SecureRandom.uuid,
66
+ skip_pull: true,
67
+ test_queue_id: KnapsackPro::Config::Env.test_queue_id,
68
+ user_seat: KnapsackPro::Config::Env.masked_user_seat,
69
+ }
70
+
71
+ action_class.new(
72
+ endpoint_path: '/v2/queues/queue',
73
+ http_method: :post,
74
+ request_hash: request_hash
75
+ )
76
+ end
77
+
78
+ def initialize(paths)
79
+ git_adapter = KnapsackPro::RepositoryAdapters::GitAdapter.new
80
+ repository_adapter = KnapsackPro::RepositoryAdapterInitiator.call
81
+
82
+ request_hash = {
83
+ attempt_connect_to_queue: false,
84
+ batch_index: 0,
85
+ branch: KnapsackPro::Crypto::BranchEncryptor.call(repository_adapter.branch),
86
+ build_author: git_adapter.build_author,
87
+ can_initialize_queue: true,
88
+ commit_authors: git_adapter.commit_authors,
89
+ commit_hash: repository_adapter.commit_hash,
90
+ fixed_queue_split: KnapsackPro::Config::Env.fixed_queue_split_?,
91
+ node_index: KnapsackPro::Config::Env.ci_node_index,
92
+ node_total: KnapsackPro::Config::Env.ci_node_total,
93
+ node_uuid: SecureRandom.uuid,
94
+ skip_pull: true,
95
+ paths: KnapsackPro::Crypto::Encryptor.paths(paths),
96
+ test_queue_id: KnapsackPro::Config::Env.test_queue_id,
97
+ user_seat: KnapsackPro::Config::Env.masked_user_seat,
98
+ }
99
+
100
+ action_class.new(
101
+ endpoint_path: '/v2/queues/queue',
102
+ http_method: :post,
103
+ request_hash: request_hash
104
+ )
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -86,9 +86,9 @@ module KnapsackPro
86
86
  end
87
87
 
88
88
  def make_request(&block)
89
- retries ||= 0
89
+ attempt_count ||= 1
90
90
 
91
- @http_response = block.call
91
+ @http_response = block.call(attempt_count)
92
92
  @response_body = parse_response_body(http_response.body)
93
93
 
94
94
  request_uuid = http_response.header['X-Request-Id'] || 'N/A'
@@ -108,11 +108,11 @@ module KnapsackPro
108
108
  response_body
109
109
  rescue ServerError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EPIPE, EOFError,
110
110
  SocketError, Net::OpenTimeout, Net::ReadTimeout, OpenSSL::SSL::SSLError => e
111
- retries += 1
112
- log_diagnostics(e, retries)
113
- @http.set_debug_output(@http_debug_output) if retries == max_request_retries - 1
114
- if retries < max_request_retries
115
- backoff(retries)
111
+ attempt_count += 1
112
+ log_diagnostics(e, attempt_count)
113
+ @http.set_debug_output(@http_debug_output) if attempt_count == max_request_retries
114
+ if attempt_count <= max_request_retries
115
+ backoff(attempt_count)
116
116
  rotate_ip
117
117
  retry
118
118
  else
@@ -120,7 +120,7 @@ module KnapsackPro
120
120
  end
121
121
  end
122
122
 
123
- def log_diagnostics(error, retries)
123
+ def log_diagnostics(error, attempt_count)
124
124
  message = [
125
125
  action.http_method.to_s.upcase,
126
126
  endpoint_uri,
@@ -129,7 +129,7 @@ module KnapsackPro
129
129
  logger.warn(message)
130
130
  logger.warn('Request failed due to:')
131
131
  logger.warn(error.inspect)
132
- return if retries < max_request_retries
132
+ return if attempt_count < max_request_retries + 1
133
133
 
134
134
  error.backtrace.each { |line| logger.warn(line) }
135
135
  logger.warn('Net::HTTP debug output:')
@@ -156,8 +156,8 @@ module KnapsackPro
156
156
  end
157
157
  end
158
158
 
159
- def backoff(retries)
160
- wait = retries * REQUEST_RETRY_TIMEBOX
159
+ def backoff(attempt_count)
160
+ wait = (attempt_count - 1) * REQUEST_RETRY_TIMEBOX
161
161
  print_every = 2 # seconds
162
162
  (wait / print_every).ceil.times do |i|
163
163
  if i.zero?
@@ -204,8 +204,8 @@ module KnapsackPro
204
204
 
205
205
  def post
206
206
  build_http(endpoint_uri)
207
- make_request do
208
- @http.post(endpoint_uri.path, action.request_hash.to_json, json_headers)
207
+ make_request do |attempt_count|
208
+ @http.post(endpoint_uri.path, action.request_hash.merge(attempt_count: attempt_count).to_json, json_headers)
209
209
  end
210
210
  end
211
211
 
@@ -213,7 +213,7 @@ module KnapsackPro
213
213
  uri = endpoint_uri
214
214
  uri.query = URI.encode_www_form(action.request_hash)
215
215
  build_http(uri)
216
- make_request do
216
+ make_request do |_attempt_count|
217
217
  @http.get(uri, json_headers)
218
218
  end
219
219
  end
@@ -231,7 +231,7 @@ module KnapsackPro
231
231
  return 6 if KnapsackPro::Config::Env.regular_mode?
232
232
 
233
233
  # default number of attempts
234
- 3
234
+ 4
235
235
  end
236
236
  end
237
237
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/http"
5
+
6
+ module KnapsackPro
7
+ class RetryFailedTests
8
+ def initialize(branch)
9
+ @branch = branch || `git branch --show-current`.chomp
10
+ end
11
+
12
+ def call(runner_args)
13
+ $stderr.puts "Branch: #{branch}"
14
+
15
+ failed_paths = fetch_failed_paths
16
+ return ($stderr.puts "Nothing to run") if failed_paths.size.zero?
17
+
18
+ $stderr.puts "Retrying #{failed_paths.size} tests..."
19
+ exec Gem.bin_path("rspec-core", "rspec"), *(runner_args + failed_paths)
20
+ end
21
+
22
+ private
23
+
24
+ attr_accessor :branch
25
+
26
+ def fetch_failed_paths
27
+ headers = {
28
+ 'Accept' => 'application/json',
29
+ 'KNAPSACK-PRO-TEST-SUITE-TOKEN' => ENV.fetch("KNAPSACK_PRO_TEST_SUITE_TOKEN")
30
+ }
31
+
32
+ uri = URI.parse("#{KnapsackPro::Config::Env.endpoint}/v2/test_paths")
33
+ uri.query = URI.encode_www_form(branch: KnapsackPro::Crypto::BranchEncryptor.call(branch))
34
+
35
+ http = Net::HTTP.new(uri.host, uri.port)
36
+ http.use_ssl = (uri.scheme == 'https')
37
+ http.open_timeout = 5
38
+ http.read_timeout = 5
39
+
40
+ response = http.get(uri.request_uri, headers)
41
+ abort response.inspect if (300..).cover?(response.code.to_i)
42
+
43
+ parsed = JSON.parse(response.body)
44
+ abort parsed.inspect if parsed['errors']
45
+
46
+ parsed.fetch('failed_paths')
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module KnapsackPro
6
+ class Commands < Thor
7
+ map "queue:rspec" => :queue_rspec
8
+ map "queue:cucumber" => :queue_cucumber
9
+ map "queue:minitest" => :queue_minitest
10
+
11
+ def self.exit_on_failure?
12
+ true
13
+ end
14
+
15
+ desc "rspec ['arguments']", "Parallelize RSpec with Knapsack Pro in Regular Mode"
16
+ def rspec(arguments = "")
17
+ require "knapsack_pro"
18
+ KnapsackPro::Runners::RSpecRunner.run(arguments)
19
+ end
20
+
21
+ desc "queue:rspec ['arguments']", "Parallelize RSpec with Knapsack Pro in Queue Mode"
22
+ def queue_rspec(arguments = "")
23
+ require "knapsack_pro"
24
+ KnapsackPro::Runners::Queue::RSpecRunner.run(arguments)
25
+ end
26
+
27
+ desc "cucumber ['arguments']", "Parallelize Cucumber with Knapsack Pro in Regular Mode"
28
+ def cucumber(arguments = "")
29
+ require "knapsack_pro"
30
+ KnapsackPro::Runners::CucumberRunner.run(arguments)
31
+ end
32
+
33
+ desc "queue:cucumber ['arguments']", "Parallelize Cucumber with Knapsack Pro in Queue Mode"
34
+ def queue_cucumber(arguments = "")
35
+ require "knapsack_pro"
36
+ KnapsackPro::Runners::Queue::CucumberRunner.run(arguments)
37
+ end
38
+
39
+ desc "minitest ['arguments']", "Parallelize Minitest with Knapsack Pro in Regular Mode"
40
+ def minitest(arguments = "")
41
+ require "knapsack_pro"
42
+ KnapsackPro::Runners::MinitestRunner.run(arguments)
43
+ end
44
+
45
+ desc "queue:minitest ['arguments']", "Parallelize Minitest with Knapsack Pro in Queue Mode"
46
+ def queue_minitest(arguments = "")
47
+ require "knapsack_pro"
48
+ KnapsackPro::Runners::Queue::MinitestRunner.run(arguments)
49
+ end
50
+
51
+ desc "test_unit ['arguments']", "Parallelize TestUnit with Knapsack Pro in Regular Mode"
52
+ def test_unit(arguments = "")
53
+ require "knapsack_pro"
54
+ KnapsackPro::Runners::TestUnitRunner.run(arguments)
55
+ end
56
+
57
+ desc "spinach ['arguments']", "Parallelize Spinach with Knapsack Pro in Regular Mode"
58
+ def spinach(arguments = "")
59
+ require "knapsack_pro"
60
+ KnapsackPro::Runners::SpinachRunner.run(arguments)
61
+ end
62
+
63
+ desc "retry [-b] [-- test_runner_args]", "Retry RSpec the tests that failed on the previous Knapsack Pro run on BRANCH."
64
+ long_desc <<~DESC
65
+ \x5knapsack_pro retry
66
+ \x5knapsack_pro retry --branch feature -- --format progress
67
+ DESC
68
+ option :branch, type: :string, aliases: :b, desc: "Default: current branch."
69
+ def retry(*runner_args)
70
+ require_relative "./commands/retry_failed_tests"
71
+ KnapsackPro::RetryFailedTests.new(options[:branch]).call(runner_args)
72
+ end
73
+ end
74
+ end
@@ -49,6 +49,9 @@ module KnapsackPro
49
49
 
50
50
  nil
51
51
  end
52
+
53
+ def test_queue_id
54
+ end
52
55
  end
53
56
  end
54
57
  end
@@ -47,6 +47,10 @@ module KnapsackPro
47
47
  def ci_provider
48
48
  "Buildkite"
49
49
  end
50
+
51
+ def test_queue_id
52
+ node_build_id
53
+ end
50
54
  end
51
55
  end
52
56
  end
@@ -43,6 +43,12 @@ module KnapsackPro
43
43
  def ci_provider
44
44
  "CircleCI"
45
45
  end
46
+
47
+ def test_queue_id
48
+ # CIRCLE_PIPELINE_NUMBER does not exist in Circle, set it with:
49
+ # `CIRCLE_PIPELINE_NUMBER: << pipeline.number >>`
50
+ ENV['CIRCLE_PIPELINE_NUMBER']
51
+ end
46
52
  end
47
53
  end
48
54
  end
@@ -57,6 +57,10 @@ module KnapsackPro
57
57
  def ci_provider
58
58
  "GitHub Actions"
59
59
  end
60
+
61
+ def test_queue_id
62
+ node_build_id
63
+ end
60
64
  end
61
65
  end
62
66
  end
@@ -51,6 +51,10 @@ module KnapsackPro
51
51
  def ci_provider
52
52
  "Gitlab CI"
53
53
  end
54
+
55
+ def test_queue_id
56
+ ENV['CI_PIPELINE_ID']
57
+ end
54
58
  end
55
59
  end
56
60
  end
@@ -164,6 +164,12 @@ module KnapsackPro
164
164
  end
165
165
  end
166
166
 
167
+ def fixed_queue_split_?
168
+ return @fixed_queue_split_ if defined?(@fixed_queue_split_)
169
+
170
+ @fixed_queue_split_ = env_for('KNAPSACK_PRO_FIXED_QUEUE_SPLIT', :fixed_queue_split).to_s == "true"
171
+ end
172
+
167
173
  def fixed_queue_split?
168
174
  fixed_queue_split.to_s == 'true'
169
175
  end
@@ -260,6 +266,30 @@ module KnapsackPro
260
266
  ENV.fetch('KNAPSACK_PRO_FALLBACK_MODE_ERROR_EXIT_CODE', 1).to_i
261
267
  end
262
268
 
269
+ def test_queue_id
270
+ knapsack_env_name = 'KNAPSACK_PRO_TEST_QUEUE_ID'
271
+ knapsack_env_value = ENV[knapsack_env_name]
272
+
273
+ ci_env_value = detected_ci.new.test_queue_id
274
+
275
+ if !knapsack_env_value.nil? && !ci_env_value.nil? && knapsack_env_value != ci_env_value.to_s
276
+ warn("You have set the environment variable #{knapsack_env_name} to #{knapsack_env_value} which could be automatically determined from the CI environment as #{ci_env_value}.")
277
+ end
278
+
279
+ id = knapsack_env_value != nil ? knapsack_env_value : ci_env_value
280
+ return id unless id.nil?
281
+
282
+ triplet = [ci_node_total, branch, commit_hash]
283
+ return triplet.join('-') if triplet.all?
284
+
285
+ raise("Missing test_queue_id. See: #{KnapsackPro::Urls::KNAPSACK_PRO_TEST_QUEUE_ID}")
286
+ end
287
+
288
+ def node_uuid
289
+ env_name = 'KNAPSACK_PRO_NODE_UUID'
290
+ ENV[env_name] || raise("Missing environment variable #{env_name}. Please report this as a bug: #{KnapsackPro::Urls::SUPPORT}")
291
+ end
292
+
263
293
  private
264
294
 
265
295
  def required_env(env_name)
@@ -11,6 +11,14 @@ module KnapsackPro
11
11
  end
12
12
  end
13
13
 
14
+ def self.paths(paths)
15
+ if KnapsackPro::Config::Env.test_files_encrypted?
16
+ paths.map { |path| Digestor.salt_hexdigest(path) }
17
+ else
18
+ paths
19
+ end
20
+ end
21
+
14
22
  def initialize(test_files)
15
23
  @test_files = test_files
16
24
  end
@@ -23,17 +23,37 @@ module KnapsackPro
23
23
  @group = {}
24
24
  @paths = {}
25
25
  @suite_started = now
26
- @scheduled_paths = []
26
+ @batched_scheduled_paths = []
27
27
  @split_by_test_example_file_paths = Set.new
28
+ @current_batch_examples = []
28
29
  end
29
30
 
30
- def scheduled_paths=(scheduled_paths)
31
- @scheduled_paths = scheduled_paths
32
- @scheduled_paths.each do |path|
33
- if KnapsackPro::Adapters::RSpecAdapter.id_path?(path)
34
- file_path = KnapsackPro::Adapters::RSpecAdapter.parse_file_path(path)
35
- @split_by_test_example_file_paths << file_path
31
+ def current_batch_failed_paths
32
+ examples_by_file_path = @current_batch_examples
33
+ .group_by { |example| file_path_for(example) }
34
+
35
+ paths =
36
+ examples_by_file_path.flat_map do |file_path, examples|
37
+ failed_id_paths = examples.filter { |example| example.execution_result.status.to_s == "failed" }.map(&:id)
38
+
39
+ # Other nodes may have run some examples from this file, it's not safe to compact.
40
+ next failed_id_paths if rspec_split_by_test_example?(file_path)
41
+ next file_path if failed_id_paths.size == examples.size
42
+ failed_id_paths
36
43
  end
44
+
45
+ paths.map { |path| KnapsackPro::TestFileCleaner.clean(path) }
46
+ end
47
+
48
+ def schedule(paths)
49
+ @current_batch_examples = []
50
+ @batched_scheduled_paths << paths
51
+
52
+ paths.each do |path|
53
+ next unless KnapsackPro::Adapters::RSpecAdapter.id_path?(path)
54
+
55
+ file_path = KnapsackPro::Adapters::RSpecAdapter.parse_file_path(path)
56
+ @split_by_test_example_file_paths << KnapsackPro::TestFileCleaner.clean(file_path)
37
57
  end
38
58
  end
39
59
 
@@ -68,7 +88,7 @@ module KnapsackPro
68
88
  KnapsackPro::Adapters::RSpecAdapter.parse_file_path(example[:path])
69
89
  end
70
90
 
71
- missing = (@scheduled_paths - recorded_paths).each_with_object({}) do |path, object|
91
+ missing = (@batched_scheduled_paths.flatten - recorded_paths).each_with_object({}) do |path, object|
72
92
  object[path] = { path: path, time_execution: 0.0 }
73
93
  end
74
94
 
@@ -87,12 +107,12 @@ module KnapsackPro
87
107
  now - @suite_started
88
108
  end
89
109
 
90
- def unexecuted_test_files
110
+ def unexecuted_test_paths
91
111
  pending_paths = @paths.values
92
112
  .filter { |example| example[:time_execution] == 0.0 }
93
113
  .map { |example| example[:path] }
94
114
 
95
- not_run_paths = @scheduled_paths -
115
+ not_run_paths = @batched_scheduled_paths.flatten -
96
116
  @paths.values
97
117
  .map { |example| example[:path] }
98
118
 
@@ -123,6 +143,8 @@ module KnapsackPro
123
143
  path = path_for(example)
124
144
  return if path.nil?
125
145
 
146
+ @current_batch_examples << example
147
+
126
148
  time_execution = time_execution_for(example, started_at)
127
149
  if accumulator.key?(path)
128
150
  accumulator[path][:time_execution] += time_execution
@@ -10,10 +10,10 @@ module KnapsackPro
10
10
  .find { |f| f.class.to_s == "KnapsackPro::Formatters::TimeTracker" }
11
11
  end
12
12
 
13
- def self.unexecuted_test_files
13
+ def self.unexecuted_test_paths
14
14
  time_tracker = call
15
15
  return [] unless time_tracker
16
- time_tracker.unexecuted_test_files
16
+ time_tracker.unexecuted_test_paths
17
17
  end
18
18
  end
19
19
  end
@@ -24,7 +24,15 @@ module KnapsackPro
24
24
  end
25
25
 
26
26
  def test_files
27
- response.fetch('test_files')
27
+ if response.key?('test_files')
28
+ response.fetch('test_files')
29
+ else
30
+ response.fetch('paths').map { |path| { "path" => path, "time_execution" => nil } }
31
+ end
32
+ end
33
+
34
+ def id
35
+ response.fetch('batch_id', nil)
28
36
  end
29
37
 
30
38
  private
@@ -36,40 +44,38 @@ module KnapsackPro
36
44
  @test_suite = args.fetch(:test_suite)
37
45
  @ci_node_total = args.fetch(:ci_node_total)
38
46
  @ci_node_index = args.fetch(:ci_node_index)
39
- @ci_node_build_id = args.fetch(:ci_node_build_id)
40
47
  @repository_adapter = args.fetch(:repository_adapter)
41
48
  @fallback_mode = false
49
+ @batch_index = -1
50
+ @batch_id = nil
42
51
  end
43
52
 
44
- def test_file_paths(can_initialize_queue, executed_test_files, batch_uuid: SecureRandom.uuid)
53
+ def test_file_paths(can_initialize_queue, executed_test_files, batch_uuid: SecureRandom.uuid, time_tracker: nil)
54
+ @batch_index += 1
45
55
  return [] if @fallback_mode
46
56
 
47
- batch = pull_tests_from_queue(can_initialize_queue, batch_uuid)
57
+ batch = pull_tests_from_queue(can_initialize_queue, batch_uuid, time_tracker: time_tracker)
48
58
 
49
59
  return switch_to_fallback_mode(executed_test_files: executed_test_files) if batch.connection_failed?
50
60
  return normalize_test_files(batch.test_files) if batch.queue_exists?
51
61
 
52
62
  test_files_result = test_suite.calculate_test_files
53
63
 
54
- return try_initializing_queue(test_files_result.test_files, batch_uuid) if test_files_result.quick?
64
+ return try_initializing_queue(test_files_result.test_files, batch_uuid, time_tracker: time_tracker) if test_files_result.quick?
55
65
 
56
66
  # The tests to run were found slowly. By that time, the queue could have already been initialized by another CI node.
57
67
  # Attempt to pull tests from the queue to avoid the attempt to initialize the queue unnecessarily (queue initialization is an expensive request with a big test files payload).
58
- batch = pull_tests_from_queue(can_initialize_queue, batch_uuid)
68
+ batch = pull_tests_from_queue(can_initialize_queue, batch_uuid, time_tracker: time_tracker)
59
69
 
60
70
  return switch_to_fallback_mode(executed_test_files: executed_test_files) if batch.connection_failed?
61
71
  return normalize_test_files(batch.test_files) if batch.queue_exists?
62
72
 
63
- try_initializing_queue(test_files_result.test_files, batch_uuid)
73
+ try_initializing_queue(test_files_result.test_files, batch_uuid, time_tracker: time_tracker)
64
74
  end
65
75
 
66
76
  private
67
77
 
68
- attr_reader :test_suite,
69
- :ci_node_total,
70
- :ci_node_index,
71
- :ci_node_build_id,
72
- :repository_adapter
78
+ attr_reader :test_suite, :ci_node_total, :ci_node_index, :repository_adapter
73
79
 
74
80
  def encrypted_branch
75
81
  KnapsackPro::Crypto::BranchEncryptor.call(repository_adapter.branch)
@@ -80,7 +86,7 @@ module KnapsackPro
80
86
  KnapsackPro::TestFilePresenter.paths(decrypted_test_files)
81
87
  end
82
88
 
83
- def build_action(can_initialize_queue:, attempt_connect_to_queue:, batch_uuid:, test_files: nil)
89
+ def build_action_v1(can_initialize_queue:, attempt_connect_to_queue:, batch_uuid:, test_files: nil)
84
90
  if can_initialize_queue && !attempt_connect_to_queue
85
91
  raise 'Test files are required when initializing a new queue.' if test_files.nil?
86
92
  test_files = KnapsackPro::Crypto::Encryptor.call(test_files)
@@ -93,29 +99,89 @@ module KnapsackPro
93
99
  branch: encrypted_branch,
94
100
  node_total: ci_node_total,
95
101
  node_index: ci_node_index,
96
- node_build_id: ci_node_build_id,
97
102
  test_files: test_files,
98
103
  batch_uuid: batch_uuid
99
104
  )
100
105
  end
101
106
 
102
- def pull_tests_from_queue(can_initialize_queue, batch_uuid)
103
- action = build_action(can_initialize_queue: can_initialize_queue, attempt_connect_to_queue: can_initialize_queue, batch_uuid: batch_uuid)
107
+ def build_action_v2(can_initialize_queue:, attempt_connect_to_queue:, time_tracker:, paths: nil)
108
+ if can_initialize_queue && !attempt_connect_to_queue
109
+ raise 'Test files are required when initializing a new queue.' if paths.nil?
110
+ paths = KnapsackPro::Crypto::Encryptor.paths(paths)
111
+ end
112
+
113
+ KnapsackPro::Client::API::V2::Queues.queue(
114
+ can_initialize_queue: can_initialize_queue,
115
+ attempt_connect_to_queue: attempt_connect_to_queue,
116
+ commit_hash: repository_adapter.commit_hash,
117
+ branch: encrypted_branch,
118
+ node_total: ci_node_total,
119
+ node_index: ci_node_index,
120
+ paths: paths,
121
+ failed_paths: time_tracker.current_batch_failed_paths,
122
+ batch_index: @batch_index,
123
+ batch_id: @batch_id
124
+ )
125
+ end
126
+
127
+ def pull_tests_from_queue(can_initialize_queue, batch_uuid, time_tracker: nil)
128
+ if time_tracker.nil?
129
+ pull_tests_from_queue_v1(can_initialize_queue, batch_uuid)
130
+ else
131
+ pull_tests_from_queue_v2(can_initialize_queue, time_tracker)
132
+ end
133
+ end
134
+
135
+ def pull_tests_from_queue_v1(can_initialize_queue, batch_uuid)
136
+ action = build_action_v1(can_initialize_queue: can_initialize_queue, attempt_connect_to_queue: can_initialize_queue, batch_uuid: batch_uuid)
104
137
  connection = KnapsackPro::Client::Connection.new(action)
105
138
  response = connection.call
106
139
  Batch.new(connection, response)
107
140
  end
108
141
 
109
- def initialize_queue(tests_to_run, batch_uuid)
110
- action = build_action(can_initialize_queue: true, attempt_connect_to_queue: false, batch_uuid: batch_uuid, test_files: tests_to_run)
142
+ def pull_tests_from_queue_v2(can_initialize_queue, time_tracker)
143
+ action = build_action_v2(can_initialize_queue: can_initialize_queue, attempt_connect_to_queue: can_initialize_queue, time_tracker: time_tracker)
144
+ connection = KnapsackPro::Client::Connection.new(action)
145
+ response = connection.call
146
+ Batch.new(connection, response).tap do |batch|
147
+ @batch_id = batch.id unless batch.connection_failed?
148
+ end
149
+ end
150
+
151
+ def initialize_queue_v1(tests_to_run, batch_uuid)
152
+ action = build_action_v1(can_initialize_queue: true, attempt_connect_to_queue: false, batch_uuid: batch_uuid, test_files: tests_to_run)
111
153
  connection = KnapsackPro::Client::Connection.new(action)
112
154
  response = connection.call
113
155
  Batch.new(connection, response)
114
156
  end
115
157
 
116
- def try_initializing_queue(tests, batch_uuid)
117
- result = initialize_queue(tests, batch_uuid)
158
+ def initialize_queue_v2(paths, time_tracker)
159
+ action = build_action_v2(can_initialize_queue: true, attempt_connect_to_queue: false, paths: paths, time_tracker: time_tracker)
160
+ connection = KnapsackPro::Client::Connection.new(action)
161
+ response = connection.call
162
+ Batch.new(connection, response).tap do |batch|
163
+ @batch_id = batch.id unless batch.connection_failed?
164
+ end
165
+ end
166
+
167
+ def try_initializing_queue(tests, batch_uuid, time_tracker: nil)
168
+ if time_tracker.nil?
169
+ try_initializing_queue_v1(tests, batch_uuid)
170
+ else
171
+ paths = tests.map { |test| test.fetch("path") }
172
+ try_initializing_queue_v2(paths, time_tracker)
173
+ end
174
+ end
175
+
176
+ def try_initializing_queue_v1(tests, batch_uuid)
177
+ result = initialize_queue_v1(tests, batch_uuid)
178
+ return switch_to_fallback_mode(executed_test_files: []) if result.connection_failed?
179
+
180
+ normalize_test_files(result.test_files)
181
+ end
118
182
 
183
+ def try_initializing_queue_v2(paths, time_tracker)
184
+ result = initialize_queue_v2(paths, time_tracker)
119
185
  return switch_to_fallback_mode(executed_test_files: []) if result.connection_failed?
120
186
 
121
187
  normalize_test_files(result.test_files)
@@ -7,8 +7,7 @@ module KnapsackPro
7
7
  test_suite: test_suite,
8
8
  ci_node_total: env.ci_node_total,
9
9
  ci_node_index: env.ci_node_index,
10
- ci_node_build_id: env.ci_node_build_id,
11
- repository_adapter: repository_adapter,
10
+ repository_adapter: repository_adapter
12
11
  )
13
12
  end
14
13
  end
@@ -42,8 +42,7 @@ module KnapsackPro
42
42
  end
43
43
 
44
44
  if test_files.empty?
45
- KnapsackPro.logger.warn("No test files were executed on this CI node.")
46
- KnapsackPro.logger.debug("This CI node likely started work late after the test files were already executed by other CI nodes consuming the queue.")
45
+ KnapsackPro.logger.info("No tests were executed because the test queue is empty.")
47
46
  end
48
47
 
49
48
  measured_test_files = test_files
@@ -43,18 +43,39 @@ module KnapsackPro
43
43
  private
44
44
 
45
45
  def git_commit_authors
46
- if KnapsackPro::Config::Env.ci? && shallow_repository?
47
- command = 'git fetch --shallow-since "one month ago" --quiet 2>/dev/null'
48
- begin
49
- Timeout.timeout(5) do
50
- `#{command}`
51
- end
52
- rescue Timeout::Error
53
- KnapsackPro.logger.debug("Skip the `#{command}` command because it took too long.")
54
- end
46
+ git_unshallow if KnapsackPro::Config::Env.ci? && shallow_repository?
47
+ `git log --since "one month ago" 2>/dev/null | git shortlog --summary --email 2>/dev/null`
48
+ end
49
+
50
+ def git_unshallow
51
+ args = ['git', 'fetch', '--quiet', '--shallow-since', 'one month ago']
52
+
53
+ begin
54
+ pid = Process.spawn(*args, [:out, :err] => File::NULL)
55
+ rescue StandardError => e
56
+ KnapsackPro.logger.debug("Failed to unshallow (#{args.join(' ')}): #{e.message}")
57
+ return
55
58
  end
56
59
 
57
- `git log --since "one month ago" 2>/dev/null | git shortlog --summary --email 2>/dev/null`
60
+ begin
61
+ Timeout.timeout(5) { safe_waitpid(pid) }
62
+ rescue Timeout::Error
63
+ safe_kill(pid)
64
+ Timeout.timeout(1) { safe_waitpid(pid) } rescue Timeout::Error
65
+ KnapsackPro.logger.debug("Failed to unshallow (#{args.join(' ')}) in 5 seconds")
66
+ end
67
+ end
68
+
69
+ def safe_waitpid(pid)
70
+ Process.waitpid(pid)
71
+ rescue Errno::ECHILD
72
+ nil
73
+ end
74
+
75
+ def safe_kill(pid)
76
+ Process.kill('KILL', pid)
77
+ rescue Errno::ESRCH
78
+ nil
58
79
  end
59
80
 
60
81
  def git_build_author
@@ -23,7 +23,7 @@ module KnapsackPro
23
23
  private
24
24
 
25
25
  def test_queue_exists?
26
- action = KnapsackPro::Client::API::V1::Queues.connect
26
+ action = KnapsackPro::Client::API::V2::Queues.connect
27
27
  connection = KnapsackPro::Client::Connection.new(action)
28
28
  response = connection.call
29
29
 
@@ -53,7 +53,7 @@ module KnapsackPro
53
53
  exit 1
54
54
  end
55
55
 
56
- action = KnapsackPro::Client::API::V1::Queues.initialize(paths)
56
+ action = KnapsackPro::Client::API::V2::Queues.initialize(paths)
57
57
  connection = KnapsackPro::Client::Connection.new(action)
58
58
  response = connection.call
59
59
 
@@ -26,7 +26,8 @@ module KnapsackPro
26
26
  def test_file_paths(args)
27
27
  can_initialize_queue = args.fetch(:can_initialize_queue)
28
28
  executed_test_files = args.fetch(:executed_test_files)
29
- allocator.test_file_paths(can_initialize_queue, executed_test_files)
29
+ time_tracker = args.fetch(:time_tracker, nil)
30
+ allocator.test_file_paths(can_initialize_queue, executed_test_files, time_tracker: time_tracker)
30
31
  end
31
32
 
32
33
  def test_dir
@@ -13,6 +13,7 @@ module KnapsackPro
13
13
  KnapsackPro::Extensions::RSpecExtension.setup!
14
14
 
15
15
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_rspec
16
+ ENV['KNAPSACK_PRO_NODE_UUID'] = SecureRandom.uuid
16
17
 
17
18
  rspec_pure = KnapsackPro::Pure::Queue::RSpecPure.new
18
19
 
@@ -64,7 +65,7 @@ module KnapsackPro
64
65
  KnapsackPro.logger.error("Exception message: #{exception.message}")
65
66
  KnapsackPro.logger.error("Exception backtrace: #{exception.backtrace&.join("\n")}")
66
67
 
67
- message = @rspec_pure.exit_summary(unexecuted_test_files)
68
+ message = @rspec_pure.exit_summary(unexecuted_test_paths)
68
69
  KnapsackPro.logger.warn(message) if message
69
70
 
70
71
  exit_code = @rspec_pure.error_exit_code(@rspec_runner.knapsack__error_exit_code)
@@ -178,15 +179,14 @@ module KnapsackPro
178
179
  end
179
180
 
180
181
  def pull_tests_from_queue(can_initialize_queue: false)
182
+ time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
181
183
  test_file_paths = test_file_paths(
182
184
  can_initialize_queue: can_initialize_queue,
183
- executed_test_files: @node_test_file_paths
185
+ executed_test_files: @node_test_file_paths,
186
+ time_tracker: time_tracker
184
187
  )
185
188
  @node_test_file_paths += test_file_paths
186
-
187
- time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
188
- time_tracker.scheduled_paths = @node_test_file_paths
189
-
189
+ time_tracker.schedule(test_file_paths)
190
190
  test_file_paths
191
191
  end
192
192
 
@@ -225,8 +225,8 @@ module KnapsackPro
225
225
  end
226
226
  end
227
227
 
228
- def unexecuted_test_files
229
- KnapsackPro::Formatters::TimeTrackerFetcher.unexecuted_test_files
228
+ def unexecuted_test_paths
229
+ KnapsackPro::Formatters::TimeTrackerFetcher.unexecuted_test_paths
230
230
  end
231
231
  end
232
232
  end
@@ -21,7 +21,7 @@ module KnapsackPro
21
21
  end
22
22
 
23
23
  slow_id_paths = adapter_class.calculate_slow_id_paths
24
- test_files = adapter_class.concat_paths(all_test_files_to_run, slow_id_paths)
24
+ test_files = adapter_class.concat_test_files(all_test_files_to_run, slow_id_paths)
25
25
  @result = Result.new(test_files, false)
26
26
  end
27
27
 
@@ -18,7 +18,7 @@ module KnapsackPro
18
18
 
19
19
  INSTALLATION_GUIDE = "#{HOST}/perma/ruby/installation-guide"
20
20
 
21
- KNAPSACK_PRO_CI_NODE_BUILD_ID= "#{HOST}/perma/ruby/knapsack-pro-ci-node-build-id"
21
+ KNAPSACK_PRO_CI_NODE_BUILD_ID = "#{HOST}/perma/ruby/knapsack-pro-ci-node-build-id"
22
22
 
23
23
  QUEUE_MODE__CONNECTION_ERROR_WITH_FALLBACK_ENABLED_FALSE = "#{HOST}/perma/ruby/queue-mode-connection-error-with-fallback-enabled-false"
24
24
 
@@ -35,5 +35,7 @@ module KnapsackPro
35
35
  SPLIT_BY_TEST_EXAMPLES = "#{HOST}/perma/ruby/split-by-test-examples"
36
36
 
37
37
  TEST_UNIT__TEST_FILE_PATH_DETECTION = "#{HOST}/perma/ruby/test-unit-test-file-path-detection"
38
+
39
+ KNAPSACK_PRO_TEST_QUEUE_ID = "#{HOST}/perma/ruby/knapsack-pro-test-queue-id"
38
40
  end
39
41
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KnapsackPro
4
- VERSION = '9.2.2'
4
+ VERSION = '10.0.0'
5
5
  end
data/lib/knapsack_pro.rb CHANGED
@@ -33,7 +33,6 @@ require_relative 'knapsack_pro/client/api/action'
33
33
  require_relative 'knapsack_pro/client/api/v1/base'
34
34
  require_relative 'knapsack_pro/client/api/v1/build_distributions'
35
35
  require_relative 'knapsack_pro/client/api/v1/build_subsets'
36
- require_relative 'knapsack_pro/client/api/v1/queues'
37
36
  require_relative 'knapsack_pro/client/connection'
38
37
  require_relative 'knapsack_pro/repository_adapters/base_adapter'
39
38
  require_relative 'knapsack_pro/repository_adapters/env_adapter'
@@ -83,6 +82,20 @@ require_relative 'knapsack_pro/crypto/decryptor'
83
82
  require_relative 'knapsack_pro/crypto/digestor'
84
83
  require_relative 'knapsack_pro/pure/queue/rspec_pure'
85
84
 
85
+ module KnapsackPro
86
+ module Client
87
+ module API
88
+ module V1
89
+ autoload(:Queues, 'knapsack_pro/client/api/v1/queues')
90
+ end
91
+
92
+ module V2
93
+ autoload(:Queues, 'knapsack_pro/client/api/v2/queues')
94
+ end
95
+ end
96
+ end
97
+ end
98
+
86
99
  require 'knapsack_pro/railtie' if defined?(Rails::Railtie)
87
100
 
88
101
  module KnapsackPro
@@ -20,6 +20,7 @@ namespace :knapsack_pro do
20
20
 
21
21
  ENV.delete('SPEC_OPTS') # Ignore `SPEC_OPTS` to not affect the RSpec execution within this rake task
22
22
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_rspec
23
+ ENV['KNAPSACK_PRO_TEST_RUNNER'] = 'rspec'
23
24
 
24
25
  KnapsackPro::RSpec::TestQueueInitializer.new.call(args[:rspec_args].to_s)
25
26
  end
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knapsack_pro
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.2.2
4
+ version: 10.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ArturT
8
- bindir: bin
8
+ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: thor
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.4'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.4'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: bundler
28
42
  requirement: !ruby/object:Gem::Requirement
@@ -93,20 +107,6 @@ dependencies:
93
107
  - - "~>"
94
108
  - !ruby/object:Gem::Version
95
109
  version: '0'
96
- - !ruby/object:Gem::Dependency
97
- name: rspec
98
- requirement: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - "~>"
101
- - !ruby/object:Gem::Version
102
- version: '3.0'
103
- type: :development
104
- prerelease: false
105
- version_requirements: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - "~>"
108
- - !ruby/object:Gem::Version
109
- version: '3.0'
110
110
  - !ruby/object:Gem::Dependency
111
111
  name: rspec-its
112
112
  requirement: !ruby/object:Gem::Requirement
@@ -202,7 +202,7 @@ executables:
202
202
  extensions: []
203
203
  extra_rdoc_files: []
204
204
  files:
205
- - bin/knapsack_pro
205
+ - exe/knapsack_pro
206
206
  - lib/knapsack_pro.rb
207
207
  - lib/knapsack_pro/adapters/base_adapter.rb
208
208
  - lib/knapsack_pro/adapters/cucumber_adapter.rb
@@ -218,7 +218,10 @@ files:
218
218
  - lib/knapsack_pro/client/api/v1/build_distributions.rb
219
219
  - lib/knapsack_pro/client/api/v1/build_subsets.rb
220
220
  - lib/knapsack_pro/client/api/v1/queues.rb
221
+ - lib/knapsack_pro/client/api/v2/queues.rb
221
222
  - lib/knapsack_pro/client/connection.rb
223
+ - lib/knapsack_pro/commands.rb
224
+ - lib/knapsack_pro/commands/retry_failed_tests.rb
222
225
  - lib/knapsack_pro/config/ci/app_veyor.rb
223
226
  - lib/knapsack_pro/config/ci/base.rb
224
227
  - lib/knapsack_pro/config/ci/buildkite.rb
@@ -303,6 +306,7 @@ metadata:
303
306
  documentation_uri: https://docs.knapsackpro.com/knapsack_pro-ruby/guide/
304
307
  homepage_uri: https://knapsackpro.com
305
308
  source_code_uri: https://github.com/KnapsackPro/knapsack_pro-ruby
309
+ rubygems_mfa_required: 'true'
306
310
  rdoc_options: []
307
311
  require_paths:
308
312
  - lib
@@ -317,7 +321,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
317
321
  - !ruby/object:Gem::Version
318
322
  version: '0'
319
323
  requirements: []
320
- rubygems_version: 4.0.3
324
+ rubygems_version: 4.0.6
321
325
  specification_version: 4
322
326
  summary: Knapsack Pro splits tests across parallel CI nodes and ensures each parallel
323
327
  job finish work at a similar time.
data/bin/knapsack_pro DELETED
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require_relative '../lib/knapsack_pro'
4
-
5
- runner = ARGV[0]
6
- arguments = ARGV[1]
7
-
8
- MAP = {
9
- 'rspec' => KnapsackPro::Runners::RSpecRunner,
10
- 'queue:rspec' => KnapsackPro::Runners::Queue::RSpecRunner,
11
- 'cucumber' => KnapsackPro::Runners::CucumberRunner,
12
- 'queue:cucumber' => KnapsackPro::Runners::Queue::CucumberRunner,
13
- 'minitest' => KnapsackPro::Runners::MinitestRunner,
14
- 'queue:minitest' => KnapsackPro::Runners::Queue::MinitestRunner,
15
- 'test_unit' => KnapsackPro::Runners::TestUnitRunner,
16
- 'spinach' => KnapsackPro::Runners::SpinachRunner,
17
- }
18
-
19
- runner_class = MAP[runner]
20
-
21
- if runner_class
22
- runner_class.run(arguments)
23
- else
24
- raise 'Undefined runner. Please provide runner name and optional arguments, for instance: knapsack_pro rspec "--color --profile"'
25
- end