knapsack_pro 0.0.1 → 0.1.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 (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