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
@@ -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