knapsack_pro 3.8.0 → 7.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +377 -23
- data/.github/dependabot.yml +11 -0
- data/.github/pull_request_template.md +22 -0
- data/.gitignore +4 -0
- data/CHANGELOG.md +325 -1
- data/Gemfile +9 -0
- data/README.md +3 -10
- data/bin/test +15 -0
- data/knapsack_pro.gemspec +7 -6
- data/lib/knapsack_pro/adapters/base_adapter.rb +17 -2
- data/lib/knapsack_pro/adapters/cucumber_adapter.rb +3 -3
- data/lib/knapsack_pro/adapters/minitest_adapter.rb +2 -0
- data/lib/knapsack_pro/adapters/rspec_adapter.rb +88 -49
- data/lib/knapsack_pro/adapters/spinach_adapter.rb +2 -0
- data/lib/knapsack_pro/adapters/test_unit_adapter.rb +2 -0
- data/lib/knapsack_pro/allocator.rb +2 -0
- data/lib/knapsack_pro/allocator_builder.rb +2 -0
- data/lib/knapsack_pro/base_allocator_builder.rb +8 -25
- data/lib/knapsack_pro/build_distribution_fetcher.rb +2 -0
- data/lib/knapsack_pro/client/api/action.rb +2 -0
- data/lib/knapsack_pro/client/api/v1/base.rb +2 -0
- data/lib/knapsack_pro/client/api/v1/build_distributions.rb +5 -0
- data/lib/knapsack_pro/client/api/v1/build_subsets.rb +2 -0
- data/lib/knapsack_pro/client/api/v1/queues.rb +6 -1
- data/lib/knapsack_pro/client/connection.rb +5 -6
- data/lib/knapsack_pro/config/ci/app_veyor.rb +18 -0
- data/lib/knapsack_pro/config/ci/base.rb +27 -0
- data/lib/knapsack_pro/config/ci/buildkite.rb +18 -0
- data/lib/knapsack_pro/config/ci/circle.rb +18 -0
- data/lib/knapsack_pro/config/ci/cirrus_ci.rb +18 -0
- data/lib/knapsack_pro/config/ci/codefresh.rb +18 -0
- data/lib/knapsack_pro/config/ci/codeship.rb +18 -0
- data/lib/knapsack_pro/config/ci/github_actions.rb +26 -0
- data/lib/knapsack_pro/config/ci/gitlab_ci.rb +20 -1
- data/lib/knapsack_pro/config/ci/heroku.rb +18 -0
- data/lib/knapsack_pro/config/ci/semaphore.rb +16 -0
- data/lib/knapsack_pro/config/ci/semaphore2.rb +19 -0
- data/lib/knapsack_pro/config/ci/travis.rb +18 -0
- data/lib/knapsack_pro/config/env.rb +46 -22
- data/lib/knapsack_pro/config/env_generator.rb +2 -0
- data/lib/knapsack_pro/config/temp_files.rb +8 -4
- data/lib/knapsack_pro/crypto/branch_encryptor.rb +2 -0
- data/lib/knapsack_pro/crypto/decryptor.rb +2 -0
- data/lib/knapsack_pro/crypto/digestor.rb +2 -0
- data/lib/knapsack_pro/crypto/encryptor.rb +2 -0
- data/lib/knapsack_pro/extensions/rspec_extension.rb +137 -0
- data/lib/knapsack_pro/formatters/rspec_json_formatter.rb +2 -0
- data/lib/knapsack_pro/formatters/time_tracker.rb +152 -0
- data/lib/knapsack_pro/formatters/time_tracker_fetcher.rb +20 -0
- data/lib/knapsack_pro/hooks/queue.rb +2 -0
- data/lib/knapsack_pro/logger_wrapper.rb +2 -0
- data/lib/knapsack_pro/mask_string.rb +9 -0
- data/lib/knapsack_pro/presenter.rb +6 -3
- data/lib/knapsack_pro/pure/queue/rspec_pure.rb +92 -0
- data/lib/knapsack_pro/queue_allocator.rb +2 -0
- data/lib/knapsack_pro/queue_allocator_builder.rb +2 -0
- data/lib/knapsack_pro/railtie.rb +2 -0
- data/lib/knapsack_pro/report.rb +15 -9
- data/lib/knapsack_pro/repository_adapter_initiator.rb +2 -0
- data/lib/knapsack_pro/repository_adapters/base_adapter.rb +2 -0
- data/lib/knapsack_pro/repository_adapters/env_adapter.rb +2 -0
- data/lib/knapsack_pro/repository_adapters/git_adapter.rb +50 -0
- data/lib/knapsack_pro/runners/base_runner.rb +2 -0
- data/lib/knapsack_pro/runners/cucumber_runner.rb +2 -0
- data/lib/knapsack_pro/runners/minitest_runner.rb +2 -0
- data/lib/knapsack_pro/runners/queue/base_runner.rb +29 -0
- data/lib/knapsack_pro/runners/queue/cucumber_runner.rb +9 -6
- data/lib/knapsack_pro/runners/queue/minitest_runner.rb +13 -6
- data/lib/knapsack_pro/runners/queue/rspec_runner.rb +128 -135
- data/lib/knapsack_pro/runners/rspec_runner.rb +22 -3
- data/lib/knapsack_pro/runners/spinach_runner.rb +2 -0
- data/lib/knapsack_pro/runners/test_unit_runner.rb +2 -0
- data/lib/knapsack_pro/slow_test_file_determiner.rb +2 -0
- data/lib/knapsack_pro/slow_test_file_finder.rb +2 -0
- data/lib/knapsack_pro/task_loader.rb +2 -0
- data/lib/knapsack_pro/test_case_detectors/rspec_test_example_detector.rb +2 -0
- data/lib/knapsack_pro/test_case_mergers/base_merger.rb +2 -0
- data/lib/knapsack_pro/test_case_mergers/rspec_merger.rb +2 -0
- data/lib/knapsack_pro/test_file_cleaner.rb +2 -0
- data/lib/knapsack_pro/test_file_finder.rb +2 -0
- data/lib/knapsack_pro/test_file_pattern.rb +2 -0
- data/lib/knapsack_pro/test_file_presenter.rb +2 -0
- data/lib/knapsack_pro/test_files_with_test_cases_composer.rb +2 -0
- data/lib/knapsack_pro/test_flat_distributor.rb +2 -0
- data/lib/knapsack_pro/tracker.rb +3 -3
- data/lib/knapsack_pro/urls.rb +4 -0
- data/lib/knapsack_pro/utils.rb +2 -0
- data/lib/knapsack_pro/version.rb +3 -1
- data/lib/knapsack_pro.rb +5 -3
- data/lib/tasks/cucumber.rake +2 -0
- data/lib/tasks/encrypted_branch_names.rake +2 -0
- data/lib/tasks/encrypted_test_file_names.rake +2 -0
- data/lib/tasks/minitest.rake +2 -0
- data/lib/tasks/queue/cucumber.rake +13 -0
- data/lib/tasks/queue/minitest.rake +13 -0
- data/lib/tasks/queue/rspec.rake +13 -0
- data/lib/tasks/rspec.rake +5 -0
- data/lib/tasks/salt.rake +2 -0
- data/lib/tasks/spinach.rake +2 -0
- data/lib/tasks/test_unit.rake +2 -0
- data/spec/integration/api/build_distributions_subset_spec.rb +1 -0
- data/spec/integration/runners/queue/rspec_runner.rb +80 -0
- data/spec/integration/runners/queue/rspec_runner_spec.rb +2232 -0
- data/spec/knapsack_pro/adapters/base_adapter_spec.rb +30 -11
- data/spec/knapsack_pro/adapters/cucumber_adapter_spec.rb +2 -5
- data/spec/knapsack_pro/adapters/rspec_adapter_spec.rb +146 -174
- data/spec/knapsack_pro/base_allocator_builder_spec.rb +22 -48
- data/spec/knapsack_pro/client/api/v1/build_distributions_spec.rb +19 -27
- data/spec/knapsack_pro/client/api/v1/queues_spec.rb +23 -43
- data/spec/knapsack_pro/client/connection_spec.rb +59 -7
- data/spec/knapsack_pro/config/ci/app_veyor_spec.rb +22 -8
- data/spec/knapsack_pro/config/ci/base_spec.rb +1 -0
- data/spec/knapsack_pro/config/ci/buildkite_spec.rb +51 -16
- data/spec/knapsack_pro/config/ci/circle_spec.rb +48 -13
- data/spec/knapsack_pro/config/ci/cirrus_ci_spec.rb +12 -12
- data/spec/knapsack_pro/config/ci/codefresh_spec.rb +21 -6
- data/spec/knapsack_pro/config/ci/codeship_spec.rb +20 -6
- data/spec/knapsack_pro/config/ci/github_actions_spec.rb +37 -10
- data/spec/knapsack_pro/config/ci/gitlab_ci_spec.rb +48 -13
- data/spec/knapsack_pro/config/ci/heroku_spec.rb +12 -12
- data/spec/knapsack_pro/config/ci/semaphore2_spec.rb +11 -11
- data/spec/knapsack_pro/config/ci/semaphore_spec.rb +12 -12
- data/spec/knapsack_pro/config/ci/travis_spec.rb +8 -8
- data/spec/knapsack_pro/config/env_spec.rb +204 -124
- data/spec/knapsack_pro/formatters/time_tracker_specs.rb +424 -0
- data/spec/knapsack_pro/hooks/queue_spec.rb +2 -2
- data/spec/knapsack_pro/presenter_spec.rb +1 -1
- data/spec/knapsack_pro/pure/queue/rspec_pure_spec.rb +224 -0
- data/spec/knapsack_pro/repository_adapters/git_adapter_spec.rb +72 -0
- data/spec/knapsack_pro/runners/queue/cucumber_runner_spec.rb +18 -16
- data/spec/knapsack_pro/runners/queue/minitest_runner_spec.rb +17 -14
- data/spec/knapsack_pro/runners/rspec_runner_spec.rb +40 -23
- data/spec/knapsack_pro/test_case_detectors/rspec_test_example_detector_spec.rb +1 -0
- data/spec/knapsack_pro/tracker_spec.rb +0 -4
- data/spec/knapsack_pro_spec.rb +3 -3
- data/spec/spec_helper.rb +0 -1
- metadata +26 -23
- data/lib/knapsack_pro/config/ci/snap_ci.rb +0 -35
- data/lib/knapsack_pro/config/ci/solano_ci.rb +0 -32
- data/lib/knapsack_pro/extensions/time.rb +0 -7
- data/lib/knapsack_pro/formatters/rspec_queue_profile_formatter_extension.rb +0 -56
- data/lib/knapsack_pro/formatters/rspec_queue_summary_formatter.rb +0 -112
- data/spec/knapsack_pro/config/ci/snap_ci_spec.rb +0 -104
- data/spec/knapsack_pro/config/ci/solano_ci_spec.rb +0 -73
- data/spec/knapsack_pro/extensions/time_spec.rb +0 -5
- data/spec/knapsack_pro/runners/queue/rspec_runner_spec.rb +0 -342
@@ -1,6 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module KnapsackPro
|
2
4
|
module Config
|
3
5
|
module CI
|
6
|
+
# Semaphore Classic is deprecated
|
7
|
+
# https://semaphoreci.com/blog/semaphore-classic-deprecation
|
4
8
|
class Semaphore < Base
|
5
9
|
def node_total
|
6
10
|
ENV['SEMAPHORE_THREAD_COUNT']
|
@@ -26,6 +30,18 @@ module KnapsackPro
|
|
26
30
|
def project_dir
|
27
31
|
ENV['SEMAPHORE_PROJECT_DIR']
|
28
32
|
end
|
33
|
+
|
34
|
+
def detected
|
35
|
+
ENV.key?('SEMAPHORE_BUILD_NUMBER') ? self.class : nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def fixed_queue_split
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def ci_provider
|
43
|
+
"Semaphore CI 1.0"
|
44
|
+
end
|
29
45
|
end
|
30
46
|
end
|
31
47
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# https://docs.semaphoreci.com/article/12-environment-variables
|
2
4
|
module KnapsackPro
|
3
5
|
module Config
|
@@ -29,6 +31,23 @@ module KnapsackPro
|
|
29
31
|
"#{ENV['HOME']}/#{ENV['SEMAPHORE_GIT_DIR']}"
|
30
32
|
end
|
31
33
|
end
|
34
|
+
|
35
|
+
def user_seat
|
36
|
+
# not provided
|
37
|
+
end
|
38
|
+
|
39
|
+
def detected
|
40
|
+
# check 2 keys to be sure we are using Semaphore 2.0
|
41
|
+
ENV.key?('SEMAPHORE') && ENV.key?('SEMAPHORE_WORKFLOW_ID') ? self.class : nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def fixed_queue_split
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
def ci_provider
|
49
|
+
"Semaphore CI 2.0"
|
50
|
+
end
|
32
51
|
end
|
33
52
|
end
|
34
53
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module KnapsackPro
|
2
4
|
module Config
|
3
5
|
module CI
|
@@ -17,6 +19,22 @@ module KnapsackPro
|
|
17
19
|
def project_dir
|
18
20
|
ENV['TRAVIS_BUILD_DIR']
|
19
21
|
end
|
22
|
+
|
23
|
+
def user_seat
|
24
|
+
# not provided
|
25
|
+
end
|
26
|
+
|
27
|
+
def detected
|
28
|
+
ENV.key?('TRAVIS') ? self.class : nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def fixed_queue_split
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def ci_provider
|
36
|
+
"Travis CI"
|
37
|
+
end
|
20
38
|
end
|
21
39
|
end
|
22
40
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module KnapsackPro
|
2
4
|
module Config
|
3
5
|
class Env
|
@@ -23,9 +25,10 @@ module KnapsackPro
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def ci_node_build_id
|
26
|
-
|
28
|
+
env_name = 'KNAPSACK_PRO_CI_NODE_BUILD_ID'
|
29
|
+
ENV[env_name] ||
|
27
30
|
ci_env_for(:node_build_id) ||
|
28
|
-
|
31
|
+
raise("Missing environment variable #{env_name}. Read more at #{KnapsackPro::Urls::KNAPSACK_PRO_CI_NODE_BUILD_ID}")
|
29
32
|
end
|
30
33
|
|
31
34
|
def ci_node_retry_count
|
@@ -58,6 +61,17 @@ module KnapsackPro
|
|
58
61
|
ci_env_for(:project_dir)
|
59
62
|
end
|
60
63
|
|
64
|
+
def user_seat
|
65
|
+
ENV['KNAPSACK_PRO_USER_SEAT'] ||
|
66
|
+
ci_env_for(:user_seat)
|
67
|
+
end
|
68
|
+
|
69
|
+
def masked_user_seat
|
70
|
+
return unless user_seat
|
71
|
+
|
72
|
+
KnapsackPro::MaskString.call(user_seat)
|
73
|
+
end
|
74
|
+
|
61
75
|
def test_file_pattern
|
62
76
|
ENV['KNAPSACK_PRO_TEST_FILE_PATTERN']
|
63
77
|
end
|
@@ -130,14 +144,6 @@ module KnapsackPro
|
|
130
144
|
test_files_encrypted == 'true'
|
131
145
|
end
|
132
146
|
|
133
|
-
def modify_default_rspec_formatters
|
134
|
-
ENV.fetch('KNAPSACK_PRO_MODIFY_DEFAULT_RSPEC_FORMATTERS', true)
|
135
|
-
end
|
136
|
-
|
137
|
-
def modify_default_rspec_formatters?
|
138
|
-
modify_default_rspec_formatters.to_s == 'true'
|
139
|
-
end
|
140
|
-
|
141
147
|
def branch_encrypted
|
142
148
|
ENV['KNAPSACK_PRO_BRANCH_ENCRYPTED']
|
143
149
|
end
|
@@ -175,7 +181,16 @@ module KnapsackPro
|
|
175
181
|
end
|
176
182
|
|
177
183
|
def fixed_queue_split
|
178
|
-
|
184
|
+
@fixed_queue_split ||= begin
|
185
|
+
env_name = 'KNAPSACK_PRO_FIXED_QUEUE_SPLIT'
|
186
|
+
computed = ENV.fetch(env_name, ci_env_for(:fixed_queue_split)).to_s
|
187
|
+
|
188
|
+
if !ENV.key?(env_name)
|
189
|
+
KnapsackPro.logger.info("#{env_name} is not set. Using default value: #{computed}. Learn more at #{KnapsackPro::Urls::FIXED_QUEUE_SPLIT}")
|
190
|
+
end
|
191
|
+
|
192
|
+
computed
|
193
|
+
end
|
179
194
|
end
|
180
195
|
|
181
196
|
def fixed_queue_split?
|
@@ -235,21 +250,25 @@ module KnapsackPro
|
|
235
250
|
end
|
236
251
|
|
237
252
|
def ci_env_for(env_name)
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
ci = ci_class.new
|
245
|
-
value = ci.send(env_name)
|
246
|
-
break unless value.nil?
|
253
|
+
detected_ci.new.send(env_name)
|
254
|
+
end
|
255
|
+
|
256
|
+
def detected_ci
|
257
|
+
detected = KnapsackPro::Config::CI.constants.map do |constant|
|
258
|
+
Object.const_get("KnapsackPro::Config::CI::#{constant}").new.detected
|
247
259
|
end
|
248
|
-
|
260
|
+
.compact
|
261
|
+
.first
|
262
|
+
|
263
|
+
detected || KnapsackPro::Config::CI::Base
|
264
|
+
end
|
265
|
+
|
266
|
+
def ci_provider
|
267
|
+
detected_ci.new.ci_provider
|
249
268
|
end
|
250
269
|
|
251
270
|
def log_level
|
252
|
-
LOG_LEVELS[ENV['KNAPSACK_PRO_LOG_LEVEL'].to_s.downcase] || ::Logger::
|
271
|
+
LOG_LEVELS[ENV['KNAPSACK_PRO_LOG_LEVEL'].to_s.downcase] || ::Logger::INFO
|
253
272
|
end
|
254
273
|
|
255
274
|
def log_dir
|
@@ -264,6 +283,11 @@ module KnapsackPro
|
|
264
283
|
ENV['KNAPSACK_PRO_TEST_RUNNER_ADAPTER'] = adapter_class.to_s.split('::').last
|
265
284
|
end
|
266
285
|
|
286
|
+
def ci?
|
287
|
+
ENV.fetch('CI', 'false').downcase == 'true' ||
|
288
|
+
detected_ci != KnapsackPro::Config::CI::Base
|
289
|
+
end
|
290
|
+
|
267
291
|
private
|
268
292
|
|
269
293
|
def required_env(env_name)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module KnapsackPro
|
2
4
|
module Config
|
3
5
|
class TempFiles
|
@@ -22,10 +24,12 @@ module KnapsackPro
|
|
22
24
|
end
|
23
25
|
|
24
26
|
def self.gitignore_file_content
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
<<~GITIGNORE
|
28
|
+
# This directory is used by knapsack_pro gem for storing temporary files during tests runtime.
|
29
|
+
# Ignore all files, and do not commit this directory into your repository.
|
30
|
+
# Learn more at https://knapsackpro.com
|
31
|
+
*
|
32
|
+
GITIGNORE
|
29
33
|
end
|
30
34
|
|
31
35
|
def self.create_gitignore_file!
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KnapsackPro
|
4
|
+
module Extensions
|
5
|
+
# Facade to abstract calls to internal RSpec methods.
|
6
|
+
# To allow comparing the monkey patch with the original RSpec code, keep a similar method structure and permalink to the source.
|
7
|
+
module RSpecExtension
|
8
|
+
Seed = Struct.new(:value, :used?)
|
9
|
+
|
10
|
+
def self.setup!
|
11
|
+
RSpec::Core::World.prepend(World)
|
12
|
+
RSpec::Core::Runner.prepend(Runner)
|
13
|
+
RSpec::Core::Configuration.prepend(Configuration)
|
14
|
+
end
|
15
|
+
|
16
|
+
module World
|
17
|
+
# Based on:
|
18
|
+
# https://github.com/rspec/rspec-core/blob/f8c8880dabd8f0544a6f91d8d4c857c1bd8df903/lib/rspec/core/world.rb#L171
|
19
|
+
#
|
20
|
+
# Filters are not announced because we do not load tests during setup. It would print `No examples found.` and we don't want that.
|
21
|
+
def knapsack__announce_filters
|
22
|
+
fail_if_config_and_cli_options_invalid
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Runner
|
27
|
+
# Based on:
|
28
|
+
# https://github.com/rspec/rspec-core/blob/f8c8880dabd8f0544a6f91d8d4c857c1bd8df903/lib/rspec/core/runner.rb#L98
|
29
|
+
#
|
30
|
+
# `@configuration.load_spec_files` is not called because we load tests in batches with `knapsack__load_spec_files_batch` later on.
|
31
|
+
def knapsack__setup(stream_error = $stderr, stream_out = $stdout)
|
32
|
+
configure(stream_error, stream_out)
|
33
|
+
ensure
|
34
|
+
world.knapsack__announce_filters
|
35
|
+
end
|
36
|
+
|
37
|
+
def knapsack__wants_to_quit?
|
38
|
+
world.wants_to_quit
|
39
|
+
end
|
40
|
+
|
41
|
+
def knapsack__rspec_is_quitting?
|
42
|
+
world.respond_to?(:rspec_is_quitting) && world.rspec_is_quitting
|
43
|
+
end
|
44
|
+
|
45
|
+
def knapsack__exit_early
|
46
|
+
_exit_status = configuration.reporter.exit_early(exit_code)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Based on:
|
50
|
+
# https://github.com/rspec/rspec-core/blob/f8c8880dabd8f0544a6f91d8d4c857c1bd8df903/lib/rspec/core/configuration.rb#L546
|
51
|
+
def knapsack__error_exit_code
|
52
|
+
configuration.error_exit_code # nil unless `--error-exit-code` is specified
|
53
|
+
end
|
54
|
+
|
55
|
+
# must be called after `Runner#knapsack__setup` that loads the `spec_helper.rb` configuration
|
56
|
+
def knapsack__deprecated_run_all_when_everything_filtered_enabled?
|
57
|
+
configuration.respond_to?(:run_all_when_everything_filtered) && !!configuration.run_all_when_everything_filtered
|
58
|
+
end
|
59
|
+
|
60
|
+
def knapsack__seed
|
61
|
+
Seed.new(configuration.seed.to_s, configuration.seed_used?)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param test_file_paths Array[String]
|
65
|
+
# Example: ['a_spec.rb', 'b_spec.rb[1:1]']
|
66
|
+
def knapsack__load_spec_files_batch(test_file_paths)
|
67
|
+
world.reset
|
68
|
+
|
69
|
+
# Reset filters, but do not reset `configuration.static_config_filter_manager` to preserve the --tag option
|
70
|
+
filter_manager = RSpec::Core::FilterManager.new
|
71
|
+
options.configure_filter_manager(filter_manager)
|
72
|
+
configuration.filter_manager = filter_manager
|
73
|
+
|
74
|
+
configuration.knapsack__load_spec_files(test_file_paths)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Based on:
|
78
|
+
# https://github.com/rspec/rspec-core/blob/f8c8880dabd8f0544a6f91d8d4c857c1bd8df903/lib/rspec/core/runner.rb#L113
|
79
|
+
#
|
80
|
+
# Ignore `configuration.fail_if_no_examples` in Queue Mode:
|
81
|
+
# * a late CI node, started after all tests were executed by other nodes, is expected to receive an empty batch
|
82
|
+
# * a batch could contain tests with no examples (e.g. commented out)
|
83
|
+
#
|
84
|
+
# @return [Fixnum] exit status code.
|
85
|
+
def knapsack__run_specs(queue_runner)
|
86
|
+
# Based on:
|
87
|
+
# https://github.com/rspec/rspec-core/blob/f8c8880dabd8f0544a6f91d8d4c857c1bd8df903/lib/rspec/core/world.rb#L53
|
88
|
+
ordering_strategy = configuration.ordering_registry.fetch(:global)
|
89
|
+
node_examples_passed = true
|
90
|
+
|
91
|
+
configuration.reporter.report(_expected_example_count = 0) do |reporter|
|
92
|
+
configuration.with_suite_hooks do
|
93
|
+
queue_runner.with_batch do |test_file_paths|
|
94
|
+
knapsack__load_spec_files_batch(test_file_paths)
|
95
|
+
|
96
|
+
examples_passed = ordering_strategy.order(world.example_groups).map do |example_group|
|
97
|
+
queue_runner.handle_signal!
|
98
|
+
example_group.run(reporter)
|
99
|
+
end.all?
|
100
|
+
|
101
|
+
node_examples_passed = false unless examples_passed
|
102
|
+
|
103
|
+
knapsack__persist_example_statuses
|
104
|
+
|
105
|
+
if reporter.fail_fast_limit_met?
|
106
|
+
queue_runner.log_fail_fast_limit_met
|
107
|
+
break
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
exit_code(node_examples_passed)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Based on:
|
117
|
+
# https://github.com/rspec/rspec-core/blob/f8c8880dabd8f0544a6f91d8d4c857c1bd8df903/lib/rspec/core/runner.rb#L90
|
118
|
+
def knapsack__persist_example_statuses
|
119
|
+
persist_example_statuses
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
module Configuration
|
124
|
+
# Based on:
|
125
|
+
# https://github.com/rspec/rspec-core/blob/f8c8880dabd8f0544a6f91d8d4c857c1bd8df903/lib/rspec/core/configuration.rb#L1619
|
126
|
+
def knapsack__load_spec_files(test_file_paths)
|
127
|
+
batch_of_files_to_run = get_files_to_run(test_file_paths)
|
128
|
+
batch_of_files_to_run.each do |f|
|
129
|
+
file = File.expand_path(f)
|
130
|
+
load_file_handling_errors(:load, file)
|
131
|
+
loaded_spec_files << file
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module KnapsackPro
|
6
|
+
module Formatters
|
7
|
+
class TimeTracker
|
8
|
+
::RSpec::Core::Formatters.register self,
|
9
|
+
:example_group_started,
|
10
|
+
:example_started,
|
11
|
+
:example_finished,
|
12
|
+
:example_group_finished
|
13
|
+
|
14
|
+
attr_reader :output # RSpec < v3.10.2
|
15
|
+
|
16
|
+
def initialize(_output)
|
17
|
+
@output = StringIO.new
|
18
|
+
@time_each = nil
|
19
|
+
@time_all = nil
|
20
|
+
@before_all = 0.0
|
21
|
+
@group = {}
|
22
|
+
@paths = {}
|
23
|
+
@suite_started = now
|
24
|
+
end
|
25
|
+
|
26
|
+
def example_group_started(notification)
|
27
|
+
return unless top_level_group?(notification.group)
|
28
|
+
@time_all = now
|
29
|
+
end
|
30
|
+
|
31
|
+
def example_started(_notification)
|
32
|
+
@before_all = now - @time_all if @before_all == 0.0
|
33
|
+
@time_each = now
|
34
|
+
end
|
35
|
+
|
36
|
+
def example_finished(notification)
|
37
|
+
record_example(@group, notification.example, @time_each)
|
38
|
+
@time_all = now
|
39
|
+
end
|
40
|
+
|
41
|
+
def example_group_finished(notification)
|
42
|
+
return unless top_level_group?(notification.group)
|
43
|
+
|
44
|
+
after_all = @time_all.nil? ? 0.0 : now - @time_all
|
45
|
+
add_hooks_time(@group, @before_all, after_all)
|
46
|
+
@before_all = 0.0
|
47
|
+
@paths = merge(@paths, @group)
|
48
|
+
@group = {}
|
49
|
+
end
|
50
|
+
|
51
|
+
def queue(scheduled_paths)
|
52
|
+
recorded_paths = @paths.values.map do |example|
|
53
|
+
KnapsackPro::Adapters::RSpecAdapter.parse_file_path(example[:path])
|
54
|
+
end
|
55
|
+
|
56
|
+
missing = (scheduled_paths - recorded_paths).each_with_object({}) do |path, object|
|
57
|
+
object[path] = { path: path, time_execution: 0.0 }
|
58
|
+
end
|
59
|
+
|
60
|
+
merge(@paths, missing).values.map do |example|
|
61
|
+
example.transform_keys(&:to_s)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def batch
|
66
|
+
@paths.values.map do |example|
|
67
|
+
example.transform_keys(&:to_s)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def duration
|
72
|
+
now - @suite_started
|
73
|
+
end
|
74
|
+
|
75
|
+
def unexecuted_test_files(scheduled_paths)
|
76
|
+
pending_paths = @paths.values
|
77
|
+
.filter { |example| example[:time_execution] == 0.0 }
|
78
|
+
.map { |example| example[:path] }
|
79
|
+
|
80
|
+
not_run_paths = scheduled_paths -
|
81
|
+
@paths.values
|
82
|
+
.map { |example| example[:path] }
|
83
|
+
|
84
|
+
pending_paths + not_run_paths
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def top_level_group?(group)
|
90
|
+
group.metadata[:parent_example_group].nil?
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_hooks_time(group, before_all, after_all)
|
94
|
+
group.each do |_, example|
|
95
|
+
next if example[:time_execution] == 0.0
|
96
|
+
example[:time_execution] += before_all + after_all
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def record_example(accumulator, example, started_at)
|
101
|
+
path = path_for(example)
|
102
|
+
return if path.nil?
|
103
|
+
|
104
|
+
time_execution = time_execution_for(example, started_at)
|
105
|
+
if accumulator.key?(path)
|
106
|
+
accumulator[path][:time_execution] += time_execution
|
107
|
+
else
|
108
|
+
accumulator[path] = { path: path, time_execution: time_execution }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def path_for(example)
|
113
|
+
file = file_path_for(example)
|
114
|
+
return nil if file == ""
|
115
|
+
|
116
|
+
path = rspec_split_by_test_example?(file) ? example.id : file
|
117
|
+
KnapsackPro::TestFileCleaner.clean(path)
|
118
|
+
end
|
119
|
+
|
120
|
+
def rspec_split_by_test_example?(file)
|
121
|
+
return false unless KnapsackPro::Config::Env.rspec_split_by_test_examples?
|
122
|
+
return false unless KnapsackPro::Adapters::RSpecAdapter.slow_test_file?(KnapsackPro::Adapters::RSpecAdapter, file)
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
126
|
+
def file_path_for(example)
|
127
|
+
KnapsackPro::Adapters::RSpecAdapter.file_path_for(example)
|
128
|
+
end
|
129
|
+
|
130
|
+
def time_execution_for(example, started_at)
|
131
|
+
if example.execution_result.status.to_s == "pending"
|
132
|
+
0.0
|
133
|
+
else
|
134
|
+
(now - started_at).to_f
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def merge(h1, h2)
|
139
|
+
h1.merge(h2) do |key, v1, v2|
|
140
|
+
{
|
141
|
+
path: key,
|
142
|
+
time_execution: v1[:time_execution] + v2[:time_execution]
|
143
|
+
}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def now
|
148
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module KnapsackPro
|
4
|
+
module Formatters
|
5
|
+
class TimeTrackerFetcher
|
6
|
+
def self.call
|
7
|
+
::RSpec
|
8
|
+
.configuration
|
9
|
+
.formatters
|
10
|
+
.find { |f| f.class.to_s == "KnapsackPro::Formatters::TimeTracker" }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.unexecuted_test_files(scheduled_paths)
|
14
|
+
time_tracker = call
|
15
|
+
return [] unless time_tracker
|
16
|
+
time_tracker.unexecuted_test_files(scheduled_paths)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,9 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module KnapsackPro
|
2
4
|
class Presenter
|
3
5
|
class << self
|
4
|
-
def global_time
|
5
|
-
|
6
|
-
|
6
|
+
def global_time(time = nil)
|
7
|
+
time = KnapsackPro.tracker.global_time if time.nil?
|
8
|
+
global_time = pretty_seconds(time)
|
9
|
+
"Global test execution duration: #{global_time}"
|
7
10
|
end
|
8
11
|
|
9
12
|
def pretty_seconds(seconds)
|