knapsack_pro 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -3
  3. data/CHANGELOG.md +4 -0
  4. data/README.md +368 -4
  5. data/bin/knapsack_pro +20 -0
  6. data/circle.yml +2 -0
  7. data/knapsack_pro.gemspec +6 -1
  8. data/lib/knapsack_pro.rb +74 -0
  9. data/lib/knapsack_pro/adapters/base_adapter.rb +30 -0
  10. data/lib/knapsack_pro/adapters/cucumber_adapter.rb +40 -0
  11. data/lib/knapsack_pro/adapters/minitest_adapter.rb +52 -0
  12. data/lib/knapsack_pro/adapters/rspec_adapter.rb +48 -0
  13. data/lib/knapsack_pro/allocator.rb +40 -0
  14. data/lib/knapsack_pro/allocator_builder.rb +40 -0
  15. data/lib/knapsack_pro/client/api/action.rb +17 -0
  16. data/lib/knapsack_pro/client/api/v1/base.rb +15 -0
  17. data/lib/knapsack_pro/client/api/v1/build_distributions.rb +29 -0
  18. data/lib/knapsack_pro/client/api/v1/build_subsets.rb +29 -0
  19. data/lib/knapsack_pro/client/connection.rb +94 -0
  20. data/lib/knapsack_pro/config/ci/base.rb +22 -0
  21. data/lib/knapsack_pro/config/ci/buildkite.rb +27 -0
  22. data/lib/knapsack_pro/config/ci/circle.rb +29 -0
  23. data/lib/knapsack_pro/config/ci/semaphore.rb +28 -0
  24. data/lib/knapsack_pro/config/env.rb +111 -0
  25. data/lib/knapsack_pro/logger_wrapper.rb +15 -0
  26. data/lib/knapsack_pro/presenter.rb +25 -0
  27. data/lib/knapsack_pro/report.rb +20 -0
  28. data/lib/knapsack_pro/repository_adapter_initiator.rb +12 -0
  29. data/lib/knapsack_pro/repository_adapters/base_adapter.rb +14 -0
  30. data/lib/knapsack_pro/repository_adapters/env_adapter.rb +13 -0
  31. data/lib/knapsack_pro/repository_adapters/git_adapter.rb +19 -0
  32. data/lib/knapsack_pro/runners/base_runner.rb +31 -0
  33. data/lib/knapsack_pro/runners/cucumber_runner.rb +16 -0
  34. data/lib/knapsack_pro/runners/minitest_runner.rb +26 -0
  35. data/lib/knapsack_pro/runners/rspec_runner.rb +16 -0
  36. data/lib/knapsack_pro/task_loader.rb +11 -0
  37. data/lib/knapsack_pro/test_file_cleaner.rb +7 -0
  38. data/lib/knapsack_pro/test_file_finder.rb +33 -0
  39. data/lib/knapsack_pro/test_file_pattern.rb +7 -0
  40. data/lib/knapsack_pro/test_file_presenter.rb +11 -0
  41. data/lib/knapsack_pro/test_flat_distributor.rb +84 -0
  42. data/lib/knapsack_pro/tracker.rb +64 -0
  43. data/lib/knapsack_pro/version.rb +1 -1
  44. data/lib/tasks/cucumber.rake +7 -0
  45. data/lib/tasks/minitest.rake +7 -0
  46. data/lib/tasks/rspec.rake +7 -0
  47. data/spec/fixtures/vcr_cassettes/api/v1/build_distributions/subset/invalid_test_suite_token.yml +50 -0
  48. data/spec/fixtures/vcr_cassettes/api/v1/build_distributions/subset/success.yml +52 -0
  49. data/spec/fixtures/vcr_cassettes/api/v1/build_subsets/create/invalid_test_suite_token.yml +50 -0
  50. data/spec/fixtures/vcr_cassettes/api/v1/build_subsets/create/success.yml +50 -0
  51. data/spec/integration/api/build_distributions_subset_spec.rb +74 -0
  52. data/spec/integration/api/build_subsets_create_spec.rb +76 -0
  53. data/spec/knapsack_pro/adapters/base_adapter_spec.rb +63 -0
  54. data/spec/knapsack_pro/adapters/cucumber_adapter_spec.rb +71 -0
  55. data/spec/knapsack_pro/adapters/minitest_adapter_spec.rb +107 -0
  56. data/spec/knapsack_pro/adapters/rspec_adapter_spec.rb +94 -0
  57. data/spec/knapsack_pro/allocator_builder_spec.rb +45 -0
  58. data/spec/knapsack_pro/allocator_spec.rb +80 -0
  59. data/spec/knapsack_pro/client/api/action_spec.rb +17 -0
  60. data/spec/knapsack_pro/client/api/v1/base_spec.rb +2 -0
  61. data/spec/knapsack_pro/client/api/v1/build_distributions_spec.rb +35 -0
  62. data/spec/knapsack_pro/client/api/v1/build_subsets_spec.rb +35 -0
  63. data/spec/knapsack_pro/client/connection_spec.rb +138 -0
  64. data/spec/knapsack_pro/config/ci/base_spec.rb +7 -0
  65. data/spec/knapsack_pro/config/ci/buildkite_spec.rb +74 -0
  66. data/spec/knapsack_pro/config/ci/circle_spec.rb +74 -0
  67. data/spec/knapsack_pro/config/ci/semaphore_spec.rb +74 -0
  68. data/spec/knapsack_pro/config/env_spec.rb +368 -0
  69. data/spec/knapsack_pro/logger_wrapper_spec.rb +15 -0
  70. data/spec/knapsack_pro/presenter_spec.rb +57 -0
  71. data/spec/knapsack_pro/report_spec.rb +68 -0
  72. data/spec/knapsack_pro/repository_adapter_initiator_spec.rb +21 -0
  73. data/spec/knapsack_pro/repository_adapters/base_adapter_spec.rb +13 -0
  74. data/spec/knapsack_pro/repository_adapters/env_adapter_spec.rb +27 -0
  75. data/spec/knapsack_pro/repository_adapters/git_adapter_spec.rb +27 -0
  76. data/spec/knapsack_pro/runners/base_runner_spec.rb +61 -0
  77. data/spec/knapsack_pro/runners/cucumber_runner_spec.rb +47 -0
  78. data/spec/knapsack_pro/runners/minitest_runner_spec.rb +34 -0
  79. data/spec/knapsack_pro/runners/rspec_runner_spec.rb +49 -0
  80. data/spec/knapsack_pro/task_loader_spec.rb +14 -0
  81. data/spec/knapsack_pro/test_file_cleaner_spec.rb +11 -0
  82. data/spec/knapsack_pro/test_file_finder_spec.rb +35 -0
  83. data/spec/knapsack_pro/test_file_pattern_spec.rb +23 -0
  84. data/spec/knapsack_pro/test_file_presenter_spec.rb +22 -0
  85. data/spec/knapsack_pro/test_flat_distributor_spec.rb +60 -0
  86. data/spec/knapsack_pro/tracker_spec.rb +102 -0
  87. data/spec/knapsack_pro_spec.rb +56 -0
  88. data/spec/spec_helper.rb +11 -1
  89. data/spec/support/fakes/cucumber.rb +12 -0
  90. data/spec/support/fakes/minitest.rb +12 -0
  91. data/spec/support/shared_examples/adapter.rb +17 -0
  92. data/spec_fake/controllers/users_controller_spec.rb +0 -0
  93. data/spec_fake/models/admin_spec.rb +0 -0
  94. data/spec_fake/models/user_spec.rb +0 -0
  95. data/spec_fake/spec_helper.rb +0 -0
  96. data/test_fake/a_test.rb +0 -0
  97. data/test_fake/b_test.rb +0 -0
  98. metadata +212 -12
  99. data/Gemfile.lock +0 -69
  100. data/spec/knapsack_spec.rb +0 -3
@@ -0,0 +1,94 @@
1
+ describe KnapsackPro::Adapters::RSpecAdapter do
2
+ it do
3
+ expect(described_class::TEST_DIR_PATTERN).to eq 'spec/**/*_spec.rb'
4
+ end
5
+
6
+ context do
7
+ before { expect(::RSpec).to receive(:configure) }
8
+ it_behaves_like 'adapter'
9
+ end
10
+
11
+ describe '.test_path' do
12
+ let(:current_example_metadata) do
13
+ {
14
+ file_path: '1_shared_example.rb',
15
+ parent_example_group: {
16
+ file_path: '2_shared_example.rb',
17
+ parent_example_group: {
18
+ file_path: 'a_spec.rb'
19
+ }
20
+ }
21
+ }
22
+ end
23
+
24
+ subject { described_class.test_path(current_example_metadata) }
25
+
26
+ it { should eql 'a_spec.rb' }
27
+
28
+ context 'with turnip features' do
29
+ let(:current_example_metadata) do
30
+ {
31
+ file_path: "./spec/features/logging_in.feature",
32
+ turnip: true,
33
+ parent_example_group: {
34
+ file_path: "gems/turnip-1.2.4/lib/turnip/rspec.rb",
35
+ }
36
+ }
37
+ end
38
+
39
+ subject { described_class.test_path(current_example_metadata) }
40
+
41
+ it { should eql './spec/features/logging_in.feature' }
42
+ end
43
+ end
44
+
45
+ describe 'bind methods' do
46
+ let(:config) { double }
47
+
48
+ describe '#bind_time_tracker' do
49
+ let(:tracker) { instance_double(KnapsackPro::Tracker) }
50
+ let(:logger) { instance_double(Logger) }
51
+ let(:test_path) { 'spec/a_spec.rb' }
52
+ let(:global_time) { 'Global time: 01m 05s' }
53
+ let(:example_group) { double }
54
+ let(:current_example) do
55
+ OpenStruct.new(metadata: {
56
+ example_group: example_group
57
+ })
58
+ end
59
+
60
+ it do
61
+ expect(config).to receive(:before).with(:each).and_yield
62
+ expect(config).to receive(:after).with(:each).and_yield
63
+ expect(config).to receive(:after).with(:suite).and_yield
64
+ expect(::RSpec).to receive(:configure).and_yield(config)
65
+
66
+ expect(::RSpec).to receive(:current_example).twice.and_return(current_example)
67
+ expect(described_class).to receive(:test_path).with(example_group).and_return(test_path)
68
+
69
+ allow(KnapsackPro).to receive(:tracker).and_return(tracker)
70
+ expect(tracker).to receive(:current_test_path=).with(test_path)
71
+ expect(tracker).to receive(:start_timer)
72
+
73
+ expect(tracker).to receive(:stop_timer)
74
+
75
+ expect(KnapsackPro::Presenter).to receive(:global_time).and_return(global_time)
76
+ expect(KnapsackPro).to receive(:logger).and_return(logger)
77
+ expect(logger).to receive(:info).with(global_time)
78
+
79
+ subject.bind_time_tracker
80
+ end
81
+ end
82
+
83
+ describe '#bind_save_report' do
84
+ it do
85
+ expect(config).to receive(:after).with(:suite).and_yield
86
+ expect(::RSpec).to receive(:configure).and_yield(config)
87
+
88
+ expect(KnapsackPro::Report).to receive(:save)
89
+
90
+ subject.bind_save_report
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,45 @@
1
+ describe KnapsackPro::AllocatorBuilder 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
+
25
+ expect(KnapsackPro::Allocator).to receive(:new).with({
26
+ test_files: test_files,
27
+ ci_node_total: ci_node_total,
28
+ ci_node_index: ci_node_index,
29
+ repository_adapter: repository_adapter,
30
+ }).and_return(allocator)
31
+ end
32
+
33
+ it { should eq allocator }
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
+ end
@@ -0,0 +1,80 @@
1
+ describe KnapsackPro::Allocator do
2
+ let(:test_files) { double }
3
+ let(:ci_node_total) { double }
4
+ let(:ci_node_index) { double }
5
+ let(:repository_adapter) { instance_double(KnapsackPro::RepositoryAdapters::EnvAdapter, commit_hash: double, branch: double) }
6
+
7
+ let(:allocator) do
8
+ described_class.new(
9
+ test_files: test_files,
10
+ ci_node_total: ci_node_total,
11
+ ci_node_index: ci_node_index,
12
+ repository_adapter: repository_adapter
13
+ )
14
+ end
15
+
16
+ describe '#test_file_paths' do
17
+ let(:response) { double }
18
+
19
+ subject { allocator.test_file_paths }
20
+
21
+ before do
22
+ action = double
23
+ expect(KnapsackPro::Client::API::V1::BuildDistributions).to receive(:subset).with({
24
+ commit_hash: repository_adapter.commit_hash,
25
+ branch: repository_adapter.branch,
26
+ node_total: ci_node_total,
27
+ node_index: ci_node_index,
28
+ test_files: test_files,
29
+ }).and_return(action)
30
+
31
+ connection = instance_double(KnapsackPro::Client::Connection,
32
+ call: response,
33
+ success?: success?,
34
+ errors?: errors?)
35
+ expect(KnapsackPro::Client::Connection).to receive(:new).with(action).and_return(connection)
36
+ end
37
+
38
+ context 'when successful request to API' do
39
+ let(:success?) { true }
40
+
41
+ context 'when response has errors' do
42
+ let(:errors?) { true }
43
+
44
+ it do
45
+ expect { subject }.to raise_error(ArgumentError)
46
+ end
47
+ end
48
+
49
+ context 'when response has no errors' do
50
+ let(:errors?) { false }
51
+ let(:response) do
52
+ {
53
+ 'test_files' => [
54
+ { 'path' => 'a_spec.rb' },
55
+ { 'path' => 'b_spec.rb' },
56
+ ]
57
+ }
58
+ end
59
+
60
+ it { should eq ['a_spec.rb', 'b_spec.rb'] }
61
+ end
62
+ end
63
+
64
+ context 'when not successful request to API' do
65
+ let(:success?) { false }
66
+ let(:errors?) { false }
67
+
68
+ before do
69
+ test_flat_distributor = instance_double(KnapsackPro::TestFlatDistributor)
70
+ expect(KnapsackPro::TestFlatDistributor).to receive(:new).with(test_files, ci_node_total).and_return(test_flat_distributor)
71
+ expect(test_flat_distributor).to receive(:test_files_for_node).with(ci_node_index).and_return([
72
+ { 'path' => 'c_spec.rb' },
73
+ { 'path' => 'd_spec.rb' },
74
+ ])
75
+ end
76
+
77
+ it { should eq ['c_spec.rb', 'd_spec.rb'] }
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,17 @@
1
+ describe KnapsackPro::Client::API::Action do
2
+ let(:endpoint_path) { double }
3
+ let(:http_method) { double }
4
+ let(:request_hash) { double }
5
+
6
+ subject do
7
+ described_class.new(
8
+ endpoint_path: endpoint_path,
9
+ http_method: http_method,
10
+ request_hash: request_hash
11
+ )
12
+ end
13
+
14
+ its(:endpoint_path) { should eq endpoint_path }
15
+ its(:http_method) { should eq http_method }
16
+ its(:request_hash) { should eq request_hash }
17
+ end
@@ -0,0 +1,2 @@
1
+ describe KnapsackPro::Client::API::V1::Base do
2
+ end
@@ -0,0 +1,35 @@
1
+ describe KnapsackPro::Client::API::V1::BuildDistributions do
2
+ describe '.subset' do
3
+ let(:commit_hash) { double }
4
+ let(:branch) { double }
5
+ let(:node_total) { double }
6
+ let(:node_index) { double }
7
+ let(:test_files) { double }
8
+
9
+ subject do
10
+ described_class.subset(
11
+ commit_hash: commit_hash,
12
+ branch: branch,
13
+ node_total: node_total,
14
+ node_index: node_index,
15
+ test_files: test_files
16
+ )
17
+ end
18
+
19
+ it do
20
+ action = double
21
+ expect(KnapsackPro::Client::API::Action).to receive(:new).with({
22
+ endpoint_path: '/v1/build_distributions/subset',
23
+ http_method: :post,
24
+ request_hash: {
25
+ commit_hash: commit_hash,
26
+ branch: branch,
27
+ node_total: node_total,
28
+ node_index: node_index,
29
+ test_files: test_files
30
+ }
31
+ }).and_return(action)
32
+ expect(subject).to eq action
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ describe KnapsackPro::Client::API::V1::BuildSubsets do
2
+ describe '.create' do
3
+ let(:commit_hash) { double }
4
+ let(:branch) { double }
5
+ let(:node_total) { double }
6
+ let(:node_index) { double }
7
+ let(:test_files) { double }
8
+
9
+ subject do
10
+ described_class.create(
11
+ commit_hash: commit_hash,
12
+ branch: branch,
13
+ node_total: node_total,
14
+ node_index: node_index,
15
+ test_files: test_files
16
+ )
17
+ end
18
+
19
+ it do
20
+ action = double
21
+ expect(KnapsackPro::Client::API::Action).to receive(:new).with({
22
+ endpoint_path: '/v1/build_subsets',
23
+ http_method: :post,
24
+ request_hash: {
25
+ commit_hash: commit_hash,
26
+ branch: branch,
27
+ node_total: node_total,
28
+ node_index: node_index,
29
+ test_files: test_files
30
+ }
31
+ }).and_return(action)
32
+ expect(subject).to eq action
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,138 @@
1
+ describe KnapsackPro::Client::Connection do
2
+ let(:endpoint_path) { '/v1/fake_endpoint' }
3
+ let(:http_method) { :post }
4
+ let(:request_hash) { { fake: 'hash' } }
5
+ let(:action) do
6
+ instance_double(KnapsackPro::Client::API::Action,
7
+ endpoint_path: endpoint_path,
8
+ http_method: http_method,
9
+ request_hash: request_hash)
10
+ end
11
+
12
+ let(:connection) { described_class.new(action) }
13
+
14
+ before do
15
+ stub_const('ENV', {
16
+ 'KNAPSACK_PRO_ENDPOINT' => 'http://api.knapsackpro.dev:3000',
17
+ 'KNAPSACK_PRO_TEST_SUITE_TOKEN' => '3fa64859337f6e56409d49f865d13fd7',
18
+ })
19
+ end
20
+
21
+ describe '#call' do
22
+ let(:logger) { instance_double(Logger) }
23
+
24
+ subject { connection.call }
25
+
26
+ context 'when http method is POST' do
27
+ before do
28
+ http = instance_double(Net::HTTP)
29
+
30
+ expect(Net::HTTP).to receive(:new).with('api.knapsackpro.dev', 3000).and_return(http)
31
+
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)
35
+
36
+ header = { 'X-Request-Id' => 'fake-uuid' }
37
+ http_response = instance_double(Net::HTTPOK, body: body, header: header)
38
+ expect(http).to receive(:post).with(
39
+ endpoint_path,
40
+ "{\"fake\":\"hash\",\"test_suite_token\":\"3fa64859337f6e56409d49f865d13fd7\"}",
41
+ { "Content-Type" => "application/json", "Accept" => "application/json" }
42
+ ).and_return(http_response)
43
+
44
+ expect(KnapsackPro).to receive(:logger).exactly(3).and_return(logger)
45
+ expect(logger).to receive(:info).with('API request UUID: fake-uuid')
46
+ expect(logger).to receive(:info).with('API response:')
47
+ end
48
+
49
+ context 'when body response is json' do
50
+ let(:body) { '{"errors": "value"}' }
51
+
52
+ it do
53
+ parsed_response = { 'errors' => 'value' }
54
+
55
+ expect(logger).to receive(:error).with(parsed_response)
56
+
57
+ expect(subject).to eq(parsed_response)
58
+ expect(connection.success?).to be true
59
+ expect(connection.errors?).to be true
60
+ end
61
+ end
62
+
63
+ context 'when body response is empty' do
64
+ let(:body) { '' }
65
+
66
+ it do
67
+ expect(logger).to receive(:info).with('')
68
+
69
+ expect(subject).to eq('')
70
+ expect(connection.success?).to be true
71
+ expect(connection.errors?).to be false
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ describe '#success?' do
78
+ subject { connection.success? }
79
+
80
+ before do
81
+ allow(connection).to receive(:response).and_return(response)
82
+ end
83
+
84
+ context 'when response has no value' do
85
+ let(:response) { nil }
86
+
87
+ it { should be false }
88
+ end
89
+
90
+ context 'when response has value' do
91
+ let(:response) do
92
+ { 'fake' => 'response' }
93
+ end
94
+
95
+ it { should be true }
96
+ end
97
+ end
98
+
99
+ describe '#errors?' do
100
+ subject { connection.errors? }
101
+
102
+ before do
103
+ allow(connection).to receive(:response).and_return(response)
104
+ end
105
+
106
+ context 'when response has no value' do
107
+ let(:response) { nil }
108
+
109
+ it { should be false }
110
+ end
111
+
112
+ context 'when response has value' do
113
+ context 'when response has no errors' do
114
+ let(:response) do
115
+ { 'fake' => 'response' }
116
+ end
117
+
118
+ it { should be false }
119
+ end
120
+
121
+ context 'when response has errors' do
122
+ let(:response) do
123
+ { 'errors' => [{ 'field' => 'is wrong' }] }
124
+ end
125
+
126
+ it { should be true }
127
+ end
128
+
129
+ context 'when response has error (i.e. internal server error)' do
130
+ let(:response) do
131
+ { 'error' => 'Internal Server Error' }
132
+ end
133
+
134
+ it { should be true }
135
+ end
136
+ end
137
+ end
138
+ end