Module: CLI::Kit::System
- Extended by:
- T::Sig
- Defined in:
- lib/cli/kit/system.rb,
lib/cli/kit/support/test_helper.rb
Constant Summary collapse
- SUDO_PROMPT =
CLI::UI.fmt('{{info:(sudo)}} Password: ')
Class Method Summary collapse
- .capture2(cmd, *a, sudo: false, env: {}, **kwargs) ⇒ Object
- .capture2e(cmd, *a, sudo: false, env: {}, **kwargs) ⇒ Object
- .capture3(cmd, *a, sudo: false, env: {}, **kwargs) ⇒ Object
-
.error_message ⇒ Object
Returns the errors associated to a test run.
-
.fake(*a, stdout: '', stderr: '', allow: nil, success: nil, sudo: false, env: {}) ⇒ Object
Sets up an expectation for a command and stubs out the call (unless allow is true).
- .original_capture2 ⇒ Object
- .original_capture2e ⇒ Object
- .original_capture3 ⇒ Object
- .original_system ⇒ Object
- .os ⇒ Object
- .popen2(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) ⇒ Object
- .popen2e(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) ⇒ Object
- .popen3(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) ⇒ Object
-
.reset! ⇒ Object
Resets the faked commands.
- .split_partial_characters(data) ⇒ Object
- .sudo_reason(msg) ⇒ Object
- .system(cmd, *a, sudo: false, env: {}, stdin: nil, **kwargs) ⇒ Object
- .which(cmd, env) ⇒ Object
Methods included from T::Sig
Class Method Details
.capture2(cmd, *a, sudo: false, env: {}, **kwargs) ⇒ Object
61 62 63 |
# File 'lib/cli/kit/system.rb', line 61 def capture2(cmd, *args, sudo: false, env: ENV.to_h, **kwargs) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :capture2) end |
.capture2e(cmd, *a, sudo: false, env: {}, **kwargs) ⇒ Object
92 93 94 |
# File 'lib/cli/kit/system.rb', line 92 def capture2e(cmd, *args, sudo: false, env: ENV.to_h, **kwargs) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :capture2e) end |
.capture3(cmd, *a, sudo: false, env: {}, **kwargs) ⇒ Object
124 125 126 |
# File 'lib/cli/kit/system.rb', line 124 def capture3(cmd, *args, sudo: false, env: ENV.to_h, **kwargs) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :capture3) end |
.error_message ⇒ Object
Returns the errors associated to a test run
#### Returns ‘errors` (String) a string representing errors found on this run, nil if none
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/cli/kit/support/test_helper.rb', line 178 def 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 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: {}) ⇒ Object
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
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/cli/kit/support/test_helper.rb', line 147 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 ⇒ Object
76 77 78 |
# File 'lib/cli/kit/support/test_helper.rb', line 76 def capture2(cmd, *args, sudo: false, env: ENV.to_h, **kwargs) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :capture2) end |
.original_capture2e ⇒ Object
95 96 97 |
# File 'lib/cli/kit/support/test_helper.rb', line 95 def capture2e(cmd, *args, sudo: false, env: ENV.to_h, **kwargs) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :capture2e) end |
.original_capture3 ⇒ Object
114 115 116 |
# File 'lib/cli/kit/support/test_helper.rb', line 114 def capture3(cmd, *args, sudo: false, env: ENV.to_h, **kwargs) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :capture3) end |
.original_system ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/cli/kit/support/test_helper.rb', line 60 def system(cmd, *args, sudo: false, env: ENV.to_h, stdin: nil, **kwargs, &block) cmd, args = apply_sudo(cmd, args, sudo) out_r, out_w = IO.pipe err_r, err_w = IO.pipe in_stream = if stdin stdin elsif STDIN.closed? :close else STDIN end cmd, args = resolve_path(cmd, args, env) pid = T.unsafe(Process).spawn(env, cmd, *args, 0 => in_stream, :out => out_w, :err => err_w, **kwargs) out_w.close err_w.close handlers = if block_given? { out_r => ->(data) { yield(data.force_encoding(Encoding::UTF_8), '') }, err_r => ->(data) { yield('', data.force_encoding(Encoding::UTF_8)) }, } else { out_r => ->(data) { STDOUT.write(data) }, err_r => ->(data) { STDOUT.write(data) }, } end previous_trailing = Hash.new('') loop do break if Process.wait(pid, Process::WNOHANG) ios = [err_r, out_r].reject(&:closed?) next if ios.empty? readers, = IO.select(ios, [], [], 1) next if readers.nil? # If IO.select times out we iterate again so we can check if the process has exited readers.each do |io| data, trailing = split_partial_characters(io.readpartial(4096)) handlers[io].call(previous_trailing[io] + data) previous_trailing[io] = trailing rescue IOError io.close end end $CHILD_STATUS end |
.os ⇒ Object
300 301 302 303 304 305 306 |
# File 'lib/cli/kit/system.rb', line 300 def os return :mac if /darwin/.match(RUBY_PLATFORM) return :linux if /linux/.match(RUBY_PLATFORM) return :windows if /mingw/.match(RUBY_PLATFORM) raise "Could not determine OS from platform #{RUBY_PLATFORM}" end |
.popen2(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) ⇒ Object
142 143 144 |
# File 'lib/cli/kit/system.rb', line 142 def popen2(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :popen2, &block) end |
.popen2e(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) ⇒ Object
160 161 162 |
# File 'lib/cli/kit/system.rb', line 160 def popen2e(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :popen2e, &block) end |
.popen3(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) ⇒ Object
178 179 180 |
# File 'lib/cli/kit/system.rb', line 178 def popen3(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :popen3, &block) end |
.reset! ⇒ Object
Resets the faked commands
170 171 172 |
# File 'lib/cli/kit/support/test_helper.rb', line 170 def reset! @delegate_open3 = {} end |
.split_partial_characters(data) ⇒ Object
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/cli/kit/system.rb', line 264 def split_partial_characters(data) last_byte = T.must(data.getbyte(-1)) return [data, ''] if (last_byte & 0b1000_0000).zero? # UTF-8 is up to 4 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 = -[4, data.bytesize].min final_bytes = T.must(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 start_byte = final_bytes[partial_character_sub_index] full_size = if start_byte & 0b1111_1000 == 0b1111_0000 4 elsif start_byte & 0b1111_0000 == 0b1110_0000 3 elsif start_byte & 0b1110_0000 == 0b110_00000 2 else nil # Not a valid UTF-8 character end return [data, ''] if full_size.nil? # Bail out for non UTF-8 if final_bytes.size - partial_character_sub_index == full_size # We have a full UTF-8 character, so we can just return the data return [data, ''] end partial_character_index = min_bound + partial_character_sub_index [T.must(data.byteslice(0...partial_character_index)), T.must(data.byteslice(partial_character_index..-1))] end |
.sudo_reason(msg) ⇒ Object
24 25 26 27 28 29 30 31 32 |
# File 'lib/cli/kit/system.rb', line 24 def sudo_reason(msg) # See if sudo has a cached password %x(env SUDO_ASKPASS=/usr/bin/false sudo -A true > /dev/null 2>&1) return if $CHILD_STATUS.success? CLI::UI.with_frame_color(:blue) do puts(CLI::UI.fmt("{{i}} #{msg}")) end end |
.system(cmd, *a, sudo: false, env: {}, stdin: nil, **kwargs) ⇒ Object
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/cli/kit/system.rb', line 209 def system(cmd, *args, sudo: false, env: ENV.to_h, stdin: nil, **kwargs, &block) cmd, args = apply_sudo(cmd, args, sudo) out_r, out_w = IO.pipe err_r, err_w = IO.pipe in_stream = if stdin stdin elsif STDIN.closed? :close else STDIN end cmd, args = resolve_path(cmd, args, env) pid = T.unsafe(Process).spawn(env, cmd, *args, 0 => in_stream, :out => out_w, :err => err_w, **kwargs) out_w.close err_w.close handlers = if block_given? { out_r => ->(data) { yield(data.force_encoding(Encoding::UTF_8), '') }, err_r => ->(data) { yield('', data.force_encoding(Encoding::UTF_8)) }, } else { out_r => ->(data) { STDOUT.write(data) }, err_r => ->(data) { STDOUT.write(data) }, } end previous_trailing = Hash.new('') loop do break if Process.wait(pid, Process::WNOHANG) ios = [err_r, out_r].reject(&:closed?) next if ios.empty? readers, = IO.select(ios, [], [], 1) next if readers.nil? # If IO.select times out we iterate again so we can check if the process has exited readers.each do |io| data, trailing = split_partial_characters(io.readpartial(4096)) handlers[io].call(previous_trailing[io] + data) previous_trailing[io] = trailing rescue IOError io.close end end $CHILD_STATUS end |
.which(cmd, env) ⇒ Object
309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/cli/kit/system.rb', line 309 def which(cmd, env) exts = os == :windows ? (env['PATHEXT'] || 'exe').split(';') : [''] (env['PATH'] || '').split(File::PATH_SEPARATOR).each do |path| exts.each do |ext| exe = File.join(path, "#{cmd}#{ext}") return exe if File.executable?(exe) && !File.directory?(exe) end end nil end |