knapsack_pro 0.17.0 → 0.18.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +151 -2
  4. data/bin/knapsack_pro +1 -0
  5. data/knapsack_pro.gemspec +1 -1
  6. data/lib/knapsack_pro.rb +8 -0
  7. data/lib/knapsack_pro/adapters/base_adapter.rb +10 -0
  8. data/lib/knapsack_pro/adapters/rspec_adapter.rb +8 -0
  9. data/lib/knapsack_pro/allocator.rb +28 -14
  10. data/lib/knapsack_pro/allocator_builder.rb +1 -29
  11. data/lib/knapsack_pro/base_allocator_builder.rb +35 -0
  12. data/lib/knapsack_pro/client/api/v1/queues.rb +27 -0
  13. data/lib/knapsack_pro/config/ci/base.rb +3 -0
  14. data/lib/knapsack_pro/config/ci/buildkite.rb +4 -0
  15. data/lib/knapsack_pro/config/ci/circle.rb +4 -1
  16. data/lib/knapsack_pro/config/ci/semaphore.rb +4 -0
  17. data/lib/knapsack_pro/config/ci/snap_ci.rb +4 -0
  18. data/lib/knapsack_pro/config/ci/travis.rb +4 -0
  19. data/lib/knapsack_pro/config/env.rb +22 -0
  20. data/lib/knapsack_pro/config/env_generator.rb +19 -0
  21. data/lib/knapsack_pro/queue_allocator.rb +52 -0
  22. data/lib/knapsack_pro/queue_allocator_builder.rb +13 -0
  23. data/lib/knapsack_pro/report.rb +35 -0
  24. data/lib/knapsack_pro/runners/queue/base_runner.rb +34 -0
  25. data/lib/knapsack_pro/runners/queue/rspec_runner.rb +42 -0
  26. data/lib/knapsack_pro/task_loader.rb +1 -1
  27. data/lib/knapsack_pro/version.rb +1 -1
  28. data/lib/tasks/queue/rspec.rake +9 -0
  29. data/lib/tasks/salt.rake +0 -2
  30. data/spec/knapsack_pro/adapters/base_adapter_spec.rb +39 -8
  31. data/spec/knapsack_pro/adapters/rspec_adapter_spec.rb +11 -0
  32. data/spec/knapsack_pro/allocator_builder_spec.rb +2 -12
  33. data/spec/knapsack_pro/base_allocator_builder_spec.rb +22 -0
  34. data/spec/knapsack_pro/client/api/v1/queues_spec.rb +42 -0
  35. data/spec/knapsack_pro/config/ci/base_spec.rb +1 -0
  36. data/spec/knapsack_pro/config/ci/buildkite_spec.rb +13 -0
  37. data/spec/knapsack_pro/config/ci/circle_spec.rb +13 -0
  38. data/spec/knapsack_pro/config/ci/semaphore_spec.rb +13 -0
  39. data/spec/knapsack_pro/config/ci/snap_ci_spec.rb +13 -0
  40. data/spec/knapsack_pro/config/ci/travis_spec.rb +13 -0
  41. data/spec/knapsack_pro/config/env_generator_spec.rb +52 -0
  42. data/spec/knapsack_pro/config/env_spec.rb +90 -0
  43. data/spec/knapsack_pro/queue_allocator_builder_spec.rb +38 -0
  44. data/spec/knapsack_pro/queue_allocator_spec.rb +85 -0
  45. data/spec/knapsack_pro/report_spec.rb +69 -0
  46. data/spec/knapsack_pro/runners/queue/base_runner_spec.rb +67 -0
  47. data/spec/knapsack_pro/runners/queue/rspec_runner_spec.rb +118 -0
  48. metadata +26 -4
@@ -112,5 +112,16 @@ describe KnapsackPro::Adapters::RSpecAdapter do
112
112
  subject.bind_save_report
113
113
  end
114
114
  end
115
+
116
+ describe '#bind_save_queue_report' do
117
+ it do
118
+ expect(config).to receive(:after).with(:suite).and_yield
119
+ expect(::RSpec).to receive(:configure).and_yield(config)
120
+
121
+ expect(KnapsackPro::Report).to receive(:save_subset_queue_to_file)
122
+
123
+ subject.bind_save_queue_report
124
+ end
125
+ end
115
126
  end
116
127
  end
@@ -22,24 +22,14 @@ describe KnapsackPro::AllocatorBuilder do
22
22
  ci_node_index = double
23
23
  expect(KnapsackPro::Config::Env).to receive(:ci_node_index).and_return(ci_node_index)
24
24
 
25
- expect(KnapsackPro::Allocator).to receive(:new).with({
25
+ expect(KnapsackPro::Allocator).to receive(:new).with(
26
26
  test_files: test_files,
27
27
  ci_node_total: ci_node_total,
28
28
  ci_node_index: ci_node_index,
29
29
  repository_adapter: repository_adapter,
30
- }).and_return(allocator)
30
+ ).and_return(allocator)
31
31
  end
32
32
 
33
33
  it { should eq allocator }
34
34
  end
35
-
36
- describe '#test_dir' do
37
- subject { allocator_builder.test_dir }
38
-
39
- before do
40
- expect(KnapsackPro::TestFilePattern).to receive(:call).and_return('spec/**{,/*/**}/*_spec.rb')
41
- end
42
-
43
- it { should eq 'spec' }
44
- end
45
35
  end
@@ -0,0 +1,22 @@
1
+ describe KnapsackPro::BaseAllocatorBuilder do
2
+ let(:adapter_class) { KnapsackPro::Adapters::BaseAdapter }
3
+ let(:allocator_builder) { described_class.new(adapter_class) }
4
+
5
+ describe '#allocator' do
6
+ subject { allocator_builder.allocator }
7
+
8
+ it do
9
+ expect { subject }.to raise_error(NotImplementedError)
10
+ end
11
+ end
12
+
13
+ describe '#test_dir' do
14
+ subject { allocator_builder.test_dir }
15
+
16
+ before do
17
+ expect(KnapsackPro::TestFilePattern).to receive(:call).with(adapter_class).and_return('spec/**{,/*/**}/*_spec.rb')
18
+ end
19
+
20
+ it { should eq 'spec' }
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ describe KnapsackPro::Client::API::V1::Queues do
2
+ describe '.queue' do
3
+ let(:can_initialize_queue) { double }
4
+ let(:commit_hash) { double }
5
+ let(:branch) { double }
6
+ let(:node_total) { double }
7
+ let(:node_index) { double }
8
+ let(:test_files) { double }
9
+
10
+ subject do
11
+ described_class.queue(
12
+ can_initialize_queue: can_initialize_queue,
13
+ commit_hash: commit_hash,
14
+ branch: branch,
15
+ node_total: node_total,
16
+ node_index: node_index,
17
+ test_files: test_files
18
+ )
19
+ end
20
+
21
+ it do
22
+ node_build_id = double
23
+ expect(KnapsackPro::Config::Env).to receive(:ci_node_build_id).and_return(node_build_id)
24
+
25
+ action = double
26
+ expect(KnapsackPro::Client::API::Action).to receive(:new).with({
27
+ endpoint_path: '/v1/queues/queue',
28
+ http_method: :post,
29
+ request_hash: {
30
+ can_initialize_queue: can_initialize_queue,
31
+ commit_hash: commit_hash,
32
+ branch: branch,
33
+ node_total: node_total,
34
+ node_index: node_index,
35
+ node_build_id: node_build_id,
36
+ test_files: test_files
37
+ }
38
+ }).and_return(action)
39
+ expect(subject).to eq action
40
+ end
41
+ end
42
+ end
@@ -1,6 +1,7 @@
1
1
  describe KnapsackPro::Config::CI::Base do
2
2
  its(:node_total) { should be nil }
3
3
  its(:node_index) { should be nil }
4
+ its(:node_build_id) { should be nil }
4
5
  its(:commit_hash) { should be nil }
5
6
  its(:branch) { should be nil }
6
7
  its(:project_dir) { should be nil }
@@ -33,6 +33,19 @@ describe KnapsackPro::Config::CI::Buildkite do
33
33
  end
34
34
  end
35
35
 
36
+ describe '#node_build_id' do
37
+ subject { described_class.new.node_build_id }
38
+
39
+ context 'when environment exists' do
40
+ let(:env) { { 'BUILDKITE_BUILD_NUMBER' => 1514 } }
41
+ it { should eql 1514 }
42
+ end
43
+
44
+ context "when environment doesn't exist" do
45
+ it { should be nil }
46
+ end
47
+ end
48
+
36
49
  describe '#commit_hash' do
37
50
  subject { described_class.new.commit_hash }
38
51
 
@@ -33,6 +33,19 @@ describe KnapsackPro::Config::CI::Circle do
33
33
  end
34
34
  end
35
35
 
36
+ describe '#node_build_id' do
37
+ subject { described_class.new.node_build_id }
38
+
39
+ context 'when environment exists' do
40
+ let(:env) { { 'CIRCLE_BUILD_NUM' => 123 } }
41
+ it { should eql 123 }
42
+ end
43
+
44
+ context "when environment doesn't exist" do
45
+ it { should be nil }
46
+ end
47
+ end
48
+
36
49
  describe '#commit_hash' do
37
50
  subject { described_class.new.commit_hash }
38
51
 
@@ -33,6 +33,19 @@ describe KnapsackPro::Config::CI::Semaphore do
33
33
  end
34
34
  end
35
35
 
36
+ describe '#node_build_id' do
37
+ subject { described_class.new.node_build_id }
38
+
39
+ context 'when environment exists' do
40
+ let(:env) { { 'SEMAPHORE_BUILD_NUMBER' => 23 } }
41
+ it { should eql 23 }
42
+ end
43
+
44
+ context "when environment doesn't exist" do
45
+ it { should be nil }
46
+ end
47
+ end
48
+
36
49
  describe '#commit_hash' do
37
50
  subject { described_class.new.commit_hash }
38
51
 
@@ -33,6 +33,19 @@ describe KnapsackPro::Config::CI::SnapCI do
33
33
  end
34
34
  end
35
35
 
36
+ describe '#node_build_id' do
37
+ subject { described_class.new.node_build_id }
38
+
39
+ context 'when environment exists' do
40
+ let(:env) { { 'SNAP_PIPELINE_COUNTER' => 123 } }
41
+ it { should eql 123 }
42
+ end
43
+
44
+ context "when environment doesn't exist" do
45
+ it { should be nil }
46
+ end
47
+ end
48
+
36
49
  describe '#commit_hash' do
37
50
  subject { described_class.new.commit_hash }
38
51
 
@@ -19,6 +19,19 @@ describe KnapsackPro::Config::CI::Travis do
19
19
  it { should be nil }
20
20
  end
21
21
 
22
+ describe '#node_build_id' do
23
+ subject { described_class.new.node_build_id }
24
+
25
+ context 'when environment exists' do
26
+ let(:env) { { 'TRAVIS_BUILD_NUMBER' => 4 } }
27
+ it { should eql 4 }
28
+ end
29
+
30
+ context "when environment doesn't exist" do
31
+ it { should be nil }
32
+ end
33
+ end
34
+
22
35
  describe '#commit_hash' do
23
36
  subject { described_class.new.commit_hash }
24
37
 
@@ -0,0 +1,52 @@
1
+ describe KnapsackPro::Config::EnvGenerator do
2
+ describe '.set_queue_id' do
3
+ subject { described_class.set_queue_id }
4
+
5
+ context 'when queue id exists' do
6
+ before do
7
+ stub_const("ENV", { 'KNAPSACK_PRO_QUEUE_ID' => 'fake-queue-id' })
8
+ end
9
+
10
+ it do
11
+ expect { subject }.to raise_error('Queue ID already generated.')
12
+ end
13
+ end
14
+
15
+ context "when queue id doesn't exist" do
16
+ before { stub_const("ENV", {}) }
17
+
18
+ it do
19
+ subject
20
+ expect(ENV['KNAPSACK_PRO_QUEUE_ID']).not_to be_nil
21
+ end
22
+
23
+ it do
24
+ now = DateTime.new(2016, 1, 9, 0, 0, 0)
25
+
26
+ Timecop.freeze(now) do
27
+ uuid = 'fake-uuid'
28
+ expect(SecureRandom).to receive(:uuid).and_return(uuid)
29
+
30
+ subject
31
+
32
+ expect(ENV['KNAPSACK_PRO_QUEUE_ID']).to eq '1452297600_fake-uuid'
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ describe '.set_subset_queue_id' do
39
+ subject { described_class.set_subset_queue_id }
40
+
41
+ before { stub_const("ENV", {}) }
42
+
43
+ it do
44
+ uuid = 'fake-uuid'
45
+ expect(SecureRandom).to receive(:uuid).and_return(uuid)
46
+
47
+ subject
48
+
49
+ expect(ENV['KNAPSACK_PRO_SUBSET_QUEUE_ID']).to eq uuid
50
+ end
51
+ end
52
+ end
@@ -47,6 +47,29 @@ describe KnapsackPro::Config::Env do
47
47
  end
48
48
  end
49
49
 
50
+ describe '.ci_node_build_id' do
51
+ subject { described_class.ci_node_build_id }
52
+
53
+ context 'when ENV exists' do
54
+ context 'when KNAPSACK_PRO_CI_NODE_BUILD_ID has value' do
55
+ before { stub_const("ENV", { 'KNAPSACK_PRO_CI_NODE_BUILD_ID' => '7' }) }
56
+ it { should eq '7' }
57
+ end
58
+
59
+ context 'when CI environment has value' do
60
+ before do
61
+ expect(described_class).to receive(:ci_env_for).with(:node_build_id).and_return('8')
62
+ end
63
+
64
+ it { should eq '8' }
65
+ end
66
+ end
67
+
68
+ context "when ENV doesn't exist" do
69
+ it { should eq 'missing-build-id' }
70
+ end
71
+ end
72
+
50
73
  describe '.commit_hash' do
51
74
  subject { described_class.commit_hash }
52
75
 
@@ -178,6 +201,73 @@ describe KnapsackPro::Config::Env do
178
201
  end
179
202
  end
180
203
 
204
+ describe '.queue_recording_enabled' do
205
+ subject { described_class.queue_recording_enabled }
206
+
207
+ context 'when ENV exists' do
208
+ let(:queue_recording_enabled) { 'true' }
209
+ before { stub_const("ENV", { 'KNAPSACK_PRO_QUEUE_RECORDING_ENABLED' => queue_recording_enabled }) }
210
+ it { should eq queue_recording_enabled }
211
+ end
212
+
213
+ context "when ENV doesn't exist" do
214
+ it { should be_nil }
215
+ end
216
+ end
217
+
218
+ describe '.queue_recording_enabled?' do
219
+ subject { described_class.queue_recording_enabled? }
220
+
221
+ before do
222
+ expect(described_class).to receive(:queue_recording_enabled).and_return(queue_recording_enabled)
223
+ end
224
+
225
+ context 'when enabled' do
226
+ let(:queue_recording_enabled) { 'true' }
227
+
228
+ it { should be true }
229
+ end
230
+
231
+ context 'when disabled' do
232
+ let(:queue_recording_enabled) { nil }
233
+
234
+ it { should be false }
235
+ end
236
+ end
237
+
238
+ describe '.queue_id' do
239
+ subject { described_class.queue_id }
240
+
241
+ context 'when ENV exists' do
242
+ let(:queue_id) { 'fake-queue-id' }
243
+ before { stub_const("ENV", { 'KNAPSACK_PRO_QUEUE_ID' => queue_id }) }
244
+ it { should eq queue_id }
245
+ end
246
+
247
+ context "when ENV doesn't exist" do
248
+ before { stub_const("ENV", {}) }
249
+ it do
250
+ expect { subject }.to raise_error('Missing Queue ID')
251
+ end
252
+ end
253
+ end
254
+
255
+ describe '.subset_queue_id' do
256
+ subject { described_class.subset_queue_id }
257
+
258
+ context 'when ENV exists' do
259
+ let(:subset_queue_id) { 'fake-subset-queue-id' }
260
+ before { stub_const("ENV", { 'KNAPSACK_PRO_SUBSET_QUEUE_ID' => subset_queue_id }) }
261
+ it { should eq subset_queue_id }
262
+ end
263
+
264
+ context "when ENV doesn't exist" do
265
+ it do
266
+ expect { subject }.to raise_error('Missing Subset Queue ID')
267
+ end
268
+ end
269
+ end
270
+
181
271
  describe '.test_files_encrypted' do
182
272
  subject { described_class.test_files_encrypted }
183
273
 
@@ -0,0 +1,38 @@
1
+ describe KnapsackPro::QueueAllocatorBuilder do
2
+ let(:adapter_class) { KnapsackPro::Adapters::BaseAdapter }
3
+ let(:allocator_builder) { described_class.new(adapter_class) }
4
+
5
+ describe '#allocator' do
6
+ let(:allocator) { double }
7
+
8
+ subject { allocator_builder.allocator }
9
+
10
+ before do
11
+ test_file_pattern = double
12
+ expect(KnapsackPro::TestFilePattern).to receive(:call).with(adapter_class).and_return(test_file_pattern)
13
+
14
+ test_files = double
15
+ expect(KnapsackPro::TestFileFinder).to receive(:call).with(test_file_pattern).and_return(test_files)
16
+
17
+ repository_adapter = double
18
+ expect(KnapsackPro::RepositoryAdapterInitiator).to receive(:call).and_return(repository_adapter)
19
+
20
+ ci_node_total = double
21
+ expect(KnapsackPro::Config::Env).to receive(:ci_node_total).and_return(ci_node_total)
22
+ ci_node_index = double
23
+ expect(KnapsackPro::Config::Env).to receive(:ci_node_index).and_return(ci_node_index)
24
+ ci_node_build_id = double
25
+ expect(KnapsackPro::Config::Env).to receive(:ci_node_build_id).and_return(ci_node_build_id)
26
+
27
+ expect(KnapsackPro::QueueAllocator).to receive(:new).with(
28
+ test_files: test_files,
29
+ ci_node_total: ci_node_total,
30
+ ci_node_index: ci_node_index,
31
+ ci_node_build_id: ci_node_build_id,
32
+ repository_adapter: repository_adapter,
33
+ ).and_return(allocator)
34
+ end
35
+
36
+ it { should eq allocator }
37
+ end
38
+ end
@@ -0,0 +1,85 @@
1
+ describe KnapsackPro::QueueAllocator do
2
+ let(:test_files) { double }
3
+ let(:ci_node_total) { double }
4
+ let(:ci_node_index) { double }
5
+ let(:ci_node_build_id) { double }
6
+ let(:repository_adapter) { instance_double(KnapsackPro::RepositoryAdapters::EnvAdapter, commit_hash: double, branch: double) }
7
+
8
+ let(:queue_allocator) do
9
+ described_class.new(
10
+ test_files: test_files,
11
+ ci_node_total: ci_node_total,
12
+ ci_node_index: ci_node_index,
13
+ ci_node_build_id: ci_node_build_id,
14
+ repository_adapter: repository_adapter
15
+ )
16
+ end
17
+
18
+ describe '#test_file_paths' do
19
+ let(:can_initialize_queue) { double }
20
+ let(:response) { double }
21
+
22
+ subject { queue_allocator.test_file_paths(can_initialize_queue) }
23
+
24
+ before do
25
+ encrypted_test_files = double
26
+ expect(KnapsackPro::Crypto::Encryptor).to receive(:call).with(test_files).and_return(encrypted_test_files)
27
+
28
+ action = double
29
+ expect(KnapsackPro::Client::API::V1::Queues).to receive(:queue).with(
30
+ can_initialize_queue: can_initialize_queue,
31
+ commit_hash: repository_adapter.commit_hash,
32
+ branch: repository_adapter.branch,
33
+ node_total: ci_node_total,
34
+ node_index: ci_node_index,
35
+ node_build_id: ci_node_build_id,
36
+ test_files: encrypted_test_files,
37
+ ).and_return(action)
38
+
39
+ connection = instance_double(KnapsackPro::Client::Connection,
40
+ call: response,
41
+ success?: success?,
42
+ errors?: errors?)
43
+ expect(KnapsackPro::Client::Connection).to receive(:new).with(action).and_return(connection)
44
+ end
45
+
46
+ context 'when successful request to API' do
47
+ let(:success?) { true }
48
+
49
+ context 'when response has errors' do
50
+ let(:errors?) { true }
51
+
52
+ it do
53
+ expect { subject }.to raise_error(ArgumentError)
54
+ end
55
+ end
56
+
57
+ context 'when response has no errors' do
58
+ let(:errors?) { false }
59
+ let(:response) do
60
+ {
61
+ 'test_files' => [
62
+ { 'path' => 'a_spec.rb' },
63
+ { 'path' => 'b_spec.rb' },
64
+ ]
65
+ }
66
+ end
67
+
68
+ before do
69
+ expect(KnapsackPro::Crypto::Decryptor).to receive(:call).with(test_files, response['test_files']).and_call_original
70
+ end
71
+
72
+ it { should eq ['a_spec.rb', 'b_spec.rb'] }
73
+ end
74
+ end
75
+
76
+ context 'when not successful request to API' do
77
+ let(:success?) { false }
78
+ let(:errors?) { false }
79
+
80
+ it do
81
+ expect { subject }.to raise_error("Couldn't connect with Knapsack Pro API. Response: #{response}")
82
+ end
83
+ end
84
+ end
85
+ end