rspec-conductor 1.0.6 → 1.0.7

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: 53a28166d7b4df7bb4cfa3433a706ba3a5f892f4011535bb801718bc7886a638
4
- data.tar.gz: 360ea91cb2e087b0f561616af2d37442bdbdb572b54520658a638feb2d7ebba4
3
+ metadata.gz: 6cb890370ce6bc38b652f6c998ca34fa8974c73677bb53794d5adddeda060db2
4
+ data.tar.gz: 3b2124f2014a5ae4c7f5e7c3b5aebeb8b6c1b624cab65d29b31d0219d23ac358
5
5
  SHA512:
6
- metadata.gz: db723da5eb4be4e544bb4890427a83f96944b6c2fb293845fa5a892cb356d030d407ea246f46331d52f231a7158565e3ce18d4c0127adefe8a38fe515e0d738a
7
- data.tar.gz: 3ceb9915c3da36f68c499cbe5d65787b9f88f46ed049e51c619b0b479d7e101fc963a2b90e62da0afba174e9960c07426d71b10cf13e8324edebf11df33a61ac
6
+ metadata.gz: a68df5d5e08d98b6c1b24a9e843432fa78e54c594dc0ff360bb49294de55d6f6eb730788c8d4386e2aa57e0856fdd18536560289f4dd2a22a228a67c1e9f5a8f
7
+ data.tar.gz: a88156e67f821bbeb2ef00bce93e2b6b327d5000fe2876a34c422a324294879417d63a30d260990775f6cd405c74811f03f62747ea782fc4757bcbcb75241062
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## [1.0.7] - 2026-02-16
2
+
3
+ - Move all output code into the formatter base class (lay some groundwork to address some minor issues with the fancy formatter)
4
+ - Disable rspec's --require command line parameter. Use --prefork-require and --postfork-require in the rspec-conductor cli instead (reported by @cb341)
5
+
1
6
  ## [1.0.6] - 2026-02-14
2
7
 
3
8
  - Better RSpec arguments handling, for example, --pattern and --exclude-pattern should be supported better (reported by @cb341)
data/README.md CHANGED
@@ -95,6 +95,8 @@ I will also try my best to keep supporting as many rubies / rspec versions as I
95
95
  * This is a common issue with ruby code, compiled libraries and forking. Set `OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES` environment variable to work around this
96
96
  * Something gets loaded that shouldn't get loaded, or in a different order
97
97
  * There are two simple ways to hook into preloads, exposed as CLI flags, `--prefork-require` (defaults to `config/application.rb`) and `--postfork-require` (defaults to either `rails_helper.rb` or `spec_helper.rb`, whichever is present on your machine). You can set any of those to whatever you need and control the load order
98
+ * `--require` options are ignored in command-line arguments or my `.rspec`
99
+ * rspec-conductor uses the full rspec configuration machinery to parse all the params. That includes `.rspec`, which, by default, includes `--require spec_helper` or `--require rails_helper` in most setups. If you need tighter control over what you load, use either `--prefork-require` or `--postfork-require`, these are actual ruby files, you can put any code or any `require` you need there.
98
100
 
99
101
  ## FAQ
100
102
 
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Conductor
5
+ class Base
6
+ include Util::ANSI
7
+
8
+ def initialize(**_kwargs)
9
+ end
10
+
11
+ def handle_worker_message(_worker_process, _message, _results)
12
+ end
13
+
14
+ def print_startup_banner(worker_count:, seed:, spec_files_count:)
15
+ puts "RSpec Conductor starting with #{worker_count} workers (seed: #{seed})"
16
+ puts "Running #{spec_files_count} spec files\n\n"
17
+ end
18
+
19
+ def print_summary(results, seed:)
20
+ puts "\n\n"
21
+ puts "Randomized with seed #{seed}"
22
+ puts "#{colorize("#{results.passed} passed", :green)}, #{colorize("#{results.failed} failed", :red)}, #{colorize("#{results.pending} pending", :yellow)}"
23
+ puts colorize("Worker crashes: #{results.worker_crashes}", :red) if results.worker_crashes.positive?
24
+
25
+ if results.errors.any?
26
+ puts "\nFailures:\n\n"
27
+ results.errors.each_with_index do |error, i|
28
+ puts " #{i + 1}) #{error[:description]}"
29
+ puts colorize(" #{error[:message]}", :red) if error[:message]
30
+ puts colorize(" #{error[:location]}", :cyan)
31
+ if error[:backtrace]&.any?
32
+ puts " Backtrace:"
33
+ error[:backtrace].each { |line| puts " #{line}" }
34
+ end
35
+ puts
36
+ end
37
+ end
38
+
39
+ puts "Specs took: #{results.specs_runtime.round(2)}s"
40
+ puts "Total runtime: #{results.total_runtime.round(2)}s"
41
+ puts "Suite: #{results.success? ? colorize("PASSED", :green) : colorize("FAILED", :red)}"
42
+ end
43
+
44
+ def print_debug(string)
45
+ $stderr.puts string
46
+ end
47
+
48
+ def print_retry_message(message)
49
+ puts <<~EOM
50
+ \nRetried: #{message[:description]}
51
+ #{message[:location]}
52
+ #{message[:exception_class]}: #{message[:message]}
53
+ Backtrace:
54
+ #{message[:backtrace].map { " #{_1}" }.join("\n")}
55
+ EOM
56
+ end
57
+
58
+ def print_shut_down_banner
59
+ puts "Shutting down..."
60
+ end
61
+
62
+ def colorize(string, colors, **kwargs)
63
+ $stdout.tty? ? super(string, colors, **kwargs) : string
64
+ end
65
+ end
66
+ end
67
+ end
@@ -3,15 +3,15 @@
3
3
  module RSpec
4
4
  module Conductor
5
5
  module Formatters
6
- class CI
7
- include Util::ANSI
8
-
6
+ class CI < Base
9
7
  DEFAULT_PRINTOUT_INTERVAL = 10
10
8
 
11
9
  # @option printout_interval how often a printout happens, in seconds
12
- def initialize(printout_interval: DEFAULT_PRINTOUT_INTERVAL)
10
+ def initialize(printout_interval: DEFAULT_PRINTOUT_INTERVAL, **kwargs)
13
11
  @printout_interval = printout_interval
14
12
  @last_printout = Time.now
13
+
14
+ super(**kwargs)
15
15
  end
16
16
 
17
17
  def handle_worker_message(_worker_process, message, results)
@@ -5,14 +5,12 @@ require "pathname"
5
5
  module RSpec
6
6
  module Conductor
7
7
  module Formatters
8
- class Fancy
9
- include Util::ANSI
10
-
8
+ class Fancy < Base
11
9
  def self.recommended?
12
10
  $stdout.tty? && $stdout.winsize[0] >= 30 && $stdout.winsize[1] >= 80
13
11
  end
14
12
 
15
- def initialize(worker_count:)
13
+ def initialize(worker_count:, **kwargs)
16
14
  @worker_processes = {}
17
15
  @terminal = Util::Terminal.new
18
16
  @last_rendered_lines = []
@@ -27,6 +25,8 @@ module RSpec
27
25
  @dots_line = @terminal.line(truncate: false)
28
26
  @terminal.puts
29
27
  @last_error_line = @terminal.line(truncate: false)
28
+
29
+ super(**kwargs)
30
30
  end
31
31
 
32
32
  def handle_worker_message(worker_process, message, results)
@@ -68,7 +68,7 @@ module RSpec
68
68
 
69
69
  def update_worker_status_line(worker_process)
70
70
  status = colorize("Worker #{worker_process.number}: ", :cyan)
71
- status << if worker_process.status == :shut_down
71
+ status += if worker_process.status == :shut_down
72
72
  "(finished)"
73
73
  elsif worker_process.status == :terminated
74
74
  colorize("(terminated)", :red)
@@ -1,9 +1,7 @@
1
1
  module RSpec
2
2
  module Conductor
3
3
  module Formatters
4
- class Plain
5
- include Util::ANSI
6
-
4
+ class Plain < Base
7
5
  def handle_worker_message(_worker_process, message, _results)
8
6
  public_send(message[:type], message) if respond_to?(message[:type])
9
7
  end
@@ -27,11 +25,7 @@ module RSpec
27
25
  private
28
26
 
29
27
  def print(string, color)
30
- if $stdout.tty?
31
- $stdout.print(colorize(string, color))
32
- else
33
- $stdout.print(string)
34
- end
28
+ $stdout.print(colorize(string, color))
35
29
  end
36
30
  end
37
31
  end
@@ -35,19 +35,21 @@ module RSpec
35
35
  @rspec_args = rspec_args
36
36
  @worker_processes = {}
37
37
  @spec_queue = []
38
- @formatter = case opts[:formatter]
39
- when "ci"
40
- Formatters::CI.new
41
- when "fancy"
42
- Formatters::Fancy.new(worker_count: worker_count)
43
- when "plain"
44
- Formatters::Plain.new
45
- else
46
- (!@verbose && Formatters::Fancy.recommended?) ? Formatters::Fancy.new(worker_count: worker_count) : Formatters::Plain.new
47
- end
38
+ @formatter_class = case opts[:formatter]
39
+ when "ci"
40
+ Formatters::CI
41
+ when "fancy"
42
+ Formatters::Fancy
43
+ when "plain"
44
+ Formatters::Plain
45
+ else
46
+ (!@verbose && Formatters::Fancy.recommended?) ? Formatters::Fancy : Formatters::Plain
47
+ end
48
+ @formatter = @formatter_class.new(worker_count: @worker_count)
48
49
  @results = Results.new
49
50
 
50
51
  Dir.chdir(Conductor.root)
52
+ ENV['PARALLEL_TEST_GROUPS'] = worker_count.to_s # parallel_tests backward-compatibility
51
53
  end
52
54
 
53
55
  def run
@@ -56,14 +58,13 @@ module RSpec
56
58
  preload_application
57
59
 
58
60
  $stdout.sync = true
59
- puts "RSpec Conductor starting with #{@worker_count} workers (seed: #{@seed})"
60
- puts "Running #{@spec_queue.size} spec files\n\n"
61
+ @formatter.print_startup_banner(worker_count: @worker_count, seed: @seed, spec_files_count: @spec_queue.size)
61
62
 
62
63
  start_workers
63
64
  run_event_loop
64
65
  @results.suite_complete
65
66
 
66
- print_summary
67
+ @formatter.print_summary(@results, seed: @seed)
67
68
  exit_with_status
68
69
  end
69
70
 
@@ -99,12 +100,15 @@ module RSpec
99
100
  return if @results.shutting_down?
100
101
 
101
102
  @results.shut_down
102
- puts "Shutting down..."
103
+ @formatter.print_shut_down_banner
103
104
  @worker_processes.each_value { |w| w.socket&.send_message({ type: :shutdown }) }
104
105
  end
105
106
 
106
107
  def build_spec_queue
107
108
  config_options = RSpec::Core::ConfigurationOptions.new(@rspec_args)
109
+ # a bit of a hack, but if they want to require something explicitly, they should use either --prefork-require or --postfork-require,
110
+ # as it is now, it messes with the preloads
111
+ config_options.options.delete(:requires)
108
112
  if config_options.options[:files_or_directories_to_run].empty?
109
113
  config_options.options[:files_or_directories_to_run] = ["spec"]
110
114
  end
@@ -187,9 +191,7 @@ module RSpec
187
191
  when :example_pending
188
192
  @results.example_pending
189
193
  when :example_retried
190
- if @display_retry_backtraces
191
- puts "\nExample #{message[:description]} retried:\n #{message[:location]}\n #{message[:exception_class]}: #{message[:message]}\n#{message[:backtrace].map { " #{_1}" }.join("\n")}\n"
192
- end
194
+ @formatter.print_retry_message(message) if @display_retry_backtraces
193
195
  when :spec_complete
194
196
  @results.spec_file_complete
195
197
  worker_process.current_spec = nil
@@ -248,35 +250,6 @@ module RSpec
248
250
  nil
249
251
  end
250
252
 
251
- def print_summary
252
- puts "\n\n"
253
- puts "Randomized with seed #{@seed}"
254
- puts "#{colorize("#{@results.passed} passed", :green)}, #{colorize("#{@results.failed} failed", :red)}, #{colorize("#{@results.pending} pending", :yellow)}"
255
- puts colorize("Worker crashes: #{@results.worker_crashes}", :red) if @results.worker_crashes.positive?
256
-
257
- if @results.errors.any?
258
- puts "\nFailures:\n\n"
259
- @results.errors.each_with_index do |error, i|
260
- puts " #{i + 1}) #{error[:description]}"
261
- puts " #{error[:location]}"
262
- puts " #{error[:message]}" if error[:message]
263
- if error[:backtrace]&.any?
264
- puts " Backtrace:"
265
- error[:backtrace].each { |line| puts " #{line}" }
266
- end
267
- puts
268
- end
269
- end
270
-
271
- puts "Specs took: #{@results.specs_runtime.round(2)}s"
272
- puts "Total runtime: #{@results.total_runtime.round(2)}s"
273
- puts "Suite: #{@results.success? ? colorize("PASSED", :green) : colorize("FAILED", :red)}"
274
- end
275
-
276
- def colorize(string, color)
277
- $stdout.tty? ? Util::ANSI.colorize(string, color) : string
278
- end
279
-
280
253
  def exit_with_status
281
254
  Kernel.exit(@results.success? ? 0 : 1)
282
255
  end
@@ -284,7 +257,7 @@ module RSpec
284
257
  def debug(message)
285
258
  return unless @verbose
286
259
 
287
- $stderr.puts "[conductor] #{message}"
260
+ @formatter.print_debug("[conductor] #{message}")
288
261
  end
289
262
  end
290
263
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RSpec
4
4
  module Conductor
5
- VERSION = "1.0.6"
5
+ VERSION = "1.0.7"
6
6
  end
7
7
  end
@@ -62,11 +62,17 @@ module RSpec
62
62
  @default_path = RSpec.configuration.default_path || "spec"
63
63
  @default_full_path = File.expand_path(@default_path)
64
64
 
65
- if Dir.exist?(@default_full_path) && !$LOAD_PATH.include?(@default_full_path)
66
- $LOAD_PATH.unshift(@default_full_path)
67
- end
65
+ add_load_path(File.expand_path("lib"))
66
+ add_load_path(@default_full_path)
67
+
68
+ debug "Load path: #{$LOAD_PATH.inspect}"
69
+ end
70
+
71
+ def add_load_path(path)
72
+ return unless File.directory?(path)
73
+ return if $LOAD_PATH.include?(path)
68
74
 
69
- debug "Load path (spec dirs): #{$LOAD_PATH.inspect}"
75
+ $LOAD_PATH.unshift(path)
70
76
  end
71
77
 
72
78
  def suppress_output
@@ -162,7 +168,7 @@ module RSpec
162
168
  end
163
169
 
164
170
  def parsed_options
165
- @parsed_options ||= RSpec::Core::ConfigurationOptions.new(@rspec_args)
171
+ @parsed_options ||= RSpec::Core::ConfigurationOptions.new(@rspec_args).tap { |co| co.options.delete(:requires) }
166
172
  end
167
173
 
168
174
  def debug(message)
@@ -37,6 +37,7 @@ require_relative "conductor/results"
37
37
  require_relative "conductor/worker_process"
38
38
  require_relative "conductor/cli"
39
39
  require_relative "conductor/rspec_subscriber"
40
+ require_relative "conductor/formatters/base"
40
41
  require_relative "conductor/formatters/plain"
41
42
  require_relative "conductor/formatters/ci"
42
43
  require_relative "conductor/formatters/fancy"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-conductor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Abramov
@@ -42,6 +42,7 @@ files:
42
42
  - lib/rspec/conductor.rb
43
43
  - lib/rspec/conductor/cli.rb
44
44
  - lib/rspec/conductor/ext/rspec.rb
45
+ - lib/rspec/conductor/formatters/base.rb
45
46
  - lib/rspec/conductor/formatters/ci.rb
46
47
  - lib/rspec/conductor/formatters/fancy.rb
47
48
  - lib/rspec/conductor/formatters/plain.rb