• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "sysdeps.h"
31 
32 class ShellServiceTest : public ::testing::Test {
33   public:
SetUpTestCase()34     static void SetUpTestCase() {
35         // This is normally done in main.cpp.
36         saved_sigpipe_handler_ = signal(SIGPIPE, SIG_IGN);
37 
38     }
39 
TearDownTestCase()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 
TearDown()51     virtual void TearDown() override {
52         void CleanupTestSubprocess();
53     }
54 
55     static sighandler_t saved_sigpipe_handler_;
56 
57     int subprocess_fd_ = -1;
58 };
59 
60 sighandler_t ShellServiceTest::saved_sigpipe_handler_ = nullptr;
61 
StartTestSubprocess(const char * command,SubprocessType type,SubprocessProtocol protocol)62 void ShellServiceTest::StartTestSubprocess(
63         const char* command, SubprocessType type, SubprocessProtocol protocol) {
64     subprocess_fd_ = StartSubprocess(command, nullptr, type, protocol);
65     ASSERT_TRUE(subprocess_fd_ >= 0);
66 }
67 
CleanupTestSubprocess()68 void ShellServiceTest::CleanupTestSubprocess() {
69     if (subprocess_fd_ >= 0) {
70         adb_close(subprocess_fd_);
71         subprocess_fd_ = -1;
72     }
73 }
74 
75 namespace {
76 
77 // Reads raw data from |fd| until it closes or errors.
ReadRaw(int fd)78 std::string ReadRaw(int fd) {
79     char buffer[1024];
80     char *cur_ptr = buffer, *end_ptr = buffer + sizeof(buffer);
81 
82     while (1) {
83         int bytes = adb_read(fd, cur_ptr, end_ptr - cur_ptr);
84         if (bytes <= 0) {
85             return std::string(buffer, cur_ptr);
86         }
87         cur_ptr += bytes;
88     }
89 }
90 
91 // Reads shell protocol data from |fd| until it closes or errors. Fills
92 // |stdout| and |stderr| with their respective data, and returns the exit code
93 // read from the protocol or -1 if an exit code packet was not received.
ReadShellProtocol(int fd,std::string * stdout,std::string * stderr)94 int ReadShellProtocol(int fd, std::string* stdout, std::string* stderr) {
95     int exit_code = -1;
96     stdout->clear();
97     stderr->clear();
98 
99     ShellProtocol* protocol = new ShellProtocol(fd);
100     while (protocol->Read()) {
101         switch (protocol->id()) {
102             case ShellProtocol::kIdStdout:
103                 stdout->append(protocol->data(), protocol->data_length());
104                 break;
105             case ShellProtocol::kIdStderr:
106                 stderr->append(protocol->data(), protocol->data_length());
107                 break;
108             case ShellProtocol::kIdExit:
109                 EXPECT_EQ(-1, exit_code) << "Multiple exit packets received";
110                 EXPECT_EQ(1u, protocol->data_length());
111                 exit_code = protocol->data()[0];
112                 break;
113             default:
114                 ADD_FAILURE() << "Unidentified packet ID: " << protocol->id();
115         }
116     }
117     delete protocol;
118 
119     return exit_code;
120 }
121 
122 // Checks if each line in |lines| exists in the same order in |output|. Blank
123 // lines in |output| are ignored for simplicity.
ExpectLinesEqual(const std::string & output,const std::vector<std::string> & lines)124 bool ExpectLinesEqual(const std::string& output,
125                       const std::vector<std::string>& lines) {
126     auto output_lines = android::base::Split(output, "\r\n");
127     size_t i = 0;
128 
129     for (const std::string& line : lines) {
130         // Skip empty lines in output.
131         while (i < output_lines.size() && output_lines[i].empty()) {
132             ++i;
133         }
134         if (i >= output_lines.size()) {
135             ADD_FAILURE() << "Ran out of output lines";
136             return false;
137         }
138         EXPECT_EQ(line, output_lines[i]);
139         ++i;
140     }
141 
142     while (i < output_lines.size() && output_lines[i].empty()) {
143         ++i;
144     }
145     EXPECT_EQ(i, output_lines.size()) << "Found unmatched output lines";
146     return true;
147 }
148 
149 }  // namespace
150 
151 // Tests a raw subprocess with no protocol.
TEST_F(ShellServiceTest,RawNoProtocolSubprocess)152 TEST_F(ShellServiceTest, RawNoProtocolSubprocess) {
153     // [ -t 0 ] checks if stdin is connected to a terminal.
154     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
155             "echo foo; echo bar >&2; [ -t 0 ]; echo $?",
156             SubprocessType::kRaw, SubprocessProtocol::kNone));
157 
158     // [ -t 0 ] == 0 means we have a terminal (PTY). Even when requesting a raw subprocess, without
159     // the shell protocol we should always force a PTY to ensure proper cleanup.
160     ExpectLinesEqual(ReadRaw(subprocess_fd_), {"foo", "bar", "0"});
161 }
162 
163 // Tests a PTY subprocess with no protocol.
TEST_F(ShellServiceTest,PtyNoProtocolSubprocess)164 TEST_F(ShellServiceTest, PtyNoProtocolSubprocess) {
165     // [ -t 0 ] checks if stdin is connected to a terminal.
166     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
167             "echo foo; echo bar >&2; [ -t 0 ]; echo $?",
168             SubprocessType::kPty, SubprocessProtocol::kNone));
169 
170     // [ -t 0 ] == 0 means we have a terminal (PTY).
171     ExpectLinesEqual(ReadRaw(subprocess_fd_), {"foo", "bar", "0"});
172 }
173 
174 // Tests a raw subprocess with the shell protocol.
TEST_F(ShellServiceTest,RawShellProtocolSubprocess)175 TEST_F(ShellServiceTest, RawShellProtocolSubprocess) {
176     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
177             "echo foo; echo bar >&2; echo baz; exit 24",
178             SubprocessType::kRaw, SubprocessProtocol::kShell));
179 
180     std::string stdout, stderr;
181     EXPECT_EQ(24, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
182     ExpectLinesEqual(stdout, {"foo", "baz"});
183     ExpectLinesEqual(stderr, {"bar"});
184 }
185 
186 // Tests a PTY subprocess with the shell protocol.
TEST_F(ShellServiceTest,PtyShellProtocolSubprocess)187 TEST_F(ShellServiceTest, PtyShellProtocolSubprocess) {
188     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
189             "echo foo; echo bar >&2; echo baz; exit 50",
190             SubprocessType::kPty, SubprocessProtocol::kShell));
191 
192     // PTY always combines stdout and stderr but the shell protocol should
193     // still give us an exit code.
194     std::string stdout, stderr;
195     EXPECT_EQ(50, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
196     ExpectLinesEqual(stdout, {"foo", "bar", "baz"});
197     ExpectLinesEqual(stderr, {});
198 }
199 
200 // Tests an interactive PTY session.
TEST_F(ShellServiceTest,InteractivePtySubprocess)201 TEST_F(ShellServiceTest, InteractivePtySubprocess) {
202     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
203             "", SubprocessType::kPty, SubprocessProtocol::kShell));
204 
205     // Use variable substitution so echoed input is different from output.
206     const char* commands[] = {"TEST_STR=abc123",
207                               "echo --${TEST_STR}--",
208                               "exit"};
209 
210     ShellProtocol* protocol = new ShellProtocol(subprocess_fd_);
211     for (std::string command : commands) {
212         // Interactive shell requires a newline to complete each command.
213         command.push_back('\n');
214         memcpy(protocol->data(), command.data(), command.length());
215         ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, command.length()));
216     }
217     delete protocol;
218 
219     std::string stdout, stderr;
220     EXPECT_EQ(0, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
221     // An unpredictable command prompt makes parsing exact output difficult but
222     // it should at least contain echoed input and the expected output.
223     for (const char* command : commands) {
224         EXPECT_FALSE(stdout.find(command) == std::string::npos);
225     }
226     EXPECT_FALSE(stdout.find("--abc123--") == std::string::npos);
227 }
228 
229 // Tests closing raw subprocess stdin.
TEST_F(ShellServiceTest,CloseClientStdin)230 TEST_F(ShellServiceTest, CloseClientStdin) {
231     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
232             "cat; echo TEST_DONE",
233             SubprocessType::kRaw, SubprocessProtocol::kShell));
234 
235     std::string input = "foo\nbar";
236     ShellProtocol* protocol = new ShellProtocol(subprocess_fd_);
237     memcpy(protocol->data(), input.data(), input.length());
238     ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, input.length()));
239     ASSERT_TRUE(protocol->Write(ShellProtocol::kIdCloseStdin, 0));
240     delete protocol;
241 
242     std::string stdout, stderr;
243     EXPECT_EQ(0, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
244     ExpectLinesEqual(stdout, {"foo", "barTEST_DONE"});
245     ExpectLinesEqual(stderr, {});
246 }
247 
248 // Tests that nothing breaks when the stdin/stdout pipe closes.
TEST_F(ShellServiceTest,CloseStdinStdoutSubprocess)249 TEST_F(ShellServiceTest, CloseStdinStdoutSubprocess) {
250     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
251             "exec 0<&-; exec 1>&-; echo bar >&2",
252             SubprocessType::kRaw, SubprocessProtocol::kShell));
253 
254     std::string stdout, stderr;
255     EXPECT_EQ(0, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
256     ExpectLinesEqual(stdout, {});
257     ExpectLinesEqual(stderr, {"bar"});
258 }
259 
260 // Tests that nothing breaks when the stderr pipe closes.
TEST_F(ShellServiceTest,CloseStderrSubprocess)261 TEST_F(ShellServiceTest, CloseStderrSubprocess) {
262     ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
263             "exec 2>&-; echo foo",
264             SubprocessType::kRaw, SubprocessProtocol::kShell));
265 
266     std::string stdout, stderr;
267     EXPECT_EQ(0, ReadShellProtocol(subprocess_fd_, &stdout, &stderr));
268     ExpectLinesEqual(stdout, {"foo"});
269     ExpectLinesEqual(stderr, {});
270 }
271