Class: CemAcpt::RSpecUtils::Command

Inherits:
Object
  • Object
show all
Includes:
LoggingAsync
Defined in:
lib/cem_acpt/rspec_utils.rb

Overview

Holds and formats a RSpec command

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from LoggingAsync

async_debug, #async_debug, async_error, #async_error, async_fatal, #async_fatal, async_info, #async_info, async_warn, #async_warn, included, log_write_thread, #log_write_thread

Constructor Details

#initialize(opts = {}) ⇒ Command

Returns a new instance of Command.

Parameters:

  • opts (Hash) (defaults to: {})

    options hash for the RSpec command

Options Hash (opts):

  • :test_path (String)

    The path (or glob path) to the test file(s) to run. If blank, runs all.

  • :format (Hash)

    Format options for rspec where the key is the format (documentation, json, etc) and the value is the out-file path. If you do not want to save the results of a format to a file, the value should be `nil`.

  • :debug (Boolean)

    True if RSpec should run in debug mode, false if not. Mutually exclusive with `:quiet`. Default is `false`.

  • :quiet (Boolean)

    True if no output should be logged from RSpec command, false if output should be logged. Mutually exclusive with `:debug`. Default is `false`.

  • :use_bundler (Boolean)

    Whether or not `bundle exec` should be used to run the RSpec command. Default is `true`.

  • :bundle_install (Boolean)

    Whether or not to run `bundle install` before the RSpec command if `use_bundler` is `true`.

  • :use_shell (Boolean)

    Whether or not to add `$SHELL` as a prefix to the command

  • :env (Hash)

    Environment variables to prepend to the command



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/cem_acpt/rspec_utils.rb', line 35

def initialize(opts = {})
  @test_path = opts[:test_path]&.shellescape
  @format = opts.fetch(:format, {})
  @debug = opts.fetch(:debug, false)
  @quiet = @debug ? false : opts.fetch(:quiet, false)
  @use_bundler = opts.fetch(:use_bundler, false)
  @bundle_install = opts.fetch(:bundle_install, false)
  @env = opts.fetch(:env, {})
  @pty_pid = nil
  validate_and_set_bin_paths(opts)
end

Instance Attribute Details

#debugObject (readonly)

Returns the value of attribute debug.



18
19
20
# File 'lib/cem_acpt/rspec_utils.rb', line 18

def debug
  @debug
end

#formatObject (readonly)

Returns the value of attribute format.



18
19
20
# File 'lib/cem_acpt/rspec_utils.rb', line 18

def format
  @format
end

#pty_pidObject (readonly)

Returns the value of attribute pty_pid.



18
19
20
# File 'lib/cem_acpt/rspec_utils.rb', line 18

def pty_pid
  @pty_pid
end

#test_pathObject (readonly)

Returns the value of attribute test_path.



18
19
20
# File 'lib/cem_acpt/rspec_utils.rb', line 18

def test_path
  @test_path
end

#use_bundlerObject (readonly)

Returns the value of attribute use_bundler.



18
19
20
# File 'lib/cem_acpt/rspec_utils.rb', line 18

def use_bundler
  @use_bundler
end

Instance Method Details

#envHash

Environment variables that will be used for the RSpec command

Returns:

  • (Hash)

    A Hash of environment variables with each key pair being: <var name> => <var value>



74
75
76
# File 'lib/cem_acpt/rspec_utils.rb', line 74

def env
  @debug ? @env.merge({ 'RSPEC_DEBUG' => 'true' }) : @env
end

#execute(pty: true, log_prefix: 'RSPEC') ⇒ Object

Executes the RSpec command on the current machine

Parameters:

  • pty (Boolean) (defaults to: true)

    If true, execute command in a PTY. If false, execute command directly.

  • log_prefix (String) (defaults to: 'RSPEC')

    A prefix to add to log messages generated while the command is running.



97
98
99
100
101
102
103
# File 'lib/cem_acpt/rspec_utils.rb', line 97

def execute(pty: true, log_prefix: 'RSPEC')
  if pty
    execute_pty(log_prefix: log_prefix)
  else
    execute_no_pty(log_prefix: log_prefix)
  end
end

#execute_no_pty(log_prefix: 'RSPEC') ⇒ Integer

Executes the RSpec command using Open3.popen2e(). The output stream, which is both stderr and stdout, is read in real-time in a non-blocking manner.

Parameters:

  • log_prefix (String) (defaults to: 'RSPEC')

    A prefix to add to the log messages that are output from the RSpec command.

Returns:

  • (Integer)

    The exit code of the RSpec command



130
131
132
133
134
135
136
137
138
139
# File 'lib/cem_acpt/rspec_utils.rb', line 130

def execute_no_pty(log_prefix: 'RSPEC')
  async_info("Executing RSpec command '#{self}' with Open3.popen2e()...", log_prefix)
  exit_status = nil
  Open3.popen2e(env, to_s) do |stdin, std_out_err, wait_thr|
    stdin.close
    quiet ? wait_io(std_out_err) : read_io(std_out_err, log_prefix: log_prefix)
    exit_status = wait_thr.value
  end
  exit_status
end

#execute_pty(log_prefix: 'RSPEC') ⇒ Integer

Executes the RSpec command in a psuedo-terminal (PTY). First, it spawns a process for $SHELL, sets environment variables `export_envs`, then calls the current RSpec command in the shell and exits with the last exit code `$?`. Output is read from the RSpec command in near real-time in a blocking manner unless the `:quiet` option has been specified.

Parameters:

  • log_prefix (String) (defaults to: 'RSPEC')

    A prefix to add to the log messages that are output from the RSpec command.

Returns:

  • (Integer)

    The exit code of the RSpec command



113
114
115
116
117
118
119
120
121
122
123
# File 'lib/cem_acpt/rspec_utils.rb', line 113

def execute_pty(log_prefix: 'RSPEC')
  async_debug("Executing RSpec command '#{self}' in PTY...", log_prefix)
  PTY.spawn(env, ENV['SHELL']) do |r, w, pid|
    @pty_pid = pid
    async_debug("Spawned RSpec PTY with PID #{@pty_pid}", log_prefix)
    export_envs(w)
    w.puts "#{self}; exit $?"
    quiet ? wait_io(r) : read_io(r, log_prefix: log_prefix)
  end
  $CHILD_STATUS
end

#kill_ptyObject

Kills the PTY process with `SIGKILL` if the process exists



142
143
144
145
146
# File 'lib/cem_acpt/rspec_utils.rb', line 142

def kill_pty
  Process.kill('KILL', @pty_pid) unless @pty_pid.nil?
rescue Errno::ESRCH
  true
end

#quietObject



61
62
63
# File 'lib/cem_acpt/rspec_utils.rb', line 61

def quiet
  @quiet && !debug
end

#set_debugObject

Sets debug mode to `true`



48
49
50
51
52
53
54
# File 'lib/cem_acpt/rspec_utils.rb', line 48

def set_debug
  @debug = true
  if @quiet
    async_debug('Setting :quiet to false because :debug is now true.')
    @quiet = false
  end
end

#to_aObject

Returns an array representation of the RSpec command



79
80
81
82
83
84
85
86
87
# File 'lib/cem_acpt/rspec_utils.rb', line 79

def to_a
  cmd = cmd_base.dup
  cmd << test_path if test_path
  format.each do |fmt, out|
    cmd += ['--format', fmt.to_s.shellescape]
    cmd += ['--out', out.to_s.shellescape] if out
  end
  cmd.compact
end

#to_sObject

Returns a string representation of the RSpec command



90
91
92
# File 'lib/cem_acpt/rspec_utils.rb', line 90

def to_s
  to_a.join(' ')
end

#unset_debugObject

Sets debug mode to `false`



57
58
59
# File 'lib/cem_acpt/rspec_utils.rb', line 57

def unset_debug
  @debug = false
end

#with_format(fmt, out: nil) ⇒ Object

Adds a new format to the RSpec command

Parameters:

  • fmt (String)

    The name of the format (i.e. “documentation”, “json”, etc.)

  • out (String) (defaults to: nil)

    If specified, saves the specified format to a file at this path



68
69
70
# File 'lib/cem_acpt/rspec_utils.rb', line 68

def with_format(fmt, out: nil)
  @format[fmt.to_sym] = out
end