knapsack_pro 6.0.4 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) 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 +95 -0
  6. data/Gemfile +9 -0
  7. data/README.md +7 -9
  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 +24 -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 +100 -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 +127 -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 +2405 -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 +56 -24
  30. data/spec/knapsack_pro/config/env_spec.rb +1 -35
  31. data/spec/knapsack_pro/formatters/time_tracker_fetcher_spec.rb +30 -0
  32. data/spec/knapsack_pro/formatters/time_tracker_specs.rb +8 -37
  33. data/spec/knapsack_pro/hooks/queue_spec.rb +2 -2
  34. data/spec/knapsack_pro/presenter_spec.rb +1 -1
  35. data/spec/knapsack_pro/pure/queue/rspec_pure_spec.rb +248 -0
  36. data/spec/knapsack_pro/runners/queue/cucumber_runner_spec.rb +16 -16
  37. data/spec/knapsack_pro/runners/queue/minitest_runner_spec.rb +14 -14
  38. data/spec/knapsack_pro_spec.rb +3 -3
  39. metadata +19 -12
  40. data/lib/knapsack_pro/formatters/rspec_queue_profile_formatter_extension.rb +0 -58
  41. data/lib/knapsack_pro/formatters/rspec_queue_summary_formatter.rb +0 -145
  42. 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,100 @@
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, has_require_rails_helper_option, rails_helper_exists, test_dir)
38
+ (args || '').split
39
+ .yield_self { args_with_at_least_one_formatter(_1, has_format_option) }
40
+ .yield_self { args_with_require_rails_helper_if_needed(_1, has_require_rails_helper_option, rails_helper_exists) }
41
+ .yield_self { args_with_default_options(_1, test_dir) }
42
+ end
43
+
44
+ def rspec_command(args, test_file_paths, scope)
45
+ messages = []
46
+ return messages if test_file_paths.empty?
47
+
48
+ case scope
49
+ when :batch_finished
50
+ messages << 'To retry the last batch of tests fetched from the Queue API, please run the following command on your machine:'
51
+ when :queue_finished
52
+ messages << 'To retry all the tests assigned to this CI node, please run the following command on your machine:'
53
+ end
54
+
55
+ stringified_cli_args = args.join(' ')
56
+ FORMATTERS.each do |formatter|
57
+ stringified_cli_args.sub!(" --format #{formatter}", '')
58
+ end
59
+
60
+ messages << "bundle exec rspec #{stringified_cli_args} " + KnapsackPro::TestFilePresenter.stringify_paths(test_file_paths)
61
+
62
+ messages
63
+ end
64
+
65
+ def exit_summary(unexecuted_test_files)
66
+ return if unexecuted_test_files.empty?
67
+
68
+ "Unexecuted tests on this CI node (including pending tests): #{unexecuted_test_files.join(' ')}"
69
+ end
70
+
71
+ private
72
+
73
+ def args_with_at_least_one_formatter(cli_args, has_format_option)
74
+ return cli_args if has_format_option
75
+
76
+ cli_args + ['--format', 'progress']
77
+ end
78
+
79
+ def args_with_require_rails_helper_if_needed(cli_args, has_require_rails_helper_option, rails_helper_exists)
80
+ return cli_args if has_require_rails_helper_option
81
+ return cli_args unless rails_helper_exists
82
+
83
+ cli_args + ['--require', 'rails_helper']
84
+ end
85
+
86
+ def args_with_default_options(cli_args, test_dir)
87
+ new_cli_args = cli_args + [
88
+ '--default-path', test_dir,
89
+ ]
90
+
91
+ FORMATTERS.each do |formatter|
92
+ new_cli_args += ['--format', formatter]
93
+ end
94
+
95
+ new_cli_args
96
+ end
97
+ end
98
+ end
99
+ end
100
+ 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