knapsack_pro 5.7.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
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