knapsack_pro 1.18.2 → 1.19.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c57b67afbab19446717852ab88147438653644d7953ada292a41f36c681a520d
4
- data.tar.gz: eef083501ca2a40b09cf189e27ea8ecf7aa94f7813649b6404cc08e3cb20f0cc
3
+ metadata.gz: 824a069d97efb456cec01426d85ebee23d4eba646620483a09def750fba06007
4
+ data.tar.gz: 4c0bd1456766af5889562764590862d64626399e12d9af184343c1c6eabd4cb7
5
5
  SHA512:
6
- metadata.gz: e8e3d39c85e16d78e09c3131b1e07803bc115ba587c948d7643b6785eac90d58ffb3e7ef64ccdd7049ed59cf3def65a772316ee12babb4dc272c7d4279f50bf4
7
- data.tar.gz: fd81ab872ece61ceff06015e4070f71bf951d29a5cdbe584da8e0f3a7cee0973ad53c0695ea51c6d8c5a428e1f4944027565aa5b2898a05e9b95d84703252813
6
+ metadata.gz: 3b557f2efc472c09108e33c7c9ef612a141986a79d7e1969a5bbea198d57341b3ac2c8a9e76a1ab8767993922405a813eac545fcaf23d1e28986ff3c9e5d586e
7
+ data.tar.gz: ae5853cec21a9453fa290c26ac90eebce49231848edd5b638a5cdea58a9f897f9f2bcac82c26ac953b57f53ef327e66bceebe2c67e79e36cc0a6f6d8b96d0ff4
@@ -1,5 +1,29 @@
1
1
  # Change Log
2
2
 
3
+ ### 1.19.0
4
+
5
+ * RSpec split test files by test examples (by individual `it`s)
6
+
7
+ https://github.com/KnapsackPro/knapsack_pro-ruby/pull/102
8
+
9
+ __Note:__ See [PR](https://github.com/KnapsackPro/knapsack_pro-ruby/pull/102) for more details. This is an experimental feature and it may not work for very large test suite.
10
+
11
+ __How to use it__: In order to split RSpec test files by test examples across parallel CI nodes you need to set flag:
12
+
13
+ ```
14
+ KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES=true
15
+ ```
16
+
17
+ Thanks to that your CI build speed can be faster. We recommend using this feature with Queue Mode to ensure parallel CI nodes finish work at a similar time which gives you the shortest CI build time.
18
+
19
+ Doing tests split by test examples can generate a lot of logs by `knapsack_pro` gem in Queue Mode. We recommend to set log level to:
20
+
21
+ ```
22
+ KNAPSACK_PRO_LOG_LEVEL=warn
23
+ ```
24
+
25
+ https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v1.18.2...v1.19.0
26
+
3
27
  ### 1.18.2
4
28
 
5
29
  * If `KnapsackPro::Hooks::Queue.before_queue` hook has block of code that raises an exception then ensure the hook was called only once.
data/README.md CHANGED
@@ -82,6 +82,8 @@ We keep this old FAQ in README to not break old links spread across the web. You
82
82
  - [KNAPSACK_PRO_FIXED_QUEUE_SPLIT (remember queue split on retry CI node)](#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node)
83
83
  - [KNAPSACK_PRO_MODIFY_DEFAULT_RSPEC_FORMATTERS (hide duplicated summary of pending and failed tests)](#knapsack_pro_modify_default_rspec_formatters-hide-duplicated-summary-of-pending-and-failed-tests)
84
84
  - [Supported test runners in queue mode](#supported-test-runners-in-queue-mode)
85
+ - [Split test files by test cases](#split-test-files-by-test-cases)
86
+ - [RSpec split test files by test examples (individual `it`)](#rspec-split-test-files-by-test-examples-individual-it)
85
87
  - [Extra configuration for CI server](#extra-configuration-for-ci-server)
86
88
  - [Info about ENV variables](#info-about-env-variables)
87
89
  - [KNAPSACK_PRO_FIXED_TEST_SUITE_SPLIT (test suite split based on seed)](#knapsack_pro_fixed_test_suite_split-test-suite-split-based-on-seed)
@@ -97,6 +99,7 @@ We keep this old FAQ in README to not break old links spread across the web. You
97
99
  - [Test file names encryption](#test-file-names-encryption)
98
100
  - [How to enable test file names encryption?](#how-to-enable-test-file-names-encryption)
99
101
  - [How to debug test file names?](#how-to-debug-test-file-names)
102
+ - [Preview encrypted RSpec test example paths?](#preview-encrypted-rspec-test-example-paths)
100
103
  - [How to enable branch names encryption?](#how-to-enable-branch-names-encryption)
101
104
  - [How to debug branch names?](#how-to-debug-branch-names)
102
105
  - [Supported CI providers](#supported-ci-providers)
@@ -592,6 +595,33 @@ At this moment the queue mode works for:
592
595
  * Minitest
593
596
  * Cucumber
594
597
 
598
+ ## Split test files by test cases
599
+
600
+ __Note:__ this is an experimental feature. It works for Regular Mode and Queue Mode. For large test suite with a few thousand test files, it may generate too many RSpec test example paths that may lead to too large JSON payload in request to Knapsack Pro API and this could trigger the API timeout.
601
+
602
+ Please give us feedback so we could improve the feature.
603
+ https://knapsackpro.com/contact
604
+
605
+ __How it works__: You can split slow test file by test cases. Thanks to that the test file can be split across parallel CI nodes because test cases from the test file will run on different CI nodes.
606
+
607
+ This is helpful when you have one or a few very slow test files that are a bottleneck for CI build speed and you don't want to manually create a few smaller test files from the slow test files. Instead, you can tell `knapsack_pro` gem to split your test files by test cases across parallel CI nodes.
608
+
609
+ ### RSpec split test files by test examples (by individual `it`s)
610
+
611
+ In order to split RSpec test files by test examples across parallel CI nodes you need to set flag:
612
+
613
+ ```
614
+ KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES=true
615
+ ```
616
+
617
+ Thanks to that your CI build speed can be faster. We recommend using this feature with Queue Mode to ensure parallel CI nodes finish work at a similar time which gives you the shortest CI build time.
618
+
619
+ Doing tests split by test examples can generate a lot of logs by `knapsack_pro` gem in Queue Mode. We recommend to set log level to:
620
+
621
+ ```
622
+ KNAPSACK_PRO_LOG_LEVEL=warn
623
+ ```
624
+
595
625
  ## Extra configuration for CI server
596
626
 
597
627
  ### Info about ENV variables
@@ -781,6 +811,16 @@ KNAPSACK_PRO_SALT=xxx bundle exec rake knapsack_pro:encrypted_test_file_names[rs
781
811
 
782
812
  You can pass the name of test runner like `rspec`, `minitest`, `test_unit`, `cucumber`, `spinach` as argument to rake task.
783
813
 
814
+ ##### Preview encrypted RSpec test example paths?
815
+
816
+ If you split RSpec tests by test examples (by individual `it`) you can preview encrypted test example paths this way:
817
+
818
+ ```bash
819
+ KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES=true \
820
+ KNAPSACK_PRO_SALT=xxx \
821
+ bundle exec rake knapsack_pro:encrypted_test_file_names[rspec]
822
+ ```
823
+
784
824
  #### How to enable branch names encryption?
785
825
 
786
826
  You need to add environment variable `KNAPSACK_PRO_BRANCH_ENCRYPTED=true` to your CI server.
@@ -2316,7 +2356,7 @@ Now you can preview logs in `Artifacts` tab in the Circle CI build view.
2316
2356
 
2317
2357
  #### How to split tests based on test level instead of test file level?
2318
2358
 
2319
- If you want to split one big test file (test file with long time execution) across multiple CI nodes then you can:
2359
+ If you want to split one big test file (test file with long time execution) across multiple CI nodes then you can [check this tip](#split-test-files-by-test-cases) or use other methods like:
2320
2360
 
2321
2361
  ##### A. Create multiple small test files
2322
2362
 
@@ -68,6 +68,7 @@ require_relative 'knapsack_pro/runners/queue/base_runner'
68
68
  require_relative 'knapsack_pro/runners/queue/rspec_runner'
69
69
  require_relative 'knapsack_pro/runners/queue/cucumber_runner'
70
70
  require_relative 'knapsack_pro/runners/queue/minitest_runner'
71
+ require_relative 'knapsack_pro/test_case_detectors/rspec_test_example_detector'
71
72
  require_relative 'knapsack_pro/crypto/encryptor'
72
73
  require_relative 'knapsack_pro/crypto/branch_encryptor'
73
74
  require_relative 'knapsack_pro/crypto/decryptor'
@@ -22,14 +22,20 @@ module KnapsackPro
22
22
  def bind_time_tracker
23
23
  ::RSpec.configure do |config|
24
24
  config.around(:each) do |example|
25
- current_example_group =
26
- if ::RSpec.respond_to?(:current_example)
27
- ::RSpec.current_example.metadata[:example_group]
25
+ KnapsackPro.tracker.current_test_path =
26
+ if KnapsackPro::Config::Env.rspec_split_by_test_examples?
27
+ example.id
28
28
  else
29
- example.metadata
29
+ current_example_group =
30
+ if ::RSpec.respond_to?(:current_example)
31
+ ::RSpec.current_example.metadata[:example_group]
32
+ else
33
+ example.metadata
34
+ end
35
+
36
+ KnapsackPro::Adapters::RSpecAdapter.test_path(current_example_group)
30
37
  end
31
38
 
32
- KnapsackPro.tracker.current_test_path = KnapsackPro::Adapters::RSpecAdapter.test_path(current_example_group)
33
39
  KnapsackPro.tracker.start_timer
34
40
 
35
41
  example.run
@@ -18,7 +18,33 @@ module KnapsackPro
18
18
  end
19
19
 
20
20
  def test_dir
21
- KnapsackPro::Config::Env.test_dir || test_file_pattern.split('/').first.gsub(/({)/, '')
21
+ KnapsackPro::Config::Env.test_dir || TestFilePattern.test_dir(adapter_class)
22
+ end
23
+
24
+ def test_files
25
+ found_test_files = KnapsackPro::TestFileFinder.call(test_file_pattern)
26
+
27
+ if adapter_class == KnapsackPro::Adapters::RSpecAdapter && KnapsackPro::Config::Env.rspec_split_by_test_examples?
28
+ test_files_count = found_test_files.size
29
+
30
+ KnapsackPro.logger.warn("Generating RSpec test examples JSON report to prepare your test suite to be split by test examples (by individual 'it's. Thanks to that a single test file can be split across parallel CI nodes). Analyzing #{test_files_count} test files.")
31
+
32
+ if test_files_count > 1000
33
+ KnapsackPro.logger.warn("You have more than 1000 test files, it may take longer to generate test examples. Please wait...")
34
+ end
35
+
36
+ # generate RSpec JSON report in separate process to not pollute RSpec state
37
+ cmd = 'bundle exec rake knapsack_pro:rspec_test_example_detector'
38
+ unless Kernel.system(cmd)
39
+ raise "Could not generate JSON report for RSpec. Rake task failed when running #{cmd}"
40
+ end
41
+
42
+ # read JSON report
43
+ detector = KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector.new
44
+ detector.test_file_example_paths
45
+ else
46
+ found_test_files
47
+ end
22
48
  end
23
49
 
24
50
  private
@@ -36,9 +62,5 @@ module KnapsackPro
36
62
  def test_file_pattern
37
63
  TestFilePattern.call(adapter_class)
38
64
  end
39
-
40
- def test_files
41
- KnapsackPro::TestFileFinder.call(test_file_pattern)
42
- end
43
65
  end
44
66
  end
@@ -167,6 +167,14 @@ module KnapsackPro
167
167
  ENV.fetch('KNAPSACK_PRO_CUCUMBER_QUEUE_PREFIX', 'bundle exec')
168
168
  end
169
169
 
170
+ def rspec_split_by_test_examples
171
+ ENV.fetch('KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES', false)
172
+ end
173
+
174
+ def rspec_split_by_test_examples?
175
+ rspec_split_by_test_examples.to_s == 'true'
176
+ end
177
+
170
178
  def test_suite_token
171
179
  env_name = 'KNAPSACK_PRO_TEST_SUITE_TOKEN'
172
180
  ENV[env_name] || raise("Missing environment variable #{env_name}. You should set environment variable like #{env_name}_RSPEC (note there is suffix _RSPEC at the end). knapsack_pro gem will set #{env_name} based on #{env_name}_RSPEC value. If you use other test runner than RSpec then use proper suffix.")
@@ -16,7 +16,9 @@ module KnapsackPro
16
16
  cli_args = (args || '').split
17
17
  # if user didn't provide the format then use explicitly default progress formatter
18
18
  # in order to avoid KnapsackPro::Formatters::RSpecQueueSummaryFormatter being the only default formatter
19
- cli_args += ['--format', 'progress'] unless cli_args.include?('--format')
19
+ if !cli_args.include?('--format') && !cli_args.include?('-f')
20
+ cli_args += ['--format', 'progress']
21
+ end
20
22
  cli_args += [
21
23
  '--format', KnapsackPro::Formatters::RSpecQueueSummaryFormatter.to_s,
22
24
  '--default-path', runner.test_dir,
@@ -0,0 +1,68 @@
1
+ module KnapsackPro
2
+ module TestCaseDetectors
3
+ class RSpecTestExampleDetector
4
+ REPORT_DIR = 'tmp/knapsack_pro/test_case_detectors/rspec'
5
+ REPORT_PATH = "#{REPORT_DIR}/rspec_dry_run_json_report.json"
6
+
7
+ def generate_json_report
8
+ require 'rspec/core'
9
+
10
+ ensure_report_dir_exists
11
+ remove_old_json_report
12
+
13
+ test_file_paths = KnapsackPro::TestFileFinder.call(test_file_pattern)
14
+
15
+ cli_args = [
16
+ '--dry-run',
17
+ '--format', 'json',
18
+ '--out', REPORT_PATH,
19
+ '--default-path', test_dir,
20
+ ] + test_file_paths.map { |t| t.fetch('path') }
21
+ options = RSpec::Core::ConfigurationOptions.new(cli_args)
22
+ exit_code = RSpec::Core::Runner.new(options).run($stderr, $stdout)
23
+ if exit_code != 0
24
+ raise 'There was problem to generate test examples for test suite'
25
+ end
26
+ end
27
+
28
+ def test_file_example_paths
29
+ raise "No report found at #{REPORT_PATH}" unless File.exists?(REPORT_PATH)
30
+
31
+ json_report = File.read(REPORT_PATH)
32
+ hash_report = JSON.parse(json_report)
33
+ hash_report
34
+ .fetch('examples')
35
+ .map { |e| e.fetch('id') }
36
+ .map { |path_with_example_id| test_file_hash_for(path_with_example_id) }
37
+ end
38
+
39
+ private
40
+
41
+ def adapter_class
42
+ KnapsackPro::Adapters::RSpecAdapter
43
+ end
44
+
45
+ def test_dir
46
+ KnapsackPro::Config::Env.test_dir || KnapsackPro::TestFilePattern.test_dir(adapter_class)
47
+ end
48
+
49
+ def test_file_pattern
50
+ KnapsackPro::TestFilePattern.call(adapter_class)
51
+ end
52
+
53
+ def ensure_report_dir_exists
54
+ FileUtils.mkdir_p(REPORT_DIR)
55
+ end
56
+
57
+ def remove_old_json_report
58
+ File.delete(REPORT_PATH) if File.exists?(REPORT_PATH)
59
+ end
60
+
61
+ def test_file_hash_for(test_file_path)
62
+ {
63
+ 'path' => TestFileCleaner.clean(test_file_path)
64
+ }
65
+ end
66
+ end
67
+ end
68
+ end
@@ -3,5 +3,10 @@ module KnapsackPro
3
3
  def self.call(adapter_class)
4
4
  KnapsackPro::Config::Env.test_file_pattern || adapter_class::TEST_DIR_PATTERN
5
5
  end
6
+
7
+ def self.test_dir(adapter_class)
8
+ test_file_pattern = call(adapter_class)
9
+ test_file_pattern.split('/').first.gsub(/({)/, '')
10
+ end
6
11
  end
7
12
  end
@@ -31,7 +31,7 @@ module KnapsackPro
31
31
 
32
32
  def current_test_path
33
33
  raise("current_test_path needs to be set by Knapsack Pro Adapter's bind method") unless @current_test_path
34
- @current_test_path.sub(/^\.\//, '')
34
+ KnapsackPro::TestFileCleaner.clean(@current_test_path)
35
35
  end
36
36
 
37
37
  def set_prerun_tests(test_file_paths)
@@ -1,3 +1,3 @@
1
1
  module KnapsackPro
2
- VERSION = '1.18.2'
2
+ VERSION = '1.19.0'
3
3
  end
@@ -19,8 +19,15 @@ namespace :knapsack_pro do
19
19
  raise('Provide adapter name like rspec, minitest, test_unit, cucumber, spinach')
20
20
  end
21
21
 
22
- test_file_pattern = KnapsackPro::TestFilePattern.call(adapter_class)
23
- test_files = KnapsackPro::TestFileFinder.call(test_file_pattern)
22
+ test_files =
23
+ if adapter_class == KnapsackPro::Adapters::RSpecAdapter && KnapsackPro::Config::Env.rspec_split_by_test_examples?
24
+ detector = KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector.new
25
+ detector.generate_json_report
26
+ detector.test_file_example_paths
27
+ else
28
+ test_file_pattern = KnapsackPro::TestFilePattern.call(adapter_class)
29
+ KnapsackPro::TestFileFinder.call(test_file_pattern)
30
+ end
24
31
 
25
32
  test_file_names = []
26
33
  test_files.each do |t|
@@ -4,4 +4,10 @@ namespace :knapsack_pro do
4
4
  task :rspec, [:rspec_args] do |_, args|
5
5
  KnapsackPro::Runners::RSpecRunner.run(args[:rspec_args])
6
6
  end
7
+
8
+ desc "Generate JSON report for test suite based on default test pattern or based on defined pattern with ENV vars"
9
+ task :rspec_test_example_detector do
10
+ detector = KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector.new
11
+ detector.generate_json_report
12
+ end
7
13
  end
@@ -70,37 +70,75 @@ describe KnapsackPro::Adapters::RSpecAdapter do
70
70
  describe '#bind_time_tracker' do
71
71
  let(:tracker) { instance_double(KnapsackPro::Tracker) }
72
72
  let(:logger) { instance_double(Logger) }
73
- let(:test_path) { 'spec/a_spec.rb' }
74
73
  let(:global_time) { 'Global time: 01m 05s' }
75
- let(:example_group) { double }
76
- let(:current_example) do
77
- OpenStruct.new(metadata: {
78
- example_group: example_group
79
- })
74
+
75
+ context 'when rspec split by test examples is disabled' do
76
+ let(:test_path) { 'spec/a_spec.rb' }
77
+ let(:example) { double }
78
+ let(:example_group) { double }
79
+ let(:current_example) do
80
+ OpenStruct.new(metadata: {
81
+ example_group: example_group
82
+ })
83
+ end
84
+
85
+ before do
86
+ expect(KnapsackPro::Config::Env).to receive(:rspec_split_by_test_examples?).and_return(false)
87
+ end
88
+
89
+ it do
90
+ expect(config).to receive(:around).with(:each).and_yield(example)
91
+ expect(config).to receive(:after).with(:suite).and_yield
92
+ expect(::RSpec).to receive(:configure).and_yield(config)
93
+
94
+ expect(::RSpec).to receive(:current_example).twice.and_return(current_example)
95
+ expect(described_class).to receive(:test_path).with(example_group).and_return(test_path)
96
+
97
+ allow(KnapsackPro).to receive(:tracker).and_return(tracker)
98
+ expect(tracker).to receive(:current_test_path=).with(test_path)
99
+ expect(tracker).to receive(:start_timer)
100
+
101
+ expect(example).to receive(:run)
102
+
103
+ expect(tracker).to receive(:stop_timer)
104
+
105
+ expect(KnapsackPro::Presenter).to receive(:global_time).and_return(global_time)
106
+ expect(KnapsackPro).to receive(:logger).and_return(logger)
107
+ expect(logger).to receive(:debug).with(global_time)
108
+
109
+ subject.bind_time_tracker
110
+ end
80
111
  end
81
- let(:example) { double }
82
112
 
83
- it do
84
- expect(config).to receive(:around).with(:each).and_yield(example)
85
- expect(config).to receive(:after).with(:suite).and_yield
86
- expect(::RSpec).to receive(:configure).and_yield(config)
113
+ context 'when rspec split by test examples is enabled' do
114
+ let(:test_example_path) { 'spec/a_spec.rb[1:1]' }
115
+ let(:example) { double }
87
116
 
88
- expect(::RSpec).to receive(:current_example).twice.and_return(current_example)
89
- expect(described_class).to receive(:test_path).with(example_group).and_return(test_path)
117
+ before do
118
+ expect(KnapsackPro::Config::Env).to receive(:rspec_split_by_test_examples?).and_return(true)
119
+ end
120
+
121
+ it do
122
+ expect(config).to receive(:around).with(:each).and_yield(example)
123
+ expect(config).to receive(:after).with(:suite).and_yield
124
+ expect(::RSpec).to receive(:configure).and_yield(config)
90
125
 
91
- allow(KnapsackPro).to receive(:tracker).and_return(tracker)
92
- expect(tracker).to receive(:current_test_path=).with(test_path)
93
- expect(tracker).to receive(:start_timer)
126
+ expect(example).to receive(:id).and_return(test_example_path)
94
127
 
95
- expect(example).to receive(:run)
128
+ allow(KnapsackPro).to receive(:tracker).and_return(tracker)
129
+ expect(tracker).to receive(:current_test_path=).with(test_example_path)
130
+ expect(tracker).to receive(:start_timer)
96
131
 
97
- expect(tracker).to receive(:stop_timer)
132
+ expect(example).to receive(:run)
98
133
 
99
- expect(KnapsackPro::Presenter).to receive(:global_time).and_return(global_time)
100
- expect(KnapsackPro).to receive(:logger).and_return(logger)
101
- expect(logger).to receive(:debug).with(global_time)
134
+ expect(tracker).to receive(:stop_timer)
102
135
 
103
- subject.bind_time_tracker
136
+ expect(KnapsackPro::Presenter).to receive(:global_time).and_return(global_time)
137
+ expect(KnapsackPro).to receive(:logger).and_return(logger)
138
+ expect(logger).to receive(:debug).with(global_time)
139
+
140
+ subject.bind_time_tracker
141
+ end
104
142
  end
105
143
  end
106
144
 
@@ -8,11 +8,8 @@ describe KnapsackPro::AllocatorBuilder do
8
8
  subject { allocator_builder.allocator }
9
9
 
10
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
11
  test_files = double
15
- expect(KnapsackPro::TestFileFinder).to receive(:call).with(test_file_pattern).and_return(test_files)
12
+ expect(allocator_builder).to receive(:test_files).and_return(test_files)
16
13
 
17
14
  repository_adapter = double
18
15
  expect(KnapsackPro::RepositoryAdapterInitiator).to receive(:call).and_return(repository_adapter)
@@ -99,4 +99,80 @@ describe KnapsackPro::BaseAllocatorBuilder do
99
99
  end
100
100
  end
101
101
  end
102
+
103
+ describe '#test_files' do
104
+ subject { allocator_builder.test_files }
105
+
106
+ context 'when looking for test files on disk by default' do
107
+ it do
108
+ test_file_pattern = double
109
+ expect(KnapsackPro::TestFilePattern).to receive(:call).with(adapter_class).and_return(test_file_pattern)
110
+
111
+ test_files = double
112
+ expect(KnapsackPro::TestFileFinder).to receive(:call).with(test_file_pattern).and_return(test_files)
113
+
114
+ expect(subject).to eq test_files
115
+ end
116
+ end
117
+
118
+ context 'when RSpec adapter and rspec split by test examples is enabled' do
119
+ let(:adapter_class) { KnapsackPro::Adapters::RSpecAdapter }
120
+ let(:test_files) { double(size: 1000) }
121
+ let(:cmd) { 'bundle exec rake knapsack_pro:rspec_test_example_detector' }
122
+
123
+ before do
124
+ expect(KnapsackPro::Config::Env).to receive(:rspec_split_by_test_examples?).and_return(true)
125
+
126
+ test_file_pattern = double
127
+ expect(KnapsackPro::TestFilePattern).to receive(:call).with(adapter_class).and_return(test_file_pattern)
128
+
129
+ expect(KnapsackPro::TestFileFinder).to receive(:call).with(test_file_pattern).and_return(test_files)
130
+
131
+ expect(Kernel).to receive(:system).with(cmd).and_return(cmd_result)
132
+ end
133
+
134
+ context 'when rake task to detect RSpec test examples works' do
135
+ let(:cmd_result) { true }
136
+ let(:test_file_example_paths) { double }
137
+ let(:logger) { instance_double(Logger) }
138
+
139
+ before do
140
+ rspec_test_example_detector = instance_double(KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector)
141
+ expect(KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector).to receive(:new).and_return(rspec_test_example_detector)
142
+ expect(rspec_test_example_detector).to receive(:test_file_example_paths).and_return(test_file_example_paths)
143
+
144
+ expect(KnapsackPro).to receive(:logger).at_least(1).and_return(logger)
145
+ end
146
+
147
+ context 'when up to 1000 test files detected on disk' do
148
+ let(:test_files) { double(size: 1000) }
149
+
150
+ it do
151
+ expect(logger).to receive(:warn).with("Generating RSpec test examples JSON report to prepare your test suite to be split by test examples (by individual 'it's. Thanks to that a single test file can be split across parallel CI nodes). Analyzing 1000 test files.")
152
+
153
+ expect(subject).to eq test_file_example_paths
154
+ end
155
+ end
156
+
157
+ context 'when more than 1000 test files detected on disk' do
158
+ let(:test_files) { double(size: 1001) }
159
+
160
+ it do
161
+ expect(logger).to receive(:warn).with("Generating RSpec test examples JSON report to prepare your test suite to be split by test examples (by individual 'it's. Thanks to that a single test file can be split across parallel CI nodes). Analyzing 1001 test files.")
162
+ expect(logger).to receive(:warn).with('You have more than 1000 test files, it may take longer to generate test examples. Please wait...')
163
+
164
+ expect(subject).to eq test_file_example_paths
165
+ end
166
+ end
167
+ end
168
+
169
+ context 'when rake task to detect RSpec test examples failed' do
170
+ let(:cmd_result) { false }
171
+
172
+ it do
173
+ expect { subject }.to raise_error(RuntimeError, 'Could not generate JSON report for RSpec. Rake task failed when running bundle exec rake knapsack_pro:rspec_test_example_detector')
174
+ end
175
+ end
176
+ end
177
+ end
102
178
  end
@@ -609,6 +609,41 @@ describe KnapsackPro::Config::Env do
609
609
  end
610
610
  end
611
611
 
612
+ describe '.rspec_split_by_test_examples' do
613
+ subject { described_class.rspec_split_by_test_examples }
614
+
615
+ context 'when ENV exists' do
616
+ before { stub_const("ENV", { 'KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES' => true }) }
617
+ it { should eq true }
618
+ end
619
+
620
+ context "when ENV doesn't exist" do
621
+ before { stub_const("ENV", {}) }
622
+ it { should be false }
623
+ end
624
+ end
625
+
626
+ describe '.rspec_split_by_test_examples?' do
627
+ subject { described_class.rspec_split_by_test_examples? }
628
+
629
+ context 'when ENV exists' do
630
+ context 'when KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES=true' do
631
+ before { stub_const("ENV", { 'KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES' => 'true' }) }
632
+ it { should be true }
633
+ end
634
+
635
+ context 'when KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES=false' do
636
+ before { stub_const("ENV", { 'KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES' => 'false' }) }
637
+ it { should be false }
638
+ end
639
+ end
640
+
641
+ context "when ENV doesn't exist" do
642
+ before { stub_const("ENV", {}) }
643
+ it { should be false }
644
+ end
645
+ end
646
+
612
647
  describe '.test_suite_token' do
613
648
  subject { described_class.test_suite_token }
614
649
 
@@ -8,11 +8,8 @@ describe KnapsackPro::QueueAllocatorBuilder do
8
8
  subject { allocator_builder.allocator }
9
9
 
10
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
11
  test_files = double
15
- expect(KnapsackPro::TestFileFinder).to receive(:call).with(test_file_pattern).and_return(test_files)
12
+ expect(allocator_builder).to receive(:test_files).and_return(test_files)
16
13
 
17
14
  repository_adapter = double
18
15
  expect(KnapsackPro::RepositoryAdapterInitiator).to receive(:call).and_return(repository_adapter)
@@ -55,7 +55,7 @@ describe KnapsackPro::Runners::Queue::RSpecRunner do
55
55
  end
56
56
  end
57
57
 
58
- context 'when format param is provided' do
58
+ context 'when format param is provided as --format' do
59
59
  let(:args) { '--format documentation' }
60
60
 
61
61
  it 'uses provided format param instead of default formatter progress' do
@@ -79,6 +79,31 @@ describe KnapsackPro::Runners::Queue::RSpecRunner do
79
79
  subject
80
80
  end
81
81
  end
82
+
83
+ context 'when format param is provided as -f' do
84
+ let(:args) { '-f d' }
85
+
86
+ it 'uses provided format param instead of default formatter progress' do
87
+ expected_exitstatus = 0
88
+ expected_accumulator = {
89
+ status: :completed,
90
+ exitstatus: expected_exitstatus
91
+ }
92
+ accumulator = {
93
+ status: :next,
94
+ runner: runner,
95
+ can_initialize_queue: true,
96
+ args: ['-f', 'd', '--format', 'KnapsackPro::Formatters::RSpecQueueSummaryFormatter', '--default-path', 'fake-test-dir'],
97
+ exitstatus: 0,
98
+ all_test_file_paths: [],
99
+ }
100
+ expect(described_class).to receive(:run_tests).with(accumulator).and_return(expected_accumulator)
101
+
102
+ expect(Kernel).to receive(:exit).with(expected_exitstatus)
103
+
104
+ subject
105
+ end
106
+ end
82
107
  end
83
108
 
84
109
  context 'when args not provided' do
@@ -0,0 +1,89 @@
1
+ describe KnapsackPro::TestCaseDetectors::RSpecTestExampleDetector do
2
+ let(:report_dir) { 'tmp/knapsack_pro/test_case_detectors/rspec' }
3
+ let(:report_path) { 'tmp/knapsack_pro/test_case_detectors/rspec/rspec_dry_run_json_report.json' }
4
+
5
+ describe '#generate_json_report' do
6
+ subject { described_class.new.generate_json_report }
7
+
8
+ before do
9
+ expect(FileUtils).to receive(:mkdir_p).with(report_dir)
10
+
11
+ expect(File).to receive(:exists?).with(report_path).and_return(true)
12
+ expect(File).to receive(:delete).with(report_path)
13
+
14
+ test_file_pattern = double
15
+ adapter_class = KnapsackPro::Adapters::RSpecAdapter
16
+ expect(KnapsackPro::TestFilePattern).to receive(:call).with(adapter_class).and_return(test_file_pattern)
17
+
18
+ test_file_paths = [
19
+ { 'path' => 'spec/a_spec.rb' },
20
+ { 'path' => 'spec/b_spec.rb' },
21
+ ]
22
+ expect(KnapsackPro::TestFileFinder).to receive(:call).with(test_file_pattern).and_return(test_file_paths)
23
+
24
+ test_dir = 'spec'
25
+ expect(KnapsackPro::Config::Env).to receive(:test_dir).and_return(nil)
26
+ expect(KnapsackPro::TestFilePattern).to receive(:test_dir).with(adapter_class).and_return(test_dir)
27
+
28
+ options = double
29
+ expect(RSpec::Core::ConfigurationOptions).to receive(:new).with([
30
+ '--dry-run',
31
+ '--format', 'json',
32
+ '--out', report_path,
33
+ '--default-path', test_dir,
34
+ 'spec/a_spec.rb', 'spec/b_spec.rb',
35
+ ]).and_return(options)
36
+
37
+ rspec_core_runner = double
38
+ expect(RSpec::Core::Runner).to receive(:new).with(options).and_return(rspec_core_runner)
39
+ expect(rspec_core_runner).to receive(:run).with($stderr, $stdout).and_return(exit_code)
40
+ end
41
+
42
+ context 'when exit code from RSpec::Core::Runner is 0' do
43
+ let(:exit_code) { 0 }
44
+
45
+ it do
46
+ expect(subject).to be_nil
47
+ end
48
+ end
49
+
50
+ context 'when exit code from RSpec::Core::Runner is 1' do
51
+ let(:exit_code) { 1 }
52
+
53
+ it do
54
+ expect { subject }.to raise_error(RuntimeError, 'There was problem to generate test examples for test suite')
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#test_file_example_paths' do
60
+ subject { described_class.new.test_file_example_paths }
61
+
62
+ context 'when json report exists' do
63
+ it do
64
+ expect(File).to receive(:exists?).with(report_path).and_return(true)
65
+
66
+ json_file = {
67
+ 'examples' => [
68
+ { id: './spec/a_spec.rb[1:1]' },
69
+ { id: './spec/a_spec.rb[1:2]' },
70
+ ]
71
+ }.to_json
72
+ expect(File).to receive(:read).with(report_path).and_return(json_file)
73
+
74
+ expect(subject).to eq([
75
+ { 'path' => 'spec/a_spec.rb[1:1]' },
76
+ { 'path' => 'spec/a_spec.rb[1:2]' },
77
+ ])
78
+ end
79
+ end
80
+
81
+ context 'when json report does not exist' do
82
+ it do
83
+ expect(File).to receive(:exists?).with(report_path).and_return(false)
84
+
85
+ expect { subject }.to raise_error(RuntimeError, 'No report found at tmp/knapsack_pro/test_case_detectors/rspec/rspec_dry_run_json_report.json')
86
+ end
87
+ end
88
+ end
89
+ end
@@ -20,4 +20,30 @@ describe KnapsackPro::TestFilePattern do
20
20
  it { should eq 'test/**{,/*/**}/*_test.rb' }
21
21
  end
22
22
  end
23
+
24
+ describe '#test_dir' do
25
+ let(:adapter_class) { KnapsackPro::Adapters::BaseAdapter }
26
+
27
+ subject { described_class.test_dir(adapter_class) }
28
+
29
+ before do
30
+ expect(described_class).to receive(:call).with(adapter_class).and_return(test_file_pattern)
31
+ end
32
+
33
+ context 'when default test file pattern' do
34
+ let(:test_file_pattern) { 'spec/**{,/*/**}/*_spec.rb' }
35
+
36
+ it 'extracts test directory from the pattern' do
37
+ expect(subject).to eq 'spec'
38
+ end
39
+ end
40
+
41
+ context 'when test file pattern has multiple patterns' do
42
+ let(:test_file_pattern) { '{spec/*_spec.rb,spec2/controllers/**/*_spec.rb}' }
43
+
44
+ it 'extracts test directory from the first pattern' do
45
+ expect(subject).to eq 'spec'
46
+ end
47
+ end
48
+ end
23
49
  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: 1.18.2
4
+ version: 1.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ArturT
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-18 00:00:00.000000000 Z
11
+ date: 2020-04-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -281,6 +281,7 @@ files:
281
281
  - lib/knapsack_pro/runners/spinach_runner.rb
282
282
  - lib/knapsack_pro/runners/test_unit_runner.rb
283
283
  - lib/knapsack_pro/task_loader.rb
284
+ - lib/knapsack_pro/test_case_detectors/rspec_test_example_detector.rb
284
285
  - lib/knapsack_pro/test_file_cleaner.rb
285
286
  - lib/knapsack_pro/test_file_finder.rb
286
287
  - lib/knapsack_pro/test_file_pattern.rb
@@ -364,6 +365,7 @@ files:
364
365
  - spec/knapsack_pro/runners/spinach_runner_spec.rb
365
366
  - spec/knapsack_pro/runners/test_unit_runner_spec.rb
366
367
  - spec/knapsack_pro/task_loader_spec.rb
368
+ - spec/knapsack_pro/test_case_detectors/rspec_test_example_detector_spec.rb
367
369
  - spec/knapsack_pro/test_file_cleaner_spec.rb
368
370
  - spec/knapsack_pro/test_file_finder_spec.rb
369
371
  - spec/knapsack_pro/test_file_pattern_spec.rb
@@ -477,6 +479,7 @@ test_files:
477
479
  - spec/knapsack_pro/runners/spinach_runner_spec.rb
478
480
  - spec/knapsack_pro/runners/test_unit_runner_spec.rb
479
481
  - spec/knapsack_pro/task_loader_spec.rb
482
+ - spec/knapsack_pro/test_case_detectors/rspec_test_example_detector_spec.rb
480
483
  - spec/knapsack_pro/test_file_cleaner_spec.rb
481
484
  - spec/knapsack_pro/test_file_finder_spec.rb
482
485
  - spec/knapsack_pro/test_file_pattern_spec.rb