knapsack_pro 6.0.4 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +80 -19
  3. data/.github/pull_request_template.md +22 -0
  4. data/.gitignore +4 -0
  5. data/CHANGELOG.md +87 -0
  6. data/Gemfile +9 -0
  7. data/README.md +0 -4
  8. data/knapsack_pro.gemspec +2 -1
  9. data/lib/knapsack_pro/adapters/base_adapter.rb +7 -2
  10. data/lib/knapsack_pro/adapters/cucumber_adapter.rb +1 -3
  11. data/lib/knapsack_pro/adapters/rspec_adapter.rb +16 -9
  12. data/lib/knapsack_pro/config/env.rb +1 -9
  13. data/lib/knapsack_pro/extensions/rspec_extension.rb +137 -0
  14. data/lib/knapsack_pro/formatters/time_tracker.rb +10 -26
  15. data/lib/knapsack_pro/formatters/time_tracker_fetcher.rb +6 -0
  16. data/lib/knapsack_pro/presenter.rb +1 -1
  17. data/lib/knapsack_pro/pure/queue/rspec_pure.rb +92 -0
  18. data/lib/knapsack_pro/runners/queue/base_runner.rb +6 -1
  19. data/lib/knapsack_pro/runners/queue/cucumber_runner.rb +6 -6
  20. data/lib/knapsack_pro/runners/queue/minitest_runner.rb +6 -6
  21. data/lib/knapsack_pro/runners/queue/rspec_runner.rb +124 -173
  22. data/lib/knapsack_pro/urls.rb +2 -0
  23. data/lib/knapsack_pro/version.rb +1 -1
  24. data/lib/knapsack_pro.rb +1 -0
  25. data/spec/integration/runners/queue/rspec_runner.rb +80 -0
  26. data/spec/integration/runners/queue/rspec_runner_spec.rb +2232 -0
  27. data/spec/knapsack_pro/adapters/base_adapter_spec.rb +17 -11
  28. data/spec/knapsack_pro/adapters/cucumber_adapter_spec.rb +2 -5
  29. data/spec/knapsack_pro/adapters/rspec_adapter_spec.rb +2 -24
  30. data/spec/knapsack_pro/config/env_spec.rb +1 -35
  31. data/spec/knapsack_pro/formatters/time_tracker_specs.rb +8 -37
  32. data/spec/knapsack_pro/hooks/queue_spec.rb +2 -2
  33. data/spec/knapsack_pro/presenter_spec.rb +1 -1
  34. data/spec/knapsack_pro/pure/queue/rspec_pure_spec.rb +224 -0
  35. data/spec/knapsack_pro/runners/queue/cucumber_runner_spec.rb +16 -16
  36. data/spec/knapsack_pro/runners/queue/minitest_runner_spec.rb +14 -14
  37. data/spec/knapsack_pro_spec.rb +3 -3
  38. metadata +17 -12
  39. data/lib/knapsack_pro/formatters/rspec_queue_profile_formatter_extension.rb +0 -58
  40. data/lib/knapsack_pro/formatters/rspec_queue_summary_formatter.rb +0 -145
  41. data/spec/knapsack_pro/runners/queue/rspec_runner_spec.rb +0 -536
@@ -9,24 +9,18 @@ module KnapsackPro
9
9
  :example_group_started,
10
10
  :example_started,
11
11
  :example_finished,
12
- :example_group_finished,
13
- :stop
12
+ :example_group_finished
14
13
 
15
14
  attr_reader :output # RSpec < v3.10.2
16
15
 
17
- # Called at the beginning of each batch,
18
- # but only the first instance of this class is used,
19
- # so don't rely on the initializer to reset values.
20
16
  def initialize(_output)
21
17
  @output = StringIO.new
22
18
  @time_each = nil
23
19
  @time_all = nil
24
20
  @before_all = 0.0
25
21
  @group = {}
26
- @batch = {}
27
- @queue = {}
22
+ @paths = {}
28
23
  @suite_started = now
29
- @batch_started = now
30
24
  end
31
25
 
32
26
  def example_group_started(notification)
@@ -47,21 +41,15 @@ module KnapsackPro
47
41
  def example_group_finished(notification)
48
42
  return unless top_level_group?(notification.group)
49
43
 
50
- add_hooks_time(@group, @before_all, now - @time_all)
51
- @batch = merge(@batch, @group)
44
+ after_all = @time_all.nil? ? 0.0 : now - @time_all
45
+ add_hooks_time(@group, @before_all, after_all)
52
46
  @before_all = 0.0
47
+ @paths = merge(@paths, @group)
53
48
  @group = {}
54
49
  end
55
50
 
56
- # Called at the end of each batch
57
- def stop(_notification)
58
- @queue = merge(@queue, @batch)
59
- @batch = {}
60
- @batch_started = now
61
- end
62
-
63
51
  def queue(scheduled_paths)
64
- recorded_paths = @queue.values.map do |example|
52
+ recorded_paths = @paths.values.map do |example|
65
53
  KnapsackPro::Adapters::RSpecAdapter.parse_file_path(example[:path])
66
54
  end
67
55
 
@@ -69,13 +57,13 @@ module KnapsackPro
69
57
  object[path] = { path: path, time_execution: 0.0 }
70
58
  end
71
59
 
72
- merge(@queue, missing).values.map do |example|
60
+ merge(@paths, missing).values.map do |example|
73
61
  example.transform_keys(&:to_s)
74
62
  end
75
63
  end
76
64
 
77
65
  def batch
78
- @batch.values.map do |example|
66
+ @paths.values.map do |example|
79
67
  example.transform_keys(&:to_s)
80
68
  end
81
69
  end
@@ -84,17 +72,13 @@ module KnapsackPro
84
72
  now - @suite_started
85
73
  end
86
74
 
87
- def batch_duration
88
- now - @batch_started
89
- end
90
-
91
75
  def unexecuted_test_files(scheduled_paths)
92
- pending_paths = (@queue.values + @batch.values)
76
+ pending_paths = @paths.values
93
77
  .filter { |example| example[:time_execution] == 0.0 }
94
78
  .map { |example| example[:path] }
95
79
 
96
80
  not_run_paths = scheduled_paths -
97
- (@queue.values + @batch.values)
81
+ @paths.values
98
82
  .map { |example| example[:path] }
99
83
 
100
84
  pending_paths + not_run_paths
@@ -9,6 +9,12 @@ module KnapsackPro
9
9
  .formatters
10
10
  .find { |f| f.class.to_s == "KnapsackPro::Formatters::TimeTracker" }
11
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
12
18
  end
13
19
  end
14
20
  end
@@ -6,7 +6,7 @@ module KnapsackPro
6
6
  def global_time(time = nil)
7
7
  time = KnapsackPro.tracker.global_time if time.nil?
8
8
  global_time = pretty_seconds(time)
9
- "Global time execution for tests: #{global_time}"
9
+ "Global test execution duration: #{global_time}"
10
10
  end
11
11
 
12
12
  def pretty_seconds(seconds)
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KnapsackPro
4
+ module Pure
5
+ module Queue
6
+ class RSpecPure
7
+ FAILURE_EXIT_CODE = 1
8
+ FORMATTERS = [
9
+ 'KnapsackPro::Formatters::TimeTracker',
10
+ ]
11
+
12
+ def add_knapsack_pro_formatters_to(spec_opts)
13
+ return spec_opts unless spec_opts
14
+ return spec_opts if FORMATTERS.all? { |formatter| spec_opts.include?(formatter) }
15
+
16
+ FORMATTERS.each do |formatter|
17
+ next if spec_opts.include?(formatter)
18
+ spec_opts += " --format #{formatter}"
19
+ end
20
+
21
+ spec_opts
22
+ end
23
+
24
+ def error_exit_code(rspec_error_exit_code)
25
+ rspec_error_exit_code || FAILURE_EXIT_CODE
26
+ end
27
+
28
+ def args_with_seed_option_added_when_viable(order_option, seed, args)
29
+ return args if order_option && !order_option.include?('rand')
30
+ return args if order_option && order_option.to_s.split(':')[1]
31
+
32
+ return args unless seed.used?
33
+
34
+ args + ['--seed', seed.value]
35
+ end
36
+
37
+ def prepare_cli_args(args, has_format_option, test_dir)
38
+ (args || '').split
39
+ .yield_self { args_with_at_least_one_formatter(_1, has_format_option) }
40
+ .yield_self { args_with_default_options(_1, test_dir) }
41
+ end
42
+
43
+ def rspec_command(args, test_file_paths, scope)
44
+ messages = []
45
+ return messages if test_file_paths.empty?
46
+
47
+ case scope
48
+ when :batch_finished
49
+ messages << 'To retry the last batch of tests fetched from the Queue API, please run the following command on your machine:'
50
+ when :queue_finished
51
+ messages << 'To retry all the tests assigned to this CI node, please run the following command on your machine:'
52
+ end
53
+
54
+ stringified_cli_args = args.join(' ')
55
+ FORMATTERS.each do |formatter|
56
+ stringified_cli_args.sub!(" --format #{formatter}", '')
57
+ end
58
+
59
+ messages << "bundle exec rspec #{stringified_cli_args} " + KnapsackPro::TestFilePresenter.stringify_paths(test_file_paths)
60
+
61
+ messages
62
+ end
63
+
64
+ def exit_summary(unexecuted_test_files)
65
+ return if unexecuted_test_files.empty?
66
+
67
+ "Unexecuted tests on this CI node (including pending tests): #{unexecuted_test_files.join(' ')}"
68
+ end
69
+
70
+ private
71
+
72
+ def args_with_at_least_one_formatter(cli_args, has_format_option)
73
+ return cli_args if has_format_option
74
+
75
+ cli_args + ['--format', 'progress']
76
+ end
77
+
78
+ def args_with_default_options(cli_args, test_dir)
79
+ new_cli_args = cli_args + [
80
+ '--default-path', test_dir,
81
+ ]
82
+
83
+ FORMATTERS.each do |formatter|
84
+ new_cli_args += ['--format', formatter]
85
+ end
86
+
87
+ new_cli_args
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -4,6 +4,7 @@ module KnapsackPro
4
4
  module Runners
5
5
  module Queue
6
6
  class BaseRunner
7
+ TerminationError = Class.new(StandardError)
7
8
  TERMINATION_SIGNALS = %w(HUP INT TERM ABRT QUIT USR1 USR2)
8
9
 
9
10
  @@terminate_process = false
@@ -42,13 +43,17 @@ module KnapsackPro
42
43
  end
43
44
 
44
45
  def self.handle_signal!
45
- raise 'Knapsack Pro process was terminated!' if @@terminate_process
46
+ raise TerminationError.new('Knapsack Pro process was terminated!') if @@terminate_process
46
47
  end
47
48
 
48
49
  def self.set_terminate_process
49
50
  @@terminate_process = true
50
51
  end
51
52
 
53
+ def set_terminate_process
54
+ self.class.set_terminate_process
55
+ end
56
+
52
57
  def trap_signals
53
58
  TERMINATION_SIGNALS.each do |signal|
54
59
  Signal.trap(signal) {
@@ -21,7 +21,7 @@ module KnapsackPro
21
21
  can_initialize_queue: true,
22
22
  args: args,
23
23
  exitstatus: 0,
24
- all_test_file_paths: [],
24
+ node_test_file_paths: [],
25
25
  }
26
26
  while accumulator[:status] == :next
27
27
  handle_signal!
@@ -36,15 +36,15 @@ module KnapsackPro
36
36
  can_initialize_queue = accumulator.fetch(:can_initialize_queue)
37
37
  args = accumulator.fetch(:args)
38
38
  exitstatus = accumulator.fetch(:exitstatus)
39
- all_test_file_paths = accumulator.fetch(:all_test_file_paths)
39
+ node_test_file_paths = accumulator.fetch(:node_test_file_paths)
40
40
 
41
41
  test_file_paths = runner.test_file_paths(
42
42
  can_initialize_queue: can_initialize_queue,
43
- executed_test_files: all_test_file_paths
43
+ executed_test_files: node_test_file_paths
44
44
  )
45
45
 
46
46
  if test_file_paths.empty?
47
- unless all_test_file_paths.empty?
47
+ unless node_test_file_paths.empty?
48
48
  KnapsackPro::Adapters::CucumberAdapter.verify_bind_method_called
49
49
  end
50
50
 
@@ -65,7 +65,7 @@ module KnapsackPro
65
65
 
66
66
  KnapsackPro::Hooks::Queue.call_before_subset_queue
67
67
 
68
- all_test_file_paths += test_file_paths
68
+ node_test_file_paths += test_file_paths
69
69
 
70
70
  result_exitstatus = cucumber_run(runner, test_file_paths, args)
71
71
  exitstatus = result_exitstatus if result_exitstatus != 0
@@ -80,7 +80,7 @@ module KnapsackPro
80
80
  can_initialize_queue: false,
81
81
  args: args,
82
82
  exitstatus: exitstatus,
83
- all_test_file_paths: all_test_file_paths,
83
+ node_test_file_paths: node_test_file_paths,
84
84
  }
85
85
  end
86
86
  end
@@ -32,7 +32,7 @@ module KnapsackPro
32
32
  can_initialize_queue: true,
33
33
  args: cli_args,
34
34
  exitstatus: 0,
35
- all_test_file_paths: [],
35
+ node_test_file_paths: [],
36
36
  }
37
37
  while accumulator[:status] == :next
38
38
  handle_signal!
@@ -47,15 +47,15 @@ module KnapsackPro
47
47
  can_initialize_queue = accumulator.fetch(:can_initialize_queue)
48
48
  args = accumulator.fetch(:args)
49
49
  exitstatus = accumulator.fetch(:exitstatus)
50
- all_test_file_paths = accumulator.fetch(:all_test_file_paths)
50
+ node_test_file_paths = accumulator.fetch(:node_test_file_paths)
51
51
 
52
52
  test_file_paths = runner.test_file_paths(
53
53
  can_initialize_queue: can_initialize_queue,
54
- executed_test_files: all_test_file_paths
54
+ executed_test_files: node_test_file_paths
55
55
  )
56
56
 
57
57
  if test_file_paths.empty?
58
- unless all_test_file_paths.empty?
58
+ unless node_test_file_paths.empty?
59
59
  KnapsackPro::Adapters::MinitestAdapter.verify_bind_method_called
60
60
  end
61
61
 
@@ -76,7 +76,7 @@ module KnapsackPro
76
76
 
77
77
  KnapsackPro::Hooks::Queue.call_before_subset_queue
78
78
 
79
- all_test_file_paths += test_file_paths
79
+ node_test_file_paths += test_file_paths
80
80
 
81
81
  result = minitest_run(runner, test_file_paths, args)
82
82
  exitstatus = 1 unless result
@@ -91,7 +91,7 @@ module KnapsackPro
91
91
  can_initialize_queue: false,
92
92
  args: args,
93
93
  exitstatus: exitstatus,
94
- all_test_file_paths: all_test_file_paths,
94
+ node_test_file_paths: node_test_file_paths,
95
95
  }
96
96
  end
97
97
  end
@@ -4,223 +4,174 @@ module KnapsackPro
4
4
  module Runners
5
5
  module Queue
6
6
  class RSpecRunner < BaseRunner
7
- @@used_seed = nil
8
-
9
- def self.run(args)
7
+ def self.run(args, stream_error = $stderr, stream_out = $stdout)
10
8
  require 'rspec/core'
9
+ require_relative '../../extensions/rspec_extension'
11
10
  require_relative '../../formatters/time_tracker'
12
11
  require_relative '../../formatters/time_tracker_fetcher'
13
- require_relative '../../formatters/rspec_queue_summary_formatter'
14
- require_relative '../../formatters/rspec_queue_profile_formatter_extension'
12
+
13
+ KnapsackPro::Extensions::RSpecExtension.setup!
15
14
 
16
15
  ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_rspec
17
- ENV['KNAPSACK_PRO_QUEUE_RECORDING_ENABLED'] = 'true'
18
- ENV['KNAPSACK_PRO_QUEUE_ID'] = KnapsackPro::Config::EnvGenerator.set_queue_id
19
16
 
20
- KnapsackPro::Config::Env.set_test_runner_adapter(adapter_class)
21
- runner = new(adapter_class)
22
-
23
- cli_args = (args || '').split
24
- adapter_class.ensure_no_tag_option_when_rspec_split_by_test_examples_enabled!(cli_args)
25
-
26
- # when format option is not defined by user then use progress formatter to show tests execution progress
27
- cli_args += ['--format', 'progress'] unless adapter_class.has_format_option?(cli_args)
28
-
29
- cli_args += [
30
- # shows summary of all tests executed in Queue Mode at the very end
31
- '--format', KnapsackPro::Formatters::RSpecQueueSummaryFormatter.to_s,
32
- '--format', KnapsackPro::Formatters::TimeTracker.to_s,
33
- '--default-path', runner.test_dir,
34
- ]
35
-
36
- accumulator = {
37
- status: :next,
38
- runner: runner,
39
- can_initialize_queue: true,
40
- args: cli_args,
41
- exitstatus: 0,
42
- all_test_file_paths: [],
43
- }
44
- while accumulator[:status] == :next
45
- handle_signal!
46
- accumulator = run_tests(accumulator)
47
- end
17
+ rspec_pure = KnapsackPro::Pure::Queue::RSpecPure.new
48
18
 
49
- Kernel.exit(accumulator[:exitstatus])
19
+ queue_runner = new(KnapsackPro::Adapters::RSpecAdapter, rspec_pure, args, stream_error, stream_out)
20
+ queue_runner.run
50
21
  end
51
22
 
52
- def self.run_tests(accumulator)
53
- runner = accumulator.fetch(:runner)
54
- can_initialize_queue = accumulator.fetch(:can_initialize_queue)
55
- args = accumulator.fetch(:args)
56
- exitstatus = accumulator.fetch(:exitstatus)
57
- all_test_file_paths = accumulator.fetch(:all_test_file_paths)
23
+ def initialize(adapter_class, rspec_pure, args, stream_error, stream_out)
24
+ super(adapter_class)
25
+ @adapter_class = adapter_class
26
+ @rspec_pure = rspec_pure
27
+ has_format_option = @adapter_class.has_format_option?((args || '').split)
28
+ @cli_args = rspec_pure.prepare_cli_args(args, has_format_option, test_dir)
29
+ @stream_error = stream_error
30
+ @stream_out = stream_out
31
+ @node_test_file_paths = []
32
+ @rspec_runner = nil # RSpec::Core::Runner is lazy initialized
33
+ end
58
34
 
59
- test_file_paths = runner.test_file_paths(
60
- can_initialize_queue: can_initialize_queue,
61
- executed_test_files: all_test_file_paths
62
- )
35
+ # Based on:
36
+ # https://github.com/rspec/rspec-core/blob/f8c8880dabd8f0544a6f91d8d4c857c1bd8df903/lib/rspec/core/runner.rb#L85
37
+ #
38
+ # @return [Fixnum] exit status code.
39
+ # 0 if all specs passed,
40
+ # or the configured failure exit code (1 by default) if specs failed.
41
+ def run
42
+ pre_run_setup
43
+
44
+ if @rspec_runner.knapsack__wants_to_quit?
45
+ exit_code = @rspec_runner.knapsack__exit_early
46
+ Kernel.exit(exit_code)
47
+ end
48
+
49
+ begin
50
+ exit_code = @rspec_runner.knapsack__run_specs(self)
51
+ rescue KnapsackPro::Runners::Queue::BaseRunner::TerminationError
52
+ exit_code = @rspec_pure.error_exit_code(@rspec_runner.knapsack__error_exit_code)
53
+ Kernel.exit(exit_code)
54
+ rescue Exception => exception
55
+ KnapsackPro.logger.error("An unexpected exception happened. RSpec cannot handle it. The exception: #{exception.inspect}")
63
56
 
64
- if test_file_paths.empty?
65
- unless all_test_file_paths.empty?
66
- KnapsackPro::Adapters::RSpecAdapter.verify_bind_method_called
57
+ message = @rspec_pure.exit_summary(unexecuted_test_files)
58
+ KnapsackPro.logger.warn(message) if message
67
59
 
68
- KnapsackPro::Formatters::RSpecQueueSummaryFormatter.print_summary
69
- KnapsackPro::Formatters::RSpecQueueProfileFormatterExtension.print_summary
60
+ exit_code = @rspec_pure.error_exit_code(@rspec_runner.knapsack__error_exit_code)
61
+ Kernel.exit(exit_code)
62
+ end
70
63
 
71
- args += ['--seed', @@used_seed] if @@used_seed
64
+ post_run_tasks(exit_code)
65
+ end
72
66
 
73
- log_rspec_command(args, all_test_file_paths, :end_of_queue)
74
- end
67
+ def with_batch
68
+ can_initialize_queue = true
75
69
 
76
- KnapsackPro::Hooks::Queue.call_after_queue
70
+ loop do
71
+ handle_signal!
72
+ test_file_paths = pull_tests_from_queue(can_initialize_queue: can_initialize_queue)
73
+ can_initialize_queue = false
77
74
 
78
- time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
79
- KnapsackPro::Report.save_node_queue_to_api(time_tracker&.queue(all_test_file_paths) || [])
75
+ break if test_file_paths.empty?
80
76
 
81
- return {
82
- status: :completed,
83
- exitstatus: exitstatus,
84
- }
85
- else
86
77
  subset_queue_id = KnapsackPro::Config::EnvGenerator.set_subset_queue_id
87
78
  ENV['KNAPSACK_PRO_SUBSET_QUEUE_ID'] = subset_queue_id
88
79
 
89
80
  KnapsackPro::Hooks::Queue.call_before_subset_queue
90
81
 
91
- all_test_file_paths += test_file_paths
92
- cli_args = args + test_file_paths
93
-
94
- ensure_spec_opts_have_knapsack_pro_formatters
95
- options = ::RSpec::Core::ConfigurationOptions.new(cli_args)
96
- rspec_runner = ::RSpec::Core::Runner.new(options)
97
-
98
- begin
99
- exit_code = rspec_runner.run($stderr, $stdout)
100
- exitstatus = exit_code if exit_code != 0
101
- rescue Exception => exception
102
- KnapsackPro.logger.error("Having exception when running RSpec: #{exception.inspect}")
103
- KnapsackPro::Formatters::RSpecQueueSummaryFormatter.print_exit_summary(all_test_file_paths)
104
- KnapsackPro::Hooks::Queue.call_after_subset_queue
105
- KnapsackPro::Hooks::Queue.call_after_queue
106
- Kernel.exit(1)
107
- raise
108
- else
109
- if rspec_runner.world.wants_to_quit
110
- KnapsackPro.logger.warn('RSpec wants to quit.')
111
- set_terminate_process
112
- end
113
- if rspec_runner.world.respond_to?(:rspec_is_quitting) && rspec_runner.world.rspec_is_quitting
114
- KnapsackPro.logger.warn('RSpec is quitting.')
115
- set_terminate_process
116
- end
117
-
118
- printable_args = args_with_seed_option_added_when_viable(args, rspec_runner)
119
- log_rspec_command(printable_args, test_file_paths, :subset_queue)
120
-
121
- rspec_clear_examples
122
-
123
- KnapsackPro::Hooks::Queue.call_after_subset_queue
124
-
125
- return {
126
- status: :next,
127
- runner: runner,
128
- can_initialize_queue: false,
129
- args: args,
130
- exitstatus: exitstatus,
131
- all_test_file_paths: all_test_file_paths,
132
- }
133
- end
134
- end
135
- end
82
+ yield test_file_paths
136
83
 
137
- def self.ensure_spec_opts_have_knapsack_pro_formatters
138
- return unless ENV['SPEC_OPTS']
84
+ KnapsackPro::Hooks::Queue.call_after_subset_queue
139
85
 
140
- if [
141
- ENV['SPEC_OPTS'].include?(KnapsackPro::Formatters::RSpecQueueSummaryFormatter.to_s),
142
- ENV['SPEC_OPTS'].include?(KnapsackPro::Formatters::TimeTracker.to_s),
143
- ].all?
144
- return
145
- end
86
+ if @rspec_runner.knapsack__wants_to_quit?
87
+ KnapsackPro.logger.warn('RSpec wants to quit.')
88
+ set_terminate_process
89
+ end
90
+ if @rspec_runner.knapsack__rspec_is_quitting?
91
+ KnapsackPro.logger.warn('RSpec is quitting.')
92
+ set_terminate_process
93
+ end
146
94
 
147
- unless ENV['SPEC_OPTS'].include?(KnapsackPro::Formatters::RSpecQueueSummaryFormatter.to_s)
148
- ENV['SPEC_OPTS'] = "#{ENV['SPEC_OPTS']} --format #{KnapsackPro::Formatters::RSpecQueueSummaryFormatter}"
95
+ log_rspec_batch_command(test_file_paths)
149
96
  end
97
+ end
150
98
 
151
- unless ENV['SPEC_OPTS'].include?(KnapsackPro::Formatters::TimeTracker.to_s)
152
- ENV['SPEC_OPTS'] = "#{ENV['SPEC_OPTS']} --format #{KnapsackPro::Formatters::TimeTracker}"
153
- end
99
+ def handle_signal!
100
+ self.class.handle_signal!
101
+ end
102
+
103
+ def log_fail_fast_limit_met
104
+ KnapsackPro.logger.warn('Test execution has been canceled because the RSpec --fail-fast option is enabled. It will cause other CI nodes to run tests longer because they need to consume more tests from the Knapsack Pro Queue API.')
154
105
  end
155
106
 
156
107
  private
157
108
 
158
- def self.adapter_class
159
- KnapsackPro::Adapters::RSpecAdapter
109
+ def pre_run_setup
110
+ ENV['KNAPSACK_PRO_QUEUE_RECORDING_ENABLED'] = 'true'
111
+ ENV['KNAPSACK_PRO_QUEUE_ID'] = KnapsackPro::Config::EnvGenerator.set_queue_id
112
+
113
+ KnapsackPro::Config::Env.set_test_runner_adapter(@adapter_class)
114
+
115
+ ENV['SPEC_OPTS'] = @rspec_pure.add_knapsack_pro_formatters_to(ENV['SPEC_OPTS'])
116
+ @adapter_class.ensure_no_tag_option_when_rspec_split_by_test_examples_enabled!(@cli_args)
117
+
118
+ rspec_configuration_options = ::RSpec::Core::ConfigurationOptions.new(@cli_args)
119
+ @rspec_runner = ::RSpec::Core::Runner.new(rspec_configuration_options)
120
+ @rspec_runner.knapsack__setup(@stream_error, @stream_out)
121
+
122
+ ensure_no_deprecated_run_all_when_everything_filtered_option!
160
123
  end
161
124
 
162
- def self.log_rspec_command(cli_args, test_file_paths, type)
163
- case type
164
- when :subset_queue
165
- KnapsackPro.logger.info("To retry the last batch of tests fetched from the API Queue, please run the following command on your machine:")
166
- when :end_of_queue
167
- KnapsackPro.logger.info("To retry all the tests assigned to this CI node, please run the following command on your machine:")
168
- end
125
+ def post_run_tasks(exit_code)
126
+ @adapter_class.verify_bind_method_called
169
127
 
170
- stringified_cli_args = cli_args.join(' ')
171
- .sub(" --format #{KnapsackPro::Formatters::RSpecQueueSummaryFormatter}", '')
172
- .sub(" --format #{KnapsackPro::Formatters::TimeTracker}", '')
128
+ log_rspec_queue_command
173
129
 
174
- KnapsackPro.logger.info(
175
- "bundle exec rspec #{stringified_cli_args} " +
176
- KnapsackPro::TestFilePresenter.stringify_paths(test_file_paths)
177
- )
130
+ time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
131
+ KnapsackPro::Report.save_node_queue_to_api(time_tracker&.queue(@node_test_file_paths))
132
+
133
+ Kernel.exit(exit_code)
178
134
  end
179
135
 
180
- # Clear rspec examples without the shared examples:
181
- # https://github.com/rspec/rspec-core/pull/2379
182
- #
183
- # Keep formatters and report to accumulate info about failed/pending tests
184
- def self.rspec_clear_examples
185
- if ::RSpec::ExampleGroups.respond_to?(:remove_all_constants)
186
- ::RSpec::ExampleGroups.remove_all_constants
187
- else
188
- ::RSpec::ExampleGroups.constants.each do |constant|
189
- ::RSpec::ExampleGroups.__send__(:remove_const, constant)
190
- end
191
- end
192
- ::RSpec.world.example_groups.clear
193
- ::RSpec.configuration.start_time = ::RSpec::Core::Time.now
194
-
195
- if KnapsackPro::Config::Env.rspec_split_by_test_examples?
196
- # Reset example group counts to ensure scoped example ids in metadata
197
- # have correct index (not increased by each subsequent run).
198
- # Solves this problem: https://github.com/rspec/rspec-core/issues/2721
199
- ::RSpec.world.instance_variable_set(:@example_group_counts_by_spec_file, Hash.new(0))
200
- end
136
+ def ensure_no_deprecated_run_all_when_everything_filtered_option!
137
+ return unless @rspec_runner.knapsack__deprecated_run_all_when_everything_filtered_enabled?
201
138
 
202
- # skip reset filters for old RSpec versions
203
- if ::RSpec.configuration.respond_to?(:reset_filters)
204
- ::RSpec.configuration.reset_filters
205
- end
139
+ error_message = "The run_all_when_everything_filtered option is deprecated. See: #{KnapsackPro::Urls::RSPEC__DEPRECATED_RUN_ALL_WHEN_EVERYTHING_FILTERED}"
140
+ KnapsackPro.logger.error(error_message)
141
+ raise error_message
206
142
  end
207
143
 
208
- def self.args_with_seed_option_added_when_viable(args, rspec_runner)
209
- order_option = adapter_class.order_option(args)
144
+ def pull_tests_from_queue(can_initialize_queue: false)
145
+ test_file_paths = test_file_paths(
146
+ can_initialize_queue: can_initialize_queue,
147
+ executed_test_files: @node_test_file_paths
148
+ )
149
+ @node_test_file_paths += test_file_paths
150
+ test_file_paths
151
+ end
210
152
 
211
- if order_option
212
- # Don't add the seed option for order other than random, e.g. `defined`
213
- return args unless order_option.include?('rand')
214
- # Don't add the seed option if the seed is already set in args, e.g. `rand:12345`
215
- return args if order_option.to_s.split(':')[1]
216
- end
153
+ def log_rspec_batch_command(test_file_paths)
154
+ order_option = @adapter_class.order_option(@cli_args)
155
+ printable_args = @rspec_pure.args_with_seed_option_added_when_viable(order_option, @rspec_runner.knapsack__seed, @cli_args)
156
+ messages = @rspec_pure.rspec_command(printable_args, test_file_paths, :batch_finished)
157
+ log_info_messages(messages)
158
+ end
217
159
 
218
- # Don't add the seed option if the seed was not used (i.e. a different order is being used, e.g. `defined`)
219
- return args unless rspec_runner.configuration.seed_used?
160
+ def log_rspec_queue_command
161
+ order_option = @adapter_class.order_option(@cli_args)
162
+ printable_args = @rspec_pure.args_with_seed_option_added_when_viable(order_option, @rspec_runner.knapsack__seed, @cli_args)
163
+ messages = @rspec_pure.rspec_command(printable_args, @node_test_file_paths, :queue_finished)
164
+ log_info_messages(messages)
165
+ end
220
166
 
221
- @@used_seed = rspec_runner.configuration.seed.to_s
167
+ def log_info_messages(messages)
168
+ messages.each do |message|
169
+ KnapsackPro.logger.info(message)
170
+ end
171
+ end
222
172
 
223
- args + ['--seed', @@used_seed]
173
+ def unexecuted_test_files
174
+ KnapsackPro::Formatters::TimeTrackerFetcher.unexecuted_test_files(@node_test_file_paths)
224
175
  end
225
176
  end
226
177
  end