knapsack_pro 7.14.0 → 7.14.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +2 -0
  3. data/CHANGELOG.md +8 -0
  4. data/lib/knapsack_pro/adapters/base_adapter.rb +2 -2
  5. data/lib/knapsack_pro/adapters/rspec_adapter.rb +23 -4
  6. data/lib/knapsack_pro/config/env.rb +3 -15
  7. data/lib/knapsack_pro/formatters/time_tracker.rb +27 -13
  8. data/lib/knapsack_pro/formatters/time_tracker_fetcher.rb +2 -2
  9. data/lib/knapsack_pro/runners/cucumber_runner.rb +1 -1
  10. data/lib/knapsack_pro/runners/minitest_runner.rb +1 -1
  11. data/lib/knapsack_pro/runners/queue/cucumber_runner.rb +1 -1
  12. data/lib/knapsack_pro/runners/queue/minitest_runner.rb +1 -1
  13. data/lib/knapsack_pro/runners/queue/rspec_runner.rb +7 -3
  14. data/lib/knapsack_pro/runners/rspec_runner.rb +1 -1
  15. data/lib/knapsack_pro/runners/spinach_runner.rb +1 -1
  16. data/lib/knapsack_pro/runners/test_unit_runner.rb +1 -1
  17. data/lib/knapsack_pro/test_case_mergers/rspec_merger.rb +1 -7
  18. data/lib/knapsack_pro/version.rb +1 -1
  19. data/spec/integration/runners/queue/rspec_runner_spec.rb +104 -6
  20. data/spec/knapsack_pro/adapters/base_adapter_spec.rb +9 -9
  21. data/spec/knapsack_pro/adapters/rspec_adapter_spec.rb +42 -0
  22. data/spec/knapsack_pro/config/env_spec.rb +25 -68
  23. data/spec/knapsack_pro/formatters/time_tracker_fetcher_spec.rb +2 -5
  24. data/spec/knapsack_pro/formatters/time_tracker_specs.rb +4 -2
  25. data/spec/knapsack_pro/runners/cucumber_runner_spec.rb +1 -1
  26. data/spec/knapsack_pro/runners/minitest_runner_spec.rb +1 -1
  27. data/spec/knapsack_pro/runners/queue/cucumber_runner_spec.rb +1 -1
  28. data/spec/knapsack_pro/runners/queue/minitest_runner_spec.rb +1 -1
  29. data/spec/knapsack_pro/runners/rspec_runner_spec.rb +1 -1
  30. data/spec/knapsack_pro/runners/spinach_runner_spec.rb +1 -1
  31. data/spec/knapsack_pro/runners/test_unit_runner_spec.rb +1 -1
  32. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 113e9ca31d058ab9623d6a0bb278768ad0a864db8bb8776440d00ceb2a648b37
4
- data.tar.gz: '084be6d88bf072c8ece112b3de2a6707b240816e993f039701cd68e7920f7988'
3
+ metadata.gz: 74a234f9f6948aef8fb2f4c5aff8b2f56077c4cf227b78e76957c470681dc8e7
4
+ data.tar.gz: 980e6ebff8f7d49dfa4b1e7296f8d507600a052e49845e0ffde27ffa68d57272
5
5
  SHA512:
6
- metadata.gz: 97015f7b3db931f3a9f68e7ef59aecb99f0befa5202b148d5aa56736d256048c842c00babc265c47c583534f2a0dafc488c49b748b4b3084f93a2d42f6013d84
7
- data.tar.gz: 62fd8be74b14f402840ea4b9b6866a9ac008daa09d279e2cee21bd3f513d8f746f635c770c83e65db36139aa6e9ebb362301794e6aad70a9d59796dd86167d90
6
+ metadata.gz: '09abbaa277b6593990984ab0a0052b8ca7955366e3460971adb52656e5fb8298e5040af857cd14d87eb3d514127f8dbf388c7470880c86d129a409f30d55f2eb'
7
+ data.tar.gz: eef211470c7a006428b346eff52063e491461134ee7c82d02b9ebe6cd829ae2b1091e0432d8c0037f0aaafdc624f7b7707fc8e3b88dd5720721ceb6840514730
data/.circleci/config.yml CHANGED
@@ -62,6 +62,8 @@ jobs:
62
62
  - setup_knapsack_pro_ruby
63
63
  - run: gem install rubocop
64
64
  - run: rubocop --fail-level A --only Style/FrozenStringLiteralComment,Layout/EmptyLineAfterMagicComment lib/
65
+ - run: bundle update rspec
66
+ - run: bundle exec rspec --version
65
67
  - run: bundle exec rspec spec
66
68
  - run: bundle exec ruby spec/knapsack_pro/formatters/time_tracker_specs.rb
67
69
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ### 7.14.1
4
+
5
+ * Improve execution time tracking for RSpec individual test examples
6
+
7
+ https://github.com/KnapsackPro/knapsack_pro-ruby/pull/289
8
+
9
+ https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v7.14.0...v7.14.1
10
+
3
11
  ### 7.14.0
4
12
 
5
13
  * Improve debugging for hanging CI nodes: show hanging spec files in the RSpec output and a command to reproduce the current batch of tests.
@@ -59,13 +59,13 @@ module KnapsackPro
59
59
  KnapsackPro::Config::TempFiles.ensure_temp_directory_exists!
60
60
  File.write(self.class.adapter_bind_method_called_file, nil)
61
61
 
62
- if KnapsackPro::Config::Env.recording_enabled?
62
+ if KnapsackPro::Config::Env.regular_mode?
63
63
  KnapsackPro.logger.debug('Regular Mode enabled.')
64
64
  bind_time_tracker
65
65
  bind_save_report
66
66
  end
67
67
 
68
- if KnapsackPro::Config::Env.queue_recording_enabled?
68
+ if KnapsackPro::Config::Env.queue_mode?
69
69
  KnapsackPro.logger.debug('Queue Mode enabled.')
70
70
  bind_queue_mode
71
71
  end
@@ -6,6 +6,7 @@ module KnapsackPro
6
6
  module Adapters
7
7
  class RSpecAdapter < BaseAdapter
8
8
  TEST_DIR_PATTERN = 'spec/**{,/*/**}/*_spec.rb'
9
+ ID_PATH_REGEX = /.+_spec\.rb\[.+\]$/
9
10
 
10
11
  def self.split_by_test_cases_enabled?
11
12
  return false unless KnapsackPro::Config::Env.rspec_split_by_test_examples?
@@ -80,6 +81,10 @@ module KnapsackPro
80
81
  id.match(/\A(.*?)(?:\[([\d\s:,]+)\])?\z/).captures.first
81
82
  end
82
83
 
84
+ def self.id_path?(path)
85
+ ID_PATH_REGEX.match?(path)
86
+ end
87
+
83
88
  def self.rails_helper_exists?(test_dir)
84
89
  File.exist?("#{test_dir}/rails_helper.rb")
85
90
  end
@@ -95,6 +100,7 @@ module KnapsackPro
95
100
 
96
101
  def bind_time_tracker
97
102
  ensure_no_focus!
103
+ bind_regular_mode_time_tracker
98
104
  log_tests_duration
99
105
  end
100
106
 
@@ -117,10 +123,19 @@ module KnapsackPro
117
123
  ::RSpec.configure do |config|
118
124
  config.append_after(:suite) do
119
125
  time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
120
- if time_tracker
121
- formatted = KnapsackPro::Presenter.global_time(time_tracker.duration)
122
- KnapsackPro.logger.debug(formatted)
123
- end
126
+ formatted = KnapsackPro::Presenter.global_time(time_tracker.duration)
127
+ KnapsackPro.logger.debug(formatted)
128
+ end
129
+ end
130
+ end
131
+
132
+ def bind_regular_mode_time_tracker
133
+ return unless KnapsackPro::Config::Env.regular_mode?
134
+
135
+ ::RSpec.configure do |config|
136
+ config.append_before(:suite) do
137
+ time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
138
+ time_tracker.scheduled_paths = KnapsackPro::Adapters::RSpecAdapter.scheduled_paths
124
139
  end
125
140
  end
126
141
  end
@@ -158,6 +173,10 @@ module KnapsackPro
158
173
  ::RSpec.configuration
159
174
  end
160
175
 
176
+ def self.scheduled_paths
177
+ rspec_configuration.instance_variable_get(:@files_or_directories_to_run) || []
178
+ end
179
+
161
180
  def self.parsed_options(cli_args)
162
181
  ::RSpec::Core::Parser.parse(cli_args)
163
182
  rescue SystemExit
@@ -87,24 +87,12 @@ module KnapsackPro
87
87
  ENV['KNAPSACK_PRO_REPOSITORY_ADAPTER']
88
88
  end
89
89
 
90
- def recording_enabled
91
- ENV['KNAPSACK_PRO_RECORDING_ENABLED']
92
- end
93
-
94
- def recording_enabled?
95
- recording_enabled == 'true'
96
- end
97
-
98
90
  def regular_mode?
99
- recording_enabled?
100
- end
101
-
102
- def queue_recording_enabled
103
- ENV['KNAPSACK_PRO_QUEUE_RECORDING_ENABLED']
91
+ ENV['KNAPSACK_PRO_REGULAR_MODE_ENABLED'] == 'true'
104
92
  end
105
93
 
106
- def queue_recording_enabled?
107
- queue_recording_enabled == 'true'
94
+ def queue_mode?
95
+ ENV['KNAPSACK_PRO_QUEUE_MODE_ENABLED'] == 'true'
108
96
  end
109
97
 
110
98
  def queue_id
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'stringio'
4
+ require 'set'
4
5
  require_relative '../utils'
5
6
 
6
7
  module KnapsackPro
@@ -22,6 +23,18 @@ module KnapsackPro
22
23
  @group = {}
23
24
  @paths = {}
24
25
  @suite_started = now
26
+ @scheduled_paths = []
27
+ @split_by_test_example_file_paths = Set.new
28
+ end
29
+
30
+ def scheduled_paths=(scheduled_paths)
31
+ @scheduled_paths = scheduled_paths
32
+ @scheduled_paths.each do |path|
33
+ if KnapsackPro::Adapters::RSpecAdapter.id_path?(path)
34
+ file_path = KnapsackPro::Adapters::RSpecAdapter.parse_file_path(path)
35
+ @split_by_test_example_file_paths << file_path
36
+ end
37
+ end
25
38
  end
26
39
 
27
40
  def example_group_started(notification)
@@ -49,12 +62,12 @@ module KnapsackPro
49
62
  @group = {}
50
63
  end
51
64
 
52
- def queue(scheduled_paths)
65
+ def queue
53
66
  recorded_paths = @paths.values.map do |example|
54
67
  KnapsackPro::Adapters::RSpecAdapter.parse_file_path(example[:path])
55
68
  end
56
69
 
57
- missing = (scheduled_paths - recorded_paths).each_with_object({}) do |path, object|
70
+ missing = (@scheduled_paths - recorded_paths).each_with_object({}) do |path, object|
58
71
  object[path] = { path: path, time_execution: 0.0 }
59
72
  end
60
73
 
@@ -73,12 +86,12 @@ module KnapsackPro
73
86
  now - @suite_started
74
87
  end
75
88
 
76
- def unexecuted_test_files(scheduled_paths)
89
+ def unexecuted_test_files
77
90
  pending_paths = @paths.values
78
91
  .filter { |example| example[:time_execution] == 0.0 }
79
92
  .map { |example| example[:path] }
80
93
 
81
- not_run_paths = scheduled_paths -
94
+ not_run_paths = @scheduled_paths -
82
95
  @paths.values
83
96
  .map { |example| example[:path] }
84
97
 
@@ -111,21 +124,22 @@ module KnapsackPro
111
124
  end
112
125
 
113
126
  def path_for(example)
114
- file = file_path_for(example)
115
- return nil if file == ""
127
+ file_path = file_path_for(example)
128
+ return nil if file_path == ""
116
129
 
117
- path = rspec_split_by_test_example?(file) ? example.id : file
118
- KnapsackPro::TestFileCleaner.clean(path)
130
+ if rspec_split_by_test_example?(file_path)
131
+ KnapsackPro::TestFileCleaner.clean(example.id)
132
+ else
133
+ file_path
134
+ end
119
135
  end
120
136
 
121
- def rspec_split_by_test_example?(file)
122
- return false unless KnapsackPro::Config::Env.rspec_split_by_test_examples?
123
- return false unless KnapsackPro::Adapters::RSpecAdapter.slow_test_file?(KnapsackPro::Adapters::RSpecAdapter, file)
124
- true
137
+ def rspec_split_by_test_example?(file_path)
138
+ @split_by_test_example_file_paths.include?(file_path)
125
139
  end
126
140
 
127
141
  def file_path_for(example)
128
- KnapsackPro::Adapters::RSpecAdapter.file_path_for(example)
142
+ KnapsackPro::TestFileCleaner.clean(KnapsackPro::Adapters::RSpecAdapter.file_path_for(example))
129
143
  end
130
144
 
131
145
  def time_execution_for(example, started_at)
@@ -10,10 +10,10 @@ module KnapsackPro
10
10
  .find { |f| f.class.to_s == "KnapsackPro::Formatters::TimeTracker" }
11
11
  end
12
12
 
13
- def self.unexecuted_test_files(scheduled_paths)
13
+ def self.unexecuted_test_files
14
14
  time_tracker = call
15
15
  return [] unless time_tracker
16
- time_tracker.unexecuted_test_files(scheduled_paths)
16
+ time_tracker.unexecuted_test_files
17
17
  end
18
18
  end
19
19
  end
@@ -5,7 +5,7 @@ module KnapsackPro
5
5
  class CucumberRunner < BaseRunner
6
6
  def self.run(args)
7
7
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_cucumber
8
- ENV['KNAPSACK_PRO_RECORDING_ENABLED'] = 'true'
8
+ ENV['KNAPSACK_PRO_REGULAR_MODE_ENABLED'] = 'true'
9
9
 
10
10
  adapter_class = KnapsackPro::Adapters::CucumberAdapter
11
11
  KnapsackPro::Config::Env.set_test_runner_adapter(adapter_class)
@@ -5,7 +5,7 @@ module KnapsackPro
5
5
  class MinitestRunner < BaseRunner
6
6
  def self.run(args)
7
7
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_minitest
8
- ENV['KNAPSACK_PRO_RECORDING_ENABLED'] = 'true'
8
+ ENV['KNAPSACK_PRO_REGULAR_MODE_ENABLED'] = 'true'
9
9
 
10
10
  adapter_class = KnapsackPro::Adapters::MinitestAdapter
11
11
  KnapsackPro::Config::Env.set_test_runner_adapter(adapter_class)
@@ -8,7 +8,7 @@ module KnapsackPro
8
8
  require 'cucumber/rake/task'
9
9
 
10
10
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_cucumber
11
- ENV['KNAPSACK_PRO_QUEUE_RECORDING_ENABLED'] = 'true'
11
+ ENV['KNAPSACK_PRO_QUEUE_MODE_ENABLED'] = 'true'
12
12
  ENV['KNAPSACK_PRO_QUEUE_ID'] = KnapsackPro::Config::EnvGenerator.set_queue_id
13
13
 
14
14
  adapter_class = KnapsackPro::Adapters::CucumberAdapter
@@ -12,7 +12,7 @@ module KnapsackPro
12
12
  ::Minitest.class_variable_set('@@installed_at_exit', true)
13
13
 
14
14
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_minitest
15
- ENV['KNAPSACK_PRO_QUEUE_RECORDING_ENABLED'] = 'true'
15
+ ENV['KNAPSACK_PRO_QUEUE_MODE_ENABLED'] = 'true'
16
16
  ENV['KNAPSACK_PRO_QUEUE_ID'] = KnapsackPro::Config::EnvGenerator.set_queue_id
17
17
 
18
18
  adapter_class = KnapsackPro::Adapters::MinitestAdapter
@@ -143,7 +143,7 @@ module KnapsackPro
143
143
  end
144
144
 
145
145
  def pre_run_setup
146
- ENV['KNAPSACK_PRO_QUEUE_RECORDING_ENABLED'] = 'true'
146
+ ENV['KNAPSACK_PRO_QUEUE_MODE_ENABLED'] = 'true'
147
147
  ENV['KNAPSACK_PRO_QUEUE_ID'] = KnapsackPro::Config::EnvGenerator.set_queue_id
148
148
 
149
149
  KnapsackPro::Config::Env.set_test_runner_adapter(@adapter_class)
@@ -164,7 +164,7 @@ module KnapsackPro
164
164
  log_rspec_queue_command
165
165
 
166
166
  time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
167
- KnapsackPro::Report.save_node_queue_to_api(time_tracker&.queue(@node_test_file_paths))
167
+ KnapsackPro::Report.save_node_queue_to_api(time_tracker&.queue)
168
168
 
169
169
  Kernel.exit(exit_code)
170
170
  end
@@ -183,6 +183,10 @@ module KnapsackPro
183
183
  executed_test_files: @node_test_file_paths
184
184
  )
185
185
  @node_test_file_paths += test_file_paths
186
+
187
+ time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
188
+ time_tracker.scheduled_paths = @node_test_file_paths
189
+
186
190
  test_file_paths
187
191
  end
188
192
 
@@ -222,7 +226,7 @@ module KnapsackPro
222
226
  end
223
227
 
224
228
  def unexecuted_test_files
225
- KnapsackPro::Formatters::TimeTrackerFetcher.unexecuted_test_files(@node_test_file_paths)
229
+ KnapsackPro::Formatters::TimeTrackerFetcher.unexecuted_test_files
226
230
  end
227
231
  end
228
232
  end
@@ -7,7 +7,7 @@ module KnapsackPro
7
7
  require 'rspec/core'
8
8
 
9
9
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_rspec
10
- ENV['KNAPSACK_PRO_RECORDING_ENABLED'] = 'true'
10
+ ENV['KNAPSACK_PRO_REGULAR_MODE_ENABLED'] = 'true'
11
11
 
12
12
  adapter_class = KnapsackPro::Adapters::RSpecAdapter
13
13
  KnapsackPro::Config::Env.set_test_runner_adapter(adapter_class)
@@ -15,7 +15,7 @@ module KnapsackPro
15
15
 
16
16
  KnapsackPro.tracker.set_prerun_tests(runner.test_file_paths)
17
17
 
18
- cmd = %Q[KNAPSACK_PRO_RECORDING_ENABLED=true KNAPSACK_PRO_TEST_SUITE_TOKEN=#{ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN']} bundle exec spinach #{args} --features_path #{runner.test_dir} -- #{runner.stringify_test_file_paths}]
18
+ cmd = %Q[KNAPSACK_PRO_REGULAR_MODE_ENABLED=true KNAPSACK_PRO_TEST_SUITE_TOKEN=#{ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN']} bundle exec spinach #{args} --features_path #{runner.test_dir} -- #{runner.stringify_test_file_paths}]
19
19
 
20
20
  Kernel.system(cmd)
21
21
  Kernel.exit(child_status.exitstatus) unless child_status.exitstatus.zero?
@@ -5,7 +5,7 @@ module KnapsackPro
5
5
  class TestUnitRunner < BaseRunner
6
6
  def self.run(args)
7
7
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_test_unit
8
- ENV['KNAPSACK_PRO_RECORDING_ENABLED'] = 'true'
8
+ ENV['KNAPSACK_PRO_REGULAR_MODE_ENABLED'] = 'true'
9
9
 
10
10
  adapter_class = KnapsackPro::Adapters::TestUnitAdapter
11
11
  KnapsackPro::Config::Env.set_test_runner_adapter(adapter_class)
@@ -11,7 +11,7 @@ module KnapsackPro
11
11
  path = test_file.fetch('path')
12
12
  test_file_path = extract_test_file_path(path)
13
13
 
14
- if rspec_id_path?(path)
14
+ if KnapsackPro::Adapters::RSpecAdapter.id_path?(path)
15
15
  merged_test_file_examples_hash[test_file_path] ||= 0.0
16
16
  merged_test_file_examples_hash[test_file_path] += test_file.fetch('time_execution')
17
17
  else
@@ -41,12 +41,6 @@ module KnapsackPro
41
41
  def extract_test_file_path(path)
42
42
  path.gsub(/\.rb\[.+\]$/, '.rb')
43
43
  end
44
-
45
- def rspec_id_path?(path)
46
- path_with_id_regex = /.+_spec\.rb\[.+\]$/
47
-
48
- path&.match?(path_with_id_regex)
49
- end
50
44
  end
51
45
  end
52
46
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KnapsackPro
4
- VERSION = '7.14.0'
4
+ VERSION = '7.14.1'
5
5
  end
@@ -1959,7 +1959,7 @@ describe "#{KnapsackPro::Runners::Queue::RSpecRunner} - Integration tests", :cle
1959
1959
  end
1960
1960
  end
1961
1961
 
1962
- context 'when the RSpec split by examples is enabled' do
1962
+ context 'when the RSpec split by test examples is enabled' do
1963
1963
  before do
1964
1964
  ENV['KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES'] = 'true'
1965
1965
 
@@ -1975,7 +1975,7 @@ describe "#{KnapsackPro::Runners::Queue::RSpecRunner} - Integration tests", :cle
1975
1975
  ENV.delete('KNAPSACK_PRO_CI_NODE_TOTAL')
1976
1976
  end
1977
1977
 
1978
- it 'splits slow test files by examples AND ensures the test examples are executed only once' do
1978
+ it 'splits slow test files by test examples AND ensures the test examples are executed only once' do
1979
1979
  rspec_options = '--format d'
1980
1980
 
1981
1981
  spec_a = Spec.new('a_spec.rb', <<~SPEC)
@@ -2058,7 +2058,7 @@ describe "#{KnapsackPro::Runners::Queue::RSpecRunner} - Integration tests", :cle
2058
2058
  end
2059
2059
  end
2060
2060
 
2061
- context 'when the RSpec split by examples is enabled AND --tag is set' do
2061
+ context 'when the RSpec split by test examples is enabled AND --tag is set' do
2062
2062
  before do
2063
2063
  ENV['KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES'] = 'true'
2064
2064
 
@@ -2137,7 +2137,7 @@ describe "#{KnapsackPro::Runners::Queue::RSpecRunner} - Integration tests", :cle
2137
2137
  end
2138
2138
  end
2139
2139
 
2140
- context 'when the RSpec split by examples is enabled AND JSON formatter is used' do
2140
+ context 'when the RSpec split by test examples is enabled AND JSON formatter is used' do
2141
2141
  let(:json_file) { "#{SPEC_DIRECTORY}/rspec.json" }
2142
2142
 
2143
2143
  before do
@@ -2239,7 +2239,7 @@ describe "#{KnapsackPro::Runners::Queue::RSpecRunner} - Integration tests", :cle
2239
2239
  end
2240
2240
  end
2241
2241
 
2242
- context 'when the RSpec split by examples is enabled AND JUnit XML formatter is used' do
2242
+ context 'when the RSpec split by test examples is enabled AND JUnit XML formatter is used' do
2243
2243
  let(:xml_file) { "#{SPEC_DIRECTORY}/rspec.xml" }
2244
2244
 
2245
2245
  before do
@@ -2338,7 +2338,7 @@ describe "#{KnapsackPro::Runners::Queue::RSpecRunner} - Integration tests", :cle
2338
2338
  end
2339
2339
  end
2340
2340
 
2341
- context 'when the RSpec split by examples is enabled AND simplecov is used' do
2341
+ context 'when the RSpec split by test examples is enabled AND simplecov is used' do
2342
2342
  let(:coverage_dir) { "#{KNAPSACK_PRO_TMP_DIR}/coverage" }
2343
2343
  let(:coverage_file) { "#{coverage_dir}/index.html" }
2344
2344
 
@@ -2427,6 +2427,104 @@ describe "#{KnapsackPro::Runners::Queue::RSpecRunner} - Integration tests", :cle
2427
2427
  end
2428
2428
  end
2429
2429
 
2430
+ context 'when the RSpec split by test examples is enabled AND test files are split by test examples AND slow test files are not detected (for example, the user could have passed test examples like a_spec.rb[1:1] directly using KNAPSACK_PRO_TEST_FILE_LIST_SOURCE_FILE or KNAPSACK_PRO_TEST_FILE_LIST)' do
2431
+ before do
2432
+ ENV['KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES'] = 'true'
2433
+ ENV['KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN'] = ""
2434
+ ENV['KNAPSACK_PRO_CI_NODE_TOTAL'] = '2'
2435
+ end
2436
+ after do
2437
+ ENV.delete('KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES')
2438
+ ENV.delete('KNAPSACK_PRO_SLOW_TEST_FILE_PATTERN')
2439
+ ENV.delete('KNAPSACK_PRO_CI_NODE_TOTAL')
2440
+ end
2441
+
2442
+ it 'detects test execution times correctly for individual test examples even though they are not considered slow test files' do
2443
+ ENV['TEST__LOG_EXECUTION_TIMES'] = 'true'
2444
+ rspec_options = '--format d'
2445
+
2446
+ spec_a = Spec.new('a_spec.rb', <<~SPEC)
2447
+ describe 'A_describe' do
2448
+ it 'A1 test example' do
2449
+ expect(1).to eq 1
2450
+ end
2451
+ it 'A2 test example' do
2452
+ expect(1).to eq 1
2453
+ end
2454
+ end
2455
+ SPEC
2456
+
2457
+ spec_b = Spec.new('b_spec.rb', <<~SPEC)
2458
+ describe 'B_describe' do
2459
+ it 'B1 test example' do
2460
+ expect(1).to eq 1
2461
+ end
2462
+ it 'B2 test example' do
2463
+ expect(1).to eq 1
2464
+ end
2465
+ end
2466
+ SPEC
2467
+
2468
+ spec_c = Spec.new('c_spec.rb', <<~SPEC)
2469
+ describe 'C_describe' do
2470
+ it 'C1 test example' do
2471
+ expect(1).to eq 1
2472
+ end
2473
+ it 'C2 test example' do
2474
+ expect(1).to eq 1
2475
+ end
2476
+ end
2477
+ SPEC
2478
+
2479
+ generate_specs(spec_helper_with_knapsack, rspec_options, [
2480
+ [spec_a, spec_b, spec_c]
2481
+ ])
2482
+ stub_test_cases_for_slow_test_files([
2483
+ "#{spec_a.path}[1:1]",
2484
+ "#{spec_a.path}[1:2]",
2485
+ ])
2486
+ stub_spec_batches([
2487
+ ["#{spec_a.path}[1:1]", spec_b.path],
2488
+ ["#{spec_a.path}[1:2]", spec_c.path],
2489
+ ])
2490
+
2491
+ actual = subject
2492
+
2493
+ expect(actual.stdout).to include('DEBUG -- : [knapsack_pro] Detected 0 slow test files: []')
2494
+
2495
+ expect(actual.stdout).to include(
2496
+ <<~OUTPUT
2497
+ A_describe
2498
+ A1 test example
2499
+
2500
+ B_describe
2501
+ B1 test example
2502
+ B2 test example
2503
+ OUTPUT
2504
+ )
2505
+
2506
+ expect(actual.stdout).to include(
2507
+ <<~OUTPUT
2508
+ A_describe
2509
+ A2 test example
2510
+
2511
+ C_describe
2512
+ C1 test example
2513
+ C2 test example
2514
+ OUTPUT
2515
+ )
2516
+
2517
+ expect(actual.stdout.scan(/A1 test example/).size).to eq 1
2518
+ expect(actual.stdout.scan(/A2 test example/).size).to eq 1
2519
+
2520
+ expect(actual.stdout).to include('6 examples, 0 failures')
2521
+
2522
+ expect(actual.stdout).to include('[INTEGRATION TEST] test_files: 4, test files have execution time: true')
2523
+
2524
+ expect(actual.exit_code).to eq 0
2525
+ end
2526
+ end
2527
+
2430
2528
  context 'when the example_status_persistence_file_path option is used and multiple batches of tests are fetched from the Queue API and some tests are pending and failing' do
2431
2529
  let(:examples_file_path) { "#{SPEC_DIRECTORY}/examples.txt" }
2432
2530
 
@@ -135,21 +135,21 @@ describe KnapsackPro::Adapters::BaseAdapter do
135
135
 
136
136
  describe '#bind' do
137
137
  let(:temp_directory_path) { '.knapsack_pro' }
138
- let(:recording_enabled?) { false }
139
- let(:queue_recording_enabled?) { false }
138
+ let(:regular_mode?) { false }
139
+ let(:queue_mode?) { false }
140
140
 
141
141
  before do
142
142
  expect(KnapsackPro::Config::TempFiles).to receive(:ensure_temp_directory_exists!)
143
143
  expect(File).to receive(:write).with('.knapsack_pro/KnapsackPro-Adapters-BaseAdapter-bind_method_called_for_node_0.txt', nil)
144
144
 
145
- expect(KnapsackPro::Config::Env).to receive(:recording_enabled?).and_return(recording_enabled?)
146
- expect(KnapsackPro::Config::Env).to receive(:queue_recording_enabled?).and_return(queue_recording_enabled?)
145
+ expect(KnapsackPro::Config::Env).to receive(:regular_mode?).and_return(regular_mode?)
146
+ expect(KnapsackPro::Config::Env).to receive(:queue_mode?).and_return(queue_mode?)
147
147
  end
148
148
 
149
149
  after { subject.bind }
150
150
 
151
- context 'when recording enabled' do
152
- let(:recording_enabled?) { true }
151
+ context 'when regular mode enabled' do
152
+ let(:regular_mode?) { true }
153
153
 
154
154
  before do
155
155
  allow(subject).to receive(:bind_time_tracker)
@@ -165,8 +165,8 @@ describe KnapsackPro::Adapters::BaseAdapter do
165
165
  it { expect(subject).to receive(:bind_save_report) }
166
166
  end
167
167
 
168
- context 'when queue recording enabled' do
169
- let(:queue_recording_enabled?) { true }
168
+ context 'when queue mode enabled' do
169
+ let(:queue_mode?) { true }
170
170
 
171
171
  it 'calls queue hooks in proper order before binding time tracker' do
172
172
  logger = instance_double(Logger)
@@ -179,7 +179,7 @@ describe KnapsackPro::Adapters::BaseAdapter do
179
179
  end
180
180
  end
181
181
 
182
- context 'when recording disabled' do
182
+ context 'when regular mode and queue mode disabled' do
183
183
  it { expect(subject).not_to receive(:bind_save_report) }
184
184
  it { expect(subject).not_to receive(:bind_before_queue_hook) }
185
185
  it { expect(subject).not_to receive(:bind_after_queue_hook) }
@@ -215,6 +215,28 @@ describe KnapsackPro::Adapters::RSpecAdapter do
215
215
  end
216
216
  end
217
217
 
218
+ describe '.id_path?' do
219
+ subject { described_class.id_path?(path) }
220
+
221
+ context 'when the path resembles the RSpec path with id' do
222
+ let(:path) { 'spec/features/a_spec.rb[1:1:7:1]' }
223
+
224
+ it { is_expected.to be true }
225
+ end
226
+
227
+ context 'when the path resembles the RSpec path with multiple ids' do
228
+ let(:path) { 'spec/features/a_spec.rb[1:1:7:1, 1:2]' }
229
+
230
+ it { is_expected.to be true }
231
+ end
232
+
233
+ context "when the path doesn't resemble the RSpec path with id" do
234
+ let(:path) { 'spec/features/a_spec.rb' }
235
+
236
+ it { is_expected.to be false }
237
+ end
238
+ end
239
+
218
240
  describe '.rails_helper_exists?' do
219
241
  subject { described_class.rails_helper_exists?(test_dir) }
220
242
 
@@ -370,6 +392,26 @@ describe KnapsackPro::Adapters::RSpecAdapter do
370
392
  end
371
393
  end
372
394
 
395
+ describe 'private .scheduled_paths' do
396
+ subject { described_class.send(:scheduled_paths) }
397
+
398
+ context 'when the RSpec configuration has files or directories to run' do
399
+ it 'returns list of test files passed to RSpec (if this fails then the internal RSpec API changed and we must start supporting a new RSpec version as well)' do
400
+ expect(subject).not_to be_empty
401
+ end
402
+ end
403
+
404
+ context "when the RSpec configuration has no files or directories to run (when the internal RSpec API changed and we don't support the new RSpec version yet)" do
405
+ before do
406
+ expect(described_class).to receive(:rspec_configuration).and_return(double(:object_with_no_instance_variables))
407
+ end
408
+
409
+ it 'fallbacks to an empty array to not blow up Knapsack Pro' do
410
+ expect(subject).to eq([])
411
+ end
412
+ end
413
+ end
414
+
373
415
  describe 'bind methods' do
374
416
  let(:config) { double }
375
417
 
@@ -603,89 +603,43 @@ describe KnapsackPro::Config::Env do
603
603
  end
604
604
  end
605
605
 
606
- describe '.recording_enabled' do
607
- subject { described_class.recording_enabled }
608
-
609
- context 'when ENV exists' do
610
- let(:recording_enabled) { 'true' }
611
- before { stub_const("ENV", { 'KNAPSACK_PRO_RECORDING_ENABLED' => recording_enabled }) }
612
- it { should eq recording_enabled }
613
- end
614
-
615
- context "when ENV doesn't exist" do
616
- it { should be_nil }
617
- end
618
- end
619
-
620
606
  describe '.regular_mode?' do
621
607
  subject { described_class.regular_mode? }
622
608
 
623
- before do
624
- expect(described_class).to receive(:recording_enabled?).and_return(recording_enabled)
609
+ context 'when regular mode is enabled' do
610
+ let(:regular_mode_enabled) { 'true' }
611
+ before { stub_const("ENV", { 'KNAPSACK_PRO_REGULAR_MODE_ENABLED' => regular_mode_enabled }) }
612
+ it { should eq true }
625
613
  end
626
614
 
627
- context 'when recording is enabled' do
628
- let(:recording_enabled) { true }
629
- it { should be true }
630
- end
631
-
632
- context 'when recording is not enabled' do
633
- let(:recording_enabled) { false }
634
- it { should be false }
635
- end
636
- end
637
-
638
- describe '.recording_enabled?' do
639
- subject { described_class.recording_enabled? }
640
-
641
- before do
642
- expect(described_class).to receive(:recording_enabled).and_return(recording_enabled)
643
- end
644
-
645
- context 'when enabled' do
646
- let(:recording_enabled) { 'true' }
647
-
648
- it { should be true }
649
- end
650
-
651
- context 'when disabled' do
652
- let(:recording_enabled) { nil }
653
-
654
- it { should be false }
655
- end
656
- end
657
-
658
- describe '.queue_recording_enabled' do
659
- subject { described_class.queue_recording_enabled }
660
-
661
- context 'when ENV exists' do
662
- let(:queue_recording_enabled) { 'true' }
663
- before { stub_const("ENV", { 'KNAPSACK_PRO_QUEUE_RECORDING_ENABLED' => queue_recording_enabled }) }
664
- it { should eq queue_recording_enabled }
615
+ context 'when regular mode is disabled' do
616
+ let(:regular_mode_enabled) { 'false' }
617
+ before { stub_const("ENV", { 'KNAPSACK_PRO_REGULAR_MODE_ENABLED' => regular_mode_enabled }) }
618
+ it { should eq false }
665
619
  end
666
620
 
667
621
  context "when ENV doesn't exist" do
668
- it { should be_nil }
622
+ it { should false }
669
623
  end
670
624
  end
671
625
 
672
- describe '.queue_recording_enabled?' do
673
- subject { described_class.queue_recording_enabled? }
626
+ describe '.queue_mode?' do
627
+ subject { described_class.queue_mode? }
674
628
 
675
- before do
676
- expect(described_class).to receive(:queue_recording_enabled).and_return(queue_recording_enabled)
629
+ context 'when queue mode is enabled' do
630
+ let(:queue_mode_enabled) { 'true' }
631
+ before { stub_const("ENV", { 'KNAPSACK_PRO_QUEUE_MODE_ENABLED' => queue_mode_enabled }) }
632
+ it { should eq true }
677
633
  end
678
634
 
679
- context 'when enabled' do
680
- let(:queue_recording_enabled) { 'true' }
681
-
682
- it { should be true }
635
+ context 'when queue mode is disabled' do
636
+ let(:queue_mode_enabled) { 'false' }
637
+ before { stub_const("ENV", { 'KNAPSACK_PRO_QUEUE_MODE_ENABLED' => queue_mode_enabled }) }
638
+ it { should eq false }
683
639
  end
684
640
 
685
- context 'when disabled' do
686
- let(:queue_recording_enabled) { nil }
687
-
688
- it { should be false }
641
+ context "when ENV doesn't exist" do
642
+ it { should false }
689
643
  end
690
644
  end
691
645
 
@@ -1016,7 +970,10 @@ describe KnapsackPro::Config::Env do
1016
970
  describe '.rspec_split_by_test_examples?' do
1017
971
  subject { described_class.rspec_split_by_test_examples? }
1018
972
 
1019
- after(:each) do
973
+ before do
974
+ described_class.remove_instance_variable(:@rspec_split_by_test_examples) if described_class.instance_variable_defined?(:@rspec_split_by_test_examples)
975
+ end
976
+ after do
1020
977
  described_class.remove_instance_variable(:@rspec_split_by_test_examples)
1021
978
  end
1022
979
 
@@ -2,11 +2,9 @@ require(KnapsackPro.root + '/lib/knapsack_pro/formatters/time_tracker')
2
2
 
3
3
  describe KnapsackPro::Formatters::TimeTrackerFetcher do
4
4
  describe '.unexecuted_test_files' do
5
- subject { described_class.unexecuted_test_files(scheduled_paths) }
5
+ subject { described_class.unexecuted_test_files }
6
6
 
7
7
  context 'when the time tracker formatter not found' do
8
- let(:scheduled_paths) { ['a_spec.rb'] }
9
-
10
8
  it do
11
9
  expect(subject).to eq []
12
10
  end
@@ -14,12 +12,11 @@ describe KnapsackPro::Formatters::TimeTrackerFetcher do
14
12
 
15
13
  context 'when the time tracker formatter is found' do
16
14
  let(:time_tracker) { instance_double(KnapsackPro::Formatters::TimeTracker) }
17
- let(:scheduled_paths) { ['a_spec.rb', 'b_spec.rb'] }
18
15
  let(:unexecuted_test_files) { double(:unexecuted_test_files) }
19
16
 
20
17
  before do
21
18
  expect(described_class).to receive(:call).and_return(time_tracker)
22
- expect(time_tracker).to receive(:unexecuted_test_files).with(scheduled_paths).and_return(unexecuted_test_files)
19
+ expect(time_tracker).to receive(:unexecuted_test_files).and_return(unexecuted_test_files)
23
20
  end
24
21
 
25
22
  it do
@@ -343,8 +343,9 @@ class TestTimeTracker
343
343
  run_specs(spec) do |spec_paths, _, time_tracker|
344
344
  unexecuted_test_files = ["foo_spec.rb", "bar_spec.rb"]
345
345
  # Need to filter because RSpec keeps accumulating state.
346
+ time_tracker.scheduled_paths = spec_paths + unexecuted_test_files
346
347
  files = time_tracker
347
- .unexecuted_test_files(spec_paths + unexecuted_test_files)
348
+ .unexecuted_test_files
348
349
  .filter { |file| spec_paths.include?(file) || unexecuted_test_files.include?(file) }
349
350
 
350
351
  raise unless files.size == 3
@@ -399,8 +400,9 @@ class TestTimeTracker
399
400
 
400
401
  time_tracker = runner.configuration.formatters.find { |f| f.class.to_s == KnapsackPro::Formatters::TimeTracker.to_s }
401
402
  # Need to filter because RSpec keeps accumulating state.
403
+ time_tracker.scheduled_paths = paths
402
404
  times = time_tracker
403
- .queue(paths)
405
+ .queue
404
406
  .sort_by { |time| time["path"] }
405
407
  .filter do |time|
406
408
  paths.any? { |path| time["path"].start_with?(path) }
@@ -16,7 +16,7 @@ describe KnapsackPro::Runners::CucumberRunner do
16
16
  expect(KnapsackPro::Config::Env).to receive(:test_suite_token_cucumber).and_return(test_suite_token_cucumber)
17
17
 
18
18
  expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_TEST_SUITE_TOKEN', test_suite_token_cucumber)
19
- expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_RECORDING_ENABLED', 'true')
19
+ expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_REGULAR_MODE_ENABLED', 'true')
20
20
 
21
21
  expect(KnapsackPro::Config::Env).to receive(:set_test_runner_adapter).with(KnapsackPro::Adapters::CucumberAdapter)
22
22
 
@@ -39,7 +39,7 @@ describe KnapsackPro::Runners::MinitestRunner do
39
39
  expect(Rake::Task.task_defined?(task_name)).to be true
40
40
 
41
41
  expect(ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN']).to eq 'minitest-token'
42
- expect(ENV['KNAPSACK_PRO_RECORDING_ENABLED']).to eq 'true'
42
+ expect(ENV['KNAPSACK_PRO_REGULAR_MODE_ENABLED']).to eq 'true'
43
43
  end
44
44
  end
45
45
 
@@ -16,7 +16,7 @@ describe KnapsackPro::Runners::Queue::CucumberRunner do
16
16
  expect(KnapsackPro::Config::EnvGenerator).to receive(:set_queue_id).and_return(queue_id)
17
17
 
18
18
  expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_TEST_SUITE_TOKEN', test_suite_token_cucumber)
19
- expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_QUEUE_RECORDING_ENABLED', 'true')
19
+ expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_QUEUE_MODE_ENABLED', 'true')
20
20
  expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_QUEUE_ID', queue_id)
21
21
 
22
22
  expect(KnapsackPro::Config::Env).to receive(:set_test_runner_adapter).with(KnapsackPro::Adapters::CucumberAdapter)
@@ -16,7 +16,7 @@ describe KnapsackPro::Runners::Queue::MinitestRunner do
16
16
  expect(KnapsackPro::Config::EnvGenerator).to receive(:set_queue_id).and_return(queue_id)
17
17
 
18
18
  expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_TEST_SUITE_TOKEN', test_suite_token_minitest)
19
- expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_QUEUE_RECORDING_ENABLED', 'true')
19
+ expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_QUEUE_MODE_ENABLED', 'true')
20
20
  expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_QUEUE_ID', queue_id)
21
21
 
22
22
  expect(KnapsackPro::Config::Env).to receive(:set_test_runner_adapter).with(KnapsackPro::Adapters::MinitestAdapter)
@@ -16,7 +16,7 @@ describe KnapsackPro::Runners::RSpecRunner do
16
16
  expect(KnapsackPro::Config::Env).to receive(:test_suite_token_rspec).and_return(test_suite_token_rspec)
17
17
 
18
18
  expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_TEST_SUITE_TOKEN', test_suite_token_rspec)
19
- expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_RECORDING_ENABLED', 'true')
19
+ expect(ENV).to receive(:[]=).with('KNAPSACK_PRO_REGULAR_MODE_ENABLED', 'true')
20
20
 
21
21
  expect(KnapsackPro::Config::Env).to receive(:set_test_runner_adapter).with(KnapsackPro::Adapters::RSpecAdapter)
22
22
 
@@ -37,7 +37,7 @@ describe KnapsackPro::Runners::SpinachRunner do
37
37
  expect(KnapsackPro).to receive(:tracker).and_return(tracker)
38
38
  expect(tracker).to receive(:set_prerun_tests).with(test_file_paths)
39
39
 
40
- expect(Kernel).to receive(:system).with('KNAPSACK_PRO_RECORDING_ENABLED=true KNAPSACK_PRO_TEST_SUITE_TOKEN=spinach-token bundle exec spinach --custom-arg --features_path fake-test-dir -- features/a.feature features/b.feature')
40
+ expect(Kernel).to receive(:system).with('KNAPSACK_PRO_REGULAR_MODE_ENABLED=true KNAPSACK_PRO_TEST_SUITE_TOKEN=spinach-token bundle exec spinach --custom-arg --features_path fake-test-dir -- features/a.feature features/b.feature')
41
41
 
42
42
  allow(described_class).to receive(:child_status).and_return(child_status)
43
43
  end
@@ -44,7 +44,7 @@ describe KnapsackPro::Runners::TestUnitRunner do
44
44
  subject
45
45
 
46
46
  expect(ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN']).to eq 'test-unit-token'
47
- expect(ENV['KNAPSACK_PRO_RECORDING_ENABLED']).to eq 'true'
47
+ expect(ENV['KNAPSACK_PRO_REGULAR_MODE_ENABLED']).to eq 'true'
48
48
  end
49
49
  end
50
50
 
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: 7.14.0
4
+ version: 7.14.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ArturT
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-23 00:00:00.000000000 Z
11
+ date: 2025-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -433,7 +433,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
433
433
  - !ruby/object:Gem::Version
434
434
  version: '0'
435
435
  requirements: []
436
- rubygems_version: 3.5.11
436
+ rubygems_version: 3.5.22
437
437
  signing_key:
438
438
  specification_version: 4
439
439
  summary: Knapsack Pro splits tests across parallel CI nodes and ensures each parallel