module CLI::Kit::System
Constants
- SUDO_PROMPT
Public Class Methods
# 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
# 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
# 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
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
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
Resets the faked commands
# File lib/cli/kit/support/test_helper.rb, line 160 def reset! @delegate_open3 = {} end
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
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
# 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
Private Class Methods
# 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
# 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
# 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
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