knapsack_pro 1.20.0 → 1.22.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/README.md +42 -17
  4. data/lib/knapsack_pro.rb +6 -0
  5. data/lib/knapsack_pro/adapters/base_adapter.rb +16 -0
  6. data/lib/knapsack_pro/adapters/rspec_adapter.rb +11 -9
  7. data/lib/knapsack_pro/allocator.rb +7 -5
  8. data/lib/knapsack_pro/allocator_builder.rb +2 -1
  9. data/lib/knapsack_pro/base_allocator_builder.rb +41 -10
  10. data/lib/knapsack_pro/build_distribution_fetcher.rb +57 -0
  11. data/lib/knapsack_pro/client/api/v1/build_distributions.rb +13 -0
  12. data/lib/knapsack_pro/client/connection.rb +33 -15
  13. data/lib/knapsack_pro/config/env.rb +4 -0
  14. data/lib/knapsack_pro/formatters/rspec_json_formatter.rb +1 -1
  15. data/lib/knapsack_pro/queue_allocator.rb +7 -5
  16. data/lib/knapsack_pro/queue_allocator_builder.rb +2 -1
  17. data/lib/knapsack_pro/report.rb +1 -1
  18. data/lib/knapsack_pro/runners/queue/cucumber_runner.rb +10 -1
  19. data/lib/knapsack_pro/runners/queue/rspec_runner.rb +7 -0
  20. data/lib/knapsack_pro/runners/rspec_runner.rb +5 -2
  21. data/lib/knapsack_pro/slow_test_file_determiner.rb +33 -0
  22. data/lib/knapsack_pro/slow_test_file_finder.rb +27 -0
  23. data/lib/knapsack_pro/test_case_detectors/rspec_test_example_detector.rb +26 -7
  24. data/lib/knapsack_pro/test_case_mergers/base_merger.rb +29 -0
  25. data/lib/knapsack_pro/test_case_mergers/rspec_merger.rb +34 -0
  26. data/lib/knapsack_pro/test_file_finder.rb +43 -5
  27. data/lib/knapsack_pro/test_files_with_test_cases_composer.rb +22 -0
  28. data/lib/knapsack_pro/version.rb +1 -1
  29. data/spec/knapsack_pro/adapters/base_adapter_spec.rb +55 -0
  30. data/spec/knapsack_pro/adapters/rspec_adapter_spec.rb +61 -25
  31. data/spec/knapsack_pro/allocator_builder_spec.rb +7 -3
  32. data/spec/knapsack_pro/allocator_spec.rb +7 -5
  33. data/spec/knapsack_pro/base_allocator_builder_spec.rb +79 -27
  34. data/spec/knapsack_pro/build_distribution_fetcher_spec.rb +89 -0
  35. data/spec/knapsack_pro/client/api/v1/build_distributions_spec.rb +31 -0
  36. data/spec/knapsack_pro/client/connection_spec.rb +202 -104
  37. data/spec/knapsack_pro/config/env_spec.rb +14 -0
  38. data/spec/knapsack_pro/queue_allocator_builder_spec.rb +7 -3
  39. data/spec/knapsack_pro/queue_allocator_spec.rb +7 -5
  40. data/spec/knapsack_pro/report_spec.rb +1 -1
  41. data/spec/knapsack_pro/runners/queue/cucumber_runner_spec.rb +38 -25
  42. data/spec/knapsack_pro/runners/rspec_runner_spec.rb +4 -4
  43. data/spec/knapsack_pro/slow_test_file_determiner_spec.rb +74 -0
  44. data/spec/knapsack_pro/slow_test_file_finder_spec.rb +43 -0
  45. data/spec/knapsack_pro/test_case_detectors/rspec_test_example_detector_spec.rb +83 -37
  46. data/spec/knapsack_pro/test_case_mergers/base_merger_spec.rb +27 -0
  47. data/spec/knapsack_pro/test_case_mergers/rspec_merger_spec.rb +59 -0
  48. data/spec/knapsack_pro/test_file_finder_spec.rb +105 -29
  49. data/spec/knapsack_pro/test_files_with_test_cases_composer_spec.rb +41 -0
  50. metadata +20 -2
@@ -38,4 +38,35 @@ describe KnapsackPro::Client::API::V1::BuildDistributions do
38
38
  expect(subject).to eq action
39
39
  end
40
40
  end
41
+
42
+ describe '.last' do
43
+ let(:commit_hash) { double }
44
+ let(:branch) { double }
45
+ let(:node_total) { double }
46
+ let(:node_index) { double }
47
+
48
+ subject do
49
+ described_class.last(
50
+ commit_hash: commit_hash,
51
+ branch: branch,
52
+ node_total: node_total,
53
+ node_index: node_index,
54
+ )
55
+ end
56
+
57
+ it do
58
+ action = double
59
+ expect(KnapsackPro::Client::API::Action).to receive(:new).with({
60
+ endpoint_path: '/v1/build_distributions/last',
61
+ http_method: :get,
62
+ request_hash: {
63
+ commit_hash: commit_hash,
64
+ branch: branch,
65
+ node_total: node_total,
66
+ node_index: node_index,
67
+ }
68
+ }).and_return(action)
69
+ expect(subject).to eq action
70
+ end
71
+ end
41
72
  end
@@ -1,167 +1,265 @@
1
+ shared_examples 'when request got response from API' do
2
+ context 'when body response is JSON and API response code is 400' do
3
+ let(:body) { '{"errors": "value"}' }
4
+ let(:code) { '400' } # it must be string code
5
+
6
+ before do
7
+ expect(KnapsackPro).to receive(:logger).exactly(4).and_return(logger)
8
+ expect(logger).to receive(:debug).with("#{expected_http_method} http://api.knapsackpro.test:3000/v1/fake_endpoint")
9
+ expect(logger).to receive(:debug).with('API request UUID: fake-uuid')
10
+ expect(logger).to receive(:debug).with('API response:')
11
+ end
12
+
13
+ it do
14
+ parsed_response = { 'errors' => 'value' }
15
+
16
+ expect(logger).to receive(:error).with(parsed_response)
17
+
18
+ expect(subject).to eq(parsed_response)
19
+ expect(connection.success?).to be true
20
+ expect(connection.errors?).to be true
21
+ end
22
+ end
23
+
24
+ context 'when body response is JSON with build_distribution_id' do
25
+ let(:body) { '{"build_distribution_id": "seed-uuid"}' }
26
+ let(:code) { '200' } # it must be string code
27
+
28
+ before do
29
+ expect(KnapsackPro).to receive(:logger).exactly(5).and_return(logger)
30
+ expect(logger).to receive(:debug).with("#{expected_http_method} http://api.knapsackpro.test:3000/v1/fake_endpoint")
31
+ expect(logger).to receive(:debug).with('API request UUID: fake-uuid')
32
+ expect(logger).to receive(:debug).with("Test suite split seed: seed-uuid")
33
+ expect(logger).to receive(:debug).with('API response:')
34
+ end
35
+
36
+ it do
37
+ parsed_response = { 'build_distribution_id' => 'seed-uuid' }
38
+
39
+ expect(logger).to receive(:debug).with(parsed_response)
40
+
41
+ expect(subject).to eq(parsed_response)
42
+ expect(connection.success?).to be true
43
+ expect(connection.errors?).to be false
44
+ end
45
+ end
46
+
47
+ context 'when body response is empty' do
48
+ let(:body) { '' }
49
+ let(:code) { '200' } # it must be string code
50
+
51
+ before do
52
+ expect(KnapsackPro).to receive(:logger).exactly(4).and_return(logger)
53
+ expect(logger).to receive(:debug).with("#{expected_http_method} http://api.knapsackpro.test:3000/v1/fake_endpoint")
54
+ expect(logger).to receive(:debug).with('API request UUID: fake-uuid')
55
+ expect(logger).to receive(:debug).with('API response:')
56
+ end
57
+
58
+ it do
59
+ expect(logger).to receive(:debug).with('')
60
+
61
+ expect(subject).to eq('')
62
+ expect(connection.success?).to be true
63
+ expect(connection.errors?).to be false
64
+ end
65
+ end
66
+ end
67
+
68
+ shared_examples 'when retry request' do
69
+ context 'when body response is JSON and API response code is 500' do
70
+ let(:body) { '{"error": "Internal Server Error"}' }
71
+ let(:code) { '500' } # it must be string code
72
+
73
+ before do
74
+ expect(KnapsackPro).to receive(:logger).at_least(1).and_return(logger)
75
+ end
76
+
77
+ it do
78
+ expect(logger).to receive(:debug).exactly(3).with("#{expected_http_method} http://api.knapsackpro.test:3000/v1/fake_endpoint")
79
+ expect(logger).to receive(:debug).exactly(3).with('API request UUID: fake-uuid')
80
+ expect(logger).to receive(:debug).exactly(3).with('API response:')
81
+
82
+ parsed_response = { 'error' => 'Internal Server Error' }
83
+
84
+ expect(logger).to receive(:error).exactly(3).with(parsed_response)
85
+
86
+ server_error = described_class::ServerError.new(parsed_response)
87
+ expect(logger).to receive(:warn).exactly(3).with(server_error.inspect)
88
+
89
+ expect(logger).to receive(:warn).with("Wait 8s and retry request to Knapsack Pro API.")
90
+ expect(logger).to receive(:warn).with("Wait 16s and retry request to Knapsack Pro API.")
91
+ expect(Kernel).to receive(:sleep).with(8)
92
+ expect(Kernel).to receive(:sleep).with(16)
93
+
94
+ expect(subject).to eq(parsed_response)
95
+
96
+ expect(connection.success?).to be false
97
+ expect(connection.errors?).to be true
98
+ end
99
+
100
+ context 'when Fallback Mode is disabled' do
101
+ before do
102
+ expect(KnapsackPro::Config::Env).to receive(:fallback_mode_enabled?).at_least(1).and_return(false)
103
+ end
104
+
105
+ it do
106
+ expect(logger).to receive(:debug).exactly(6).with("#{expected_http_method} http://api.knapsackpro.test:3000/v1/fake_endpoint")
107
+ expect(logger).to receive(:debug).exactly(6).with('API request UUID: fake-uuid')
108
+ expect(logger).to receive(:debug).exactly(6).with('API response:')
109
+
110
+ parsed_response = { 'error' => 'Internal Server Error' }
111
+
112
+ expect(logger).to receive(:error).exactly(6).with(parsed_response)
113
+
114
+ server_error = described_class::ServerError.new(parsed_response)
115
+ expect(logger).to receive(:warn).exactly(6).with(server_error.inspect)
116
+
117
+ expect(logger).to receive(:warn).with("Wait 8s and retry request to Knapsack Pro API.")
118
+ expect(logger).to receive(:warn).with("Wait 16s and retry request to Knapsack Pro API.")
119
+ expect(logger).to receive(:warn).with("Wait 24s and retry request to Knapsack Pro API.")
120
+ expect(logger).to receive(:warn).with("Wait 32s and retry request to Knapsack Pro API.")
121
+ expect(logger).to receive(:warn).with("Wait 40s and retry request to Knapsack Pro API.")
122
+ expect(Kernel).to receive(:sleep).with(8)
123
+ expect(Kernel).to receive(:sleep).with(16)
124
+ expect(Kernel).to receive(:sleep).with(24)
125
+ expect(Kernel).to receive(:sleep).with(32)
126
+ expect(Kernel).to receive(:sleep).with(40)
127
+
128
+ expect(subject).to eq(parsed_response)
129
+
130
+ expect(connection.success?).to be false
131
+ expect(connection.errors?).to be true
132
+ end
133
+ end
134
+ end
135
+ end
136
+
1
137
  describe KnapsackPro::Client::Connection do
2
138
  let(:endpoint_path) { '/v1/fake_endpoint' }
3
- let(:http_method) { :post }
4
139
  let(:request_hash) { { fake: 'hash' } }
140
+ let(:http_method) { :post }
5
141
  let(:action) do
6
142
  instance_double(KnapsackPro::Client::API::Action,
7
143
  endpoint_path: endpoint_path,
8
144
  http_method: http_method,
9
145
  request_hash: request_hash)
10
146
  end
147
+ let(:test_suite_token) { '3fa64859337f6e56409d49f865d13fd7' }
11
148
 
12
149
  let(:connection) { described_class.new(action) }
13
150
 
14
151
  before do
15
152
  stub_const('ENV', {
16
153
  'KNAPSACK_PRO_ENDPOINT' => 'http://api.knapsackpro.test:3000',
17
- 'KNAPSACK_PRO_TEST_SUITE_TOKEN' => '3fa64859337f6e56409d49f865d13fd7',
154
+ 'KNAPSACK_PRO_TEST_SUITE_TOKEN' => test_suite_token,
18
155
  })
19
156
  end
20
157
 
21
158
  describe '#call' do
22
159
  let(:logger) { instance_double(Logger) }
160
+ let(:http) { instance_double(Net::HTTP) }
161
+ let(:http_response) do
162
+ header = { 'X-Request-Id' => 'fake-uuid' }
163
+ instance_double(Net::HTTPOK, body: body, header: header, code: code)
164
+ end
23
165
 
24
166
  subject { connection.call }
25
167
 
26
- context 'when http method is POST' do
27
- before do
28
- http = instance_double(Net::HTTP)
168
+ before do
169
+ expect(Net::HTTP).to receive(:new).with('api.knapsackpro.test', 3000).and_return(http)
29
170
 
30
- expect(Net::HTTP).to receive(:new).with('api.knapsackpro.test', 3000).and_return(http)
171
+ expect(http).to receive(:use_ssl=).with(false)
172
+ expect(http).to receive(:open_timeout=).with(15)
173
+ expect(http).to receive(:read_timeout=).with(15)
174
+ end
31
175
 
32
- expect(http).to receive(:use_ssl=).with(false)
33
- expect(http).to receive(:open_timeout=).with(15)
34
- expect(http).to receive(:read_timeout=).with(15)
176
+ context 'when http method is POST' do
177
+ let(:http_method) { :post }
35
178
 
36
- header = { 'X-Request-Id' => 'fake-uuid' }
37
- http_response = instance_double(Net::HTTPOK, body: body, header: header, code: code)
179
+ before do
38
180
  expect(http).to receive(:post).with(
39
181
  endpoint_path,
40
- "{\"fake\":\"hash\",\"test_suite_token\":\"3fa64859337f6e56409d49f865d13fd7\"}",
182
+ request_hash.to_json,
41
183
  {
42
184
  'Content-Type' => 'application/json',
43
185
  'Accept' => 'application/json',
44
186
  'KNAPSACK-PRO-CLIENT-NAME' => 'knapsack_pro-ruby',
45
187
  'KNAPSACK-PRO-CLIENT-VERSION' => KnapsackPro::VERSION,
188
+ 'KNAPSACK-PRO-TEST-SUITE-TOKEN' => test_suite_token,
46
189
  }
47
190
  ).and_return(http_response)
48
191
  end
49
192
 
50
- context 'when body response is json and API response code is 400' do
51
- let(:body) { '{"errors": "value"}' }
52
- let(:code) { '400' } # it must be string code
53
-
54
- before do
55
- expect(KnapsackPro).to receive(:logger).exactly(3).and_return(logger)
56
- expect(logger).to receive(:debug).with('API request UUID: fake-uuid')
57
- expect(logger).to receive(:debug).with('API response:')
58
- end
59
-
60
- it do
61
- parsed_response = { 'errors' => 'value' }
62
-
63
- expect(logger).to receive(:error).with(parsed_response)
64
-
65
- expect(subject).to eq(parsed_response)
66
- expect(connection.success?).to be true
67
- expect(connection.errors?).to be true
68
- end
193
+ it_behaves_like 'when request got response from API' do
194
+ let(:expected_http_method) { 'POST' }
69
195
  end
196
+ end
70
197
 
71
- context 'when body response is json with build_distribution_id' do
72
- let(:body) { '{"build_distribution_id": "seed-uuid"}' }
73
- let(:code) { '200' } # it must be string code
74
-
75
- before do
76
- expect(KnapsackPro).to receive(:logger).exactly(4).and_return(logger)
77
- expect(logger).to receive(:debug).with('API request UUID: fake-uuid')
78
- expect(logger).to receive(:debug).with("Test suite split seed: seed-uuid")
79
- expect(logger).to receive(:debug).with('API response:')
80
- end
81
-
82
- it do
83
- parsed_response = { 'build_distribution_id' => 'seed-uuid' }
84
-
85
- expect(logger).to receive(:debug).with(parsed_response)
198
+ context 'when http method is GET' do
199
+ let(:http_method) { :get }
86
200
 
87
- expect(subject).to eq(parsed_response)
88
- expect(connection.success?).to be true
89
- expect(connection.errors?).to be false
90
- end
201
+ before do
202
+ uri = URI.parse("http://api.knapsackpro.test:3000#{endpoint_path}")
203
+ uri.query = URI.encode_www_form(request_hash)
204
+ expect(http).to receive(:get).with(
205
+ uri,
206
+ {
207
+ 'Content-Type' => 'application/json',
208
+ 'Accept' => 'application/json',
209
+ 'KNAPSACK-PRO-CLIENT-NAME' => 'knapsack_pro-ruby',
210
+ 'KNAPSACK-PRO-CLIENT-VERSION' => KnapsackPro::VERSION,
211
+ 'KNAPSACK-PRO-TEST-SUITE-TOKEN' => test_suite_token,
212
+ }
213
+ ).and_return(http_response)
91
214
  end
92
215
 
93
- context 'when body response is empty' do
94
- let(:body) { '' }
95
- let(:code) { '200' } # it must be string code
96
-
97
- before do
98
- expect(KnapsackPro).to receive(:logger).exactly(3).and_return(logger)
99
- expect(logger).to receive(:debug).with('API request UUID: fake-uuid')
100
- expect(logger).to receive(:debug).with('API response:')
101
- end
102
-
103
- it do
104
- expect(logger).to receive(:debug).with('')
105
-
106
- expect(subject).to eq('')
107
- expect(connection.success?).to be true
108
- expect(connection.errors?).to be false
109
- end
216
+ it_behaves_like 'when request got response from API' do
217
+ let(:expected_http_method) { 'GET' }
110
218
  end
111
219
  end
112
220
 
113
221
  context 'when retry request for http method POST' do
114
- before do
115
- http = instance_double(Net::HTTP)
222
+ let(:http_method) { :post }
116
223
 
117
- expect(Net::HTTP).to receive(:new).exactly(3).with('api.knapsackpro.test', 3000).and_return(http)
118
-
119
- expect(http).to receive(:use_ssl=).exactly(3).with(false)
120
- expect(http).to receive(:open_timeout=).exactly(3).with(15)
121
- expect(http).to receive(:read_timeout=).exactly(3).with(15)
122
-
123
- header = { 'X-Request-Id' => 'fake-uuid' }
124
- http_response = instance_double(Net::HTTPOK, body: body, header: header, code: code)
125
- expect(http).to receive(:post).exactly(3).with(
224
+ before do
225
+ expect(http).to receive(:post).at_least(3).with(
126
226
  endpoint_path,
127
- "{\"fake\":\"hash\",\"test_suite_token\":\"3fa64859337f6e56409d49f865d13fd7\"}",
227
+ request_hash.to_json,
128
228
  {
129
229
  'Content-Type' => 'application/json',
130
230
  'Accept' => 'application/json',
131
231
  'KNAPSACK-PRO-CLIENT-NAME' => 'knapsack_pro-ruby',
132
232
  'KNAPSACK-PRO-CLIENT-VERSION' => KnapsackPro::VERSION,
233
+ 'KNAPSACK-PRO-TEST-SUITE-TOKEN' => test_suite_token,
133
234
  }
134
235
  ).and_return(http_response)
135
236
  end
136
237
 
137
- context 'when body response is json and API response code is 500' do
138
- let(:body) { '{"error": "Internal Server Error"}' }
139
- let(:code) { '500' } # it must be string code
140
-
141
- before do
142
- expect(KnapsackPro).to receive(:logger).at_least(1).and_return(logger)
143
- expect(logger).to receive(:debug).exactly(3).with('API request UUID: fake-uuid')
144
- expect(logger).to receive(:debug).exactly(3).with('API response:')
145
- end
146
-
147
- it do
148
- parsed_response = { 'error' => 'Internal Server Error' }
149
-
150
- expect(logger).to receive(:error).exactly(3).with(parsed_response)
151
-
152
- server_error = described_class::ServerError.new(parsed_response)
153
- expect(logger).to receive(:warn).exactly(3).with(server_error.inspect)
238
+ it_behaves_like 'when retry request' do
239
+ let(:expected_http_method) { 'POST' }
240
+ end
241
+ end
154
242
 
155
- expect(logger).to receive(:warn).with("Wait 4s and retry request to Knapsack Pro API.")
156
- expect(logger).to receive(:warn).with("Wait 8s and retry request to Knapsack Pro API.")
157
- expect(Kernel).to receive(:sleep).with(4)
158
- expect(Kernel).to receive(:sleep).with(8)
243
+ context 'when retry request for http method GET' do
244
+ let(:http_method) { :get }
159
245
 
160
- expect(subject).to eq(parsed_response)
246
+ before do
247
+ uri = URI.parse("http://api.knapsackpro.test:3000#{endpoint_path}")
248
+ uri.query = URI.encode_www_form(request_hash)
249
+ expect(http).to receive(:get).at_least(3).with(
250
+ uri,
251
+ {
252
+ 'Content-Type' => 'application/json',
253
+ 'Accept' => 'application/json',
254
+ 'KNAPSACK-PRO-CLIENT-NAME' => 'knapsack_pro-ruby',
255
+ 'KNAPSACK-PRO-CLIENT-VERSION' => KnapsackPro::VERSION,
256
+ 'KNAPSACK-PRO-TEST-SUITE-TOKEN' => test_suite_token,
257
+ }
258
+ ).and_return(http_response)
259
+ end
161
260
 
162
- expect(connection.success?).to be false
163
- expect(connection.errors?).to be true
164
- end
261
+ it_behaves_like 'when retry request' do
262
+ let(:expected_http_method) { 'GET' }
165
263
  end
166
264
  end
167
265
  end
@@ -183,6 +183,20 @@ describe KnapsackPro::Config::Env do
183
183
  end
184
184
  end
185
185
 
186
+ describe '.slow_test_file_pattern' do
187
+ subject { described_class.slow_test_file_pattern }
188
+
189
+ context 'when ENV exists' do
190
+ let(:slow_test_file_pattern) { 'spec/features/*_spec.rb' }
191
+ before { stub_const("ENV", { 'KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN' => slow_test_file_pattern }) }
192
+ it { should eq slow_test_file_pattern }
193
+ end
194
+
195
+ context "when ENV doesn't exist" do
196
+ it { should be_nil }
197
+ end
198
+ end
199
+
186
200
  describe '.test_file_exclude_pattern' do
187
201
  subject { described_class.test_file_exclude_pattern }
188
202
 
@@ -8,8 +8,11 @@ describe KnapsackPro::QueueAllocatorBuilder do
8
8
  subject { allocator_builder.allocator }
9
9
 
10
10
  before do
11
- test_files = double
12
- expect(allocator_builder).to receive(:test_files).and_return(test_files)
11
+ fast_and_slow_test_files_to_run = double
12
+ expect(allocator_builder).to receive(:fast_and_slow_test_files_to_run).and_return(fast_and_slow_test_files_to_run)
13
+
14
+ fallback_mode_test_files = double
15
+ expect(allocator_builder).to receive(:fallback_mode_test_files).and_return(fallback_mode_test_files)
13
16
 
14
17
  repository_adapter = double
15
18
  expect(KnapsackPro::RepositoryAdapterInitiator).to receive(:call).and_return(repository_adapter)
@@ -22,7 +25,8 @@ describe KnapsackPro::QueueAllocatorBuilder do
22
25
  expect(KnapsackPro::Config::Env).to receive(:ci_node_build_id).and_return(ci_node_build_id)
23
26
 
24
27
  expect(KnapsackPro::QueueAllocator).to receive(:new).with(
25
- test_files: test_files,
28
+ fast_and_slow_test_files_to_run: fast_and_slow_test_files_to_run,
29
+ fallback_mode_test_files: fallback_mode_test_files,
26
30
  ci_node_total: ci_node_total,
27
31
  ci_node_index: ci_node_index,
28
32
  ci_node_build_id: ci_node_build_id,