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 #include "test_utils/test_utils.h"
33
34 using namespace test_utils;
35
36 class ShellServiceTest : public ::testing::Test {
37 public:
SetUpTestCase()38 static void SetUpTestCase() {
39 // This is normally done in main.cpp.
40 saved_sigpipe_handler_ = signal(SIGPIPE, SIG_IGN);
41 }
42
TearDownTestCase()43 static void TearDownTestCase() {
44 signal(SIGPIPE, saved_sigpipe_handler_);
45 }
46
47 // Helpers to start and cleanup a subprocess. Cleanup normally does not
48 // need to be called manually unless multiple subprocesses are run from
49 // a single test.
50 void StartTestSubprocess(const char* command, SubprocessType type,
51 SubprocessProtocol protocol);
52 void CleanupTestSubprocess();
53
54 void StartTestCommandInProcess(std::string name, Command command, SubprocessProtocol protocol);
55
TearDown()56 virtual void TearDown() override { CleanupTestSubprocess(); }
57
58 static sighandler_t saved_sigpipe_handler_;
59
60 unique_fd command_fd_;
61 };
62
63 sighandler_t ShellServiceTest::saved_sigpipe_handler_ = nullptr;
64
StartTestSubprocess(const char * command,SubprocessType type,SubprocessProtocol protocol)65 void ShellServiceTest::StartTestSubprocess(
66 const char* command, SubprocessType type, SubprocessProtocol protocol) {
67 command_fd_ = StartSubprocess(command, nullptr, type, protocol);
68 ASSERT_TRUE(command_fd_ >= 0);
69 }
70
CleanupTestSubprocess()71 void ShellServiceTest::CleanupTestSubprocess() {
72 }
73
StartTestCommandInProcess(std::string name,Command command,SubprocessProtocol protocol)74 void ShellServiceTest::StartTestCommandInProcess(std::string name, Command command,
75 SubprocessProtocol protocol) {
76 command_fd_ = StartCommandInProcess(std::move(name), std::move(command), protocol);
77 ASSERT_TRUE(command_fd_ >= 0);
78 }
79
80 // Tests a raw subprocess with no protocol.
TEST_F(ShellServiceTest,RawNoProtocolSubprocess)81 TEST_F(ShellServiceTest, RawNoProtocolSubprocess) {
82 // [ -t 0 ] checks if stdin is connected to a terminal.
83 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
84 "echo foo; echo bar >&2; [ -t 0 ]; echo $?",
85 SubprocessType::kRaw, SubprocessProtocol::kNone));
86
87 // [ -t 0 ] == 0 means we have a terminal (PTY). Even when requesting a raw subprocess, without
88 // the shell protocol we should always force a PTY to ensure proper cleanup.
89 ExpectLinesEqual(ReadRaw(command_fd_), {"foo", "bar", "0"});
90 }
91
92 // Tests a PTY subprocess with no protocol.
TEST_F(ShellServiceTest,PtyNoProtocolSubprocess)93 TEST_F(ShellServiceTest, PtyNoProtocolSubprocess) {
94 // [ -t 0 ] checks if stdin is connected to a terminal.
95 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
96 "echo foo; echo bar >&2; [ -t 0 ]; echo $?",
97 SubprocessType::kPty, SubprocessProtocol::kNone));
98
99 // [ -t 0 ] == 0 means we have a terminal (PTY).
100 ExpectLinesEqual(ReadRaw(command_fd_), {"foo", "bar", "0"});
101 }
102
103 // Tests a raw subprocess with the shell protocol.
TEST_F(ShellServiceTest,RawShellProtocolSubprocess)104 TEST_F(ShellServiceTest, RawShellProtocolSubprocess) {
105 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
106 "echo foo; echo bar >&2; echo baz; exit 24",
107 SubprocessType::kRaw, SubprocessProtocol::kShell));
108
109 std::string stdout, stderr;
110 EXPECT_EQ(24, ReadShellProtocol(command_fd_, &stdout, &stderr));
111 ExpectLinesEqual(stdout, {"foo", "baz"});
112 ExpectLinesEqual(stderr, {"bar"});
113 }
114
115 // Tests a PTY subprocess with the shell protocol.
TEST_F(ShellServiceTest,PtyShellProtocolSubprocess)116 TEST_F(ShellServiceTest, PtyShellProtocolSubprocess) {
117 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
118 "echo foo; echo bar >&2; echo baz; exit 50",
119 SubprocessType::kPty, SubprocessProtocol::kShell));
120
121 // PTY always combines stdout and stderr but the shell protocol should
122 // still give us an exit code.
123 std::string stdout, stderr;
124 EXPECT_EQ(50, ReadShellProtocol(command_fd_, &stdout, &stderr));
125 ExpectLinesEqual(stdout, {"foo", "bar", "baz"});
126 ExpectLinesEqual(stderr, {});
127 }
128
129 // Tests an interactive PTY session.
TEST_F(ShellServiceTest,InteractivePtySubprocess)130 TEST_F(ShellServiceTest, InteractivePtySubprocess) {
131 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
132 "", SubprocessType::kPty, SubprocessProtocol::kShell));
133
134 // Use variable substitution so echoed input is different from output.
135 const char* commands[] = {"TEST_STR=abc123",
136 "echo --${TEST_STR}--",
137 "exit"};
138
139 ShellProtocol* protocol = new ShellProtocol(command_fd_);
140 for (std::string command : commands) {
141 // Interactive shell requires a newline to complete each command.
142 command.push_back('\n');
143 memcpy(protocol->data(), command.data(), command.length());
144 ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, command.length()));
145 }
146 delete protocol;
147
148 std::string stdout, stderr;
149 EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr));
150 // An unpredictable command prompt makes parsing exact output difficult but
151 // it should at least contain echoed input and the expected output.
152 for (const char* command : commands) {
153 EXPECT_FALSE(stdout.find(command) == std::string::npos);
154 }
155 EXPECT_FALSE(stdout.find("--abc123--") == std::string::npos);
156 }
157
158 // Tests closing raw subprocess stdin.
TEST_F(ShellServiceTest,CloseClientStdin)159 TEST_F(ShellServiceTest, CloseClientStdin) {
160 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
161 "cat; echo TEST_DONE",
162 SubprocessType::kRaw, SubprocessProtocol::kShell));
163
164 std::string input = "foo\nbar";
165 ShellProtocol* protocol = new ShellProtocol(command_fd_);
166 memcpy(protocol->data(), input.data(), input.length());
167 ASSERT_TRUE(protocol->Write(ShellProtocol::kIdStdin, input.length()));
168 ASSERT_TRUE(protocol->Write(ShellProtocol::kIdCloseStdin, 0));
169 delete protocol;
170
171 std::string stdout, stderr;
172 EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr));
173 ExpectLinesEqual(stdout, {"foo", "barTEST_DONE"});
174 ExpectLinesEqual(stderr, {});
175 }
176
177 // Tests that nothing breaks when the stdin/stdout pipe closes.
TEST_F(ShellServiceTest,CloseStdinStdoutSubprocess)178 TEST_F(ShellServiceTest, CloseStdinStdoutSubprocess) {
179 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
180 "exec 0<&-; exec 1>&-; echo bar >&2",
181 SubprocessType::kRaw, SubprocessProtocol::kShell));
182
183 std::string stdout, stderr;
184 EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr));
185 ExpectLinesEqual(stdout, {});
186 ExpectLinesEqual(stderr, {"bar"});
187 }
188
189 // Tests that nothing breaks when the stderr pipe closes.
TEST_F(ShellServiceTest,CloseStderrSubprocess)190 TEST_F(ShellServiceTest, CloseStderrSubprocess) {
191 ASSERT_NO_FATAL_FAILURE(StartTestSubprocess(
192 "exec 2>&-; echo foo",
193 SubprocessType::kRaw, SubprocessProtocol::kShell));
194
195 std::string stdout, stderr;
196 EXPECT_EQ(0, ReadShellProtocol(command_fd_, &stdout, &stderr));
197 ExpectLinesEqual(stdout, {"foo"});
198 ExpectLinesEqual(stderr, {});
199 }
200
201 // Tests an inprocess command with no protocol.
TEST_F(ShellServiceTest,RawNoProtocolInprocess)202 TEST_F(ShellServiceTest, RawNoProtocolInprocess) {
203 ASSERT_NO_FATAL_FAILURE(
204 StartTestCommandInProcess("123",
205 [](auto args, auto in, auto out, auto err) -> int {
206 EXPECT_EQ("123", args);
207 char input[10];
208 EXPECT_TRUE(ReadFdExactly(in, input, 2));
209 input[2] = 0;
210 EXPECT_STREQ("in", input);
211 WriteFdExactly(out, "out\n");
212 WriteFdExactly(err, "err\n");
213 return 0;
214 },
215 SubprocessProtocol::kNone));
216
217 WriteFdExactly(command_fd_, "in");
218 ExpectLinesEqual(ReadRaw(command_fd_), {"out", "err"});
219 }
220
221 // Tests an inprocess command with the shell protocol.
TEST_F(ShellServiceTest,RawShellProtocolInprocess)222 TEST_F(ShellServiceTest, RawShellProtocolInprocess) {
223 ASSERT_NO_FATAL_FAILURE(
224 StartTestCommandInProcess("321",
225 [](auto args, auto in, auto out, auto err) -> int {
226 EXPECT_EQ("321", args);
227 char input[10];
228 EXPECT_TRUE(ReadFdExactly(in, input, 2));
229 input[2] = 0;
230 EXPECT_STREQ("in", input);
231 WriteFdExactly(out, "out\n");
232 WriteFdExactly(err, "err\n");
233 return 0;
234 },
235 SubprocessProtocol::kShell));
236
237 {
238 auto write_protocol = std::make_unique<ShellProtocol>(command_fd_);
239 memcpy(write_protocol->data(), "in", 2);
240 write_protocol->Write(ShellProtocol::kIdStdin, 2);
241 }
242
243 std::string stdout, stderr;
244 // For in-process commands the exit code is always the default (1).
245 EXPECT_EQ(1, ReadShellProtocol(command_fd_, &stdout, &stderr));
246 ExpectLinesEqual(stdout, {"out"});
247 ExpectLinesEqual(stderr, {"err"});
248 }
249