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.
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
@@ -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
@@ -8,6 +8,9 @@ module KnapsackPro
8
8
  def node_index
9
9
  end
10
10
 
11
+ def node_build_id
12
+ end
13
+
11
14
  def commit_hash
12
15
  end
13
16
 
@@ -10,6 +10,10 @@ module KnapsackPro
10
10
  ENV['BUILDKITE_PARALLEL_JOB']
11
11
  end
12
12
 
13
+ def node_build_id
14
+ ENV['BUILDKITE_BUILD_NUMBER']
15
+ end
16
+
13
17
  def commit_hash
14
18
  ENV['BUILDKITE_COMMIT']
15
19
  end
@@ -10,6 +10,10 @@ module KnapsackPro
10
10
  ENV['CIRCLE_NODE_INDEX']
11
11
  end
12
12
 
13
+ def node_build_id
14
+ ENV['CIRCLE_BUILD_NUM']
15
+ end
16
+
13
17
  def commit_hash
14
18
  ENV['CIRCLE_SHA1']
15
19
  end
@@ -26,4 +30,3 @@ module KnapsackPro
26
30
  end
27
31
  end
28
32
  end
29
-
@@ -11,6 +11,10 @@ module KnapsackPro
11
11
  index.to_i - 1 if index
12
12
  end
13
13
 
14
+ def node_build_id
15
+ ENV['SEMAPHORE_BUILD_NUMBER']
16
+ end
17
+
14
18
  def commit_hash
15
19
  ENV['REVISION']
16
20
  end
@@ -11,6 +11,10 @@ module KnapsackPro
11
11
  index.to_i - 1 if index
12
12
  end
13
13
 
14
+ def node_build_id
15
+ ENV['SNAP_PIPELINE_COUNTER']
16
+ end
17
+
14
18
  def commit_hash
15
19
  ENV['SNAP_COMMIT']
16
20
  end
@@ -2,6 +2,10 @@ module KnapsackPro
2
2
  module Config
3
3
  module CI
4
4
  class Travis < Base
5
+ def node_build_id
6
+ ENV['TRAVIS_BUILD_NUMBER']
7
+ end
8
+
5
9
  def commit_hash
6
10
  ENV['TRAVIS_COMMIT']
7
11
  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
@@ -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
@@ -5,7 +5,7 @@ module KnapsackPro
5
5
  include ::Rake::DSL
6
6
 
7
7
  def load_tasks
8
- Dir.glob("#{KnapsackPro.root}/lib/tasks/*.rake").each { |r| import r }
8
+ Dir.glob("#{KnapsackPro.root}/lib/tasks/**/*.rake").each { |r| import r }
9
9
  end
10
10
  end
11
11
  end
@@ -1,3 +1,3 @@
1
1
  module KnapsackPro
2
- VERSION = '0.17.0'
2
+ VERSION = '0.18.0'
3
3
  end
@@ -0,0 +1,9 @@
1
+ require 'knapsack_pro'
2
+
3
+ namespace :knapsack_pro do
4
+ namespace :queue do
5
+ task :rspec, [:rspec_args] do |_, args|
6
+ KnapsackPro::Runners::Queue::RSpecRunner.run(args[:rspec_args])
7
+ end
8
+ end
9
+ end
data/lib/tasks/salt.rake CHANGED
@@ -1,5 +1,3 @@
1
- require 'securerandom'
2
-
3
1
  namespace :knapsack_pro do
4
2
  task :salt, [:size] do |_, args|
5
3
  default_size = 32
@@ -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 not enabled' do
38
- let(:recording_enabled?) { false }
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
- expect(subject).not_to receive(:bind_time_tracker)
42
- expect(subject).not_to receive(:bind_save_report)
43
- subject.bind
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