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.
- checksums.yaml +4 -4
- data/exe/knapsack_pro +5 -0
- data/lib/knapsack_pro/adapters/rspec_adapter.rb +7 -3
- data/lib/knapsack_pro/build_distribution_fetcher.rb +1 -1
- data/lib/knapsack_pro/client/api/action.rb +1 -9
- data/lib/knapsack_pro/client/api/v1/queues.rb +0 -53
- data/lib/knapsack_pro/client/api/v2/queues.rb +111 -0
- data/lib/knapsack_pro/client/connection.rb +15 -15
- data/lib/knapsack_pro/commands/retry_failed_tests.rb +49 -0
- data/lib/knapsack_pro/commands.rb +74 -0
- data/lib/knapsack_pro/config/ci/base.rb +3 -0
- data/lib/knapsack_pro/config/ci/buildkite.rb +4 -0
- data/lib/knapsack_pro/config/ci/circle.rb +6 -0
- data/lib/knapsack_pro/config/ci/github_actions.rb +4 -0
- data/lib/knapsack_pro/config/ci/gitlab_ci.rb +4 -0
- data/lib/knapsack_pro/config/env.rb +30 -0
- data/lib/knapsack_pro/crypto/encryptor.rb +8 -0
- data/lib/knapsack_pro/formatters/time_tracker.rb +32 -10
- data/lib/knapsack_pro/formatters/time_tracker_fetcher.rb +2 -2
- data/lib/knapsack_pro/queue_allocator.rb +86 -20
- data/lib/knapsack_pro/queue_allocator_builder.rb +1 -2
- data/lib/knapsack_pro/report.rb +1 -2
- data/lib/knapsack_pro/repository_adapters/git_adapter.rb +31 -10
- data/lib/knapsack_pro/rspec/test_queue_initializer.rb +2 -2
- data/lib/knapsack_pro/runners/queue/base_runner.rb +2 -1
- data/lib/knapsack_pro/runners/queue/rspec_runner.rb +8 -8
- data/lib/knapsack_pro/test_suite.rb +1 -1
- data/lib/knapsack_pro/urls.rb +3 -1
- data/lib/knapsack_pro/version.rb +1 -1
- data/lib/knapsack_pro.rb +14 -1
- data/lib/tasks/queue/rspec.rake +1 -0
- metadata +22 -18
- data/bin/knapsack_pro +0 -25
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 142cab6d40b7b31270887dd59d36daa249813769e01ee5a83aba4b78dc4cea1d
|
|
4
|
+
data.tar.gz: ee60df331664ef4bac5e3aa7906cdf41f4a5c9c69a5ebada6c542a6dc2780313
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5591eb67604230bd3f33cbc82f0662b8dd0a289834166760262ca44df22ae83651599f9294faa3a31e31c25cdc743a1b3870829bbac429c15364bbfdb28349dc
|
|
7
|
+
data.tar.gz: 3a248092779d5459d8776b94963b745f798cd55d57137764aa54b9a58ba9688ca75f349c35e0c165c6e2c8452803caa13b6cb0f5f73527e76d58354a1f364d2d
|
data/exe/knapsack_pro
ADDED
|
@@ -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
|
-
|
|
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.
|
|
145
|
+
time_tracker.schedule(KnapsackPro::Adapters::RSpecAdapter.scheduled_paths)
|
|
142
146
|
end
|
|
143
147
|
end
|
|
144
148
|
end
|
|
@@ -3,15 +3,7 @@
|
|
|
3
3
|
module KnapsackPro
|
|
4
4
|
module Client
|
|
5
5
|
module API
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
log_diagnostics(e,
|
|
113
|
-
@http.set_debug_output(@http_debug_output) if
|
|
114
|
-
if
|
|
115
|
-
backoff(
|
|
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,
|
|
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
|
|
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(
|
|
160
|
-
wait =
|
|
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
|
-
|
|
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
|
|
@@ -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
|
|
@@ -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
|
-
@
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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 = (@
|
|
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
|
|
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 = @
|
|
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.
|
|
13
|
+
def self.unexecuted_test_paths
|
|
14
14
|
time_tracker = call
|
|
15
15
|
return [] unless time_tracker
|
|
16
|
-
time_tracker.
|
|
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.
|
|
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
|
|
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
|
|
103
|
-
|
|
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
|
|
110
|
-
action =
|
|
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
|
|
117
|
-
|
|
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
|
-
|
|
11
|
-
repository_adapter: repository_adapter,
|
|
10
|
+
repository_adapter: repository_adapter
|
|
12
11
|
)
|
|
13
12
|
end
|
|
14
13
|
end
|
data/lib/knapsack_pro/report.rb
CHANGED
|
@@ -42,8 +42,7 @@ module KnapsackPro
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
if test_files.empty?
|
|
45
|
-
KnapsackPro.logger.
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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::
|
|
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::
|
|
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
|
-
|
|
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(
|
|
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
|
|
229
|
-
KnapsackPro::Formatters::TimeTrackerFetcher.
|
|
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.
|
|
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
|
|
data/lib/knapsack_pro/urls.rb
CHANGED
|
@@ -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
|
data/lib/knapsack_pro/version.rb
CHANGED
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
|
data/lib/tasks/queue/rspec.rake
CHANGED
|
@@ -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:
|
|
4
|
+
version: 10.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ArturT
|
|
8
|
-
bindir:
|
|
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
|
-
-
|
|
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.
|
|
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
|