knapsack_pro 5.7.0 → 6.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 87b8a5c7eb2a78f487c9029cdeda3a1cc367b15e248eb1f3b5aaaf1959ebe333
4
- data.tar.gz: 12fef31bb8aac248014b3d808190f7f2e7ae6217e83e4af9e40b17b6f98ed379
3
+ metadata.gz: 315a4ff2f5885be68529cc0799affd8163fcad51558e4813a254b99bd057992a
4
+ data.tar.gz: 5709757ca30a22401be4dc170a926b16f5206d494d5b80c583bf03088d8570a6
5
5
  SHA512:
6
- metadata.gz: 43379ac46ef1c5fa6a3fccf0d598831e009564ba37bdb264c681799aa893d5a9d8fbb569ce2bd83fe297b9c885117e95a5439731fd16ea35aa2f294acb88d936
7
- data.tar.gz: 1be1143ac276347a0dee92bb15b7660f36b666c9cb8d025e9b1931f463803f9efc2a38ced34d4ca27bbfaa466fc1b6afc71d2960ced12b09888ca09a6572b14b
6
+ metadata.gz: c43ee2474109d5e509fb57534857164ec0596cb018f61b44942ab261bdcb55345ac3025239fd3dde12c7d31f30841aa39aa480fb07bcab1519272788dad9c4b7
7
+ data.tar.gz: 9511b45fb2b0d59a309517ccc70ca10ebbb2b8a3cf0cfb2f2198e38a776838a402b531f64aea2ca94ce37489288dde8171b5fd36c45038014d781cba0fd5462d
data/.circleci/config.yml CHANGED
@@ -41,3 +41,5 @@ jobs:
41
41
  - run: rubocop -A --only Style/FrozenStringLiteralComment,Layout/EmptyLineAfterMagicComment lib/
42
42
 
43
43
  - run: bundle exec rspec spec
44
+
45
+ - run: bundle exec ruby spec/knapsack_pro/formatters/time_tracker_specs.rb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ### 6.0.0
4
+
5
+ * __(breaking change)__ Dropped support for Turnip < 2.0.0
6
+ * Use an RSpec Formatter to track tests' execution times more accurately
7
+ * Removed `Time.raw_now`
8
+
9
+ https://github.com/KnapsackPro/knapsack_pro-ruby/pull/229
10
+
11
+ https://github.com/KnapsackPro/knapsack_pro-ruby/compare/v5.7.0...UNRELEASED
12
+
3
13
  ### 5.7.0
4
14
 
5
15
  * Performance improvement: don't run `rake knapsack_pro:rspec_test_example_detector` when no slow test files are detected for RSpec.
data/README.md CHANGED
@@ -74,7 +74,7 @@ bundle update knapsack_pro
74
74
  RSpec:
75
75
 
76
76
  ```bash
77
- bundle exec rspec spec
77
+ bin/test
78
78
  ```
79
79
 
80
80
  Scripted tests can be found in the [Rails App With Knapsack Pro repository](https://github.com/KnapsackPro/rails-app-with-knapsack_pro/blob/master/bin/knapsack_pro_all.rb).
data/bin/test ADDED
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+
3
+ bundle exec ruby spec/knapsack_pro/formatters/time_tracker_specs.rb
4
+ export FORMATTERS_EXIT_CODE=$?
5
+
6
+ bundle exec rspec spec
7
+ export RSPEC_EXIT_CODE=$?
8
+
9
+ if [ "$FORMATTERS_EXIT_CODE" -ne "0" ]; then
10
+ exit $FORMATTERS_EXIT_CODE
11
+ fi
12
+
13
+ if [ "$RSPEC_EXIT_CODE" -ne "0" ]; then
14
+ exit $RSPEC_EXIT_CODE
15
+ fi
data/knapsack_pro.gemspec CHANGED
@@ -4,10 +4,10 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'knapsack_pro/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "knapsack_pro"
7
+ spec.name = 'knapsack_pro'
8
8
  spec.version = KnapsackPro::VERSION
9
9
  spec.authors = ['ArturT']
10
- spec.email = ['arturtrzop@gmail.com']
10
+ spec.email = ['support@knapsackpro.com']
11
11
  spec.summary = %q{Knapsack Pro splits tests across parallel CI nodes and ensures each parallel job finish work at a similar time.}
12
12
  spec.description = %q{Run tests in parallel across CI server nodes based on tests execution time. Split tests in a dynamic way to ensure parallel jobs are done at a similar time. Thanks to that your CI build time is as fast as possible. It works with many CI providers.}
13
13
  spec.homepage = 'https://knapsackpro.com'
@@ -15,15 +15,15 @@ Gem::Specification.new do |spec|
15
15
  spec.metadata = {
16
16
  'bug_tracker_uri' => 'https://github.com/KnapsackPro/knapsack_pro-ruby/issues',
17
17
  'changelog_uri' => 'https://github.com/KnapsackPro/knapsack_pro-ruby/blob/master/CHANGELOG.md',
18
- 'documentation_uri' => 'https://docs.knapsackpro.com/integration/',
18
+ 'documentation_uri' => 'https://docs.knapsackpro.com/knapsack_pro-ruby/guide/',
19
19
  'homepage_uri' => 'https://knapsackpro.com',
20
20
  'source_code_uri' => 'https://github.com/KnapsackPro/knapsack_pro-ruby'
21
21
  }
22
22
 
23
23
  spec.files = `git ls-files -z`.split("\x0")
24
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
+ spec.executables = ['knapsack_pro']
25
25
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
26
- spec.require_paths = ["lib"]
26
+ spec.require_paths = ['lib']
27
27
 
28
28
  spec.add_dependency 'rake', '>= 0'
29
29
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../formatters/time_tracker_fetcher'
4
+
3
5
  module KnapsackPro
4
6
  module Adapters
5
7
  class RSpecAdapter < BaseAdapter
@@ -54,63 +56,61 @@ module KnapsackPro
54
56
  parsed_options(cli_args)&.[](:order)
55
57
  end
56
58
 
57
- def self.test_path(example)
58
- example_group = example.metadata[:example_group]
59
-
60
- if defined?(::Turnip) && Gem::Version.new(::Turnip::VERSION) < Gem::Version.new('2.0.0')
61
- unless example_group[:turnip]
62
- until example_group[:parent_example_group].nil?
63
- example_group = example_group[:parent_example_group]
64
- end
59
+ def self.file_path_for(example)
60
+ [
61
+ -> { parse_file_path(example.id) },
62
+ -> { example.metadata[:file_path] },
63
+ -> { example.metadata[:example_group][:file_path] },
64
+ -> { top_level_group(example)[:file_path] },
65
+ ]
66
+ .each do |path|
67
+ p = path.call
68
+ return p if p.include?('_spec.rb')
65
69
  end
66
- else
67
- until example_group[:parent_example_group].nil?
68
- example_group = example_group[:parent_example_group]
69
- end
70
- end
71
70
 
72
- example_group[:file_path]
71
+ return ''
73
72
  end
74
73
 
75
- def bind_time_tracker
76
- ::RSpec.configure do |config|
77
- config.prepend_before(:context) do
78
- KnapsackPro.tracker.start_timer
79
- end
74
+ def self.parse_file_path(id)
75
+ # https://github.com/rspec/rspec-core/blob/1eeadce5aa7137ead054783c31ff35cbfe9d07cc/lib/rspec/core/example.rb#L122
76
+ id.match(/\A(.*?)(?:\[([\d\s:,]+)\])?\z/).captures.first
77
+ end
80
78
 
81
- config.around(:each) do |example|
82
- current_test_path = KnapsackPro::Adapters::RSpecAdapter.test_path(example)
83
-
84
- # Stop timer to update time for a previously run test example.
85
- # This way we count time spent in runtime for the previous test example after around(:each) is already done.
86
- # Only do that if we're in the same test file. Otherwise, `before(:all)` execution time in the current file
87
- # will be applied to the previously ran test file.
88
- if KnapsackPro.tracker.current_test_path&.start_with?(KnapsackPro::TestFileCleaner.clean(current_test_path))
89
- KnapsackPro.tracker.stop_timer
90
- end
79
+ # private
80
+ def self.top_level_group(example)
81
+ group = example.metadata[:example_group]
82
+ until group[:parent_example_group].nil?
83
+ group = group[:parent_example_group]
84
+ end
85
+ group
86
+ end
91
87
 
92
- KnapsackPro.tracker.current_test_path =
93
- if KnapsackPro::Config::Env.rspec_split_by_test_examples? && KnapsackPro::Adapters::RSpecAdapter.slow_test_file?(RSpecAdapter, current_test_path)
94
- example.id
95
- else
96
- current_test_path
97
- end
88
+ def bind_time_tracker
89
+ ensure_no_focus!
90
+ log_batch_duration
91
+ end
98
92
 
93
+ def ensure_no_focus!
94
+ ::RSpec.configure do |config|
95
+ config.around(:each) do |example|
99
96
  if example.metadata[:focus] && KnapsackPro::Adapters::RSpecAdapter.rspec_configuration.filter.rules[:focus]
100
- raise "We detected a test file path #{current_test_path} with a test using the metadata `:focus` tag. RSpec might not run some tests in the Queue Mode (causing random tests skipping problem). Please remove the `:focus` tag from your codebase. See more: #{KnapsackPro::Urls::RSPEC__SKIPS_TESTS}"
97
+ file_path = KnapsackPro::Adapters::RSpecAdapter.file_path_for(example)
98
+ file_path = KnapsackPro::TestFileCleaner.clean(file_path)
99
+
100
+ raise "Knapsack Pro found an example tagged with focus in #{file_path}, please remove it. See more: #{KnapsackPro::Urls::RSPEC__SKIPS_TESTS}"
101
101
  end
102
102
 
103
103
  example.run
104
104
  end
105
+ end
106
+ end
105
107
 
106
- config.append_after(:context) do
107
- # after(:context) hook is run one time only, after all of the examples in a group
108
- # stop timer to count time for the very last executed test example
109
- KnapsackPro.tracker.stop_timer
110
- end
111
-
108
+ def log_batch_duration
109
+ ::RSpec.configure do |config|
112
110
  config.after(:suite) do
113
- KnapsackPro.logger.debug(KnapsackPro::Presenter.global_time)
111
+ time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
112
+ formatted = KnapsackPro::Presenter.global_time(time_tracker.batch_duration)
113
+ KnapsackPro.logger.debug(formatted)
114
114
  end
115
115
  end
116
116
  end
@@ -118,7 +118,8 @@ module KnapsackPro
118
118
  def bind_save_report
119
119
  ::RSpec.configure do |config|
120
120
  config.after(:suite) do
121
- KnapsackPro::Report.save
121
+ time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
122
+ KnapsackPro::Report.save(time_tracker.batch)
122
123
  end
123
124
  end
124
125
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './time_tracker_fetcher'
4
+
3
5
  RSpec::Support.require_rspec_core('formatters/base_formatter')
4
6
  RSpec::Support.require_rspec_core('formatters/base_text_formatter')
5
7
 
@@ -76,11 +78,12 @@ module KnapsackPro
76
78
  registered_output.puts(most_recent_summary)
77
79
  end
78
80
 
79
- def self.print_exit_summary
81
+ def self.print_exit_summary(all_test_file_paths)
80
82
  registered_output.puts('Knapsack Pro Queue exited/aborted!')
81
83
  registered_output.puts('')
82
84
 
83
- unexecuted_test_files = KnapsackPro.tracker.unexecuted_test_files
85
+ time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
86
+ unexecuted_test_files = time_tracker&.unexecuted_test_files(all_test_file_paths) || []
84
87
  unless unexecuted_test_files.empty?
85
88
  registered_output.puts('Unexecuted tests on this CI node:')
86
89
  registered_output.puts(unexecuted_test_files)
@@ -119,7 +122,7 @@ module KnapsackPro
119
122
 
120
123
  def dump_summary(summary)
121
124
  colorizer = ::RSpec::Core::Formatters::ConsoleCodes
122
- duration = KnapsackPro.tracker.global_time_since_beginning
125
+ duration = KnapsackPro::Formatters::TimeTrackerFetcher.call.duration
123
126
  formatted_duration = ::RSpec::Core::Formatters::Helpers.format_duration(duration)
124
127
 
125
128
  formatted = "\nFinished in #{formatted_duration}\n" \
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module KnapsackPro
4
+ module Formatters
5
+ class TimeTracker
6
+ ::RSpec::Core::Formatters.register self,
7
+ :example_group_started,
8
+ :example_started,
9
+ :example_finished,
10
+ :example_group_finished,
11
+ :stop
12
+
13
+ # Called at the beginning of each batch,
14
+ # but only the first instance of this class is used,
15
+ # so don't rely on the initializer to reset values.
16
+ def initialize(_output)
17
+ @time_each = nil
18
+ @time_all = nil
19
+ @before_all = 0.0
20
+ @group = {}
21
+ @batch = {}
22
+ @queue = {}
23
+ @suite_started = now
24
+ @batch_started = now
25
+ end
26
+
27
+ def example_group_started(notification)
28
+ return unless top_level_group?(notification.group)
29
+ @time_all = now
30
+ end
31
+
32
+ def example_started(_notification)
33
+ @before_all = now - @time_all if @before_all == 0.0
34
+ @time_each = now
35
+ end
36
+
37
+ def example_finished(notification)
38
+ record_example(@group, notification.example, @time_each)
39
+ @time_all = now
40
+ end
41
+
42
+ def example_group_finished(notification)
43
+ return unless top_level_group?(notification.group)
44
+
45
+ add_hooks_time(@group, @before_all, now - @time_all)
46
+ @batch = merge(@batch, @group)
47
+ @before_all = 0.0
48
+ @group = {}
49
+ end
50
+
51
+ # Called at the end of each batch
52
+ def stop(_notification)
53
+ @queue = merge(@queue, @batch)
54
+ @batch = {}
55
+ @batch_started = now
56
+ end
57
+
58
+ def queue(scheduled_paths)
59
+ recorded_paths = @queue.values.map do |example|
60
+ KnapsackPro::Adapters::RSpecAdapter.parse_file_path(example[:path])
61
+ end
62
+
63
+ missing = (scheduled_paths - recorded_paths).each_with_object({}) do |path, object|
64
+ object[path] = { path: path, time_execution: 0.0 }
65
+ end
66
+
67
+ merge(@queue, missing).values.map do |example|
68
+ example.transform_keys(&:to_s)
69
+ end
70
+ end
71
+
72
+ def batch
73
+ @batch.values.map do |example|
74
+ example.transform_keys(&:to_s)
75
+ end
76
+ end
77
+
78
+ def duration
79
+ now - @suite_started
80
+ end
81
+
82
+ def batch_duration
83
+ now - @batch_started
84
+ end
85
+
86
+ def unexecuted_test_files(scheduled_paths)
87
+ pending_paths = (@queue.values + @batch.values)
88
+ .filter { |example| example[:time_execution] == 0.0 }
89
+ .map { |example| example[:path] }
90
+
91
+ not_run_paths = scheduled_paths -
92
+ (@queue.values + @batch.values)
93
+ .map { |example| example[:path] }
94
+
95
+ pending_paths + not_run_paths
96
+ end
97
+
98
+ private
99
+
100
+ def top_level_group?(group)
101
+ group.metadata[:parent_example_group].nil?
102
+ end
103
+
104
+ def add_hooks_time(group, before_all, after_all)
105
+ group.each do |_, example|
106
+ next if example[:time_execution] == 0.0
107
+ example[:time_execution] += before_all + after_all
108
+ end
109
+ end
110
+
111
+ def record_example(accumulator, example, started_at)
112
+ path = path_for(example)
113
+ time_execution = time_execution_for(example, started_at)
114
+
115
+ if accumulator.key?(path)
116
+ accumulator[path][:time_execution] += time_execution
117
+ else
118
+ accumulator[path] = { path: path, time_execution: time_execution }
119
+ end
120
+ end
121
+
122
+ def path_for(example)
123
+ file = file_path_for(example)
124
+ return "UNKNOWN_PATH" if file == ""
125
+ path = rspec_split_by_test_example?(file) ? example.id : file
126
+ KnapsackPro::TestFileCleaner.clean(path)
127
+ end
128
+
129
+ def rspec_split_by_test_example?(file)
130
+ return false unless KnapsackPro::Config::Env.rspec_split_by_test_examples?
131
+ return false unless KnapsackPro::Adapters::RSpecAdapter.slow_test_file?(KnapsackPro::Adapters::RSpecAdapter, file)
132
+ true
133
+ end
134
+
135
+ def file_path_for(example)
136
+ KnapsackPro::Adapters::RSpecAdapter.file_path_for(example)
137
+ end
138
+
139
+ def time_execution_for(example, started_at)
140
+ if example.execution_result.status.to_s == "pending"
141
+ 0.0
142
+ else
143
+ (now - started_at).to_f
144
+ end
145
+ end
146
+
147
+ def merge(h1, h2)
148
+ h1.merge(h2) do |key, v1, v2|
149
+ {
150
+ path: key,
151
+ time_execution: v1[:time_execution] + v2[:time_execution]
152
+ }
153
+ end
154
+ end
155
+
156
+ def now
157
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,12 @@
1
+ module KnapsackPro
2
+ module Formatters
3
+ class TimeTrackerFetcher
4
+ def self.call
5
+ ::RSpec
6
+ .configuration
7
+ .formatters
8
+ .find { |f| f.class.to_s == "KnapsackPro::Formatters::TimeTracker" }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -3,8 +3,9 @@
3
3
  module KnapsackPro
4
4
  class Presenter
5
5
  class << self
6
- def global_time
7
- global_time = pretty_seconds(KnapsackPro.tracker.global_time)
6
+ def global_time(time = nil)
7
+ time = KnapsackPro.tracker.global_time if time.nil?
8
+ global_time = pretty_seconds(time)
8
9
  "Global time execution for tests: #{global_time}"
9
10
  end
10
11
 
@@ -2,15 +2,17 @@
2
2
 
3
3
  module KnapsackPro
4
4
  class Report
5
- def self.save
6
- test_files = KnapsackPro.tracker.to_a
5
+ def self.save(tests = nil)
6
+ if tests.nil?
7
+ tests = KnapsackPro.tracker.to_a
8
+ end
7
9
 
8
- if test_files.empty?
10
+ if tests.empty?
9
11
  KnapsackPro.logger.warn("No test files were executed on this CI node.")
10
12
  KnapsackPro.logger.debug("When you use knapsack_pro Regular Mode, the reason for no tests executing might be a very narrow tests list. Most likely, you run only tests with a specified tag, and there were fewer test files with the tag than parallel CI nodes.")
11
13
  end
12
14
 
13
- create_build_subset(test_files)
15
+ create_build_subset(tests)
14
16
  end
15
17
 
16
18
  def self.save_subset_queue_to_file
@@ -30,11 +32,13 @@ module KnapsackPro
30
32
  end
31
33
  end
32
34
 
33
- def self.save_node_queue_to_api
34
- test_files = []
35
- Dir.glob("#{queue_path}/*.json").each do |file|
36
- report = JSON.parse(File.read(file))
37
- test_files += report
35
+ def self.save_node_queue_to_api(test_files = nil)
36
+ if test_files.nil?
37
+ test_files = []
38
+ Dir.glob("#{queue_path}/*.json").each do |file|
39
+ report = JSON.parse(File.read(file))
40
+ test_files += report
41
+ end
38
42
  end
39
43
 
40
44
  if test_files.empty?
@@ -8,6 +8,8 @@ module KnapsackPro
8
8
 
9
9
  def self.run(args)
10
10
  require 'rspec/core'
11
+ require_relative '../../formatters/time_tracker'
12
+ require_relative '../../formatters/time_tracker_fetcher'
11
13
  require_relative '../../formatters/rspec_queue_summary_formatter'
12
14
  require_relative '../../formatters/rspec_queue_profile_formatter_extension'
13
15
 
@@ -27,6 +29,7 @@ module KnapsackPro
27
29
  cli_args += [
28
30
  # shows summary of all tests executed in Queue Mode at the very end
29
31
  '--format', KnapsackPro::Formatters::RSpecQueueSummaryFormatter.to_s,
32
+ '--format', KnapsackPro::Formatters::TimeTracker.to_s,
30
33
  '--default-path', runner.test_dir,
31
34
  ]
32
35
 
@@ -72,7 +75,8 @@ module KnapsackPro
72
75
 
73
76
  KnapsackPro::Hooks::Queue.call_after_queue
74
77
 
75
- KnapsackPro::Report.save_node_queue_to_api
78
+ time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
79
+ KnapsackPro::Report.save_node_queue_to_api(time_tracker&.queue(all_test_file_paths) || [])
76
80
 
77
81
  return {
78
82
  status: :completed,
@@ -82,15 +86,12 @@ module KnapsackPro
82
86
  subset_queue_id = KnapsackPro::Config::EnvGenerator.set_subset_queue_id
83
87
  ENV['KNAPSACK_PRO_SUBSET_QUEUE_ID'] = subset_queue_id
84
88
 
85
- KnapsackPro.tracker.reset!
86
- KnapsackPro.tracker.set_prerun_tests(test_file_paths)
87
-
88
89
  KnapsackPro::Hooks::Queue.call_before_subset_queue
89
90
 
90
91
  all_test_file_paths += test_file_paths
91
92
  cli_args = args + test_file_paths
92
93
 
93
- ensure_spec_opts_have_rspec_queue_summary_formatter
94
+ ensure_spec_opts_have_knapsack_pro_formatters
94
95
  options = ::RSpec::Core::ConfigurationOptions.new(cli_args)
95
96
  rspec_runner = ::RSpec::Core::Runner.new(options)
96
97
 
@@ -99,7 +100,7 @@ module KnapsackPro
99
100
  exitstatus = exit_code if exit_code != 0
100
101
  rescue Exception => exception
101
102
  KnapsackPro.logger.error("Having exception when running RSpec: #{exception.inspect}")
102
- KnapsackPro::Formatters::RSpecQueueSummaryFormatter.print_exit_summary
103
+ KnapsackPro::Formatters::RSpecQueueSummaryFormatter.print_exit_summary(all_test_file_paths)
103
104
  KnapsackPro::Hooks::Queue.call_after_subset_queue
104
105
  KnapsackPro::Hooks::Queue.call_after_queue
105
106
  Kernel.exit(1)
@@ -121,8 +122,6 @@ module KnapsackPro
121
122
 
122
123
  KnapsackPro::Hooks::Queue.call_after_subset_queue
123
124
 
124
- KnapsackPro::Report.save_subset_queue_to_file
125
-
126
125
  return {
127
126
  status: :next,
128
127
  runner: runner,
@@ -135,13 +134,23 @@ module KnapsackPro
135
134
  end
136
135
  end
137
136
 
138
- def self.ensure_spec_opts_have_rspec_queue_summary_formatter
139
- spec_opts = ENV['SPEC_OPTS']
137
+ def self.ensure_spec_opts_have_knapsack_pro_formatters
138
+ return unless ENV['SPEC_OPTS']
139
+
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
140
146
 
141
- return unless spec_opts
142
- return if spec_opts.include?(KnapsackPro::Formatters::RSpecQueueSummaryFormatter.to_s)
147
+ unless ENV['SPEC_OPTS'].include?(KnapsackPro::Formatters::RSpecQueueSummaryFormatter.to_s)
148
+ ENV['SPEC_OPTS'] = "#{ENV['SPEC_OPTS']} --format #{KnapsackPro::Formatters::RSpecQueueSummaryFormatter}"
149
+ end
143
150
 
144
- ENV['SPEC_OPTS'] = "#{spec_opts} --format #{KnapsackPro::Formatters::RSpecQueueSummaryFormatter.to_s}"
151
+ unless ENV['SPEC_OPTS'].include?(KnapsackPro::Formatters::TimeTracker.to_s)
152
+ ENV['SPEC_OPTS'] = "#{ENV['SPEC_OPTS']} --format #{KnapsackPro::Formatters::TimeTracker}"
153
+ end
145
154
  end
146
155
 
147
156
  private
@@ -158,7 +167,9 @@ module KnapsackPro
158
167
  KnapsackPro.logger.info("To retry all the tests assigned to this CI node, please run the following command on your machine:")
159
168
  end
160
169
 
161
- stringified_cli_args = cli_args.join(' ').sub(" --format #{KnapsackPro::Formatters::RSpecQueueSummaryFormatter}", '')
170
+ stringified_cli_args = cli_args.join(' ')
171
+ .sub(" --format #{KnapsackPro::Formatters::RSpecQueueSummaryFormatter}", '')
172
+ .sub(" --format #{KnapsackPro::Formatters::TimeTracker}", '')
162
173
 
163
174
  KnapsackPro.logger.info(
164
175
  "bundle exec rspec #{stringified_cli_args} " +
@@ -19,8 +19,6 @@ module KnapsackPro
19
19
  cli_args = (args || '').split
20
20
  adapter_class.ensure_no_tag_option_when_rspec_split_by_test_examples_enabled!(cli_args)
21
21
 
22
- KnapsackPro.tracker.set_prerun_tests(runner.test_file_paths)
23
-
24
22
  require 'rspec/core/rake_task'
25
23
 
26
24
  task_name = 'knapsack_pro:rspec_run'
@@ -33,12 +31,30 @@ module KnapsackPro
33
31
  # because pattern does not accept test example path like spec/a_spec.rb[1:2]
34
32
  # instead we pass test files and test example paths to t.rspec_opts
35
33
  t.pattern = []
36
- t.rspec_opts = "#{args} --default-path #{runner.test_dir} #{runner.stringify_test_file_paths}"
34
+ t.rspec_opts = "#{args} #{self.formatters} --default-path #{runner.test_dir} #{runner.stringify_test_file_paths}"
37
35
  t.verbose = KnapsackPro::Config::Env.log_level < ::Logger::WARN
38
36
  end
39
37
  Rake::Task[task_name].invoke
40
38
  end
41
39
  end
40
+
41
+ # Use RSpec::Core::ConfigurationOptions to respect external configurations like .rspec
42
+ def self.formatters
43
+ require_relative '../formatters/time_tracker'
44
+
45
+ formatters = ::RSpec::Core::ConfigurationOptions
46
+ .new([])
47
+ .options
48
+ .fetch(:formatters, [])
49
+ .map do |formatter, output|
50
+ arg = "--format #{formatter}"
51
+ arg += " --out #{output}" if output
52
+ arg
53
+ end
54
+ formatters = ['--format progress'] if formatters.empty?
55
+ formatters += ["--format #{KnapsackPro::Formatters::TimeTracker}"]
56
+ formatters.join(' ')
57
+ end
42
58
  end
43
59
  end
44
60
  end
@@ -8,11 +8,10 @@ module KnapsackPro
8
8
  # to better allocate it in Queue Mode for future CI build runs
9
9
  DEFAULT_TEST_FILE_TIME = 0.0 # seconds
10
10
 
11
- attr_reader :global_time_since_beginning, :global_time, :test_files_with_time, :prerun_tests_loaded
11
+ attr_reader :global_time, :test_files_with_time, :prerun_tests_loaded
12
12
  attr_writer :current_test_path
13
13
 
14
14
  def initialize
15
- @global_time_since_beginning = 0
16
15
  KnapsackPro::Config::TempFiles.ensure_temp_directory_exists!
17
16
  FileUtils.mkdir_p(tracker_dir_path)
18
17
  set_defaults
@@ -70,12 +69,6 @@ module KnapsackPro
70
69
  @prerun_tests_loaded = true
71
70
  end
72
71
 
73
- def unexecuted_test_files
74
- @test_files_with_time.map do |path, hash|
75
- path unless hash[:measured_time]
76
- end.compact
77
- end
78
-
79
72
  def to_a
80
73
  # When the test files are not loaded in the memory then load them from the disk.
81
74
  # Useful for the Regular Mode when the memory is not shared between tracker instances.
@@ -141,7 +134,6 @@ module KnapsackPro
141
134
 
142
135
  def update_global_time(execution_time)
143
136
  @global_time += execution_time
144
- @global_time_since_beginning += execution_time
145
137
  end
146
138
 
147
139
  def update_test_file_time(execution_time)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KnapsackPro
4
- VERSION = '5.7.0'
4
+ VERSION = '6.0.0'
5
5
  end