module CLI::Kit::System

Constants

SUDO_PROMPT

Public Class Methods

capture2(*a, sudo: false, env: {}, **kwargs) click to toggle source
# File lib/cli/kit/support/test_helper.rb, line 70
def capture2(*a, sudo: false, env: {}, **kwargs)
  expected_command = expected_command(*a, sudo: sudo, env: env)

  # In the case of an unexpected command, expected_command will be nil
  return [nil, FakeSuccess.new(false)] if expected_command.nil?

  # Otherwise handle the command
  if expected_command[:allow]
    original_capture2(*a, sudo: sudo, env: env, **kwargs)
  else
    [
      expected_command[:stdout],
      FakeSuccess.new(expected_command[:success]),
    ]
  end
end
Also aliased as: original_capture2
capture2e(*a, sudo: false, env: {}, **kwargs) click to toggle source
# File lib/cli/kit/support/test_helper.rb, line 88
def capture2e(*a, sudo: false, env: {}, **kwargs)
  expected_command = expected_command(*a, sudo: sudo, env: env)

  # In the case of an unexpected command, expected_command will be nil
  return [nil, FakeSuccess.new(false)] if expected_command.nil?

  # Otherwise handle the command
  if expected_command[:allow]
    original_capture2ecapture2e(*a, sudo: sudo, env: env, **kwargs)
  else
    [
      expected_command[:stdout],
      FakeSuccess.new(expected_command[:success]),
    ]
  end
end
Also aliased as: original_capture2e
capture3(*a, sudo: false, env: {}, **kwargs) click to toggle source
# File lib/cli/kit/support/test_helper.rb, line 106
def capture3(*a, sudo: false, env: {}, **kwargs)
  expected_command = expected_command(*a, sudo: sudo, env: env)

  # In the case of an unexpected command, expected_command will be nil
  return [nil, nil, FakeSuccess.new(false)] if expected_command.nil?

  # Otherwise handle the command
  if expected_command[:allow]
    original_capture3(*a, sudo: sudo, env: env, **kwargs)
  else
    [
      expected_command[:stdout],
      expected_command[:stderr],
      FakeSuccess.new(expected_command[:success]),
    ]
  end
end
Also aliased as: original_capture3
error_message() click to toggle source

Returns the errors associated to a test run

#### Returns `errors` (String) a string representing errors found on this run, nil if none

# File lib/cli/kit/support/test_helper.rb, line 168
                def error_message
                  errors = {
                    unexpected: [],
                    not_run: [],
                    other: {},
                  }

                  @delegate_open3.each do |cmd, opts|
                    if opts[:unexpected]
                      errors[:unexpected] << cmd
                    elsif opts[:run]
                      error = []

                      if opts[:expected][:sudo] != opts[:actual][:sudo]
                        error << "- sudo was supposed to be #{opts[:expected][:sudo]} but was #{opts[:actual][:sudo]}"
                      end

                      if opts[:expected][:env] != opts[:actual][:env]
                        error << "- env was supposed to be #{opts[:expected][:env]} but was #{opts[:actual][:env]}"
                      end

                      errors[:other][cmd] = error.join("\n") unless error.empty?
                    else
                      errors[:not_run] << cmd
                    end
                  end

                  final_error = []

                  unless errors[:unexpected].empty?
                    final_error << CLI::UI.fmt(<<~EOF)
                    {{bold:Unexpected command invocations:}}
                    {{command:#{errors[:unexpected].join("\n")}}}
                    EOF
                  end

                  unless errors[:not_run].empty?
                    final_error << CLI::UI.fmt(<<~EOF)
                    {{bold:Expected commands were not run:}}
                    {{command:#{errors[:not_run].join("\n")}}}
                    EOF
                  end

                  unless errors[:other].empty?
                    final_error << CLI::UI.fmt(<<~EOF)
                    {{bold:Commands were not run as expected:}}
                    #{errors[:other].map { |cmd, msg| "{{command:#{cmd}}}\n#{msg}" }.join("\n\n")}
                    EOF
                  end

                  return nil if final_error.empty?
                  "\n" + final_error.join("\n") # Initial new line for formatting reasons
                end
fake(*a, stdout: "", stderr: "", allow: nil, success: nil, sudo: false, env: {}) click to toggle source

Sets up an expectation for a command and stubs out the call (unless allow is true)

#### Parameters `*a` : the command, represented as a splat `stdout` : stdout to stub the command with (defaults to empty string) `stderr` : stderr to stub the command with (defaults to empty string) `allow` : allow determines if the command will be actually run, or stubbed. Defaults to nil (stub) `success` : success status to stub the command with (Defaults to nil) `sudo` : expectation of sudo being set or not (defaults to false) `env` : expectation of env being set or not (defaults to {})

Note: Must set allow or success

# File lib/cli/kit/support/test_helper.rb, line 137
def fake(*a, stdout: "", stderr: "", allow: nil, success: nil, sudo: false, env: {})
  raise ArgumentError, "success or allow must be set" if success.nil? && allow.nil?

  @delegate_open3 ||= {}
  @delegate_open3[a.join(' ')] = {
    expected: {
      sudo: sudo,
      env: env,
    },
    actual: {
      sudo: nil,
      env: nil,
    },
    stdout: stdout,
    stderr: stderr,
    allow: allow,
    success: success,
    run: false,
  }
end
original_capture2(*a, sudo: false, env: {}, **kwargs)
Alias for: capture2
original_capture2e(*a, sudo: false, env: {}, **kwargs)
Alias for: capture2e
original_capture3(*a, sudo: false, env: {}, **kwargs)
Alias for: capture3
original_system(*a, sudo: false, env: {}, **kwargs)
Alias for: system
reset!() click to toggle source

Resets the faked commands

# File lib/cli/kit/support/test_helper.rb, line 160
def reset!
  @delegate_open3 = {}
end
split_partial_characters(data) click to toggle source

Split off trailing partial UTF-8 Characters. UTF-8 Multibyte characters start with a 11xxxxxx byte that tells how many following bytes are part of this character, followed by some number of 10xxxxxx bytes. This simple algorithm will split off a whole trailing multi-byte character.

# File lib/cli/kit/system.rb, line 150
def split_partial_characters(data)
  last_byte = data.getbyte(-1)
  return [data, ''] if (last_byte & 0b1000_0000).zero?

  # UTF-8 is up to 6 characters per rune, so we could never want to trim more than that, and we want to avoid
  # allocating an array for the whole of data with bytes
  min_bound = -[6, data.bytesize].min
  final_bytes = data.byteslice(min_bound..-1).bytes
  partial_character_sub_index = final_bytes.rindex { |byte| byte & 0b1100_0000 == 0b1100_0000 }
  # Bail out for non UTF-8
  return [data, ''] unless partial_character_sub_index
  partial_character_index = min_bound + partial_character_sub_index

  [data.byteslice(0...partial_character_index), data.byteslice(partial_character_index..-1)]
end
sudo_reason(msg) click to toggle source

Ask for sudo access with a message explaning the need for it Will make subsequent commands capable of running with sudo for a period of time

#### Parameters

  • `msg`: A message telling the user why sudo is needed

#### Usage `ctx.sudo_reason(“We need to do a thing”)`

# File lib/cli/kit/system.rb, line 20
def sudo_reason(msg)
  # See if sudo has a cached password
  `env SUDO_ASKPASS=/usr/bin/false sudo -A true`
  return if $CHILD_STATUS.success?
  CLI::UI.with_frame_color(:blue) do
    puts(CLI::UI.fmt("{{i}} #{msg}"))
  end
end
system(*a, sudo: false, env: {}, **kwargs) click to toggle source
# File lib/cli/kit/support/test_helper.rb, line 55
def system(*a, sudo: false, env: {}, **kwargs)
  expected_command = expected_command(*a, sudo: sudo, env: env)

  # In the case of an unexpected command, expected_command will be nil
  return FakeSuccess.new(false) if expected_command.nil?

  # Otherwise handle the command
  if expected_command[:allow]
    original_system(*a, sudo: sudo, env: env, **kwargs)
  else
    FakeSuccess.new(expected_command[:success])
  end
end
Also aliased as: original_system

Private Class Methods

apply_sudo(*a, sudo) click to toggle source
# File lib/cli/kit/system.rb, line 168
def apply_sudo(*a, sudo)
  a.unshift('sudo', '-S', '-p', SUDO_PROMPT, '--') if sudo
  sudo_reason(sudo) if sudo.is_a?(String)
  a
end
delegate_open3(*a, sudo: raise, env: raise, method: raise, **kwargs) click to toggle source
# File lib/cli/kit/system.rb, line 174
def delegate_open3(*a, sudo: raise, env: raise, method: raise, **kwargs)
  a = apply_sudo(*a, sudo)
  Open3.send(method, env, *resolve_path(a, env), **kwargs)
rescue Errno::EINTR
  raise(Errno::EINTR, "command interrupted: #{a.join(' ')}")
end
expected_command(*a, sudo: raise, env: raise) click to toggle source
# File lib/cli/kit/support/test_helper.rb, line 224
def expected_command(*a, sudo: raise, env: raise)
  expected_cmd = @delegate_open3[a.join(' ')]

  if expected_cmd.nil?
    @delegate_open3[a.join(' ')] = { unexpected: true }
    return nil
  end

  expected_cmd[:run] = true
  expected_cmd[:actual][:sudo] = sudo
  expected_cmd[:actual][:env] = env
  expected_cmd
end
resolve_path(a, env) click to toggle source

Ruby resolves the program to execute using its own PATH, but we want it to use the provided one, so we ensure ruby chooses to spawn a shell, which will parse our command and properly spawn our target using the provided environment.

This is important because dev clobbers its own environment such that ruby means /usr/bin/ruby, but we want it to select the ruby targeted by the active project.

See github.com/Shopify/dev/pull/625 for more details.

# File lib/cli/kit/system.rb, line 190
def resolve_path(a, env)
  # If only one argument was provided, make sure it's interpreted by a shell.
  return ["true ; " + a[0]] if a.size == 1
  return a if a.first.include?('/')

  paths = env.fetch('PATH', '').split(':')
  item = paths.detect do |f|
    command_path = "#{f}/#{a.first}"
    File.executable?(command_path) && File.file?(command_path)
  end

  a[0] = "#{item}/#{a.first}" if item
  a
end