module RSpec::Interactive

Constants

DEFAULT_HISTORY_FILE
VERSION

Attributes

configuration[RW]

Public Class Methods

check_rails() click to toggle source
# File lib/rspec-interactive.rb, line 61
def self.check_rails
  if defined?(::Rails)
    if ::Rails.application.config.cache_classes
      @error_stream.puts "warning: Rails.application.config.cache_classes enabled. Disable to ensure code is reloaded."
    end
  end
end
configure(&block) click to toggle source
# File lib/rspec-interactive.rb, line 25
def self.configure(&block)
  block.call(@configuration)
end
configure_pry() click to toggle source
# File lib/rspec-interactive.rb, line 101
def self.configure_pry
  # Prevent Pry from trapping too. It will break ctrl-c handling.
  Pry.config.should_trap_interrupts = false

  # Set up IO.
  Pry.config.input = Readline
  Pry.config.output = @output_stream
  Readline.output = @output_stream
  Readline.input = @input_stream

  # Use custom completer to get file completion.
  Pry.config.completer = RSpec::Interactive::InputCompleter

  Pry.config.history_file = @history_file
end
configure_rspec() click to toggle source
# File lib/rspec-interactive.rb, line 69
def self.configure_rspec
  RSpec.configure do |config|
   config.error_stream = @error_stream
   config.output_stream = @output_stream
  end
end
refresh() click to toggle source
# File lib/rspec-interactive.rb, line 117
def self.refresh
  @mutex.synchronize do
    @updated_files.uniq.each do |filename|
      @output_stream.puts "changed: #{filename}"
      trace = TracePoint.new(:class) do |tp|
        @configuration.on_class_load.call(tp.self)
      end
      trace.enable
      load filename
      trace.disable
      @output_stream.puts
    end
    @updated_files.clear
  end
  @configuration.refresh.call
end
rspec(args) click to toggle source
# File lib/rspec-interactive.rb, line 134
def self.rspec(args)
  parsed_args = args.flat_map do |arg|
    if arg.match(/[\*\?\[]/)
      glob = Dir.glob(arg)
      glob.empty? ? [arg] : glob
    else
      [arg]
    end
  end

  # Initialize the runner before waiting for the init thread so that the interrupt
  # handler will cancel the RSpec invocation rather than kill the app.
  @runner = RSpec::Interactive::Runner.new(parsed_args)

  if @init_thread&.alive?
    @init_thread.join
    @init_thread = nil
  end

  refresh

  # Stop saving history in case a new Pry session is started for debugging.
  Pry.config.history_save = false

  # RSpec::Interactive-specific RSpec configuration
  configure_rspec

  # Run.
  exit_code = @runner.run
  @runner = nil

  # Reenable history
  Pry.config.history_save = true

  # Reset
  RSpec.clear_examples
  RSpec.reset
  @config_cache.replay_configuration
ensure
  @runner = nil
end
rubo_cop(args) click to toggle source
# File lib/rspec-interactive.rb, line 176
def self.rubo_cop(args)
  if @init_thread&.alive?
    @init_thread.join
    @init_thread = nil
  end

  if defined?(RuboCop)
    RuboCop::CLI.new.run args
  else
    @error_stream.puts "fatal: RuboCop not found. Is the gem installed in this project?"
  end
end
start(config_file: nil, initial_rspec_args: nil, history_file: DEFAULT_HISTORY_FILE, input_stream: STDIN, output_stream: STDOUT, error_stream: STDERR) click to toggle source
# File lib/rspec-interactive.rb, line 29
def self.start(config_file: nil, initial_rspec_args: nil, history_file: DEFAULT_HISTORY_FILE, input_stream: STDIN, output_stream: STDOUT, error_stream: STDERR)
  @history_file = history_file
  @updated_files = []
  @stty_save = %x`stty -g`.chomp
  @mutex = Mutex.new
  @output_stream = output_stream
  @input_stream = input_stream
  @error_stream = error_stream
  @config_cache = RSpec::Interactive::ConfigCache.new

  @configuration = Configuration.new
  load config_file if config_file

  check_rails
  trap_interrupt
  configure_pry

  @init_thread = Thread.start {
    @config_cache.record_configuration { @configuration.configure_rspec.call }
    start_file_watcher
  }

  if initial_rspec_args
    open(@history_file, 'a') { |f| f.puts "rspec #{initial_rspec_args.strip}" }
    rspec Shellwords.split(initial_rspec_args)
  end

  Pry.start
  @listener.stop if @listener
  0
end
start_file_watcher() click to toggle source
# File lib/rspec-interactive.rb, line 89
def self.start_file_watcher
  return if @configuration.watch_dirs.empty?

  # Only polling seems to work in Docker.
  @listener = Listen.to(*@configuration.watch_dirs, only: /\.rb$/, force_polling: true) do |modified, added|
    @mutex.synchronize do
      @updated_files.concat(added + modified)
    end
  end
  @listener.start
end
trap_interrupt() click to toggle source
# File lib/rspec-interactive.rb, line 76
def self.trap_interrupt
  trap('INT') do
    if @runner
      # We are on a different thread. There is a race here. Ignore nil.
      @runner&.quit
    else
      @output_stream.puts
      system "stty", @stty_save
      exit!(0)
    end
  end
end