| 
									
										
										
										
											2019-04-19 15:38:03 +09:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-26 17:33:55 -08:00
										 |  |  | require "system_command" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-18 15:11:11 -08:00
										 |  |  | RSpec.describe SystemCommand do | 
					
						
							| 
									
										
										
										
											2018-06-01 23:26:12 +02:00
										 |  |  |   describe "#initialize" do | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |     subject(:command) do | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  |       described_class.new( | 
					
						
							|  |  |  |         "env", | 
					
						
							| 
									
										
										
										
											2018-11-02 17:18:07 +00:00
										 |  |  |         args:         env_args, | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |         env:, | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  |         must_succeed: true, | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |         sudo:, | 
					
						
							|  |  |  |         sudo_as_root:, | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  |       ) | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-06-01 23:26:12 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-20 09:07:56 +01:00
										 |  |  |     let(:env_args) { ["bash", "-c", 'printf "%s" "${A?}" "${B?}" "${C?}"'] } | 
					
						
							|  |  |  |     let(:env) { { "A" => "1", "B" => "2", "C" => "3" } } | 
					
						
							|  |  |  |     let(:sudo) { false } | 
					
						
							| 
									
										
										
										
											2023-04-26 19:51:26 -07:00
										 |  |  |     let(:sudo_as_root) { false } | 
					
						
							| 
									
										
										
										
											2018-09-20 09:07:56 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-11 09:14:53 +02:00
										 |  |  |     context "when given some environment variables" do | 
					
						
							| 
									
										
										
										
											2024-03-17 22:47:37 -07:00
										 |  |  |       it("run!.stdout") { expect(command.run!.stdout).to eq("123") } | 
					
						
							| 
									
										
										
										
											2018-06-01 23:26:12 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |       describe "the resulting command line" do | 
					
						
							| 
									
										
										
										
											2018-07-11 11:13:33 +02:00
										 |  |  |         it "includes the given variables explicitly" do | 
					
						
							| 
									
										
										
										
											2024-07-17 05:26:43 +01:00
										 |  |  |           expect(command) | 
					
						
							|  |  |  |             .to receive(:exec3) | 
					
						
							| 
									
										
										
										
											2020-12-17 15:45:50 +01:00
										 |  |  |             .with( | 
					
						
							| 
									
										
										
										
											2024-07-17 05:26:43 +01:00
										 |  |  |               an_instance_of(Hash), "/usr/bin/env", "A=1", "B=2", "C=3", | 
					
						
							| 
									
										
										
										
											2020-12-17 15:45:50 +01:00
										 |  |  |               "env", *env_args, | 
					
						
							|  |  |  |               pgroup: true | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2018-06-01 23:26:12 +02:00
										 |  |  |             .and_call_original | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  |           command.run! | 
					
						
							| 
									
										
										
										
											2018-06-01 23:26:12 +02:00
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  |     context "when given an environment variable which is set to nil" do | 
					
						
							|  |  |  |       let(:env) { { "A" => "1", "B" => "2", "C" => nil } } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it "unsets them" do | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         expect do | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  |           command.run! | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         end.to raise_error(/C: parameter (null or )?not set/) | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-26 19:51:26 -07:00
										 |  |  |     context "when given some environment variables and sudo: true, sudo_as_root: false" do | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  |       let(:sudo) { true } | 
					
						
							| 
									
										
										
										
											2023-04-26 19:51:26 -07:00
										 |  |  |       let(:sudo_as_root) { false } | 
					
						
							| 
									
										
										
										
											2018-06-01 23:26:12 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |       describe "the resulting command line" do | 
					
						
							|  |  |  |         it "includes the given variables explicitly" do | 
					
						
							| 
									
										
										
										
											2024-07-17 05:26:43 +01:00
										 |  |  |           expect(command) | 
					
						
							|  |  |  |             .to receive(:exec3) | 
					
						
							| 
									
										
										
										
											2020-12-17 15:45:50 +01:00
										 |  |  |             .with( | 
					
						
							| 
									
										
										
										
											2024-07-17 05:26:43 +01:00
										 |  |  |               an_instance_of(Hash), "/usr/bin/sudo", "-E", | 
					
						
							| 
									
										
										
										
											2023-02-13 18:40:37 -08:00
										 |  |  |               "A=1", "B=2", "C=3", "--", "env", *env_args, pgroup: nil | 
					
						
							| 
									
										
										
										
											2020-12-17 15:45:50 +01:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2024-07-17 05:26:43 +01:00
										 |  |  |             .and_wrap_original do |original_exec3, *_, &block| | 
					
						
							|  |  |  |               original_exec3.call({}, "true", &block) | 
					
						
							| 
									
										
										
										
											2018-06-01 23:26:12 +02:00
										 |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  |           command.run! | 
					
						
							| 
									
										
										
										
											2018-06-01 23:26:12 +02:00
										 |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2023-04-26 19:51:26 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     context "when given some environment variables and sudo: true, sudo_as_root: true" do | 
					
						
							|  |  |  |       let(:sudo) { true } | 
					
						
							|  |  |  |       let(:sudo_as_root) { true } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       describe "the resulting command line" do | 
					
						
							|  |  |  |         it "includes the given variables explicitly" do | 
					
						
							| 
									
										
										
										
											2024-07-17 05:26:43 +01:00
										 |  |  |           expect(command) | 
					
						
							|  |  |  |             .to receive(:exec3) | 
					
						
							| 
									
										
										
										
											2023-04-26 19:51:26 -07:00
										 |  |  |             .with( | 
					
						
							| 
									
										
										
										
											2024-07-17 05:26:43 +01:00
										 |  |  |               an_instance_of(Hash), "/usr/bin/sudo", "-u", "root", | 
					
						
							| 
									
										
										
										
											2023-04-26 19:51:26 -07:00
										 |  |  |               "-E", "A=1", "B=2", "C=3", "--", "env", *env_args, pgroup: nil | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2024-07-17 05:26:43 +01:00
										 |  |  |             .and_wrap_original do |original_exec3, *_, &block| | 
					
						
							|  |  |  |               original_exec3.call({}, "true", &block) | 
					
						
							| 
									
										
										
										
											2023-04-26 19:51:26 -07:00
										 |  |  |             end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           command.run! | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-06-01 23:26:12 +02:00
										 |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-11 09:14:53 +02:00
										 |  |  |   context "when the exit code is 0" do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |     describe "its result" do | 
					
						
							| 
									
										
										
										
											2024-03-17 22:47:37 -07:00
										 |  |  |       subject(:result) { described_class.run("true") } | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |       it { is_expected.to be_a_success } | 
					
						
							| 
									
										
										
										
											2024-03-17 22:47:37 -07:00
										 |  |  |       it(:exit_status) { expect(result.exit_status).to eq(0) } | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-11 09:14:53 +02:00
										 |  |  |   context "when the exit code is 1" do | 
					
						
							| 
									
										
										
										
											2018-07-20 17:52:44 +02:00
										 |  |  |     let(:command) { "false" } | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-19 23:15:33 +00:00
										 |  |  |     context "with a command that must succeed" do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       it "throws an error" do | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         expect do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |           described_class.run!(command) | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         end.to raise_error(ErrorDuringExecution) | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-19 23:15:33 +00:00
										 |  |  |     context "with a command that does not have to succeed" do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       describe "its result" do | 
					
						
							| 
									
										
										
										
											2024-03-17 22:47:37 -07:00
										 |  |  |         subject(:result) { described_class.run(command) } | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         it { is_expected.not_to be_a_success } | 
					
						
							| 
									
										
										
										
											2024-03-17 22:47:37 -07:00
										 |  |  |         it(:exit_status) { expect(result.exit_status).to eq(1) } | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-11 09:14:53 +02:00
										 |  |  |   context "when given a pathname" do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |     let(:command) { "/bin/ls" } | 
					
						
							|  |  |  |     let(:path)    { Pathname(Dir.mktmpdir) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     before do | 
					
						
							|  |  |  |       FileUtils.touch(path.join("somefile")) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe "its result" do | 
					
						
							| 
									
										
										
										
											2024-03-17 22:47:37 -07:00
										 |  |  |       subject(:result) { described_class.run(command, args: [path]) } | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |       it { is_expected.to be_a_success } | 
					
						
							| 
									
										
										
										
											2024-03-17 22:47:37 -07:00
										 |  |  |       it(:stdout) { expect(result.stdout).to eq("somefile\n") } | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-11 09:14:53 +02:00
										 |  |  |   context "with both STDOUT and STDERR output from upstream" do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |     let(:command) { "/bin/bash" } | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |     let(:options) do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       { args: [ | 
					
						
							| 
									
										
										
										
											2016-10-14 20:33:16 +02:00
										 |  |  |         "-c", | 
					
						
							|  |  |  |         "for i in $(seq 1 2 5); do echo $i; echo $(($i + 1)) >&2; done", | 
					
						
							|  |  |  |       ] } | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     shared_examples "it returns '1 2 3 4 5 6'" do | 
					
						
							|  |  |  |       describe "its result" do | 
					
						
							| 
									
										
										
										
											2024-03-17 22:47:37 -07:00
										 |  |  |         subject(:result) { described_class.run(command, **options) } | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         it { is_expected.to be_a_success } | 
					
						
							| 
									
										
										
										
											2024-03-17 22:47:37 -07:00
										 |  |  |         it(:stdout) { expect(result.stdout).to eq([1, 3, 5, nil].join("\n")) } | 
					
						
							|  |  |  |         it(:stderr) { expect(result.stderr).to eq([2, 4, 6, nil].join("\n")) } | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-11 09:14:53 +02:00
										 |  |  |     context "with default options" do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       it "echoes only STDERR" do | 
					
						
							| 
									
										
										
										
											2018-06-14 22:45:07 +02:00
										 |  |  |         expected = [2, 4, 6].map { |i| "#{i}\n" }.join | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         expect do | 
					
						
							| 
									
										
										
										
											2022-10-18 01:32:55 +01:00
										 |  |  |           described_class.run(command, **options) | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         end.to output(expected).to_stderr | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       include_examples("it returns '1 2 3 4 5 6'") | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-10 03:39:42 +02:00
										 |  |  |     context "with `print_stdout: true`" do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       before do | 
					
						
							|  |  |  |         options.merge!(print_stdout: true) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-02 16:01:01 +02:00
										 |  |  |       it "echoes both STDOUT and STDERR" do | 
					
						
							| 
									
										
										
										
											2022-10-18 01:32:55 +01:00
										 |  |  |         expect { described_class.run(command, **options) } | 
					
						
							| 
									
										
										
										
											2018-06-14 22:45:07 +02:00
										 |  |  |           .to output("1\n3\n5\n").to_stdout | 
					
						
							|  |  |  |           .and output("2\n4\n6\n").to_stderr | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       include_examples("it returns '1 2 3 4 5 6'") | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-10 03:39:42 +02:00
										 |  |  |     context "with `print_stdout: :debug`" do | 
					
						
							|  |  |  |       before do | 
					
						
							|  |  |  |         options.merge!(print_stdout: :debug) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it "echoes only STDERR output" do | 
					
						
							|  |  |  |         expect { described_class.run(command, **options) } | 
					
						
							|  |  |  |           .to output("2\n4\n6\n").to_stderr | 
					
						
							|  |  |  |           .and not_to_output.to_stdout | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       context "when `debug?` is true" do | 
					
						
							| 
									
										
										
										
											2024-03-30 18:44:39 -07:00
										 |  |  |         include Context | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-10 03:39:42 +02:00
										 |  |  |         let(:options) do | 
					
						
							|  |  |  |           { args: [ | 
					
						
							|  |  |  |             "-c", | 
					
						
							| 
									
										
										
										
											2023-10-11 09:42:22 +02:00
										 |  |  |             "for i in $(seq 1 2 5); do echo $i; sleep 0.1; echo $(($i + 1)) >&2; sleep 0.1; done", | 
					
						
							| 
									
										
										
										
											2023-10-10 03:39:42 +02:00
										 |  |  |           ] } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         it "echoes the command and all output to STDERR when `debug?` is true" do | 
					
						
							|  |  |  |           with_context debug: true do | 
					
						
							|  |  |  |             expect { described_class.run(command, **options) } | 
					
						
							|  |  |  |               .to output(/\A.*#{Regexp.escape(command)}.*\n1\n2\n3\n4\n5\n6\n\Z/).to_stderr | 
					
						
							|  |  |  |               .and not_to_output.to_stdout | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       include_examples("it returns '1 2 3 4 5 6'") | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     context "with `print_stderr: false`" do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       before do | 
					
						
							|  |  |  |         options.merge!(print_stderr: false) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it "echoes nothing" do | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         expect do | 
					
						
							| 
									
										
										
										
											2022-10-18 01:32:55 +01:00
										 |  |  |           described_class.run(command, **options) | 
					
						
							| 
									
										
										
										
											2023-10-10 03:39:42 +02:00
										 |  |  |         end.not_to output.to_stdout | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       include_examples("it returns '1 2 3 4 5 6'") | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-10 03:39:42 +02:00
										 |  |  |     context "with `print_stdout: true` and `print_stderr: false`" do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       before do | 
					
						
							|  |  |  |         options.merge!(print_stdout: true, print_stderr: false) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it "echoes only STDOUT" do | 
					
						
							| 
									
										
										
										
											2018-06-14 22:48:37 +02:00
										 |  |  |         expected = [1, 3, 5].map { |i| "#{i}\n" }.join | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         expect do | 
					
						
							| 
									
										
										
										
											2022-10-18 01:32:55 +01:00
										 |  |  |           described_class.run(command, **options) | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         end.to output(expected).to_stdout | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       include_examples("it returns '1 2 3 4 5 6'") | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-11 09:14:53 +02:00
										 |  |  |   context "with a very long STDERR output" do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |     let(:command) { "/bin/bash" } | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |     let(:options) do | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |       { args: [ | 
					
						
							| 
									
										
										
										
											2016-10-14 20:33:16 +02:00
										 |  |  |         "-c", | 
					
						
							|  |  |  |         "for i in $(seq 1 2 100000); do echo $i; echo $(($i + 1)) >&2; done", | 
					
						
							|  |  |  |       ] } | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-25 10:07:42 -08:00
										 |  |  |     it "returns without deadlocking", timeout: 30 do | 
					
						
							|  |  |  |       expect(described_class.run(command, **options)).to be_a_success | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-06-01 23:26:12 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-11 09:14:53 +02:00
										 |  |  |   context "when given an invalid variable name" do | 
					
						
							| 
									
										
										
										
											2018-06-01 23:26:12 +02:00
										 |  |  |     it "raises an ArgumentError" do | 
					
						
							|  |  |  |       expect { described_class.run("true", env: { "1ABC" => true }) } | 
					
						
							|  |  |  |         .to raise_error(ArgumentError, /variable name/) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-07-11 09:14:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-07-11 11:13:33 +02:00
										 |  |  |   it "looks for executables in a custom PATH" do | 
					
						
							| 
									
										
										
										
											2018-07-11 09:14:53 +02:00
										 |  |  |     mktmpdir do |path| | 
					
						
							|  |  |  |       (path/"tool").write <<~SH | 
					
						
							|  |  |  |         #!/bin/sh | 
					
						
							|  |  |  |         echo Hello, world! | 
					
						
							|  |  |  |       SH | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       FileUtils.chmod "+x", path/"tool" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(described_class.run("tool", env: { "PATH" => path }).stdout).to include "Hello, world!" | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							| 
									
										
										
										
											2018-07-24 00:09:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   describe "#run" do | 
					
						
							|  |  |  |     it "does not raise a `SystemCallError` when the executable does not exist" do | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |       expect do | 
					
						
							| 
									
										
										
										
											2018-07-24 00:09:11 +02:00
										 |  |  |         described_class.run("non_existent_executable") | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |       end.not_to raise_error | 
					
						
							| 
									
										
										
										
											2018-07-24 00:09:11 +02:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     it 'does not format `stderr` when it starts with \r' do | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |       expect do | 
					
						
							| 
									
										
										
										
											2024-01-26 17:33:55 -08:00
										 |  |  |         Class.new.extend(SystemCommand::Mixin).system_command \ | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |           "bash", | 
					
						
							|  |  |  |           args: [ | 
					
						
							|  |  |  |             "-c", | 
					
						
							|  |  |  |             'printf "\r%s" "###################                                                       27.6%" 1>&2', | 
					
						
							|  |  |  |           ] | 
					
						
							| 
									
										
										
										
											2023-04-04 15:37:24 +01:00
										 |  |  |       end.to output( | 
					
						
							| 
									
										
										
										
											2018-09-02 16:15:09 +01:00
										 |  |  |         "\r###################                                                       27.6%", | 
					
						
							|  |  |  |       ).to_stderr | 
					
						
							| 
									
										
										
										
											2018-07-30 10:11:00 +02:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-08-29 19:23:30 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     context "when given an executable with spaces and no arguments" do | 
					
						
							|  |  |  |       let(:executable) { mktmpdir/"App Uninstaller" } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-20 09:07:56 +01:00
										 |  |  |       before do | 
					
						
							| 
									
										
										
										
											2018-08-29 19:23:30 +02:00
										 |  |  |         executable.write <<~SH | 
					
						
							|  |  |  |           #!/usr/bin/env bash | 
					
						
							|  |  |  |           true | 
					
						
							|  |  |  |         SH | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         FileUtils.chmod "+x", executable | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it "does not interpret the executable as a shell line" do | 
					
						
							| 
									
										
										
										
											2024-01-26 17:33:55 -08:00
										 |  |  |         expect(Class.new.extend(SystemCommand::Mixin).system_command(executable)).to be_a_success | 
					
						
							| 
									
										
										
										
											2018-08-29 19:23:30 +02:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2019-06-28 14:50:38 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     context "when given arguments with secrets" do | 
					
						
							|  |  |  |       it "does not leak the secrets" do | 
					
						
							|  |  |  |         redacted_msg = /#{Regexp.escape("username:******")}/ | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         expect do | 
					
						
							| 
									
										
										
										
											2019-06-28 14:50:38 +08:00
										 |  |  |           described_class.run! "curl", | 
					
						
							|  |  |  |                                args:    %w[--user username:hunter2], | 
					
						
							|  |  |  |                                verbose: true, | 
					
						
							|  |  |  |                                secrets: %w[hunter2] | 
					
						
							| 
									
										
										
										
											2024-08-05 09:17:23 -07:00
										 |  |  |         end.to raise_error(ErrorDuringExecution, redacted_msg).and output(redacted_msg).to_stderr | 
					
						
							| 
									
										
										
										
											2019-06-28 14:50:38 +08:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2019-07-13 23:22:18 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       it "does not leak the secrets set by environment" do | 
					
						
							|  |  |  |         redacted_msg = /#{Regexp.escape("username:******")}/ | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         expect do | 
					
						
							| 
									
										
										
										
											2019-10-13 10:03:26 +01:00
										 |  |  |           ENV["PASSWORD"] = "hunter2" | 
					
						
							|  |  |  |           described_class.run! "curl", | 
					
						
							|  |  |  |                                args:    %w[--user username:hunter2], | 
					
						
							|  |  |  |                                verbose: true | 
					
						
							| 
									
										
										
										
											2024-08-05 09:17:23 -07:00
										 |  |  |         end.to raise_error(ErrorDuringExecution, redacted_msg).and output(redacted_msg).to_stderr | 
					
						
							| 
									
										
										
										
											2019-07-13 23:22:18 +08:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2019-06-28 14:50:38 +08:00
										 |  |  |     end | 
					
						
							| 
									
										
										
										
											2020-12-17 18:09:33 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-10 08:59:53 -05:00
										 |  |  |     context "when running a process that prints secrets" do | 
					
						
							|  |  |  |       it "does not leak the secrets" do | 
					
						
							|  |  |  |         redacted_msg = /#{Regexp.escape("username:******")}/ | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         expect do | 
					
						
							| 
									
										
										
										
											2021-11-10 08:59:53 -05:00
										 |  |  |           described_class.run! "echo", | 
					
						
							|  |  |  |                                args:         %w[username:hunter2], | 
					
						
							|  |  |  |                                verbose:      true, | 
					
						
							|  |  |  |                                print_stdout: true, | 
					
						
							|  |  |  |                                secrets:      %w[hunter2] | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         end.to output(redacted_msg).to_stdout | 
					
						
							| 
									
										
										
										
											2021-11-10 08:59:53 -05:00
										 |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       it "does not leak the secrets set by environment" do | 
					
						
							|  |  |  |         redacted_msg = /#{Regexp.escape("username:******")}/ | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         expect do | 
					
						
							| 
									
										
										
										
											2021-11-10 08:59:53 -05:00
										 |  |  |           ENV["PASSWORD"] = "hunter2" | 
					
						
							|  |  |  |           described_class.run! "echo", | 
					
						
							|  |  |  |                                args:         %w[username:hunter2], | 
					
						
							|  |  |  |                                print_stdout: true, | 
					
						
							|  |  |  |                                verbose:      true | 
					
						
							| 
									
										
										
										
											2023-03-08 23:14:46 +00:00
										 |  |  |         end.to output(redacted_msg).to_stdout | 
					
						
							| 
									
										
										
										
											2021-11-10 08:59:53 -05:00
										 |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-17 18:09:33 +01:00
										 |  |  |     context "when a `SIGINT` handler is set in the parent process" do | 
					
						
							|  |  |  |       it "is not interrupted" do | 
					
						
							|  |  |  |         start_time = Time.now | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         pid = fork do | 
					
						
							|  |  |  |           trap("INT") do | 
					
						
							|  |  |  |             # Ignore SIGINT. | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           described_class.run! "sleep", args: [5] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           exit! | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         sleep 1
 | 
					
						
							|  |  |  |         Process.kill("INT", pid) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Process.waitpid(pid) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expect(Time.now - start_time).to be >= 5
 | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							| 
									
										
										
										
											2018-07-24 00:09:11 +02:00
										 |  |  |   end | 
					
						
							| 
									
										
										
										
											2016-08-18 22:11:42 +03:00
										 |  |  | end |