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 +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +41 -1
- data/lib/knapsack_pro.rb +1 -0
- data/lib/knapsack_pro/adapters/rspec_adapter.rb +11 -5
- data/lib/knapsack_pro/base_allocator_builder.rb +27 -5
- data/lib/knapsack_pro/config/env.rb +8 -0
- data/lib/knapsack_pro/runners/queue/rspec_runner.rb +3 -1
- data/lib/knapsack_pro/test_case_detectors/rspec_test_example_detector.rb +68 -0
- data/lib/knapsack_pro/test_file_pattern.rb +5 -0
- data/lib/knapsack_pro/tracker.rb +1 -1
- data/lib/knapsack_pro/version.rb +1 -1
- data/lib/tasks/encrypted_test_file_names.rake +9 -2
- data/lib/tasks/rspec.rake +6 -0
- data/spec/knapsack_pro/adapters/rspec_adapter_spec.rb +60 -22
- data/spec/knapsack_pro/allocator_builder_spec.rb +1 -4
- data/spec/knapsack_pro/base_allocator_builder_spec.rb +76 -0
- data/spec/knapsack_pro/config/env_spec.rb +35 -0
- data/spec/knapsack_pro/queue_allocator_builder_spec.rb +1 -4
- data/spec/knapsack_pro/runners/queue/rspec_runner_spec.rb +26 -1
- data/spec/knapsack_pro/test_case_detectors/rspec_test_example_detector_spec.rb +89 -0
- data/spec/knapsack_pro/test_file_pattern_spec.rb +26 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 824a069d97efb456cec01426d85ebee23d4eba646620483a09def750fba06007
|
4
|
+
data.tar.gz: 4c0bd1456766af5889562764590862d64626399e12d9af184343c1c6eabd4cb7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b557f2efc472c09108e33c7c9ef612a141986a79d7e1969a5bbea198d57341b3ac2c8a9e76a1ab8767993922405a813eac545fcaf23d1e28986ff3c9e5d586e
|
7
|
+
data.tar.gz: ae5853cec21a9453fa290c26ac90eebce49231848edd5b638a5cdea58a9f897f9f2bcac82c26ac953b57f53ef327e66bceebe2c67e79e36cc0a6f6d8b96d0ff4
|
data/CHANGELOG.md
CHANGED
@@ -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
|
|
data/lib/knapsack_pro.rb
CHANGED
@@ -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
|
-
|
26
|
-
if ::
|
27
|
-
|
25
|
+
KnapsackPro.tracker.current_test_path =
|
26
|
+
if KnapsackPro::Config::Env.rspec_split_by_test_examples?
|
27
|
+
example.id
|
28
28
|
else
|
29
|
-
|
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 ||
|
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
|
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
|
data/lib/knapsack_pro/tracker.rb
CHANGED
@@ -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
|
34
|
+
KnapsackPro::TestFileCleaner.clean(@current_test_path)
|
35
35
|
end
|
36
36
|
|
37
37
|
def set_prerun_tests(test_file_paths)
|
data/lib/knapsack_pro/version.rb
CHANGED
@@ -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
|
-
|
23
|
-
|
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|
|
data/lib/tasks/rspec.rake
CHANGED
@@ -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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
89
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
132
|
+
expect(example).to receive(:run)
|
98
133
|
|
99
|
-
|
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
|
-
|
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(
|
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(
|
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.
|
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-
|
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
|