knapsack_pro 10.0.0 → 11.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/lib/knapsack_pro/adapters/base_adapter.rb +6 -6
- data/lib/knapsack_pro/adapters/minitest_adapter.rb +3 -3
- data/lib/knapsack_pro/adapters/rspec_adapter.rb +3 -3
- data/lib/knapsack_pro/client/api/v1/build_distributions.rb +0 -29
- data/lib/knapsack_pro/client/api/v2/queues.rb +2 -4
- data/lib/knapsack_pro/client/connection.rb +25 -12
- data/lib/knapsack_pro/commands.rb +8 -8
- data/lib/knapsack_pro/config/ci/app_veyor.rb +0 -8
- data/lib/knapsack_pro/config/ci/base.rb +0 -7
- data/lib/knapsack_pro/config/ci/buildkite.rb +1 -9
- data/lib/knapsack_pro/config/ci/circle.rb +0 -8
- data/lib/knapsack_pro/config/ci/cirrus_ci.rb +0 -8
- data/lib/knapsack_pro/config/ci/codefresh.rb +0 -8
- data/lib/knapsack_pro/config/ci/codeship.rb +0 -8
- data/lib/knapsack_pro/config/ci/github_actions.rb +2 -10
- data/lib/knapsack_pro/config/ci/gitlab_ci.rb +0 -9
- data/lib/knapsack_pro/config/ci/heroku.rb +1 -9
- data/lib/knapsack_pro/config/ci/semaphore.rb +0 -8
- data/lib/knapsack_pro/config/ci/semaphore2.rb +0 -8
- data/lib/knapsack_pro/config/ci/travis.rb +0 -8
- data/lib/knapsack_pro/config/env.rb +12 -47
- data/lib/knapsack_pro/crypto/branch_encryptor.rb +4 -5
- data/lib/knapsack_pro/extensions/rspec_extension.rb +1 -1
- data/lib/knapsack_pro/formatters/time_tracker.rb +3 -1
- data/lib/knapsack_pro/queue_allocator.rb +40 -93
- data/lib/knapsack_pro/regular_allocator.rb +41 -20
- data/lib/knapsack_pro/report.rb +37 -8
- data/lib/knapsack_pro/repository_adapters/git_adapter.rb +10 -2
- data/lib/knapsack_pro/runners/cucumber_runner.rb +2 -1
- data/lib/knapsack_pro/runners/minitest_runner.rb +2 -1
- data/lib/knapsack_pro/runners/queue/base_runner.rb +17 -8
- data/lib/knapsack_pro/runners/queue/cucumber_runner.rb +1 -1
- data/lib/knapsack_pro/runners/queue/minitest_runner.rb +1 -1
- data/lib/knapsack_pro/runners/queue/rspec_runner.rb +3 -4
- data/lib/knapsack_pro/runners/rspec_runner.rb +2 -1
- data/lib/knapsack_pro/runners/spinach_runner.rb +2 -1
- data/lib/knapsack_pro/runners/test_unit_runner.rb +2 -1
- data/lib/knapsack_pro/tracker.rb +2 -4
- data/lib/knapsack_pro/urls.rb +4 -10
- data/lib/knapsack_pro/version.rb +1 -1
- data/lib/tasks/queue/rspec.rake +5 -1
- metadata +2 -3
- data/lib/knapsack_pro/client/api/v1/queues.rb +0 -44
|
@@ -20,12 +20,6 @@ module KnapsackPro
|
|
|
20
20
|
@ci_node_index ||= (env_for('KNAPSACK_PRO_CI_NODE_INDEX', :node_index) || 0).to_i
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def ci_node_build_id
|
|
24
|
-
env_name = 'KNAPSACK_PRO_CI_NODE_BUILD_ID'
|
|
25
|
-
env_for(env_name, :node_build_id) ||
|
|
26
|
-
raise("Missing environment variable #{env_name}. Read more at #{KnapsackPro::Urls::KNAPSACK_PRO_CI_NODE_BUILD_ID}")
|
|
27
|
-
end
|
|
28
|
-
|
|
29
23
|
def ci_node_retry_count
|
|
30
24
|
(env_for('KNAPSACK_PRO_CI_NODE_RETRY_COUNT', :node_retry_count) || 0).to_i
|
|
31
25
|
end
|
|
@@ -95,12 +89,12 @@ module KnapsackPro
|
|
|
95
89
|
ENV['KNAPSACK_PRO_REPOSITORY_ADAPTER']
|
|
96
90
|
end
|
|
97
91
|
|
|
98
|
-
def
|
|
99
|
-
ENV['
|
|
92
|
+
def single_batch_mode?
|
|
93
|
+
ENV['KNAPSACK_PRO_SINGLE_BATCH_MODE_ENABLED'] == 'true'
|
|
100
94
|
end
|
|
101
95
|
|
|
102
|
-
def
|
|
103
|
-
ENV['
|
|
96
|
+
def normal_mode?
|
|
97
|
+
ENV['KNAPSACK_PRO_NORMAL_MODE_ENABLED'] == 'true'
|
|
104
98
|
end
|
|
105
99
|
|
|
106
100
|
def queue_id
|
|
@@ -143,35 +137,8 @@ module KnapsackPro
|
|
|
143
137
|
ENV.fetch('KNAPSACK_PRO_ENDPOINT', 'https://api.knapsackpro.com')
|
|
144
138
|
end
|
|
145
139
|
|
|
146
|
-
def
|
|
147
|
-
ENV.fetch('
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def fixed_test_suite_split?
|
|
151
|
-
fixed_test_suite_split.to_s == 'true'
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def fixed_queue_split
|
|
155
|
-
@fixed_queue_split ||= begin
|
|
156
|
-
env_name = 'KNAPSACK_PRO_FIXED_QUEUE_SPLIT'
|
|
157
|
-
computed = env_for(env_name, :fixed_queue_split).to_s
|
|
158
|
-
|
|
159
|
-
if !ENV.key?(env_name)
|
|
160
|
-
KnapsackPro.logger.info("#{env_name} is not set. Using default value: #{computed}. Learn more at #{KnapsackPro::Urls::FIXED_QUEUE_SPLIT}")
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
computed
|
|
164
|
-
end
|
|
165
|
-
end
|
|
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
|
-
|
|
173
|
-
def fixed_queue_split?
|
|
174
|
-
fixed_queue_split.to_s == 'true'
|
|
140
|
+
def single_batch?
|
|
141
|
+
ENV.fetch('KNAPSACK_PRO_SINGLE_BATCH', 'false') == 'true'
|
|
175
142
|
end
|
|
176
143
|
|
|
177
144
|
def cucumber_queue_prefix
|
|
@@ -269,8 +236,11 @@ module KnapsackPro
|
|
|
269
236
|
def test_queue_id
|
|
270
237
|
knapsack_env_name = 'KNAPSACK_PRO_TEST_QUEUE_ID'
|
|
271
238
|
knapsack_env_value = ENV[knapsack_env_name]
|
|
239
|
+
if knapsack_env_value == '' || knapsack_env_value&.match?(/[^a-z0-9\-_]+/i)
|
|
240
|
+
raise "Invalid test_queue_id `#{knapsack_env_value}`: Only ASCII letters, numbers, hyphens (-), and underscores (_) are allowed."
|
|
241
|
+
end
|
|
272
242
|
|
|
273
|
-
ci_env_value = detected_ci.new.test_queue_id
|
|
243
|
+
ci_env_value = detected_ci.new.test_queue_id&.gsub(/[^a-z0-9\-_]+/i, "-")
|
|
274
244
|
|
|
275
245
|
if !knapsack_env_value.nil? && !ci_env_value.nil? && knapsack_env_value != ci_env_value.to_s
|
|
276
246
|
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}.")
|
|
@@ -279,17 +249,12 @@ module KnapsackPro
|
|
|
279
249
|
id = knapsack_env_value != nil ? knapsack_env_value : ci_env_value
|
|
280
250
|
return id unless id.nil?
|
|
281
251
|
|
|
282
|
-
triplet = [ci_node_total, branch, commit_hash]
|
|
283
|
-
return triplet.join('
|
|
252
|
+
triplet = [ci_node_total, KnapsackPro::Crypto::BranchEncryptor.call(branch), commit_hash]
|
|
253
|
+
return triplet.join('_').gsub(/[^a-z0-9\-_]+/i, "-") if triplet.all?
|
|
284
254
|
|
|
285
255
|
raise("Missing test_queue_id. See: #{KnapsackPro::Urls::KNAPSACK_PRO_TEST_QUEUE_ID}")
|
|
286
256
|
end
|
|
287
257
|
|
|
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
|
-
|
|
293
258
|
private
|
|
294
259
|
|
|
295
260
|
def required_env(env_name)
|
|
@@ -22,11 +22,10 @@ module KnapsackPro
|
|
|
22
22
|
]
|
|
23
23
|
|
|
24
24
|
def self.call(branch)
|
|
25
|
-
if
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
end
|
|
25
|
+
return branch if branch.nil?
|
|
26
|
+
return branch unless KnapsackPro::Config::Env.branch_encrypted?
|
|
27
|
+
|
|
28
|
+
new(branch).call
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
def initialize(branch)
|
|
@@ -77,7 +77,7 @@ module KnapsackPro
|
|
|
77
77
|
# Based on:
|
|
78
78
|
# https://github.com/rspec/rspec-core/blob/f8c8880dabd8f0544a6f91d8d4c857c1bd8df903/lib/rspec/core/runner.rb#L113
|
|
79
79
|
#
|
|
80
|
-
# Ignore `configuration.fail_if_no_examples
|
|
80
|
+
# Ignore `configuration.fail_if_no_examples`:
|
|
81
81
|
# * a late CI node, started after all tests were executed by other nodes, is expected to receive an empty batch
|
|
82
82
|
# * a batch could contain tests with no examples (e.g. commented out)
|
|
83
83
|
#
|
|
@@ -35,10 +35,12 @@ module KnapsackPro
|
|
|
35
35
|
paths =
|
|
36
36
|
examples_by_file_path.flat_map do |file_path, examples|
|
|
37
37
|
failed_id_paths = examples.filter { |example| example.execution_result.status.to_s == "failed" }.map(&:id)
|
|
38
|
-
|
|
38
|
+
next [] if failed_id_paths.none?
|
|
39
|
+
next file_path if KnapsackPro::Config::Env.test_files_encrypted?
|
|
39
40
|
# Other nodes may have run some examples from this file, it's not safe to compact.
|
|
40
41
|
next failed_id_paths if rspec_split_by_test_example?(file_path)
|
|
41
42
|
next file_path if failed_id_paths.size == examples.size
|
|
43
|
+
|
|
42
44
|
failed_id_paths
|
|
43
45
|
end
|
|
44
46
|
|
|
@@ -14,7 +14,7 @@ module KnapsackPro
|
|
|
14
14
|
|
|
15
15
|
def queue_exists?
|
|
16
16
|
raise "Connection failed. Please report this as a bug: #{KnapsackPro::Urls::SUPPORT}" if connection_failed?
|
|
17
|
-
return false if connection.api_code == KnapsackPro::Client::API::
|
|
17
|
+
return false if connection.api_code == KnapsackPro::Client::API::V2::Queues::CODE_ATTEMPT_CONNECT_TO_QUEUE_FAILED
|
|
18
18
|
|
|
19
19
|
true
|
|
20
20
|
end
|
|
@@ -24,11 +24,11 @@ module KnapsackPro
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def test_files
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
paths.map { |path| { "path" => path, "time_execution" => nil } }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def paths
|
|
31
|
+
response.fetch('paths')
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def id
|
|
@@ -47,30 +47,31 @@ module KnapsackPro
|
|
|
47
47
|
@repository_adapter = args.fetch(:repository_adapter)
|
|
48
48
|
@fallback_mode = false
|
|
49
49
|
@batch_index = -1
|
|
50
|
-
@
|
|
50
|
+
@node_uuid = SecureRandom.uuid
|
|
51
|
+
@current_batch = nil
|
|
51
52
|
end
|
|
52
53
|
|
|
53
|
-
def test_file_paths(can_initialize_queue, executed_test_files,
|
|
54
|
+
def test_file_paths(can_initialize_queue, executed_test_files, time_tracker: nil)
|
|
54
55
|
@batch_index += 1
|
|
55
56
|
return [] if @fallback_mode
|
|
56
57
|
|
|
57
|
-
batch = pull_tests_from_queue(can_initialize_queue,
|
|
58
|
+
batch = pull_tests_from_queue(can_initialize_queue, time_tracker: time_tracker)
|
|
58
59
|
|
|
59
60
|
return switch_to_fallback_mode(executed_test_files: executed_test_files) if batch.connection_failed?
|
|
60
61
|
return normalize_test_files(batch.test_files) if batch.queue_exists?
|
|
61
62
|
|
|
62
63
|
test_files_result = test_suite.calculate_test_files
|
|
63
64
|
|
|
64
|
-
return try_initializing_queue(test_files_result.test_files
|
|
65
|
+
return try_initializing_queue(test_files_result.test_files.map { |test| test.fetch("path") }, time_tracker: time_tracker) if test_files_result.quick?
|
|
65
66
|
|
|
66
67
|
# The tests to run were found slowly. By that time, the queue could have already been initialized by another CI node.
|
|
67
68
|
# 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).
|
|
68
|
-
batch = pull_tests_from_queue(can_initialize_queue,
|
|
69
|
+
batch = pull_tests_from_queue(can_initialize_queue, time_tracker: time_tracker)
|
|
69
70
|
|
|
70
71
|
return switch_to_fallback_mode(executed_test_files: executed_test_files) if batch.connection_failed?
|
|
71
72
|
return normalize_test_files(batch.test_files) if batch.queue_exists?
|
|
72
73
|
|
|
73
|
-
try_initializing_queue(test_files_result.test_files
|
|
74
|
+
try_initializing_queue(test_files_result.test_files.map { |test| test.fetch("path") }, time_tracker: time_tracker)
|
|
74
75
|
end
|
|
75
76
|
|
|
76
77
|
private
|
|
@@ -86,102 +87,51 @@ module KnapsackPro
|
|
|
86
87
|
KnapsackPro::TestFilePresenter.paths(decrypted_test_files)
|
|
87
88
|
end
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
raise 'Test files are required when initializing a new queue.' if test_files.nil?
|
|
92
|
-
test_files = KnapsackPro::Crypto::Encryptor.call(test_files)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
KnapsackPro::Client::API::V1::Queues.queue(
|
|
96
|
-
can_initialize_queue: can_initialize_queue,
|
|
97
|
-
attempt_connect_to_queue: attempt_connect_to_queue,
|
|
98
|
-
commit_hash: repository_adapter.commit_hash,
|
|
99
|
-
branch: encrypted_branch,
|
|
100
|
-
node_total: ci_node_total,
|
|
101
|
-
node_index: ci_node_index,
|
|
102
|
-
test_files: test_files,
|
|
103
|
-
batch_uuid: batch_uuid
|
|
104
|
-
)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def build_action_v2(can_initialize_queue:, attempt_connect_to_queue:, time_tracker:, paths: nil)
|
|
90
|
+
# time_tracker is nil for non-RSpec queue runners
|
|
91
|
+
def build_action(can_initialize_queue:, attempt_connect_to_queue:, time_tracker:, paths: nil)
|
|
108
92
|
if can_initialize_queue && !attempt_connect_to_queue
|
|
109
93
|
raise 'Test files are required when initializing a new queue.' if paths.nil?
|
|
110
94
|
paths = KnapsackPro::Crypto::Encryptor.paths(paths)
|
|
111
95
|
end
|
|
112
96
|
|
|
97
|
+
failed_paths =
|
|
98
|
+
if time_tracker
|
|
99
|
+
KnapsackPro::Crypto::Encryptor.paths(time_tracker.current_batch_failed_paths)
|
|
100
|
+
elsif @batch_index.positive?
|
|
101
|
+
KnapsackPro::Crypto::Encryptor.paths(@current_batch.paths)
|
|
102
|
+
end
|
|
103
|
+
|
|
113
104
|
KnapsackPro::Client::API::V2::Queues.queue(
|
|
114
|
-
can_initialize_queue: can_initialize_queue,
|
|
115
105
|
attempt_connect_to_queue: attempt_connect_to_queue,
|
|
116
|
-
|
|
106
|
+
batch_id: @current_batch&.id,
|
|
107
|
+
batch_index: @batch_index,
|
|
117
108
|
branch: encrypted_branch,
|
|
118
|
-
|
|
109
|
+
can_initialize_queue: can_initialize_queue,
|
|
110
|
+
commit_hash: repository_adapter.commit_hash,
|
|
111
|
+
failed_paths: failed_paths,
|
|
119
112
|
node_index: ci_node_index,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
batch_id: @batch_id
|
|
113
|
+
node_total: ci_node_total,
|
|
114
|
+
node_uuid: @node_uuid,
|
|
115
|
+
paths: paths
|
|
124
116
|
)
|
|
125
117
|
end
|
|
126
118
|
|
|
127
|
-
def pull_tests_from_queue(can_initialize_queue,
|
|
128
|
-
|
|
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)
|
|
137
|
-
connection = KnapsackPro::Client::Connection.new(action)
|
|
138
|
-
response = connection.call
|
|
139
|
-
Batch.new(connection, response)
|
|
140
|
-
end
|
|
141
|
-
|
|
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)
|
|
119
|
+
def pull_tests_from_queue(can_initialize_queue, time_tracker: nil)
|
|
120
|
+
action = build_action(can_initialize_queue: can_initialize_queue, attempt_connect_to_queue: can_initialize_queue, time_tracker: time_tracker)
|
|
153
121
|
connection = KnapsackPro::Client::Connection.new(action)
|
|
154
122
|
response = connection.call
|
|
155
|
-
Batch.new(connection, response)
|
|
123
|
+
@current_batch = Batch.new(connection, response)
|
|
156
124
|
end
|
|
157
125
|
|
|
158
|
-
def
|
|
159
|
-
action =
|
|
126
|
+
def initialize_queue(paths, time_tracker: nil)
|
|
127
|
+
action = build_action(can_initialize_queue: true, attempt_connect_to_queue: false, paths: paths, time_tracker: time_tracker)
|
|
160
128
|
connection = KnapsackPro::Client::Connection.new(action)
|
|
161
129
|
response = connection.call
|
|
162
|
-
Batch.new(connection, response)
|
|
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
|
|
130
|
+
@current_batch = Batch.new(connection, response)
|
|
174
131
|
end
|
|
175
132
|
|
|
176
|
-
def
|
|
177
|
-
result =
|
|
178
|
-
return switch_to_fallback_mode(executed_test_files: []) if result.connection_failed?
|
|
179
|
-
|
|
180
|
-
normalize_test_files(result.test_files)
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def try_initializing_queue_v2(paths, time_tracker)
|
|
184
|
-
result = initialize_queue_v2(paths, time_tracker)
|
|
133
|
+
def try_initializing_queue(paths, time_tracker: nil)
|
|
134
|
+
result = initialize_queue(paths, time_tracker: time_tracker)
|
|
185
135
|
return switch_to_fallback_mode(executed_test_files: []) if result.connection_failed?
|
|
186
136
|
|
|
187
137
|
normalize_test_files(result.test_files)
|
|
@@ -191,14 +141,11 @@ module KnapsackPro
|
|
|
191
141
|
@fallback_mode = true
|
|
192
142
|
|
|
193
143
|
if !KnapsackPro::Config::Env.fallback_mode_enabled?
|
|
194
|
-
message = "Fallback Mode was disabled with KNAPSACK_PRO_FALLBACK_MODE_ENABLED=false. Please restart this CI node to retry tests. Most likely Fallback Mode was disabled due to #{KnapsackPro::Urls::
|
|
144
|
+
message = "Fallback Mode was disabled with KNAPSACK_PRO_FALLBACK_MODE_ENABLED=false. Please restart this CI node to retry tests. Most likely Fallback Mode was disabled due to #{KnapsackPro::Urls::NORMAL_MODE__CONNECTION_ERROR_WITH_FALLBACK_ENABLED_FALSE}"
|
|
195
145
|
KnapsackPro.logger.error(message)
|
|
196
146
|
raise FallbackModeError.new(message)
|
|
197
147
|
elsif KnapsackPro::Config::Env.ci_node_retry_count > 0
|
|
198
|
-
message = "knapsack_pro gem could not connect to Knapsack Pro API and the Fallback Mode cannot be used this time. Running tests in Fallback Mode are not allowed for retried parallel CI node to avoid running the wrong set of tests. Please manually retry this parallel job on your CI server then knapsack_pro gem will try to connect to Knapsack Pro API again and will run a correct set of tests for this CI node. Learn more #{KnapsackPro::Urls::
|
|
199
|
-
unless KnapsackPro::Config::Env.fixed_queue_split?
|
|
200
|
-
message += " Please ensure you have set KNAPSACK_PRO_FIXED_QUEUE_SPLIT=true to allow Knapsack Pro API remember the recorded CI node tests so when you retry failed tests on the CI node then the same set of tests will be executed. See more #{KnapsackPro::Urls::FIXED_QUEUE_SPLIT}"
|
|
201
|
-
end
|
|
148
|
+
message = "knapsack_pro gem could not connect to Knapsack Pro API and the Fallback Mode cannot be used this time. Running tests in Fallback Mode are not allowed for retried parallel CI node to avoid running the wrong set of tests. Please manually retry this parallel job on your CI server then knapsack_pro gem will try to connect to Knapsack Pro API again and will run a correct set of tests for this CI node. Learn more #{KnapsackPro::Urls::NORMAL_MODE__CONNECTION_ERROR_WITH_FALLBACK_ENABLED_TRUE_AND_POSITIVE_RETRY_COUNT}"
|
|
202
149
|
KnapsackPro.logger.error(message)
|
|
203
150
|
raise FallbackModeError.new(message)
|
|
204
151
|
else
|
|
@@ -12,7 +12,7 @@ module KnapsackPro
|
|
|
12
12
|
|
|
13
13
|
def exists?
|
|
14
14
|
raise "Connection failed. Please report this as a bug: #{KnapsackPro::Urls::SUPPORT}" if connection_failed?
|
|
15
|
-
return false if connection.api_code == KnapsackPro::Client::API::
|
|
15
|
+
return false if connection.api_code == KnapsackPro::Client::API::V2::Queues::CODE_ATTEMPT_CONNECT_TO_QUEUE_FAILED
|
|
16
16
|
|
|
17
17
|
true
|
|
18
18
|
end
|
|
@@ -22,7 +22,11 @@ module KnapsackPro
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def test_files
|
|
25
|
-
response.fetch('
|
|
25
|
+
response.fetch('paths').map { |path| { "path" => path, "time_execution" => nil } }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def id
|
|
29
|
+
response.fetch('batch_id', nil)
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
private
|
|
@@ -74,33 +78,53 @@ module KnapsackPro
|
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
def build_action(cache_read_attempt:, test_files: nil)
|
|
77
|
-
|
|
81
|
+
if cache_read_attempt # connect
|
|
82
|
+
KnapsackPro::Client::API::V2::Queues.queue(
|
|
83
|
+
attempt_connect_to_queue: true,
|
|
84
|
+
batch_index: 0,
|
|
85
|
+
branch: encrypted_branch,
|
|
86
|
+
can_initialize_queue: true,
|
|
87
|
+
commit_hash: repository_adapter.commit_hash,
|
|
88
|
+
node_index: ci_node_index,
|
|
89
|
+
node_total: ci_node_total,
|
|
90
|
+
node_uuid: ENV.fetch('KNAPSACK_PRO_NODE_UUID'),
|
|
91
|
+
single_batch: true
|
|
92
|
+
)
|
|
93
|
+
else # initialize
|
|
78
94
|
raise 'Test files are required when initializing a new test suite split.' if test_files.nil?
|
|
79
|
-
|
|
95
|
+
paths = test_files.map { |test_file| test_file.fetch("path") }
|
|
96
|
+
|
|
97
|
+
KnapsackPro::Client::API::V2::Queues.queue(
|
|
98
|
+
attempt_connect_to_queue: false,
|
|
99
|
+
batch_index: 0,
|
|
100
|
+
branch: encrypted_branch,
|
|
101
|
+
can_initialize_queue: true,
|
|
102
|
+
commit_hash: repository_adapter.commit_hash,
|
|
103
|
+
node_index: ci_node_index,
|
|
104
|
+
node_total: ci_node_total,
|
|
105
|
+
node_uuid: ENV.fetch('KNAPSACK_PRO_NODE_UUID'),
|
|
106
|
+
paths: KnapsackPro::Crypto::Encryptor.paths(paths),
|
|
107
|
+
single_batch: true
|
|
108
|
+
)
|
|
80
109
|
end
|
|
81
|
-
|
|
82
|
-
KnapsackPro::Client::API::V1::BuildDistributions.subset(
|
|
83
|
-
cache_read_attempt: cache_read_attempt,
|
|
84
|
-
commit_hash: repository_adapter.commit_hash,
|
|
85
|
-
branch: encrypted_branch,
|
|
86
|
-
node_total: ci_node_total,
|
|
87
|
-
node_index: ci_node_index,
|
|
88
|
-
test_files: test_files,
|
|
89
|
-
)
|
|
90
110
|
end
|
|
91
111
|
|
|
92
112
|
def pull_tests
|
|
93
113
|
action = build_action(cache_read_attempt: true)
|
|
94
114
|
connection = KnapsackPro::Client::Connection.new(action)
|
|
95
115
|
response = connection.call
|
|
96
|
-
Split.new(connection, response)
|
|
116
|
+
Split.new(connection, response).tap do |batch|
|
|
117
|
+
ENV['KNAPSACK_PRO_BATCH_ID'] = batch.id.to_s unless batch.connection_failed?
|
|
118
|
+
end
|
|
97
119
|
end
|
|
98
120
|
|
|
99
121
|
def initialize_test_suite_split(tests_to_run)
|
|
100
122
|
action = build_action(cache_read_attempt: false, test_files: tests_to_run)
|
|
101
123
|
connection = KnapsackPro::Client::Connection.new(action)
|
|
102
124
|
response = connection.call
|
|
103
|
-
Split.new(connection, response)
|
|
125
|
+
Split.new(connection, response).tap do |batch|
|
|
126
|
+
ENV['KNAPSACK_PRO_BATCH_ID'] = batch.id.to_s unless batch.connection_failed?
|
|
127
|
+
end
|
|
104
128
|
end
|
|
105
129
|
|
|
106
130
|
def try_initializing_test_suite_split(tests)
|
|
@@ -113,15 +137,12 @@ module KnapsackPro
|
|
|
113
137
|
|
|
114
138
|
def switch_to_fallback_mode
|
|
115
139
|
if !KnapsackPro::Config::Env.fallback_mode_enabled?
|
|
116
|
-
message = "Fallback Mode was disabled with KNAPSACK_PRO_FALLBACK_MODE_ENABLED=false. Please restart this CI node to retry tests. Most likely Fallback Mode was disabled due to #{KnapsackPro::Urls::
|
|
140
|
+
message = "Fallback Mode was disabled with KNAPSACK_PRO_FALLBACK_MODE_ENABLED=false. Please restart this CI node to retry tests. Most likely Fallback Mode was disabled due to #{KnapsackPro::Urls::SINGLE_BATCH_MODE__CONNECTION_ERROR_WITH_FALLBACK_ENABLED_FALSE}"
|
|
117
141
|
KnapsackPro.logger.error(message)
|
|
118
142
|
exit_code = KnapsackPro::Config::Env.fallback_mode_error_exit_code
|
|
119
143
|
Kernel.exit(exit_code)
|
|
120
144
|
elsif KnapsackPro::Config::Env.ci_node_retry_count > 0
|
|
121
|
-
message = "knapsack_pro gem could not connect to Knapsack Pro API and the Fallback Mode cannot be used this time. Running tests in Fallback Mode are not allowed for retried parallel CI node to avoid running the wrong set of tests. Please manually retry this parallel job on your CI server then knapsack_pro gem will try to connect to Knapsack Pro API again and will run a correct set of tests for this CI node. Learn more #{KnapsackPro::Urls::
|
|
122
|
-
unless KnapsackPro::Config::Env.fixed_test_suite_split?
|
|
123
|
-
message += " Please ensure you have set KNAPSACK_PRO_FIXED_TEST_SUITE_SPLIT=true to allow Knapsack Pro API remember the recorded CI node tests so when you retry failed tests on the CI node then the same set of tests will be executed. See more #{KnapsackPro::Urls::FIXED_TEST_SUITE_SPLIT}"
|
|
124
|
-
end
|
|
145
|
+
message = "knapsack_pro gem could not connect to Knapsack Pro API and the Fallback Mode cannot be used this time. Running tests in Fallback Mode are not allowed for retried parallel CI node to avoid running the wrong set of tests. Please manually retry this parallel job on your CI server then knapsack_pro gem will try to connect to Knapsack Pro API again and will run a correct set of tests for this CI node. Learn more #{KnapsackPro::Urls::SINGLE_BATCH_MODE__CONNECTION_ERROR_WITH_FALLBACK_ENABLED_TRUE_AND_POSITIVE_RETRY_COUNT}"
|
|
125
146
|
KnapsackPro.logger.error(message)
|
|
126
147
|
exit_code = KnapsackPro::Config::Env.fallback_mode_error_exit_code
|
|
127
148
|
Kernel.exit(exit_code)
|
data/lib/knapsack_pro/report.rb
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module KnapsackPro
|
|
4
4
|
class Report
|
|
5
|
-
def self.save(
|
|
6
|
-
if
|
|
7
|
-
|
|
5
|
+
def self.save(test_files = nil)
|
|
6
|
+
if test_files.nil?
|
|
7
|
+
test_files = KnapsackPro.tracker.to_a
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
if
|
|
11
|
-
KnapsackPro.logger.
|
|
12
|
-
KnapsackPro.logger.debug("When you use knapsack_pro Regular Mode, the reason for no tests executing might be a very narrow tests list. Most likely, you run only tests with a specified tag, and there were fewer test files with the tag than parallel CI nodes.")
|
|
10
|
+
if test_files.empty?
|
|
11
|
+
KnapsackPro.logger.info("No tests were executed because the test queue is empty.")
|
|
13
12
|
end
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
acknowledge_single_batch(test_files)
|
|
15
|
+
create_build_subset(test_files)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def self.save_subset_queue_to_file
|
|
@@ -59,6 +59,35 @@ module KnapsackPro
|
|
|
59
59
|
create_build_subset(test_files)
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
# TODO: Add support for Retry-only-Failures
|
|
63
|
+
def self.acknowledge_single_batch(test_files)
|
|
64
|
+
repository_adapter = KnapsackPro::RepositoryAdapterInitiator.call
|
|
65
|
+
paths = KnapsackPro::Utils.unsymbolize(test_files).map { |test_file| test_file.fetch('path') }
|
|
66
|
+
failed_paths = KnapsackPro::Crypto::Encryptor.paths(paths)
|
|
67
|
+
action = KnapsackPro::Client::API::V2::Queues.queue(
|
|
68
|
+
attempt_connect_to_queue: false,
|
|
69
|
+
batch_id: ENV['KNAPSACK_PRO_BATCH_ID'], # nil in Fallback Mode
|
|
70
|
+
batch_index: 1,
|
|
71
|
+
branch: KnapsackPro::Crypto::BranchEncryptor.call(repository_adapter.branch),
|
|
72
|
+
can_initialize_queue: false,
|
|
73
|
+
commit_hash: repository_adapter.commit_hash,
|
|
74
|
+
failed_paths: failed_paths,
|
|
75
|
+
node_total: KnapsackPro::Config::Env.ci_node_total,
|
|
76
|
+
node_index: KnapsackPro::Config::Env.ci_node_index,
|
|
77
|
+
node_uuid: ENV.fetch('KNAPSACK_PRO_NODE_UUID'),
|
|
78
|
+
single_batch: true
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
connection = KnapsackPro::Client::Connection.new(action)
|
|
82
|
+
response = connection.call
|
|
83
|
+
if connection.success?
|
|
84
|
+
raise ArgumentError.new(response) if connection.errors?
|
|
85
|
+
KnapsackPro.logger.debug('Acknowledged single batch.')
|
|
86
|
+
else
|
|
87
|
+
KnapsackPro.logger.warn('Single batch was not acknowledged due to connection problems.')
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
62
91
|
def self.create_build_subset(test_files)
|
|
63
92
|
repository_adapter = KnapsackPro::RepositoryAdapterInitiator.call
|
|
64
93
|
test_files = KnapsackPro::Utils.unsymbolize(test_files)
|
|
@@ -77,7 +106,7 @@ module KnapsackPro
|
|
|
77
106
|
raise ArgumentError.new(response) if connection.errors?
|
|
78
107
|
KnapsackPro.logger.debug('Saved time execution report on Knapsack Pro API server.')
|
|
79
108
|
else
|
|
80
|
-
KnapsackPro.logger.warn('Time execution report was not saved on Knapsack Pro API server due to connection
|
|
109
|
+
KnapsackPro.logger.warn('Time execution report was not saved on Knapsack Pro API server due to connection problems.')
|
|
81
110
|
end
|
|
82
111
|
end
|
|
83
112
|
|
|
@@ -4,11 +4,11 @@ module KnapsackPro
|
|
|
4
4
|
module RepositoryAdapters
|
|
5
5
|
class GitAdapter < BaseAdapter
|
|
6
6
|
def commit_hash
|
|
7
|
-
|
|
7
|
+
ENV['KNAPSACK_PRO_COMMIT_HASH'] || git_commit_hash
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def branch
|
|
11
|
-
|
|
11
|
+
ENV['KNAPSACK_PRO_BRANCH'] || git_branch
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def branches
|
|
@@ -42,6 +42,14 @@ module KnapsackPro
|
|
|
42
42
|
|
|
43
43
|
private
|
|
44
44
|
|
|
45
|
+
def git_commit_hash
|
|
46
|
+
`git -C "#{working_dir}" rev-parse HEAD`.strip
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def git_branch
|
|
50
|
+
`git -C "#{working_dir}" rev-parse --abbrev-ref HEAD`.strip
|
|
51
|
+
end
|
|
52
|
+
|
|
45
53
|
def git_commit_authors
|
|
46
54
|
git_unshallow if KnapsackPro::Config::Env.ci? && shallow_repository?
|
|
47
55
|
`git log --since "one month ago" 2>/dev/null | git shortlog --summary --email 2>/dev/null`
|
|
@@ -5,7 +5,8 @@ module KnapsackPro
|
|
|
5
5
|
class CucumberRunner < BaseRunner
|
|
6
6
|
def self.run(args)
|
|
7
7
|
ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_cucumber
|
|
8
|
-
ENV['
|
|
8
|
+
ENV['KNAPSACK_PRO_SINGLE_BATCH_MODE_ENABLED'] = 'true'
|
|
9
|
+
ENV['KNAPSACK_PRO_NODE_UUID'] = SecureRandom.uuid
|
|
9
10
|
|
|
10
11
|
adapter_class = KnapsackPro::Adapters::CucumberAdapter
|
|
11
12
|
KnapsackPro::Config::Env.set_test_runner_adapter(adapter_class)
|
|
@@ -5,7 +5,8 @@ module KnapsackPro
|
|
|
5
5
|
class MinitestRunner < BaseRunner
|
|
6
6
|
def self.run(args)
|
|
7
7
|
ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_minitest
|
|
8
|
-
ENV['
|
|
8
|
+
ENV['KNAPSACK_PRO_SINGLE_BATCH_MODE_ENABLED'] = 'true'
|
|
9
|
+
ENV['KNAPSACK_PRO_NODE_UUID'] = SecureRandom.uuid
|
|
9
10
|
|
|
10
11
|
adapter_class = KnapsackPro::Adapters::MinitestAdapter
|
|
11
12
|
KnapsackPro::Config::Env.set_test_runner_adapter(adapter_class)
|
|
@@ -5,7 +5,6 @@ module KnapsackPro
|
|
|
5
5
|
module Queue
|
|
6
6
|
class BaseRunner
|
|
7
7
|
TerminationError = Class.new(StandardError)
|
|
8
|
-
TERMINATION_SIGNALS = %w(HUP INT TERM ABRT QUIT USR1 USR2)
|
|
9
8
|
|
|
10
9
|
@@terminate_process = false
|
|
11
10
|
|
|
@@ -56,17 +55,27 @@ module KnapsackPro
|
|
|
56
55
|
end
|
|
57
56
|
|
|
58
57
|
def trap_signals
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
Signal.trap("TERM") {
|
|
59
|
+
puts "SIGTERM received: Terminating Knapsack Pro..."
|
|
60
|
+
@@terminate_process = true
|
|
61
|
+
post_trap_signals(debug: true)
|
|
62
|
+
log_threads
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Signal.trap("INT") {
|
|
66
|
+
if @@terminate_process
|
|
67
|
+
puts "SIGINT received: Terminated Knapsack Pro."
|
|
68
|
+
$stdout.flush
|
|
69
|
+
exit!(1)
|
|
70
|
+
else
|
|
71
|
+
puts "SIGINT received: Terminating Knapsack Pro... Interrupt again to force quit."
|
|
62
72
|
@@terminate_process = true
|
|
63
73
|
post_trap_signals
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
end
|
|
74
|
+
end
|
|
75
|
+
}
|
|
67
76
|
end
|
|
68
77
|
|
|
69
|
-
def post_trap_signals
|
|
78
|
+
def post_trap_signals(debug: false)
|
|
70
79
|
end
|
|
71
80
|
|
|
72
81
|
def log_threads
|
|
@@ -8,7 +8,7 @@ module KnapsackPro
|
|
|
8
8
|
require 'cucumber/rake/task'
|
|
9
9
|
|
|
10
10
|
ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_cucumber
|
|
11
|
-
ENV['
|
|
11
|
+
ENV['KNAPSACK_PRO_NORMAL_MODE_ENABLED'] = 'true'
|
|
12
12
|
ENV['KNAPSACK_PRO_QUEUE_ID'] = KnapsackPro::Config::EnvGenerator.set_queue_id
|
|
13
13
|
|
|
14
14
|
adapter_class = KnapsackPro::Adapters::CucumberAdapter
|
|
@@ -12,7 +12,7 @@ module KnapsackPro
|
|
|
12
12
|
::Minitest.class_variable_set('@@installed_at_exit', true)
|
|
13
13
|
|
|
14
14
|
ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_minitest
|
|
15
|
-
ENV['
|
|
15
|
+
ENV['KNAPSACK_PRO_NORMAL_MODE_ENABLED'] = 'true'
|
|
16
16
|
ENV['KNAPSACK_PRO_QUEUE_ID'] = KnapsackPro::Config::EnvGenerator.set_queue_id
|
|
17
17
|
|
|
18
18
|
adapter_class = KnapsackPro::Adapters::MinitestAdapter
|