knapsack_pro 2.7.0 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +1 -1
  4. data/lib/knapsack_pro/adapters/base_adapter.rb +22 -0
  5. data/lib/knapsack_pro/client/api/v1/queues.rb +4 -1
  6. data/lib/knapsack_pro/client/connection.rb +5 -0
  7. data/lib/knapsack_pro/config/env.rb +1 -0
  8. data/lib/knapsack_pro/queue_allocator.rb +19 -3
  9. data/lib/knapsack_pro/report.rb +1 -1
  10. data/lib/knapsack_pro/runners/cucumber_runner.rb +4 -1
  11. data/lib/knapsack_pro/runners/minitest_runner.rb +4 -1
  12. data/lib/knapsack_pro/runners/queue/cucumber_runner.rb +4 -0
  13. data/lib/knapsack_pro/runners/queue/minitest_runner.rb +4 -0
  14. data/lib/knapsack_pro/runners/queue/rspec_runner.rb +2 -0
  15. data/lib/knapsack_pro/runners/rspec_runner.rb +4 -1
  16. data/lib/knapsack_pro/runners/spinach_runner.rb +4 -1
  17. data/lib/knapsack_pro/runners/test_unit_runner.rb +4 -1
  18. data/lib/knapsack_pro/slow_test_file_determiner.rb +1 -1
  19. data/lib/knapsack_pro/test_case_detectors/rspec_test_example_detector.rb +11 -2
  20. data/lib/knapsack_pro/tracker.rb +1 -5
  21. data/lib/knapsack_pro/version.rb +1 -1
  22. data/spec/knapsack_pro/adapters/base_adapter_spec.rb +51 -0
  23. data/spec/knapsack_pro/client/api/v1/queues_spec.rb +31 -2
  24. data/spec/knapsack_pro/queue_allocator_spec.rb +217 -60
  25. data/spec/knapsack_pro/runners/cucumber_runner_spec.rb +2 -0
  26. data/spec/knapsack_pro/runners/minitest_runner_spec.rb +4 -0
  27. data/spec/knapsack_pro/runners/queue/cucumber_runner_spec.rb +27 -7
  28. data/spec/knapsack_pro/runners/queue/minitest_runner_spec.rb +27 -7
  29. data/spec/knapsack_pro/runners/queue/rspec_runner_spec.rb +2 -0
  30. data/spec/knapsack_pro/runners/rspec_runner_spec.rb +2 -0
  31. data/spec/knapsack_pro/runners/spinach_runner_spec.rb +2 -0
  32. data/spec/knapsack_pro/runners/test_unit_runner_spec.rb +2 -0
  33. data/spec/knapsack_pro/test_case_detectors/rspec_test_example_detector_spec.rb +1 -1
  34. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e72536de9d4d9ec4a02e935742375b9d8023522f9c21ac8340a991bfdc7982cb
4
- data.tar.gz: b7694088ff854a5579ec160fbb08c3219986a95512c2feea15ac6a359dc7aff5
3
+ metadata.gz: beae91b61b862b583d31eb96e90528168f2d744b85cf89ec657a2bb9eb3b605a
4
+ data.tar.gz: 2ddaec300265bd38f33438629a457d6e559f2a69ecb3e1b190deab30abebbdd1
5
5
  SHA512:
6
- metadata.gz: 408aaada7954066b2a3266897aa9b145ce53f2f7409f99f4c8915e122c5deef0818bc14503da59cab1f20c1376a4b205466089c14eaa8c9cd1943699298f8d86
7
- data.tar.gz: 85eef879c155557825347ede8671f1b65d9832a8e8b379e94efaf61573cbd676082d43d6219099a35cad008266ef8e16cd719553397c5b71f775ad0ab76f7e79
6
+ metadata.gz: 17eedf845e8b54b6e3fbcb9bb53a156b63d989eb09f55f7daf82a1c458838294ffa9d5da5ce55097f0ec309d64fa4c51b1ed375cb5a9ae425bf9311cdbee952d
7
+ data.tar.gz: 6533850f09175a000f07f8933b721aee0b250725c8243111631c5ae1de174f519e95213d79ad66797d088678852c4afb072b5f13c2df37802408b5b44f855dd5
@@ -1,5 +1,45 @@
1
1
  # Change Log
2
2
 
3
+ ### 2.11.0
4
+
5
+ * Verify test runner adapter bind method is called to track test files time execution and ensure `tmp/knapsack_pro` directory is not removed accidentally
6
+
7
+ https://github.com/KnapsackPro/knapsack_pro-ruby/pull/137
8
+
9
+ https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v2.10.1...v2.11.0
10
+
11
+ ### 2.10.1
12
+
13
+ * Fix RSpec split by test examples feature broken by lazy generating of JSON report with test example ids
14
+
15
+ https://github.com/KnapsackPro/knapsack_pro-ruby/pull/135
16
+
17
+ https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v2.10.0...v2.10.1
18
+
19
+ ### 2.10.0
20
+
21
+ * Add support for an attempt to connect to existing Queue on API side to reduce slow requests number
22
+
23
+ https://github.com/KnapsackPro/knapsack_pro-ruby/pull/133
24
+
25
+ https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v2.9.0...v2.10.0
26
+
27
+ ### 2.9.0
28
+
29
+ * Use `Process.clock_gettime` to measure track execution time
30
+
31
+ https://github.com/KnapsackPro/knapsack_pro-ruby/pull/132
32
+
33
+ https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v2.8.0...v2.9.0
34
+
35
+ ### 2.8.0
36
+
37
+ * More actionable error message when RSpec split by examples is not working due to RSpec dry-run failure
38
+
39
+ https://github.com/KnapsackPro/knapsack_pro-ruby/pull/130
40
+
41
+ https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v2.7.0...v2.8.0
42
+
3
43
  ### 2.7.0
4
44
 
5
45
  * Add support for env var `KNAPSACK_PRO_TEST_FILE_LIST_SOURCE_FILE` to allow accepting file containing test files to run
data/README.md CHANGED
@@ -50,7 +50,7 @@ We keep this old FAQ in README to not break old links spread across the web. You
50
50
 
51
51
  # Requirements
52
52
 
53
- `>= Ruby 2.0.0`
53
+ `>= Ruby 2.1.0`
54
54
 
55
55
  # Table of Contents
56
56
 
@@ -4,6 +4,11 @@ module KnapsackPro
4
4
  # Just example, please overwrite constant in subclass
5
5
  TEST_DIR_PATTERN = 'test/**{,/*/**}/*_test.rb'
6
6
 
7
+ def self.adapter_bind_method_called_file
8
+ adapter_name = self.to_s.gsub('::', '-')
9
+ "#{KnapsackPro::Config::Env::TMP_DIR}/#{adapter_name}-bind_method_called_for_node_#{KnapsackPro::Config::Env.ci_node_index}.txt"
10
+ end
11
+
7
12
  def self.slow_test_file?(adapter_class, test_file_path)
8
13
  @slow_test_file_paths ||=
9
14
  begin
@@ -26,7 +31,24 @@ module KnapsackPro
26
31
  adapter
27
32
  end
28
33
 
34
+ def self.verify_bind_method_called
35
+ ::Kernel.at_exit do
36
+ if File.exists?(adapter_bind_method_called_file)
37
+ File.delete(adapter_bind_method_called_file)
38
+ else
39
+ puts "\n\n"
40
+ KnapsackPro.logger.error('-'*10 + ' Configuration error ' + '-'*50)
41
+ KnapsackPro.logger.error("You forgot to call #{self}.bind method in your test runner configuration file. It is needed to record test files time execution. Please follow the installation guide to configure your project properly https://docs.knapsackpro.com/knapsack_pro-ruby/guide/")
42
+ KnapsackPro.logger.error("If you already have #{self}.bind method added and you still see this error then one of your tests must had to delete tmp/knapsack_pro directory from the disk accidentally. Please ensure you do not remove tmp/knapsack_pro directory: https://knapsackpro.com/faq/question/why-all-test-files-have-01s-time-execution-for-my-ci-build-in-user-dashboard")
43
+ Kernel.exit(1)
44
+ end
45
+ end
46
+ end
47
+
29
48
  def bind
49
+ FileUtils.mkdir_p(KnapsackPro::Config::Env::TMP_DIR)
50
+ File.write(self.class.adapter_bind_method_called_file, nil)
51
+
30
52
  if KnapsackPro::Config::Env.recording_enabled?
31
53
  KnapsackPro.logger.debug('Test suite time execution recording enabled.')
32
54
  bind_time_tracker
@@ -3,11 +3,14 @@ module KnapsackPro
3
3
  module API
4
4
  module V1
5
5
  class Queues < Base
6
+ CODE_ATTEMPT_CONNECT_TO_QUEUE_FAILED = 'ATTEMPT_CONNECT_TO_QUEUE_FAILED'
7
+
6
8
  class << self
7
9
  def queue(args)
8
10
  request_hash = {
9
11
  :fixed_queue_split => KnapsackPro::Config::Env.fixed_queue_split,
10
12
  :can_initialize_queue => args.fetch(:can_initialize_queue),
13
+ :attempt_connect_to_queue => args.fetch(:attempt_connect_to_queue),
11
14
  :commit_hash => args.fetch(:commit_hash),
12
15
  :branch => args.fetch(:branch),
13
16
  :node_total => args.fetch(:node_total),
@@ -15,7 +18,7 @@ module KnapsackPro
15
18
  :node_build_id => KnapsackPro::Config::Env.ci_node_build_id,
16
19
  }
17
20
 
18
- if request_hash[:can_initialize_queue]
21
+ if request_hash[:can_initialize_queue] && !request_hash[:attempt_connect_to_queue]
19
22
  request_hash.merge!({
20
23
  :test_files => args.fetch(:test_files)
21
24
  })
@@ -25,6 +25,11 @@ module KnapsackPro
25
25
  !!(response_body && (response_body['errors'] || response_body['error']))
26
26
  end
27
27
 
28
+ def api_code
29
+ return unless response_body
30
+ response_body['code']
31
+ end
32
+
28
33
  def server_error?
29
34
  status = http_response.code.to_i
30
35
  status >= 500 && status < 600
@@ -8,6 +8,7 @@ module KnapsackPro
8
8
  'info' => ::Logger::INFO,
9
9
  'debug' => ::Logger::DEBUG,
10
10
  }
11
+ TMP_DIR = 'tmp/knapsack_pro'
11
12
 
12
13
  class << self
13
14
  def ci_node_total
@@ -11,9 +11,18 @@ module KnapsackPro
11
11
 
12
12
  def test_file_paths(can_initialize_queue, executed_test_files)
13
13
  return [] if @fallback_activated
14
- action = build_action(can_initialize_queue)
14
+ action = build_action(can_initialize_queue, attempt_connect_to_queue: can_initialize_queue)
15
15
  connection = KnapsackPro::Client::Connection.new(action)
16
16
  response = connection.call
17
+
18
+ # when attempt to connect to existing queue on API side failed because queue does not exist yet
19
+ if can_initialize_queue && connection.success? && connection.api_code == KnapsackPro::Client::API::V1::Queues::CODE_ATTEMPT_CONNECT_TO_QUEUE_FAILED
20
+ # make attempt to initalize a new queue on API side
21
+ action = build_action(can_initialize_queue, attempt_connect_to_queue: false)
22
+ connection = KnapsackPro::Client::Connection.new(action)
23
+ response = connection.call
24
+ end
25
+
17
26
  if connection.success?
18
27
  raise ArgumentError.new(response) if connection.errors?
19
28
  prepare_test_files(response)
@@ -52,15 +61,22 @@ module KnapsackPro
52
61
  KnapsackPro::Crypto::BranchEncryptor.call(repository_adapter.branch)
53
62
  end
54
63
 
55
- def build_action(can_initialize_queue)
64
+ def build_action(can_initialize_queue, attempt_connect_to_queue:)
65
+ # read test files from disk only when needed because it can be slow operation
66
+ test_files =
67
+ if can_initialize_queue && !attempt_connect_to_queue
68
+ encrypted_test_files
69
+ end
70
+
56
71
  KnapsackPro::Client::API::V1::Queues.queue(
57
72
  can_initialize_queue: can_initialize_queue,
73
+ attempt_connect_to_queue: attempt_connect_to_queue,
58
74
  commit_hash: repository_adapter.commit_hash,
59
75
  branch: encrypted_branch,
60
76
  node_total: ci_node_total,
61
77
  node_index: ci_node_index,
62
78
  node_build_id: ci_node_build_id,
63
- test_files: encrypted_test_files,
79
+ test_files: test_files,
64
80
  )
65
81
  end
66
82
 
@@ -80,7 +80,7 @@ module KnapsackPro
80
80
 
81
81
  def self.queue_path
82
82
  queue_id = KnapsackPro::Config::Env.queue_id
83
- "tmp/knapsack_pro/queue/#{queue_id}"
83
+ "#{KnapsackPro::Config::Env::TMP_DIR}/queue/#{queue_id}"
84
84
  end
85
85
  end
86
86
  end
@@ -5,9 +5,12 @@ module KnapsackPro
5
5
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_cucumber
6
6
  ENV['KNAPSACK_PRO_RECORDING_ENABLED'] = 'true'
7
7
 
8
- runner = new(KnapsackPro::Adapters::CucumberAdapter)
8
+ adapter_class = KnapsackPro::Adapters::CucumberAdapter
9
+ runner = new(adapter_class)
9
10
 
10
11
  if runner.test_files_to_execute_exist?
12
+ adapter_class.verify_bind_method_called
13
+
11
14
  require 'cucumber/rake/task'
12
15
 
13
16
  task_name = 'knapsack_pro:cucumber_run'
@@ -5,9 +5,12 @@ module KnapsackPro
5
5
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_minitest
6
6
  ENV['KNAPSACK_PRO_RECORDING_ENABLED'] = 'true'
7
7
 
8
- runner = new(KnapsackPro::Adapters::MinitestAdapter)
8
+ adapter_class = KnapsackPro::Adapters::MinitestAdapter
9
+ runner = new(adapter_class)
9
10
 
10
11
  if runner.test_files_to_execute_exist?
12
+ adapter_class.verify_bind_method_called
13
+
11
14
  task_name = 'knapsack_pro:minitest_run'
12
15
 
13
16
  if Rake::Task.task_defined?(task_name)
@@ -39,6 +39,10 @@ module KnapsackPro
39
39
  )
40
40
 
41
41
  if test_file_paths.empty?
42
+ unless all_test_file_paths.empty?
43
+ KnapsackPro::Adapters::CucumberAdapter.verify_bind_method_called
44
+ end
45
+
42
46
  KnapsackPro::Hooks::Queue.call_after_queue
43
47
 
44
48
  KnapsackPro::Report.save_node_queue_to_api
@@ -46,6 +46,10 @@ module KnapsackPro
46
46
  )
47
47
 
48
48
  if test_file_paths.empty?
49
+ unless all_test_file_paths.empty?
50
+ KnapsackPro::Adapters::MinitestAdapter.verify_bind_method_called
51
+ end
52
+
49
53
  KnapsackPro::Hooks::Queue.call_after_queue
50
54
 
51
55
  KnapsackPro::Report.save_node_queue_to_api
@@ -53,6 +53,8 @@ module KnapsackPro
53
53
 
54
54
  if test_file_paths.empty?
55
55
  unless all_test_file_paths.empty?
56
+ KnapsackPro::Adapters::RSpecAdapter.verify_bind_method_called
57
+
56
58
  KnapsackPro::Formatters::RSpecQueueSummaryFormatter.print_summary
57
59
  KnapsackPro::Formatters::RSpecQueueProfileFormatterExtension.print_summary
58
60
 
@@ -5,9 +5,12 @@ module KnapsackPro
5
5
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_rspec
6
6
  ENV['KNAPSACK_PRO_RECORDING_ENABLED'] = 'true'
7
7
 
8
- runner = new(KnapsackPro::Adapters::RSpecAdapter)
8
+ adapter_class = KnapsackPro::Adapters::RSpecAdapter
9
+ runner = new(adapter_class)
9
10
 
10
11
  if runner.test_files_to_execute_exist?
12
+ adapter_class.verify_bind_method_called
13
+
11
14
  require 'rspec/core/rake_task'
12
15
 
13
16
  task_name = 'knapsack_pro:rspec_run'
@@ -4,9 +4,12 @@ module KnapsackPro
4
4
  def self.run(args)
5
5
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_spinach
6
6
 
7
- runner = new(KnapsackPro::Adapters::SpinachAdapter)
7
+ adapter_class = KnapsackPro::Adapters::SpinachAdapter
8
+ runner = new(adapter_class)
8
9
 
9
10
  if runner.test_files_to_execute_exist?
11
+ adapter_class.verify_bind_method_called
12
+
10
13
  cmd = %Q[KNAPSACK_PRO_RECORDING_ENABLED=true KNAPSACK_PRO_TEST_SUITE_TOKEN=#{ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN']} bundle exec spinach #{args} --features_path #{runner.test_dir} -- #{runner.stringify_test_file_paths}]
11
14
 
12
15
  Kernel.system(cmd)
@@ -5,9 +5,12 @@ module KnapsackPro
5
5
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_test_unit
6
6
  ENV['KNAPSACK_PRO_RECORDING_ENABLED'] = 'true'
7
7
 
8
- runner = new(KnapsackPro::Adapters::TestUnitAdapter)
8
+ adapter_class = KnapsackPro::Adapters::TestUnitAdapter
9
+ runner = new(adapter_class)
9
10
 
10
11
  if runner.test_files_to_execute_exist?
12
+ adapter_class.verify_bind_method_called
13
+
11
14
  require 'test/unit'
12
15
 
13
16
  cli_args =
@@ -1,7 +1,7 @@
1
1
  module KnapsackPro
2
2
  class SlowTestFileDeterminer
3
3
  TIME_THRESHOLD_PER_CI_NODE = 0.7 # 70%
4
- REPORT_DIR = 'tmp/knapsack_pro/slow_test_file_determiner'
4
+ REPORT_DIR = "#{KnapsackPro::Config::Env::TMP_DIR}/slow_test_file_determiner"
5
5
 
6
6
  # test_files: { 'path' => 'a_spec.rb', 'time_execution' => 0.0 }
7
7
  # time_execution: of build distribution (total time of CI build run)
@@ -1,7 +1,7 @@
1
1
  module KnapsackPro
2
2
  module TestCaseDetectors
3
3
  class RSpecTestExampleDetector
4
- REPORT_DIR = 'tmp/knapsack_pro/test_case_detectors/rspec'
4
+ REPORT_DIR = "#{KnapsackPro::Config::Env::TMP_DIR}/test_case_detectors/rspec"
5
5
 
6
6
  def generate_json_report
7
7
  require 'rspec/core'
@@ -33,7 +33,16 @@ module KnapsackPro
33
33
  options = ::RSpec::Core::ConfigurationOptions.new(cli_args)
34
34
  exit_code = ::RSpec::Core::Runner.new(options).run($stderr, $stdout)
35
35
  if exit_code != 0
36
- raise 'There was problem to generate test examples for test suite'
36
+ debug_cmd = ([
37
+ 'bundle exec rspec',
38
+ ] + cli_args).join(' ')
39
+
40
+ KnapsackPro.logger.error('-'*10 + ' START of actionable error message ' + '-'*50)
41
+ KnapsackPro.logger.error('There was a problem while generating test examples for the slow test files using the RSpec dry-run flag. To reproduce the error triggered by the RSpec, please try to run below command (this way, you can find out what is causing the error):')
42
+ KnapsackPro.logger.error(debug_cmd)
43
+ KnapsackPro.logger.error('-'*10 + ' END of actionable error message ' + '-'*50)
44
+
45
+ raise 'There was a problem while generating test examples for the slow test files. Please read actionable error message above.'
37
46
  end
38
47
  end
39
48
 
@@ -90,11 +90,7 @@ module KnapsackPro
90
90
  end
91
91
 
92
92
  def now_without_mock_time
93
- if defined?(Timecop)
94
- Time.now_without_mock_time
95
- else
96
- Time.raw_now
97
- end
93
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
98
94
  end
99
95
  end
100
96
  end
@@ -1,3 +1,3 @@
1
1
  module KnapsackPro
2
- VERSION = '2.7.0'
2
+ VERSION = '2.11.0'
3
3
  end
@@ -22,6 +22,27 @@ describe KnapsackPro::Adapters::BaseAdapter do
22
22
  end
23
23
  end
24
24
 
25
+ describe '.adapter_bind_method_called_file' do
26
+ subject { described_class.adapter_bind_method_called_file }
27
+
28
+ before do
29
+ expect(KnapsackPro::Config::Env).to receive(:ci_node_index).and_return(ci_node_index)
30
+ end
31
+
32
+ context 'when CI node index 0' do
33
+ let(:ci_node_index) { 0 }
34
+
35
+ it { expect(subject).to eq 'tmp/knapsack_pro/KnapsackPro-Adapters-BaseAdapter-bind_method_called_for_node_0.txt' }
36
+
37
+ end
38
+
39
+ context 'when CI node index 1' do
40
+ let(:ci_node_index) { 1 }
41
+
42
+ it { expect(subject).to eq 'tmp/knapsack_pro/KnapsackPro-Adapters-BaseAdapter-bind_method_called_for_node_1.txt' }
43
+ end
44
+ end
45
+
25
46
  describe '.slow_test_file?' do
26
47
  let(:adapter_class) { double }
27
48
  let(:slow_test_files) do
@@ -71,11 +92,41 @@ describe KnapsackPro::Adapters::BaseAdapter do
71
92
  it { should eql adapter }
72
93
  end
73
94
 
95
+ describe '.verify_bind_method_called' do
96
+ subject { described_class.verify_bind_method_called }
97
+
98
+ before do
99
+ expect(::Kernel).to receive(:at_exit).and_yield
100
+ expect(File).to receive(:exists?).with('tmp/knapsack_pro/KnapsackPro-Adapters-BaseAdapter-bind_method_called_for_node_0.txt').and_return(adapter_bind_method_called_file_exists)
101
+ end
102
+
103
+ context 'when adapter bind method called' do
104
+ let(:adapter_bind_method_called_file_exists) { true }
105
+
106
+ it do
107
+ expect(File).to receive(:delete).with('tmp/knapsack_pro/KnapsackPro-Adapters-BaseAdapter-bind_method_called_for_node_0.txt')
108
+ subject
109
+ end
110
+ end
111
+
112
+ context 'when adapter bind method was not call' do
113
+ let(:adapter_bind_method_called_file_exists) { false }
114
+
115
+ it do
116
+ expect(Kernel).to receive(:exit).with(1)
117
+ subject
118
+ end
119
+ end
120
+ end
121
+
74
122
  describe '#bind' do
75
123
  let(:recording_enabled?) { false }
76
124
  let(:queue_recording_enabled?) { false }
77
125
 
78
126
  before do
127
+ expect(FileUtils).to receive(:mkdir_p).with('tmp/knapsack_pro')
128
+ expect(File).to receive(:write).with('tmp/knapsack_pro/KnapsackPro-Adapters-BaseAdapter-bind_method_called_for_node_0.txt', nil)
129
+
79
130
  expect(KnapsackPro::Config::Env).to receive(:recording_enabled?).and_return(recording_enabled?)
80
131
  expect(KnapsackPro::Config::Env).to receive(:queue_recording_enabled?).and_return(queue_recording_enabled?)
81
132
  end
@@ -11,6 +11,7 @@ describe KnapsackPro::Client::API::V1::Queues do
11
11
  subject do
12
12
  described_class.queue(
13
13
  can_initialize_queue: can_initialize_queue,
14
+ attempt_connect_to_queue: attempt_connect_to_queue,
14
15
  commit_hash: commit_hash,
15
16
  branch: branch,
16
17
  node_total: node_total,
@@ -24,8 +25,33 @@ describe KnapsackPro::Client::API::V1::Queues do
24
25
  expect(KnapsackPro::Config::Env).to receive(:ci_node_build_id).and_return(node_build_id)
25
26
  end
26
27
 
27
- context 'when can_initialize_queue=true' do
28
+ context 'when can_initialize_queue=true and attempt_connect_to_queue=true' do
28
29
  let(:can_initialize_queue) { true }
30
+ let(:attempt_connect_to_queue) { true }
31
+
32
+ it 'does not send test_files among other params' do
33
+ action = double
34
+ expect(KnapsackPro::Client::API::Action).to receive(:new).with({
35
+ endpoint_path: '/v1/queues/queue',
36
+ http_method: :post,
37
+ request_hash: {
38
+ fixed_queue_split: fixed_queue_split,
39
+ can_initialize_queue: can_initialize_queue,
40
+ attempt_connect_to_queue: attempt_connect_to_queue,
41
+ commit_hash: commit_hash,
42
+ branch: branch,
43
+ node_total: node_total,
44
+ node_index: node_index,
45
+ node_build_id: node_build_id,
46
+ }
47
+ }).and_return(action)
48
+ expect(subject).to eq action
49
+ end
50
+ end
51
+
52
+ context 'when can_initialize_queue=true and attempt_connect_to_queue=false' do
53
+ let(:can_initialize_queue) { true }
54
+ let(:attempt_connect_to_queue) { false }
29
55
 
30
56
  it 'sends test_files among other params' do
31
57
  action = double
@@ -35,6 +61,7 @@ describe KnapsackPro::Client::API::V1::Queues do
35
61
  request_hash: {
36
62
  fixed_queue_split: fixed_queue_split,
37
63
  can_initialize_queue: can_initialize_queue,
64
+ attempt_connect_to_queue: attempt_connect_to_queue,
38
65
  commit_hash: commit_hash,
39
66
  branch: branch,
40
67
  node_total: node_total,
@@ -47,8 +74,9 @@ describe KnapsackPro::Client::API::V1::Queues do
47
74
  end
48
75
  end
49
76
 
50
- context 'when can_initialize_queue=false' do
77
+ context 'when can_initialize_queue=false and attempt_connect_to_queue=false' do
51
78
  let(:can_initialize_queue) { false }
79
+ let(:attempt_connect_to_queue) { false }
52
80
 
53
81
  it 'does not send test_files among other params' do
54
82
  action = double
@@ -58,6 +86,7 @@ describe KnapsackPro::Client::API::V1::Queues do
58
86
  request_hash: {
59
87
  fixed_queue_split: fixed_queue_split,
60
88
  can_initialize_queue: can_initialize_queue,
89
+ attempt_connect_to_queue: attempt_connect_to_queue,
61
90
  commit_hash: commit_hash,
62
91
  branch: branch,
63
92
  node_total: node_total,
@@ -18,71 +18,13 @@ describe KnapsackPro::QueueAllocator do
18
18
  end
19
19
 
20
20
  describe '#test_file_paths' do
21
- let(:can_initialize_queue) { double }
22
21
  let(:executed_test_files) { [] }
23
22
  let(:response) { double }
23
+ let(:api_code) { nil }
24
24
 
25
25
  subject { queue_allocator.test_file_paths(can_initialize_queue, executed_test_files) }
26
26
 
27
- before do
28
- encrypted_test_files = double
29
- expect(KnapsackPro::Crypto::Encryptor).to receive(:call).with(fast_and_slow_test_files_to_run).and_return(encrypted_test_files)
30
-
31
- encrypted_branch = double
32
- expect(KnapsackPro::Crypto::BranchEncryptor).to receive(:call).with(repository_adapter.branch).and_return(encrypted_branch)
33
-
34
- action = double
35
- expect(KnapsackPro::Client::API::V1::Queues).to receive(:queue).with(
36
- can_initialize_queue: can_initialize_queue,
37
- commit_hash: repository_adapter.commit_hash,
38
- branch: encrypted_branch,
39
- node_total: ci_node_total,
40
- node_index: ci_node_index,
41
- node_build_id: ci_node_build_id,
42
- test_files: encrypted_test_files,
43
- ).and_return(action)
44
-
45
- connection = instance_double(KnapsackPro::Client::Connection,
46
- call: response,
47
- success?: success?,
48
- errors?: errors?)
49
- expect(KnapsackPro::Client::Connection).to receive(:new).with(action).and_return(connection)
50
- end
51
-
52
- context 'when successful request to API' do
53
- let(:success?) { true }
54
-
55
- context 'when response has errors' do
56
- let(:errors?) { true }
57
-
58
- it do
59
- expect { subject }.to raise_error(ArgumentError)
60
- end
61
- end
62
-
63
- context 'when response has no errors' do
64
- let(:errors?) { false }
65
- let(:response) do
66
- {
67
- 'test_files' => [
68
- { 'path' => 'a_spec.rb' },
69
- { 'path' => 'b_spec.rb' },
70
- ]
71
- }
72
- end
73
-
74
- before do
75
- expect(KnapsackPro::Crypto::Decryptor).to receive(:call).with(fast_and_slow_test_files_to_run, response['test_files']).and_call_original
76
- end
77
-
78
- it { should eq ['a_spec.rb', 'b_spec.rb'] }
79
- end
80
- end
81
-
82
- context 'when not successful request to API' do
83
- let(:success?) { false }
84
- let(:errors?) { false }
85
-
27
+ shared_examples_for 'when connection to API failed (fallback mode)' do
86
28
  context 'when fallback mode is disabled' do
87
29
  before do
88
30
  expect(KnapsackPro::Config::Env).to receive(:fallback_mode_enabled?).and_return(false)
@@ -146,5 +88,220 @@ describe KnapsackPro::QueueAllocator do
146
88
  end
147
89
  end
148
90
  end
91
+
92
+ context 'when can_initialize_queue=true' do
93
+ let(:can_initialize_queue) { true }
94
+
95
+ before do
96
+ encrypted_branch = double
97
+ expect(KnapsackPro::Crypto::BranchEncryptor).to receive(:call).with(repository_adapter.branch).and_return(encrypted_branch)
98
+
99
+ action = double
100
+ expect(KnapsackPro::Client::API::V1::Queues).to receive(:queue).with(
101
+ can_initialize_queue: can_initialize_queue,
102
+ attempt_connect_to_queue: true, # when can_initialize_queue=true then expect attempt_connect_to_queue=true
103
+ commit_hash: repository_adapter.commit_hash,
104
+ branch: encrypted_branch,
105
+ node_total: ci_node_total,
106
+ node_index: ci_node_index,
107
+ node_build_id: ci_node_build_id,
108
+ test_files: nil, # when attempt_connect_to_queue=true then expect test_files is nil to make fast request to API
109
+ ).and_return(action)
110
+
111
+ connection = instance_double(KnapsackPro::Client::Connection,
112
+ call: response,
113
+ success?: success?,
114
+ errors?: errors?,
115
+ api_code: api_code)
116
+ expect(KnapsackPro::Client::Connection).to receive(:new).with(action).and_return(connection)
117
+ end
118
+
119
+ context 'when successful request to API' do
120
+ let(:success?) { true }
121
+
122
+ context 'when response has errors' do
123
+ let(:errors?) { true }
124
+
125
+ it do
126
+ expect { subject }.to raise_error(ArgumentError)
127
+ end
128
+ end
129
+
130
+ context 'when response has no errors' do
131
+ let(:errors?) { false }
132
+
133
+ context 'when response returns test files (successful attempt to connect to queue already existing on the API side)' do
134
+ let(:response) do
135
+ {
136
+ 'test_files' => [
137
+ { 'path' => 'a_spec.rb' },
138
+ { 'path' => 'b_spec.rb' },
139
+ ]
140
+ }
141
+ end
142
+
143
+ before do
144
+ expect(KnapsackPro::Crypto::Decryptor).to receive(:call).with(fast_and_slow_test_files_to_run, response['test_files']).and_call_original
145
+ end
146
+
147
+ it { should eq ['a_spec.rb', 'b_spec.rb'] }
148
+ end
149
+
150
+ context 'when response has code=ATTEMPT_CONNECT_TO_QUEUE_FAILED' do
151
+ let(:response) do
152
+ { 'code' => 'ATTEMPT_CONNECT_TO_QUEUE_FAILED' }
153
+ end
154
+ let(:api_code) { 'ATTEMPT_CONNECT_TO_QUEUE_FAILED' }
155
+
156
+ before do
157
+ encrypted_branch = double
158
+ expect(KnapsackPro::Crypto::BranchEncryptor).to receive(:call).with(repository_adapter.branch).and_return(encrypted_branch)
159
+
160
+ encrypted_test_files = double
161
+ expect(KnapsackPro::Crypto::Encryptor).to receive(:call).with(fast_and_slow_test_files_to_run).and_return(encrypted_test_files)
162
+
163
+ # 2nd request is no more an attempt to connect to queue.
164
+ # We want to try to initalize a new queue so we will also send list of test files from disk.
165
+ action = double
166
+ expect(KnapsackPro::Client::API::V1::Queues).to receive(:queue).with(
167
+ can_initialize_queue: can_initialize_queue,
168
+ attempt_connect_to_queue: false,
169
+ commit_hash: repository_adapter.commit_hash,
170
+ branch: encrypted_branch,
171
+ node_total: ci_node_total,
172
+ node_index: ci_node_index,
173
+ node_build_id: ci_node_build_id,
174
+ test_files: encrypted_test_files,
175
+ ).and_return(action)
176
+
177
+ connection = instance_double(KnapsackPro::Client::Connection,
178
+ call: response2,
179
+ success?: response2_success?,
180
+ errors?: response2_errors?,
181
+ api_code: nil)
182
+ expect(KnapsackPro::Client::Connection).to receive(:new).with(action).and_return(connection)
183
+ end
184
+
185
+ context 'when successful 2nd request to API' do
186
+ let(:response2_success?) { true }
187
+
188
+ context 'when 2nd response has errors' do
189
+ let(:response2_errors?) { true }
190
+ let(:response2) { nil }
191
+
192
+ it do
193
+ expect { subject }.to raise_error(ArgumentError)
194
+ end
195
+ end
196
+
197
+ context 'when 2nd response has no errors' do
198
+ let(:response2_errors?) { false }
199
+
200
+ context 'when 2nd response returns test files (successful attempt to connect to queue already existing on the API side)' do
201
+ let(:response2) do
202
+ {
203
+ 'test_files' => [
204
+ { 'path' => 'a_spec.rb' },
205
+ { 'path' => 'b_spec.rb' },
206
+ ]
207
+ }
208
+ end
209
+
210
+ before do
211
+ expect(KnapsackPro::Crypto::Decryptor).to receive(:call).with(fast_and_slow_test_files_to_run, response2['test_files']).and_call_original
212
+ end
213
+
214
+ it { should eq ['a_spec.rb', 'b_spec.rb'] }
215
+ end
216
+ end
217
+ end
218
+
219
+ context 'when not successful 2nd request to API' do
220
+ let(:response2_success?) { false }
221
+ let(:response2_errors?) { false }
222
+ let(:response2) { nil }
223
+
224
+ it_behaves_like 'when connection to API failed (fallback mode)'
225
+ end
226
+ end
227
+ end
228
+ end
229
+
230
+ context 'when not successful request to API' do
231
+ let(:success?) { false }
232
+ let(:errors?) { false }
233
+
234
+ it_behaves_like 'when connection to API failed (fallback mode)'
235
+ end
236
+ end
237
+
238
+ context 'when can_initialize_queue=false' do
239
+ let(:can_initialize_queue) { false }
240
+ let(:api_code) { nil }
241
+
242
+ before do
243
+ encrypted_branch = double
244
+ expect(KnapsackPro::Crypto::BranchEncryptor).to receive(:call).with(repository_adapter.branch).and_return(encrypted_branch)
245
+
246
+ action = double
247
+ expect(KnapsackPro::Client::API::V1::Queues).to receive(:queue).with(
248
+ can_initialize_queue: can_initialize_queue,
249
+ attempt_connect_to_queue: false, # when can_initialize_queue=false then expect attempt_connect_to_queue=false
250
+ commit_hash: repository_adapter.commit_hash,
251
+ branch: encrypted_branch,
252
+ node_total: ci_node_total,
253
+ node_index: ci_node_index,
254
+ node_build_id: ci_node_build_id,
255
+ test_files: nil, # when can_initialize_queue=false then expect test_files is nil to make fast request to API
256
+ ).and_return(action)
257
+
258
+ connection = instance_double(KnapsackPro::Client::Connection,
259
+ call: response,
260
+ success?: success?,
261
+ errors?: errors?,
262
+ api_code: api_code)
263
+ expect(KnapsackPro::Client::Connection).to receive(:new).with(action).and_return(connection)
264
+ end
265
+
266
+ context 'when successful request to API' do
267
+ let(:success?) { true }
268
+
269
+ context 'when response has errors' do
270
+ let(:errors?) { true }
271
+
272
+ it do
273
+ expect { subject }.to raise_error(ArgumentError)
274
+ end
275
+ end
276
+
277
+ context 'when response has no errors' do
278
+ let(:errors?) { false }
279
+
280
+ context 'when response returns test files (successful attempt to connect to queue already existing on the API side)' do
281
+ let(:response) do
282
+ {
283
+ 'test_files' => [
284
+ { 'path' => 'a_spec.rb' },
285
+ { 'path' => 'b_spec.rb' },
286
+ ]
287
+ }
288
+ end
289
+
290
+ before do
291
+ expect(KnapsackPro::Crypto::Decryptor).to receive(:call).with(fast_and_slow_test_files_to_run, response['test_files']).and_call_original
292
+ end
293
+
294
+ it { should eq ['a_spec.rb', 'b_spec.rb'] }
295
+ end
296
+ end
297
+ end
298
+
299
+ context 'when not successful request to API' do
300
+ let(:success?) { false }
301
+ let(:errors?) { false }
302
+
303
+ it_behaves_like 'when connection to API failed (fallback mode)'
304
+ end
305
+ end
149
306
  end
150
307
  end
@@ -34,6 +34,8 @@ describe KnapsackPro::Runners::CucumberRunner do
34
34
  let(:task) { double }
35
35
 
36
36
  before do
37
+ expect(KnapsackPro::Adapters::CucumberAdapter).to receive(:verify_bind_method_called)
38
+
37
39
  expect(Rake::Task).to receive(:[]).with('knapsack_pro:cucumber_run').at_least(1).and_return(task)
38
40
 
39
41
  t = double
@@ -14,6 +14,10 @@ describe KnapsackPro::Runners::MinitestRunner do
14
14
  end
15
15
 
16
16
  context 'when test files were returned by Knapsack Pro API' do
17
+ before do
18
+ expect(KnapsackPro::Adapters::MinitestAdapter).to receive(:verify_bind_method_called)
19
+ end
20
+
17
21
  it 'runs tests' do
18
22
  test_file_paths = ['test_fake/a_test.rb', 'test_fake/b_test.rb']
19
23
  runner = instance_double(described_class,
@@ -167,14 +167,34 @@ describe KnapsackPro::Runners::Queue::CucumberRunner do
167
167
  context "when test files don't exist" do
168
168
  let(:test_file_paths) { [] }
169
169
 
170
- it 'returns exit code 0' do
171
- expect(KnapsackPro::Hooks::Queue).to receive(:call_after_queue)
172
- expect(KnapsackPro::Report).to receive(:save_node_queue_to_api)
170
+ context 'when all_test_file_paths exist' do
171
+ let(:all_test_file_paths) { ['features/a.feature'] }
173
172
 
174
- expect(subject).to eq({
175
- status: :completed,
176
- exitstatus: exitstatus,
177
- })
173
+ it 'returns exit code 0' do
174
+ expect(KnapsackPro::Adapters::CucumberAdapter).to receive(:verify_bind_method_called)
175
+
176
+ expect(KnapsackPro::Hooks::Queue).to receive(:call_after_queue)
177
+ expect(KnapsackPro::Report).to receive(:save_node_queue_to_api)
178
+
179
+ expect(subject).to eq({
180
+ status: :completed,
181
+ exitstatus: exitstatus,
182
+ })
183
+ end
184
+ end
185
+
186
+ context "when all_test_file_paths don't exist" do
187
+ let(:all_test_file_paths) { [] }
188
+
189
+ it 'returns exit code 0' do
190
+ expect(KnapsackPro::Hooks::Queue).to receive(:call_after_queue)
191
+ expect(KnapsackPro::Report).to receive(:save_node_queue_to_api)
192
+
193
+ expect(subject).to eq({
194
+ status: :completed,
195
+ exitstatus: exitstatus,
196
+ })
197
+ end
178
198
  end
179
199
  end
180
200
  end
@@ -159,14 +159,34 @@ describe KnapsackPro::Runners::Queue::MinitestRunner do
159
159
  context "when test files don't exist" do
160
160
  let(:test_file_paths) { [] }
161
161
 
162
- it 'returns exit code 0' do
163
- expect(KnapsackPro::Hooks::Queue).to receive(:call_after_queue)
164
- expect(KnapsackPro::Report).to receive(:save_node_queue_to_api)
162
+ context 'when all_test_file_paths exist' do
163
+ let(:all_test_file_paths) { ['a_test.rb'] }
165
164
 
166
- expect(subject).to eq({
167
- status: :completed,
168
- exitstatus: exitstatus,
169
- })
165
+ it 'returns exit code 0' do
166
+ expect(KnapsackPro::Adapters::MinitestAdapter).to receive(:verify_bind_method_called)
167
+
168
+ expect(KnapsackPro::Hooks::Queue).to receive(:call_after_queue)
169
+ expect(KnapsackPro::Report).to receive(:save_node_queue_to_api)
170
+
171
+ expect(subject).to eq({
172
+ status: :completed,
173
+ exitstatus: exitstatus,
174
+ })
175
+ end
176
+ end
177
+
178
+ context "when all_test_file_paths don't exist" do
179
+ let(:all_test_file_paths) { [] }
180
+
181
+ it 'returns exit code 0' do
182
+ expect(KnapsackPro::Hooks::Queue).to receive(:call_after_queue)
183
+ expect(KnapsackPro::Report).to receive(:save_node_queue_to_api)
184
+
185
+ expect(subject).to eq({
186
+ status: :completed,
187
+ exitstatus: exitstatus,
188
+ })
189
+ end
170
190
  end
171
191
  end
172
192
  end
@@ -249,6 +249,8 @@ describe KnapsackPro::Runners::Queue::RSpecRunner do
249
249
  let(:all_test_file_paths) { ['a_spec.rb'] }
250
250
 
251
251
  it do
252
+ expect(KnapsackPro::Adapters::RSpecAdapter).to receive(:verify_bind_method_called)
253
+
252
254
  expect(KnapsackPro::Formatters::RSpecQueueSummaryFormatter).to receive(:print_summary)
253
255
  expect(KnapsackPro::Formatters::RSpecQueueProfileFormatterExtension).to receive(:print_summary)
254
256
 
@@ -34,6 +34,8 @@ describe KnapsackPro::Runners::RSpecRunner do
34
34
  let(:task) { double }
35
35
 
36
36
  before do
37
+ expect(KnapsackPro::Adapters::RSpecAdapter).to receive(:verify_bind_method_called)
38
+
37
39
  expect(Rake::Task).to receive(:[]).with('knapsack_pro:rspec_run').at_least(1).and_return(task)
38
40
 
39
41
  t = double
@@ -26,6 +26,8 @@ describe KnapsackPro::Runners::SpinachRunner do
26
26
  end
27
27
 
28
28
  before do
29
+ expect(KnapsackPro::Adapters::SpinachAdapter).to receive(:verify_bind_method_called)
30
+
29
31
  expect(Kernel).to receive(:system).with('KNAPSACK_PRO_RECORDING_ENABLED=true KNAPSACK_PRO_TEST_SUITE_TOKEN=spinach-token bundle exec spinach --custom-arg --features_path fake-test-dir -- features/a.feature features/b.feature')
30
32
  end
31
33
 
@@ -16,6 +16,8 @@ describe KnapsackPro::Runners::TestUnitRunner do
16
16
 
17
17
  context 'when test files were returned by Knapsack Pro API' do
18
18
  it 'runs tests' do
19
+ expect(KnapsackPro::Adapters::TestUnitAdapter).to receive(:verify_bind_method_called)
20
+
19
21
  test_file_paths = ['test-unit_fake/a_test.rb', 'test-unit_fake/b_test.rb']
20
22
  runner = instance_double(described_class,
21
23
  test_dir: 'test-unit_fake',
@@ -67,7 +67,7 @@ describe KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector do
67
67
  let(:exit_code) { 1 }
68
68
 
69
69
  it do
70
- expect { subject }.to raise_error(RuntimeError, 'There was problem to generate test examples for test suite')
70
+ expect { subject }.to raise_error(RuntimeError, 'There was a problem while generating test examples for the slow test files. Please read actionable error message above.')
71
71
  end
72
72
  end
73
73
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knapsack_pro
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 2.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ArturT
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-04 00:00:00.000000000 Z
11
+ date: 2021-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -408,7 +408,7 @@ metadata:
408
408
  documentation_uri: https://docs.knapsackpro.com/integration/
409
409
  homepage_uri: https://knapsackpro.com
410
410
  source_code_uri: https://github.com/KnapsackPro/knapsack_pro-ruby
411
- post_install_message:
411
+ post_install_message:
412
412
  rdoc_options: []
413
413
  require_paths:
414
414
  - lib
@@ -424,7 +424,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
424
424
  version: '0'
425
425
  requirements: []
426
426
  rubygems_version: 3.0.6
427
- signing_key:
427
+ signing_key:
428
428
  specification_version: 4
429
429
  summary: Knapsack Pro splits tests across parallel CI nodes and ensures each parallel
430
430
  job finish work at a similar time.