1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "shell_service.h" 18 19 #include <gtest/gtest.h> 20 21 #include <signal.h> 22 23 #include <string> 24 #include <vector> 25 26 #include <android-base/strings.h> 27 28 #include "adb.h" 29 #include "adb_io.h" 30 #include "shell_protocol.h" 31 #include "sysdeps.h" 32 33 class ShellServiceTest : public ::testing::Test { 34 public: 35 static void SetUpTestCase() { 36 // This is normally done in main.cpp. 37 saved_sigpipe_handler_ = signal(SIGPIPE, SIG_IGN); 38 } 39 40 static void TearDownTestCase() { 41 signal(SIGPIPE, saved_sigpipe_handler_); 42 } 43 44 // Helpers to start and cleanup a subprocess. Cleanup normally does not 45 // need to be called manually unless multiple subprocesses are run from 46 // a single test. 47 void StartTestSubprocess(const char* command, SubprocessType type, 48 SubprocessProtocol protocol); 49 void CleanupTestSubprocess(); 50 51 void StartTestCommandInProcess(std::string name, Command command, SubprocessProtocol protocol); 52 53 virtual void TearDown() override { CleanupTestSubprocess(); } 54 55 static sighandler_t saved_sigpipe_handler_; 56 57 unique_fd command_fd_; 58 }; 59 60 sighandler_t ShellServiceTest::saved_sigpipe_handler_ = nullptr; 61 62 void ShellServiceTest::StartTestSubprocess( 63 const char* command, SubprocessType type, SubprocessProtocol protocol) { 64 command_fd_ = StartSubprocess(command, nullptr, type, protocol); 65 ASSERT_TRUE(command_fd_ >= 0); 66 } 67 68 void ShellServiceTest::CleanupTestSubprocess() { 69 } 70 71 void ShellServiceTest::StartTestCommandInProcess(std::string name, Command command, 72 SubprocessProtocol protocol) { 73 command_fd_ = StartCommandInProcess(std::move(name), std::move(command), protocol); 74 ASSERT_TRUE(command_fd_ >= 0); 75 } 76 77 namespace { 78 79 // Reads raw data from |fd| until it closes or errors. 80 std::string ReadRaw(borrowed_fd fd) { 81 char buffer[1024]; 82 char *cur_ptr = buffer, *end_ptr = buffer + sizeof(buffer); 83 84 while (1) { 85 int bytes = adb_read(fd, cur_ptr, end_ptr - cur_ptr); 86 if (bytes <= 0) { 87 return std::string(buffer, cur_ptr); 88 } 89 cur_ptr += bytes; 90 } 91 } 92 93 // Reads shell protocol data from |fd| until it closes or errors. Fills 94 // |stdout| and |stderr| with their respective data, and returns the exit code 95 // read from the protocol or -1 if an exit code packet was not received. 96 int ReadShellProtocol(borrowed_fd fd, std::string* stdout, std::string* stderr) { 97 int exit_code = -1; 98 stdout->clear(); 99 stderr->clear(); 100 101 auto protocol = std::make_unique<ShellProtocol>(fd.get()); 102 while (protocol->Read()) { 103 switch (protocol->id()) { 104 case ShellProtocol::kIdStdout: 105 stdout->append(protocol->data(), protocol->data_length()); 106 break; 107 case ShellProtocol::kIdStderr: 108 stderr->append(protocol->data(), protocol->data_length()); 109 break; 110 case ShellProtocol::kIdExit: 111 EXPECT_EQ(-1, exit_code) << "Multiple exit packets received"; 112 EXPECT_EQ(1u, protocol->data_length()); 113 exit_code = protocol->data()[0]; 114 break; 115 default: 116 ADD_FAILURE() << "Unidentified packet ID: " << protocol->id(); 117 } 118 } 119 120 return exit_code; 121 } 122 123 // Checks if each line in |lines| exists in the same order in |output|. Blank 124 // lines in |output| are ignored for simplicity. 125 bool ExpectLinesEqual(const std::string& output, 126 const std::vector<std::string>& lines) { 127 auto output_lines = android::base::Split(output, "\r\n"); 128 size_t i = 0; 129 130 for (const std::string& line : lines) { 131 // Skip empty lines in output. 132 while (i < output_lines.size() && output_lines[i].empty()) { 133 ++i; 134 } 135 if (i >= output_lines.size()) { 136 ADD_FAILURE() << "Ran out of output lines"; 137 return false; 138 } 139 EXPECT_EQ(line, output_lines[i]); 140 ++i; 141 } 142 143 while (i < output_lines.size() && output_lines[i].empty()) { 144 ++i; 145 } 146 EXPECT_EQ(i, output_lines.size()) << "Found unmatched output lines"; 147 return true; 148 } 149 150 } // namespace 151 152 // Tests a raw subprocess with no protocol. 153 TEST_F(ShellServiceTest, RawNoProtocolSubprocess) { 154 // [ -t 0 ] checks if stdin is connected to a terminal. 155 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess( 156 "echo foo; echo bar >&2; [ -t 0 ]; echo $?", 157 SubprocessType::kRaw, SubprocessProtocol::kNone)); 158 159 // [ -t 0 ] == 0 means we have a terminal (PTY). Even when requesting a raw subprocess, without 160 // the shell protocol we should always force a PTY to ensure proper cleanup. 161 ExpectLinesEqual(ReadRaw(command_fd_), {"foo", "bar", "0"}); 162 } 163 164 // Tests a PTY subprocess with no protocol. 165 TEST_F(ShellServiceTest, PtyNoProtocolSubprocess) { 166 // [ -t 0 ] checks if stdin is connected to a terminal. 167 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess( 168 "echo foo; echo bar >&2; [ -t 0 ]; echo $?", 169 SubprocessType::kPty, SubprocessProtocol::kNone)); 170 171 // [ -t 0 ] == 0 means we have a terminal (PTY). 172 ExpectLinesEqual(ReadRaw(command_fd_), {"foo", "bar", "0"}); 173 } 174 175 // Tests a raw subprocess with the shell protocol. 176 TEST_F(ShellServiceTest, RawShellProtocolSubprocess) { 177 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess( 178 "echo foo; echo bar >&2; echo baz; exit 24", 179 SubprocessType::kRaw, SubprocessProtocol::kShell)); 180 181 std::string stdout, stderr; 182 EXPECT_EQ(24, ReadShellProtocol(command_fd_, &stdout, &stderr)); 183 ExpectLinesEqual(stdout, {"foo", "baz"}); 184 ExpectLinesEqual(stderr, {"bar"}); 185 } 186 187 // Tests a PTY subprocess with the shell protocol. 188 TEST_F(ShellServiceTest, PtyShellProtocolSubprocess) { 189 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess( 190 "echo foo; echo bar >&2; echo baz; exit 50", 191 SubprocessType::kPty, SubprocessProtocol::kShell)); 192 193 // PTY always combines stdout and stderr but the shell protocol should 194 // still give us an exit code. 195 std::string stdout, stderr; 196 EXPECT_EQ(50, ReadShellProtocol(command_fd_, &stdout, &stderr)); 197 ExpectLinesEqual(stdout, {"foo", "bar", "baz"}); 198 ExpectLinesEqual(stderr, {}); 199 } 200 201 // Tests an interactive PTY session. 202 TEST_F(ShellServiceTest, InteractivePtySubprocess) { 203 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess( 204 "", SubprocessType::kPty, SubprocessProtocol::kShell)); 205 206 // Use variable substitution so echoed input is different from output. 207 const char* commands[] = {"TEST_STR=abc123", 208 "echo --${TEST_STR}--", 209 "exit"}; 210 211 ShellProtocol* protocol = new ShellProtocol(command_fd_); 212 for (std::string command : commands) { 213 // Interactive shell requires a newline to complete each command. 214 command.push_back('\n'); 215 memcpy(protocol->data(), command.data(), command.length()); 216 ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, command.length())); 217 } 218 delete protocol; 219 220 std::string stdout, stderr; 221 EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr)); 222 // An unpredictable command prompt makes parsing exact output difficult but 223 // it should at least contain echoed input and the expected output. 224 for (const char* command : commands) { 225 EXPECT_FALSE(stdout.find(command) == std::string::npos); 226 } 227 EXPECT_FALSE(stdout.find("--abc123--") == std::string::npos); 228 } 229 230 // Tests closing raw subprocess stdin. 231 TEST_F(ShellServiceTest, CloseClientStdin) { 232 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess( 233 "cat; echo TEST_DONE", 234 SubprocessType::kRaw, SubprocessProtocol::kShell)); 235 236 std::string input = "foo\nbar"; 237 ShellProtocol* protocol = new ShellProtocol(command_fd_); 238 memcpy(protocol->data(), input.data(), input.length()); 239 ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, input.length())); 240 ASSERT_TRUE(protocol->Write(ShellProtocol::kIdCloseStdin, 0)); 241 delete protocol; 242 243 std::string stdout, stderr; 244 EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr)); 245 ExpectLinesEqual(stdout, {"foo", "barTEST_DONE"}); 246 ExpectLinesEqual(stderr, {}); 247 } 248 249 // Tests that nothing breaks when the stdin/stdout pipe closes. 250 TEST_F(ShellServiceTest, CloseStdinStdoutSubprocess) { 251 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess( 252 "exec 0<&-; exec 1>&-; echo bar >&2", 253 SubprocessType::kRaw, SubprocessProtocol::kShell)); 254 255 std::string stdout, stderr; 256 EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr)); 257 ExpectLinesEqual(stdout, {}); 258 ExpectLinesEqual(stderr, {"bar"}); 259 } 260 261 // Tests that nothing breaks when the stderr pipe closes. 262 TEST_F(ShellServiceTest, CloseStderrSubprocess) { 263 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess( 264 "exec 2>&-; echo foo", 265 SubprocessType::kRaw, SubprocessProtocol::kShell)); 266 267 std::string stdout, stderr; 268 EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr)); 269 ExpectLinesEqual(stdout, {"foo"}); 270 ExpectLinesEqual(stderr, {}); 271 } 272 273 // Tests an inprocess command with no protocol. 274 TEST_F(ShellServiceTest, RawNoProtocolInprocess) { 275 ASSERT_NO_FATAL_FAILURE( 276 StartTestCommandInProcess("123", 277 [](auto args, auto in, auto out, auto err) -> int { 278 EXPECT_EQ("123", args); 279 char input[10]; 280 EXPECT_TRUE(ReadFdExactly(in, input, 2)); 281 input[2] = 0; 282 EXPECT_STREQ("in", input); 283 WriteFdExactly(out, "out\n"); 284 WriteFdExactly(err, "err\n"); 285 return 0; 286 }, 287 SubprocessProtocol::kNone)); 288 289 WriteFdExactly(command_fd_, "in"); 290 ExpectLinesEqual(ReadRaw(command_fd_), {"out", "err"}); 291 } 292 293 // Tests an inprocess command with the shell protocol. 294 TEST_F(ShellServiceTest, RawShellProtocolInprocess) { 295 ASSERT_NO_FATAL_FAILURE( 296 StartTestCommandInProcess("321", 297 [](auto args, auto in, auto out, auto err) -> int { 298 EXPECT_EQ("321", args); 299 char input[10]; 300 EXPECT_TRUE(ReadFdExactly(in, input, 2)); 301 input[2] = 0; 302 EXPECT_STREQ("in", input); 303 WriteFdExactly(out, "out\n"); 304 WriteFdExactly(err, "err\n"); 305 return 0; 306 }, 307 SubprocessProtocol::kShell)); 308 309 { 310 auto write_protocol = std::make_unique<ShellProtocol>(command_fd_); 311 memcpy(write_protocol->data(), "in", 2); 312 write_protocol->Write(ShellProtocol::kIdStdin, 2); 313 } 314 315 std::string stdout, stderr; 316 // For in-process commands the exit code is always the default (1). 317 EXPECT_EQ(1, ReadShellProtocol(command_fd_, &stdout, &stderr)); 318 ExpectLinesEqual(stdout, {"out"}); 319 ExpectLinesEqual(stderr, {"err"}); 320 } 321