knapsack_pro 0.17.0 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +151 -2
- data/bin/knapsack_pro +1 -0
- data/knapsack_pro.gemspec +1 -1
- data/lib/knapsack_pro.rb +8 -0
- data/lib/knapsack_pro/adapters/base_adapter.rb +10 -0
- data/lib/knapsack_pro/adapters/rspec_adapter.rb +8 -0
- data/lib/knapsack_pro/allocator.rb +28 -14
- data/lib/knapsack_pro/allocator_builder.rb +1 -29
- data/lib/knapsack_pro/base_allocator_builder.rb +35 -0
- data/lib/knapsack_pro/client/api/v1/queues.rb +27 -0
- data/lib/knapsack_pro/config/ci/base.rb +3 -0
- data/lib/knapsack_pro/config/ci/buildkite.rb +4 -0
- data/lib/knapsack_pro/config/ci/circle.rb +4 -1
- data/lib/knapsack_pro/config/ci/semaphore.rb +4 -0
- data/lib/knapsack_pro/config/ci/snap_ci.rb +4 -0
- data/lib/knapsack_pro/config/ci/travis.rb +4 -0
- data/lib/knapsack_pro/config/env.rb +22 -0
- data/lib/knapsack_pro/config/env_generator.rb +19 -0
- data/lib/knapsack_pro/queue_allocator.rb +52 -0
- data/lib/knapsack_pro/queue_allocator_builder.rb +13 -0
- data/lib/knapsack_pro/report.rb +35 -0
- data/lib/knapsack_pro/runners/queue/base_runner.rb +34 -0
- data/lib/knapsack_pro/runners/queue/rspec_runner.rb +42 -0
- data/lib/knapsack_pro/task_loader.rb +1 -1
- data/lib/knapsack_pro/version.rb +1 -1
- data/lib/tasks/queue/rspec.rake +9 -0
- data/lib/tasks/salt.rake +0 -2
- data/spec/knapsack_pro/adapters/base_adapter_spec.rb +39 -8
- data/spec/knapsack_pro/adapters/rspec_adapter_spec.rb +11 -0
- data/spec/knapsack_pro/allocator_builder_spec.rb +2 -12
- data/spec/knapsack_pro/base_allocator_builder_spec.rb +22 -0
- data/spec/knapsack_pro/client/api/v1/queues_spec.rb +42 -0
- data/spec/knapsack_pro/config/ci/base_spec.rb +1 -0
- data/spec/knapsack_pro/config/ci/buildkite_spec.rb +13 -0
- data/spec/knapsack_pro/config/ci/circle_spec.rb +13 -0
- data/spec/knapsack_pro/config/ci/semaphore_spec.rb +13 -0
- data/spec/knapsack_pro/config/ci/snap_ci_spec.rb +13 -0
- data/spec/knapsack_pro/config/ci/travis_spec.rb +13 -0
- data/spec/knapsack_pro/config/env_generator_spec.rb +52 -0
- data/spec/knapsack_pro/config/env_spec.rb +90 -0
- data/spec/knapsack_pro/queue_allocator_builder_spec.rb +38 -0
- data/spec/knapsack_pro/queue_allocator_spec.rb +85 -0
- data/spec/knapsack_pro/report_spec.rb +69 -0
- data/spec/knapsack_pro/runners/queue/base_runner_spec.rb +67 -0
- data/spec/knapsack_pro/runners/queue/rspec_runner_spec.rb +118 -0
- metadata +26 -4
@@ -0,0 +1,27 @@
|
|
1
|
+
module KnapsackPro
|
2
|
+
module Client
|
3
|
+
module API
|
4
|
+
module V1
|
5
|
+
class Queues < Base
|
6
|
+
class << self
|
7
|
+
def queue(args)
|
8
|
+
action_class.new(
|
9
|
+
endpoint_path: '/v1/queues/queue',
|
10
|
+
http_method: :post,
|
11
|
+
request_hash: {
|
12
|
+
:can_initialize_queue => args.fetch(:can_initialize_queue),
|
13
|
+
:commit_hash => args.fetch(:commit_hash),
|
14
|
+
:branch => args.fetch(:branch),
|
15
|
+
:node_total => args.fetch(:node_total),
|
16
|
+
:node_index => args.fetch(:node_index),
|
17
|
+
:node_build_id => KnapsackPro::Config::Env.ci_node_build_id,
|
18
|
+
:test_files => args.fetch(:test_files)
|
19
|
+
}
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -14,6 +14,12 @@ module KnapsackPro
|
|
14
14
|
0).to_i
|
15
15
|
end
|
16
16
|
|
17
|
+
def ci_node_build_id
|
18
|
+
ENV['KNAPSACK_PRO_CI_NODE_BUILD_ID'] ||
|
19
|
+
ci_env_for(:node_build_id) ||
|
20
|
+
'missing-build-id'
|
21
|
+
end
|
22
|
+
|
17
23
|
def commit_hash
|
18
24
|
ENV['KNAPSACK_PRO_COMMIT_HASH'] ||
|
19
25
|
ci_env_for(:commit_hash)
|
@@ -45,6 +51,22 @@ module KnapsackPro
|
|
45
51
|
recording_enabled == 'true'
|
46
52
|
end
|
47
53
|
|
54
|
+
def queue_recording_enabled
|
55
|
+
ENV['KNAPSACK_PRO_QUEUE_RECORDING_ENABLED']
|
56
|
+
end
|
57
|
+
|
58
|
+
def queue_recording_enabled?
|
59
|
+
queue_recording_enabled == 'true'
|
60
|
+
end
|
61
|
+
|
62
|
+
def queue_id
|
63
|
+
ENV['KNAPSACK_PRO_QUEUE_ID'] || raise('Missing Queue ID')
|
64
|
+
end
|
65
|
+
|
66
|
+
def subset_queue_id
|
67
|
+
ENV['KNAPSACK_PRO_SUBSET_QUEUE_ID'] || raise('Missing Subset Queue ID')
|
68
|
+
end
|
69
|
+
|
48
70
|
def test_files_encrypted
|
49
71
|
ENV['KNAPSACK_PRO_TEST_FILES_ENCRYPTED']
|
50
72
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module KnapsackPro
|
2
|
+
module Config
|
3
|
+
class EnvGenerator
|
4
|
+
class << self
|
5
|
+
def set_queue_id
|
6
|
+
if ENV['KNAPSACK_PRO_QUEUE_ID']
|
7
|
+
raise 'Queue ID already generated.'
|
8
|
+
else
|
9
|
+
ENV['KNAPSACK_PRO_QUEUE_ID'] = "#{Time.now.to_i}_#{SecureRandom.uuid}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_subset_queue_id
|
14
|
+
ENV['KNAPSACK_PRO_SUBSET_QUEUE_ID'] = SecureRandom.uuid
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module KnapsackPro
|
2
|
+
class QueueAllocator
|
3
|
+
def initialize(args)
|
4
|
+
@test_files = args.fetch(:test_files)
|
5
|
+
@ci_node_total = args.fetch(:ci_node_total)
|
6
|
+
@ci_node_index = args.fetch(:ci_node_index)
|
7
|
+
@ci_node_build_id = args.fetch(:ci_node_build_id)
|
8
|
+
@repository_adapter = args.fetch(:repository_adapter)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_file_paths(can_initialize_queue)
|
12
|
+
action = build_action(can_initialize_queue)
|
13
|
+
connection = KnapsackPro::Client::Connection.new(action)
|
14
|
+
response = connection.call
|
15
|
+
if connection.success?
|
16
|
+
raise ArgumentError.new(response) if connection.errors?
|
17
|
+
prepare_test_files(response)
|
18
|
+
else
|
19
|
+
raise ArgumentError.new("Couldn't connect with Knapsack Pro API. Response: #{response}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :test_files,
|
26
|
+
:ci_node_total,
|
27
|
+
:ci_node_index,
|
28
|
+
:ci_node_build_id,
|
29
|
+
:repository_adapter
|
30
|
+
|
31
|
+
def encrypted_test_files
|
32
|
+
KnapsackPro::Crypto::Encryptor.call(test_files)
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_action(can_initialize_queue)
|
36
|
+
KnapsackPro::Client::API::V1::Queues.queue(
|
37
|
+
can_initialize_queue: can_initialize_queue,
|
38
|
+
commit_hash: repository_adapter.commit_hash,
|
39
|
+
branch: repository_adapter.branch,
|
40
|
+
node_total: ci_node_total,
|
41
|
+
node_index: ci_node_index,
|
42
|
+
node_build_id: ci_node_build_id,
|
43
|
+
test_files: encrypted_test_files,
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def prepare_test_files(response)
|
48
|
+
decrypted_test_files = KnapsackPro::Crypto::Decryptor.call(test_files, response['test_files'])
|
49
|
+
KnapsackPro::TestFilePresenter.paths(decrypted_test_files)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module KnapsackPro
|
2
|
+
class QueueAllocatorBuilder < BaseAllocatorBuilder
|
3
|
+
def allocator
|
4
|
+
KnapsackPro::QueueAllocator.new(
|
5
|
+
test_files: test_files,
|
6
|
+
ci_node_total: env.ci_node_total,
|
7
|
+
ci_node_index: env.ci_node_index,
|
8
|
+
ci_node_build_id: env.ci_node_build_id,
|
9
|
+
repository_adapter: repository_adapter,
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/knapsack_pro/report.rb
CHANGED
@@ -2,7 +2,35 @@ module KnapsackPro
|
|
2
2
|
class Report
|
3
3
|
def self.save
|
4
4
|
test_files = KnapsackPro.tracker.to_a
|
5
|
+
create_build_subset(test_files)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.save_subset_queue_to_file
|
9
|
+
test_files = KnapsackPro.tracker.to_a
|
10
|
+
subset_queue_id = KnapsackPro::Config::Env.subset_queue_id
|
11
|
+
|
12
|
+
FileUtils.mkdir_p(queue_path)
|
13
|
+
|
14
|
+
subset_queue_file_name = "#{subset_queue_id}.json"
|
15
|
+
report_path = File.join(queue_path, subset_queue_file_name)
|
16
|
+
report_json = JSON.pretty_generate(test_files)
|
17
|
+
|
18
|
+
File.open(report_path, 'w+') do |f|
|
19
|
+
f.write(report_json)
|
20
|
+
end
|
21
|
+
end
|
5
22
|
|
23
|
+
def self.save_node_queue_to_api
|
24
|
+
test_files = []
|
25
|
+
Dir.glob("#{queue_path}/*.json").each do |file|
|
26
|
+
report = JSON.parse(File.read(file))
|
27
|
+
test_files += report
|
28
|
+
end
|
29
|
+
|
30
|
+
create_build_subset(test_files)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.create_build_subset(test_files)
|
6
34
|
if test_files.empty?
|
7
35
|
KnapsackPro.logger.info("Didn't save time execution report on API server because there are no test files matching criteria on this node. Probably reason might be very narrowed tests list - you run only tests with specified tag and there are fewer test files with the tag than node total number.")
|
8
36
|
return
|
@@ -25,5 +53,12 @@ module KnapsackPro
|
|
25
53
|
KnapsackPro.logger.info('Saved time execution report on API server.')
|
26
54
|
end
|
27
55
|
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def self.queue_path
|
60
|
+
queue_id = KnapsackPro::Config::Env.queue_id
|
61
|
+
"tmp/knapsack_pro/queue/#{queue_id}"
|
62
|
+
end
|
28
63
|
end
|
29
64
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module KnapsackPro
|
2
|
+
module Runners
|
3
|
+
module Queue
|
4
|
+
class BaseRunner
|
5
|
+
def self.run(args)
|
6
|
+
raise NotImplementedError
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.run_tests(runner, can_initialize_queue, args, exitstatus)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(adapter_class)
|
14
|
+
@allocator_builder = KnapsackPro::QueueAllocatorBuilder.new(adapter_class)
|
15
|
+
@allocator = allocator_builder.allocator
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_file_paths(args)
|
19
|
+
can_initialize_queue = args.fetch(:can_initialize_queue)
|
20
|
+
allocator.test_file_paths(can_initialize_queue)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_dir
|
24
|
+
allocator_builder.test_dir
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :allocator_builder,
|
30
|
+
:allocator
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module KnapsackPro
|
2
|
+
module Runners
|
3
|
+
module Queue
|
4
|
+
class RSpecRunner < BaseRunner
|
5
|
+
def self.run(args)
|
6
|
+
require 'rspec/core'
|
7
|
+
|
8
|
+
ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_rspec
|
9
|
+
ENV['KNAPSACK_PRO_QUEUE_RECORDING_ENABLED'] = 'true'
|
10
|
+
ENV['KNAPSACK_PRO_QUEUE_ID'] = KnapsackPro::Config::EnvGenerator.set_queue_id
|
11
|
+
|
12
|
+
runner = new(KnapsackPro::Adapters::RSpecAdapter)
|
13
|
+
|
14
|
+
cli_args = (args || '').split
|
15
|
+
run_tests(runner, true, cli_args, 0)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.run_tests(runner, can_initialize_queue, args, exitstatus)
|
19
|
+
test_file_paths = runner.test_file_paths(can_initialize_queue: can_initialize_queue)
|
20
|
+
|
21
|
+
if test_file_paths.empty?
|
22
|
+
KnapsackPro::Report.save_node_queue_to_api
|
23
|
+
exit(exitstatus)
|
24
|
+
else
|
25
|
+
subset_queue_id = KnapsackPro::Config::EnvGenerator.set_subset_queue_id
|
26
|
+
ENV['KNAPSACK_PRO_SUBSET_QUEUE_ID'] = subset_queue_id
|
27
|
+
|
28
|
+
cli_args = args + [
|
29
|
+
'--default-path', runner.test_dir,
|
30
|
+
] + test_file_paths
|
31
|
+
options = RSpec::Core::ConfigurationOptions.new(cli_args)
|
32
|
+
exit_code = RSpec::Core::Runner.new(options).run($stderr, $stdout)
|
33
|
+
exitstatus = exit_code if exit_code != 0
|
34
|
+
RSpec.world.example_groups.clear
|
35
|
+
|
36
|
+
run_tests(runner, false, args, exitstatus)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/knapsack_pro/version.rb
CHANGED
data/lib/tasks/salt.rake
CHANGED
@@ -17,31 +17,54 @@ describe KnapsackPro::Adapters::BaseAdapter do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
describe '#bind' do
|
20
|
+
let(:recording_enabled?) { false }
|
21
|
+
let(:queue_recording_enabled?) { false }
|
22
|
+
|
20
23
|
before do
|
21
24
|
expect(KnapsackPro::Config::Env).to receive(:recording_enabled?).and_return(recording_enabled?)
|
25
|
+
expect(KnapsackPro::Config::Env).to receive(:queue_recording_enabled?).and_return(queue_recording_enabled?)
|
22
26
|
end
|
23
27
|
|
28
|
+
after { subject.bind }
|
29
|
+
|
24
30
|
context 'when recording enabled' do
|
25
31
|
let(:recording_enabled?) { true }
|
26
32
|
|
33
|
+
before do
|
34
|
+
allow(subject).to receive(:bind_time_tracker)
|
35
|
+
allow(subject).to receive(:bind_save_report)
|
36
|
+
end
|
37
|
+
|
27
38
|
it do
|
28
39
|
logger = instance_double(Logger)
|
29
40
|
expect(KnapsackPro).to receive(:logger).and_return(logger)
|
30
41
|
expect(logger).to receive(:info).with('Test suite time execution recording enabled.')
|
31
|
-
expect(subject).to receive(:bind_time_tracker)
|
32
|
-
expect(subject).to receive(:bind_save_report)
|
33
|
-
subject.bind
|
34
42
|
end
|
43
|
+
it { expect(subject).to receive(:bind_time_tracker) }
|
44
|
+
it { expect(subject).to receive(:bind_save_report) }
|
35
45
|
end
|
36
46
|
|
37
|
-
context 'when recording
|
38
|
-
let(:
|
47
|
+
context 'when queue recording enabled' do
|
48
|
+
let(:queue_recording_enabled?) { true }
|
49
|
+
|
50
|
+
before do
|
51
|
+
allow(subject).to receive(:bind_time_tracker)
|
52
|
+
allow(subject).to receive(:bind_save_queue_report)
|
53
|
+
end
|
39
54
|
|
40
55
|
it do
|
41
|
-
|
42
|
-
expect(
|
43
|
-
|
56
|
+
logger = instance_double(Logger)
|
57
|
+
expect(KnapsackPro).to receive(:logger).and_return(logger)
|
58
|
+
expect(logger).to receive(:info).with('Test suite time execution queue recording enabled.')
|
44
59
|
end
|
60
|
+
it { expect(subject).to receive(:bind_time_tracker) }
|
61
|
+
it { expect(subject).to receive(:bind_save_queue_report) }
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when recording disabled' do
|
65
|
+
it { expect(subject).not_to receive(:bind_time_tracker) }
|
66
|
+
it { expect(subject).not_to receive(:bind_save_report) }
|
67
|
+
it { expect(subject).not_to receive(:bind_save_queue_report) }
|
45
68
|
end
|
46
69
|
end
|
47
70
|
|
@@ -60,4 +83,12 @@ describe KnapsackPro::Adapters::BaseAdapter do
|
|
60
83
|
}.to raise_error(NotImplementedError)
|
61
84
|
end
|
62
85
|
end
|
86
|
+
|
87
|
+
describe '#bind_save_queue_report' do
|
88
|
+
it do
|
89
|
+
expect {
|
90
|
+
subject.bind_save_queue_report
|
91
|
+
}.to raise_error(NotImplementedError)
|
92
|
+
end
|
93
|
+
end
|
63
94
|
end
|