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.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +377 -23
  3. data/.github/dependabot.yml +11 -0
  4. data/.github/pull_request_template.md +22 -0
  5. data/.gitignore +4 -0
  6. data/CHANGELOG.md +325 -1
  7. data/Gemfile +9 -0
  8. data/README.md +3 -10
  9. data/bin/test +15 -0
  10. data/knapsack_pro.gemspec +7 -6
  11. data/lib/knapsack_pro/adapters/base_adapter.rb +17 -2
  12. data/lib/knapsack_pro/adapters/cucumber_adapter.rb +3 -3
  13. data/lib/knapsack_pro/adapters/minitest_adapter.rb +2 -0
  14. data/lib/knapsack_pro/adapters/rspec_adapter.rb +88 -49
  15. data/lib/knapsack_pro/adapters/spinach_adapter.rb +2 -0
  16. data/lib/knapsack_pro/adapters/test_unit_adapter.rb +2 -0
  17. data/lib/knapsack_pro/allocator.rb +2 -0
  18. data/lib/knapsack_pro/allocator_builder.rb +2 -0
  19. data/lib/knapsack_pro/base_allocator_builder.rb +8 -25
  20. data/lib/knapsack_pro/build_distribution_fetcher.rb +2 -0
  21. data/lib/knapsack_pro/client/api/action.rb +2 -0
  22. data/lib/knapsack_pro/client/api/v1/base.rb +2 -0
  23. data/lib/knapsack_pro/client/api/v1/build_distributions.rb +5 -0
  24. data/lib/knapsack_pro/client/api/v1/build_subsets.rb +2 -0
  25. data/lib/knapsack_pro/client/api/v1/queues.rb +6 -1
  26. data/lib/knapsack_pro/client/connection.rb +5 -6
  27. data/lib/knapsack_pro/config/ci/app_veyor.rb +18 -0
  28. data/lib/knapsack_pro/config/ci/base.rb +27 -0
  29. data/lib/knapsack_pro/config/ci/buildkite.rb +18 -0
  30. data/lib/knapsack_pro/config/ci/circle.rb +18 -0
  31. data/lib/knapsack_pro/config/ci/cirrus_ci.rb +18 -0
  32. data/lib/knapsack_pro/config/ci/codefresh.rb +18 -0
  33. data/lib/knapsack_pro/config/ci/codeship.rb +18 -0
  34. data/lib/knapsack_pro/config/ci/github_actions.rb +26 -0
  35. data/lib/knapsack_pro/config/ci/gitlab_ci.rb +20 -1
  36. data/lib/knapsack_pro/config/ci/heroku.rb +18 -0
  37. data/lib/knapsack_pro/config/ci/semaphore.rb +16 -0
  38. data/lib/knapsack_pro/config/ci/semaphore2.rb +19 -0
  39. data/lib/knapsack_pro/config/ci/travis.rb +18 -0
  40. data/lib/knapsack_pro/config/env.rb +46 -22
  41. data/lib/knapsack_pro/config/env_generator.rb +2 -0
  42. data/lib/knapsack_pro/config/temp_files.rb +8 -4
  43. data/lib/knapsack_pro/crypto/branch_encryptor.rb +2 -0
  44. data/lib/knapsack_pro/crypto/decryptor.rb +2 -0
  45. data/lib/knapsack_pro/crypto/digestor.rb +2 -0
  46. data/lib/knapsack_pro/crypto/encryptor.rb +2 -0
  47. data/lib/knapsack_pro/extensions/rspec_extension.rb +137 -0
  48. data/lib/knapsack_pro/formatters/rspec_json_formatter.rb +2 -0
  49. data/lib/knapsack_pro/formatters/time_tracker.rb +152 -0
  50. data/lib/knapsack_pro/formatters/time_tracker_fetcher.rb +20 -0
  51. data/lib/knapsack_pro/hooks/queue.rb +2 -0
  52. data/lib/knapsack_pro/logger_wrapper.rb +2 -0
  53. data/lib/knapsack_pro/mask_string.rb +9 -0
  54. data/lib/knapsack_pro/presenter.rb +6 -3
  55. data/lib/knapsack_pro/pure/queue/rspec_pure.rb +92 -0
  56. data/lib/knapsack_pro/queue_allocator.rb +2 -0
  57. data/lib/knapsack_pro/queue_allocator_builder.rb +2 -0
  58. data/lib/knapsack_pro/railtie.rb +2 -0
  59. data/lib/knapsack_pro/report.rb +15 -9
  60. data/lib/knapsack_pro/repository_adapter_initiator.rb +2 -0
  61. data/lib/knapsack_pro/repository_adapters/base_adapter.rb +2 -0
  62. data/lib/knapsack_pro/repository_adapters/env_adapter.rb +2 -0
  63. data/lib/knapsack_pro/repository_adapters/git_adapter.rb +50 -0
  64. data/lib/knapsack_pro/runners/base_runner.rb +2 -0
  65. data/lib/knapsack_pro/runners/cucumber_runner.rb +2 -0
  66. data/lib/knapsack_pro/runners/minitest_runner.rb +2 -0
  67. data/lib/knapsack_pro/runners/queue/base_runner.rb +29 -0
  68. data/lib/knapsack_pro/runners/queue/cucumber_runner.rb +9 -6
  69. data/lib/knapsack_pro/runners/queue/minitest_runner.rb +13 -6
  70. data/lib/knapsack_pro/runners/queue/rspec_runner.rb +128 -135
  71. data/lib/knapsack_pro/runners/rspec_runner.rb +22 -3
  72. data/lib/knapsack_pro/runners/spinach_runner.rb +2 -0
  73. data/lib/knapsack_pro/runners/test_unit_runner.rb +2 -0
  74. data/lib/knapsack_pro/slow_test_file_determiner.rb +2 -0
  75. data/lib/knapsack_pro/slow_test_file_finder.rb +2 -0
  76. data/lib/knapsack_pro/task_loader.rb +2 -0
  77. data/lib/knapsack_pro/test_case_detectors/rspec_test_example_detector.rb +2 -0
  78. data/lib/knapsack_pro/test_case_mergers/base_merger.rb +2 -0
  79. data/lib/knapsack_pro/test_case_mergers/rspec_merger.rb +2 -0
  80. data/lib/knapsack_pro/test_file_cleaner.rb +2 -0
  81. data/lib/knapsack_pro/test_file_finder.rb +2 -0
  82. data/lib/knapsack_pro/test_file_pattern.rb +2 -0
  83. data/lib/knapsack_pro/test_file_presenter.rb +2 -0
  84. data/lib/knapsack_pro/test_files_with_test_cases_composer.rb +2 -0
  85. data/lib/knapsack_pro/test_flat_distributor.rb +2 -0
  86. data/lib/knapsack_pro/tracker.rb +3 -3
  87. data/lib/knapsack_pro/urls.rb +4 -0
  88. data/lib/knapsack_pro/utils.rb +2 -0
  89. data/lib/knapsack_pro/version.rb +3 -1
  90. data/lib/knapsack_pro.rb +5 -3
  91. data/lib/tasks/cucumber.rake +2 -0
  92. data/lib/tasks/encrypted_branch_names.rake +2 -0
  93. data/lib/tasks/encrypted_test_file_names.rake +2 -0
  94. data/lib/tasks/minitest.rake +2 -0
  95. data/lib/tasks/queue/cucumber.rake +13 -0
  96. data/lib/tasks/queue/minitest.rake +13 -0
  97. data/lib/tasks/queue/rspec.rake +13 -0
  98. data/lib/tasks/rspec.rake +5 -0
  99. data/lib/tasks/salt.rake +2 -0
  100. data/lib/tasks/spinach.rake +2 -0
  101. data/lib/tasks/test_unit.rake +2 -0
  102. data/spec/integration/api/build_distributions_subset_spec.rb +1 -0
  103. data/spec/integration/runners/queue/rspec_runner.rb +80 -0
  104. data/spec/integration/runners/queue/rspec_runner_spec.rb +2232 -0
  105. data/spec/knapsack_pro/adapters/base_adapter_spec.rb +30 -11
  106. data/spec/knapsack_pro/adapters/cucumber_adapter_spec.rb +2 -5
  107. data/spec/knapsack_pro/adapters/rspec_adapter_spec.rb +146 -174
  108. data/spec/knapsack_pro/base_allocator_builder_spec.rb +22 -48
  109. data/spec/knapsack_pro/client/api/v1/build_distributions_spec.rb +19 -27
  110. data/spec/knapsack_pro/client/api/v1/queues_spec.rb +23 -43
  111. data/spec/knapsack_pro/client/connection_spec.rb +59 -7
  112. data/spec/knapsack_pro/config/ci/app_veyor_spec.rb +22 -8
  113. data/spec/knapsack_pro/config/ci/base_spec.rb +1 -0
  114. data/spec/knapsack_pro/config/ci/buildkite_spec.rb +51 -16
  115. data/spec/knapsack_pro/config/ci/circle_spec.rb +48 -13
  116. data/spec/knapsack_pro/config/ci/cirrus_ci_spec.rb +12 -12
  117. data/spec/knapsack_pro/config/ci/codefresh_spec.rb +21 -6
  118. data/spec/knapsack_pro/config/ci/codeship_spec.rb +20 -6
  119. data/spec/knapsack_pro/config/ci/github_actions_spec.rb +37 -10
  120. data/spec/knapsack_pro/config/ci/gitlab_ci_spec.rb +48 -13
  121. data/spec/knapsack_pro/config/ci/heroku_spec.rb +12 -12
  122. data/spec/knapsack_pro/config/ci/semaphore2_spec.rb +11 -11
  123. data/spec/knapsack_pro/config/ci/semaphore_spec.rb +12 -12
  124. data/spec/knapsack_pro/config/ci/travis_spec.rb +8 -8
  125. data/spec/knapsack_pro/config/env_spec.rb +204 -124
  126. data/spec/knapsack_pro/formatters/time_tracker_specs.rb +424 -0
  127. data/spec/knapsack_pro/hooks/queue_spec.rb +2 -2
  128. data/spec/knapsack_pro/presenter_spec.rb +1 -1
  129. data/spec/knapsack_pro/pure/queue/rspec_pure_spec.rb +224 -0
  130. data/spec/knapsack_pro/repository_adapters/git_adapter_spec.rb +72 -0
  131. data/spec/knapsack_pro/runners/queue/cucumber_runner_spec.rb +18 -16
  132. data/spec/knapsack_pro/runners/queue/minitest_runner_spec.rb +17 -14
  133. data/spec/knapsack_pro/runners/rspec_runner_spec.rb +40 -23
  134. data/spec/knapsack_pro/test_case_detectors/rspec_test_example_detector_spec.rb +1 -0
  135. data/spec/knapsack_pro/tracker_spec.rb +0 -4
  136. data/spec/knapsack_pro_spec.rb +3 -3
  137. data/spec/spec_helper.rb +0 -1
  138. metadata +26 -23
  139. data/lib/knapsack_pro/config/ci/snap_ci.rb +0 -35
  140. data/lib/knapsack_pro/config/ci/solano_ci.rb +0 -32
  141. data/lib/knapsack_pro/extensions/time.rb +0 -7
  142. data/lib/knapsack_pro/formatters/rspec_queue_profile_formatter_extension.rb +0 -56
  143. data/lib/knapsack_pro/formatters/rspec_queue_summary_formatter.rb +0 -112
  144. data/spec/knapsack_pro/config/ci/snap_ci_spec.rb +0 -104
  145. data/spec/knapsack_pro/config/ci/solano_ci_spec.rb +0 -73
  146. data/spec/knapsack_pro/extensions/time_spec.rb +0 -5
  147. 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
- ENV['KNAPSACK_PRO_CI_NODE_BUILD_ID'] ||
28
+ env_name = 'KNAPSACK_PRO_CI_NODE_BUILD_ID'
29
+ ENV[env_name] ||
27
30
  ci_env_for(:node_build_id) ||
28
- 'missing-build-id'
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
- ENV.fetch('KNAPSACK_PRO_FIXED_QUEUE_SPLIT', false)
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
- value = nil
239
- ci_list = KnapsackPro::Config::CI.constants - [:Base, :GitlabCI]
240
- # load GitLab CI first to avoid edge case with order of loading envs for CI_NODE_INDEX
241
- ci_list = [:GitlabCI] + ci_list
242
- ci_list.each do |ci_name|
243
- ci_class = Object.const_get("KnapsackPro::Config::CI::#{ci_name}")
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
- value
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::DEBUG
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 EnvGenerator
@@ -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
- "# This directory is used by knapsack_pro gem for storing temporary files during tests runtime.\n" <<
26
- "# Ignore all files, and do not commit this directory into your repository.\n" <<
27
- "# Learn more at https://knapsackpro.com\n" <<
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!
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module KnapsackPro
2
4
  module Crypto
3
5
  class BranchEncryptor
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module KnapsackPro
2
4
  module Crypto
3
5
  class Decryptor
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module KnapsackPro
2
4
  module Crypto
3
5
  class Digestor
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module KnapsackPro
2
4
  module Crypto
3
5
  class Encryptor
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec::Support.require_rspec_core('formatters/json_formatter')
2
4
 
3
5
  # based on https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/formatters/json_formatter.rb
@@ -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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module KnapsackPro
2
4
  module Hooks
3
5
  class Queue
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module KnapsackPro
2
4
  class LoggerWrapper
3
5
  def initialize(logger)
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KnapsackPro
4
+ class MaskString
5
+ def self.call(string)
6
+ string.gsub(/(?<=\w{2})[a-zA-Z]/, "*")
7
+ end
8
+ end
9
+ 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
- global_time = pretty_seconds(KnapsackPro.tracker.global_time)
6
- "Global time execution for tests: #{global_time}"
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)