knapsack_pro 10.0.1 → 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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/knapsack_pro/adapters/base_adapter.rb +6 -6
  3. data/lib/knapsack_pro/adapters/minitest_adapter.rb +3 -3
  4. data/lib/knapsack_pro/adapters/rspec_adapter.rb +3 -3
  5. data/lib/knapsack_pro/client/api/v1/build_distributions.rb +0 -29
  6. data/lib/knapsack_pro/client/api/v2/queues.rb +1 -3
  7. data/lib/knapsack_pro/client/connection.rb +25 -12
  8. data/lib/knapsack_pro/commands.rb +8 -8
  9. data/lib/knapsack_pro/config/ci/app_veyor.rb +0 -8
  10. data/lib/knapsack_pro/config/ci/base.rb +0 -7
  11. data/lib/knapsack_pro/config/ci/buildkite.rb +1 -9
  12. data/lib/knapsack_pro/config/ci/circle.rb +0 -8
  13. data/lib/knapsack_pro/config/ci/cirrus_ci.rb +0 -8
  14. data/lib/knapsack_pro/config/ci/codefresh.rb +0 -8
  15. data/lib/knapsack_pro/config/ci/codeship.rb +0 -8
  16. data/lib/knapsack_pro/config/ci/github_actions.rb +2 -10
  17. data/lib/knapsack_pro/config/ci/gitlab_ci.rb +0 -9
  18. data/lib/knapsack_pro/config/ci/heroku.rb +1 -9
  19. data/lib/knapsack_pro/config/ci/semaphore.rb +0 -8
  20. data/lib/knapsack_pro/config/ci/semaphore2.rb +0 -8
  21. data/lib/knapsack_pro/config/ci/travis.rb +0 -8
  22. data/lib/knapsack_pro/config/env.rb +12 -42
  23. data/lib/knapsack_pro/crypto/branch_encryptor.rb +4 -5
  24. data/lib/knapsack_pro/extensions/rspec_extension.rb +1 -1
  25. data/lib/knapsack_pro/queue_allocator.rb +33 -88
  26. data/lib/knapsack_pro/regular_allocator.rb +41 -20
  27. data/lib/knapsack_pro/report.rb +37 -8
  28. data/lib/knapsack_pro/repository_adapters/git_adapter.rb +10 -2
  29. data/lib/knapsack_pro/runners/cucumber_runner.rb +2 -1
  30. data/lib/knapsack_pro/runners/minitest_runner.rb +2 -1
  31. data/lib/knapsack_pro/runners/queue/base_runner.rb +17 -8
  32. data/lib/knapsack_pro/runners/queue/cucumber_runner.rb +1 -1
  33. data/lib/knapsack_pro/runners/queue/minitest_runner.rb +1 -1
  34. data/lib/knapsack_pro/runners/queue/rspec_runner.rb +3 -3
  35. data/lib/knapsack_pro/runners/rspec_runner.rb +2 -1
  36. data/lib/knapsack_pro/runners/spinach_runner.rb +2 -1
  37. data/lib/knapsack_pro/runners/test_unit_runner.rb +2 -1
  38. data/lib/knapsack_pro/tracker.rb +2 -4
  39. data/lib/knapsack_pro/urls.rb +4 -10
  40. data/lib/knapsack_pro/version.rb +1 -1
  41. data/lib/tasks/queue/rspec.rake +5 -1
  42. metadata +2 -3
  43. 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 regular_mode?
99
- ENV['KNAPSACK_PRO_REGULAR_MODE_ENABLED'] == 'true'
92
+ def single_batch_mode?
93
+ ENV['KNAPSACK_PRO_SINGLE_BATCH_MODE_ENABLED'] == 'true'
100
94
  end
101
95
 
102
- def queue_mode?
103
- ENV['KNAPSACK_PRO_QUEUE_MODE_ENABLED'] == 'true'
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 fixed_test_suite_split
147
- ENV.fetch('KNAPSACK_PRO_FIXED_TEST_SUITE_SPLIT', true)
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,8 +249,8 @@ 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('-') if triplet.all?
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
@@ -22,11 +22,10 @@ module KnapsackPro
22
22
  ]
23
23
 
24
24
  def self.call(branch)
25
- if KnapsackPro::Config::Env.branch_encrypted?
26
- new(branch).call
27
- else
28
- branch
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` in Queue Mode:
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
  #
@@ -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::V1::Queues::CODE_ATTEMPT_CONNECT_TO_QUEUE_FAILED
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
- 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
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,31 +47,31 @@ module KnapsackPro
47
47
  @repository_adapter = args.fetch(:repository_adapter)
48
48
  @fallback_mode = false
49
49
  @batch_index = -1
50
- @batch_id = nil
51
50
  @node_uuid = SecureRandom.uuid
51
+ @current_batch = nil
52
52
  end
53
53
 
54
- def test_file_paths(can_initialize_queue, executed_test_files, batch_uuid: SecureRandom.uuid, time_tracker: nil)
54
+ def test_file_paths(can_initialize_queue, executed_test_files, time_tracker: nil)
55
55
  @batch_index += 1
56
56
  return [] if @fallback_mode
57
57
 
58
- batch = pull_tests_from_queue(can_initialize_queue, batch_uuid, time_tracker: time_tracker)
58
+ batch = pull_tests_from_queue(can_initialize_queue, time_tracker: time_tracker)
59
59
 
60
60
  return switch_to_fallback_mode(executed_test_files: executed_test_files) if batch.connection_failed?
61
61
  return normalize_test_files(batch.test_files) if batch.queue_exists?
62
62
 
63
63
  test_files_result = test_suite.calculate_test_files
64
64
 
65
- return try_initializing_queue(test_files_result.test_files, batch_uuid, time_tracker: time_tracker) if test_files_result.quick?
65
+ return try_initializing_queue(test_files_result.test_files.map { |test| test.fetch("path") }, time_tracker: time_tracker) if test_files_result.quick?
66
66
 
67
67
  # The tests to run were found slowly. By that time, the queue could have already been initialized by another CI node.
68
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).
69
- batch = pull_tests_from_queue(can_initialize_queue, batch_uuid, time_tracker: time_tracker)
69
+ batch = pull_tests_from_queue(can_initialize_queue, time_tracker: time_tracker)
70
70
 
71
71
  return switch_to_fallback_mode(executed_test_files: executed_test_files) if batch.connection_failed?
72
72
  return normalize_test_files(batch.test_files) if batch.queue_exists?
73
73
 
74
- try_initializing_queue(test_files_result.test_files, batch_uuid, time_tracker: time_tracker)
74
+ try_initializing_queue(test_files_result.test_files.map { |test| test.fetch("path") }, time_tracker: time_tracker)
75
75
  end
76
76
 
77
77
  private
@@ -87,38 +87,28 @@ module KnapsackPro
87
87
  KnapsackPro::TestFilePresenter.paths(decrypted_test_files)
88
88
  end
89
89
 
90
- def build_action_v1(can_initialize_queue:, attempt_connect_to_queue:, batch_uuid:, test_files: nil)
91
- if can_initialize_queue && !attempt_connect_to_queue
92
- raise 'Test files are required when initializing a new queue.' if test_files.nil?
93
- test_files = KnapsackPro::Crypto::Encryptor.call(test_files)
94
- end
95
-
96
- KnapsackPro::Client::API::V1::Queues.queue(
97
- can_initialize_queue: can_initialize_queue,
98
- attempt_connect_to_queue: attempt_connect_to_queue,
99
- commit_hash: repository_adapter.commit_hash,
100
- branch: encrypted_branch,
101
- node_total: ci_node_total,
102
- node_index: ci_node_index,
103
- test_files: test_files,
104
- batch_uuid: batch_uuid
105
- )
106
- end
107
-
108
- 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)
109
92
  if can_initialize_queue && !attempt_connect_to_queue
110
93
  raise 'Test files are required when initializing a new queue.' if paths.nil?
111
94
  paths = KnapsackPro::Crypto::Encryptor.paths(paths)
112
95
  end
113
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
+
114
104
  KnapsackPro::Client::API::V2::Queues.queue(
115
105
  attempt_connect_to_queue: attempt_connect_to_queue,
116
- batch_id: @batch_id,
106
+ batch_id: @current_batch&.id,
117
107
  batch_index: @batch_index,
118
108
  branch: encrypted_branch,
119
109
  can_initialize_queue: can_initialize_queue,
120
110
  commit_hash: repository_adapter.commit_hash,
121
- failed_paths: KnapsackPro::Crypto::Encryptor.paths(time_tracker.current_batch_failed_paths),
111
+ failed_paths: failed_paths,
122
112
  node_index: ci_node_index,
123
113
  node_total: ci_node_total,
124
114
  node_uuid: @node_uuid,
@@ -126,64 +116,22 @@ module KnapsackPro
126
116
  )
127
117
  end
128
118
 
129
- def pull_tests_from_queue(can_initialize_queue, batch_uuid, time_tracker: nil)
130
- if time_tracker.nil?
131
- pull_tests_from_queue_v1(can_initialize_queue, batch_uuid)
132
- else
133
- pull_tests_from_queue_v2(can_initialize_queue, time_tracker)
134
- end
135
- end
136
-
137
- def pull_tests_from_queue_v1(can_initialize_queue, batch_uuid)
138
- action = build_action_v1(can_initialize_queue: can_initialize_queue, attempt_connect_to_queue: can_initialize_queue, batch_uuid: batch_uuid)
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)
139
121
  connection = KnapsackPro::Client::Connection.new(action)
140
122
  response = connection.call
141
- Batch.new(connection, response)
123
+ @current_batch = Batch.new(connection, response)
142
124
  end
143
125
 
144
- def pull_tests_from_queue_v2(can_initialize_queue, time_tracker)
145
- action = build_action_v2(can_initialize_queue: can_initialize_queue, attempt_connect_to_queue: can_initialize_queue, time_tracker: time_tracker)
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)
146
128
  connection = KnapsackPro::Client::Connection.new(action)
147
129
  response = connection.call
148
- Batch.new(connection, response).tap do |batch|
149
- @batch_id = batch.id unless batch.connection_failed?
150
- end
151
- end
152
-
153
- def initialize_queue_v1(tests_to_run, batch_uuid)
154
- action = build_action_v1(can_initialize_queue: true, attempt_connect_to_queue: false, batch_uuid: batch_uuid, test_files: tests_to_run)
155
- connection = KnapsackPro::Client::Connection.new(action)
156
- response = connection.call
157
- Batch.new(connection, response)
158
- end
159
-
160
- def initialize_queue_v2(paths, time_tracker)
161
- action = build_action_v2(can_initialize_queue: true, attempt_connect_to_queue: false, paths: paths, time_tracker: time_tracker)
162
- connection = KnapsackPro::Client::Connection.new(action)
163
- response = connection.call
164
- Batch.new(connection, response).tap do |batch|
165
- @batch_id = batch.id unless batch.connection_failed?
166
- end
130
+ @current_batch = Batch.new(connection, response)
167
131
  end
168
132
 
169
- def try_initializing_queue(tests, batch_uuid, time_tracker: nil)
170
- if time_tracker.nil?
171
- try_initializing_queue_v1(tests, batch_uuid)
172
- else
173
- paths = tests.map { |test| test.fetch("path") }
174
- try_initializing_queue_v2(paths, time_tracker)
175
- end
176
- end
177
-
178
- def try_initializing_queue_v1(tests, batch_uuid)
179
- result = initialize_queue_v1(tests, batch_uuid)
180
- return switch_to_fallback_mode(executed_test_files: []) if result.connection_failed?
181
-
182
- normalize_test_files(result.test_files)
183
- end
184
-
185
- def try_initializing_queue_v2(paths, time_tracker)
186
- 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)
187
135
  return switch_to_fallback_mode(executed_test_files: []) if result.connection_failed?
188
136
 
189
137
  normalize_test_files(result.test_files)
@@ -193,14 +141,11 @@ module KnapsackPro
193
141
  @fallback_mode = true
194
142
 
195
143
  if !KnapsackPro::Config::Env.fallback_mode_enabled?
196
- 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::QUEUE_MODE__CONNECTION_ERROR_WITH_FALLBACK_ENABLED_FALSE}"
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}"
197
145
  KnapsackPro.logger.error(message)
198
146
  raise FallbackModeError.new(message)
199
147
  elsif KnapsackPro::Config::Env.ci_node_retry_count > 0
200
- 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::QUEUE_MODE__CONNECTION_ERROR_WITH_FALLBACK_ENABLED_TRUE_AND_POSITIVE_RETRY_COUNT}"
201
- unless KnapsackPro::Config::Env.fixed_queue_split?
202
- 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}"
203
- 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}"
204
149
  KnapsackPro.logger.error(message)
205
150
  raise FallbackModeError.new(message)
206
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::V1::BuildDistributions::TEST_SUITE_SPLIT_CACHE_MISS_CODE
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('test_files')
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
- unless cache_read_attempt
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
- test_files = KnapsackPro::Crypto::Encryptor.call(test_files)
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::REGULAR_MODE__CONNECTION_ERROR_WITH_FALLBACK_ENABLED_FALSE}"
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::REGULAR_MODE__CONNECTION_ERROR_WITH_FALLBACK_ENABLED_TRUE_AND_POSITIVE_RETRY_COUNT}"
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)
@@ -2,17 +2,17 @@
2
2
 
3
3
  module KnapsackPro
4
4
  class Report
5
- def self.save(tests = nil)
6
- if tests.nil?
7
- tests = KnapsackPro.tracker.to_a
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 tests.empty?
11
- KnapsackPro.logger.warn("No test files were executed on this CI node.")
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
- create_build_subset(tests)
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 problem.')
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
- `git -C "#{working_dir}" rev-parse HEAD`.strip
7
+ ENV['KNAPSACK_PRO_COMMIT_HASH'] || git_commit_hash
8
8
  end
9
9
 
10
10
  def branch
11
- `git -C "#{working_dir}" rev-parse --abbrev-ref HEAD`.strip
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['KNAPSACK_PRO_REGULAR_MODE_ENABLED'] = 'true'
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['KNAPSACK_PRO_REGULAR_MODE_ENABLED'] = 'true'
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
- TERMINATION_SIGNALS.each do |signal|
60
- Signal.trap(signal) {
61
- puts "#{signal} signal has been received. Terminating Knapsack Pro..."
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
- log_threads
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['KNAPSACK_PRO_QUEUE_MODE_ENABLED'] = 'true'
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['KNAPSACK_PRO_QUEUE_MODE_ENABLED'] = 'true'
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
@@ -118,10 +118,10 @@ module KnapsackPro
118
118
 
119
119
  private
120
120
 
121
- def post_trap_signals
121
+ def post_trap_signals(debug: false)
122
122
  RSpec.world.wants_to_quit = true
123
123
 
124
- log_current_batch_rspec_command
124
+ log_current_batch_rspec_command if debug
125
125
  end
126
126
 
127
127
  def log_current_tests(threads)
@@ -144,7 +144,7 @@ module KnapsackPro
144
144
  end
145
145
 
146
146
  def pre_run_setup
147
- ENV['KNAPSACK_PRO_QUEUE_MODE_ENABLED'] = 'true'
147
+ ENV['KNAPSACK_PRO_NORMAL_MODE_ENABLED'] = 'true'
148
148
  ENV['KNAPSACK_PRO_QUEUE_ID'] = KnapsackPro::Config::EnvGenerator.set_queue_id
149
149
 
150
150
  KnapsackPro::Config::Env.set_test_runner_adapter(@adapter_class)
@@ -7,8 +7,9 @@ module KnapsackPro
7
7
  require 'rspec/core'
8
8
 
9
9
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_rspec
10
- ENV['KNAPSACK_PRO_REGULAR_MODE_ENABLED'] = 'true'
10
+ ENV['KNAPSACK_PRO_SINGLE_BATCH_MODE_ENABLED'] = 'true'
11
11
  ENV['KNAPSACK_PRO_RSPEC_OPTIONS'] = args.to_s
12
+ ENV['KNAPSACK_PRO_NODE_UUID'] = SecureRandom.uuid
12
13
 
13
14
  adapter_class = KnapsackPro::Adapters::RSpecAdapter
14
15
  KnapsackPro::Config::Env.set_test_runner_adapter(adapter_class)