blob: a92d4a711df1481e2aa621a61a238f0ab9bcacdc [file] [log] [blame]
Josh Gao93dee962020-02-06 17:52:38 -08001#!/usr/bin/env python3
Josh Gao191c1542015-12-09 11:26:11 -08002# -*- coding: utf-8 -*-
3#
4# Copyright (C) 2015 The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18from __future__ import print_function
19
20import contextlib
21import hashlib
22import os
23import posixpath
24import random
25import re
26import shlex
27import shutil
28import signal
29import socket
30import string
31import subprocess
32import sys
33import tempfile
Josh Gaoc970aef2018-03-19 15:35:11 -070034import threading
Josh Gaofe50bb72016-06-22 18:27:22 -070035import time
Josh Gao191c1542015-12-09 11:26:11 -080036import unittest
37
Josh Gao74b7ec72019-01-11 14:42:08 -080038from datetime import datetime
39
Josh Gao191c1542015-12-09 11:26:11 -080040import adb
41
Josh Gao191c1542015-12-09 11:26:11 -080042def requires_root(func):
43 def wrapper(self, *args):
44 if self.device.get_prop('ro.debuggable') != '1':
45 raise unittest.SkipTest('requires rootable build')
46
47 was_root = self.device.shell(['id', '-un'])[0].strip() == 'root'
48 if not was_root:
49 self.device.root()
50 self.device.wait()
51
52 try:
53 func(self, *args)
54 finally:
55 if not was_root:
56 self.device.unroot()
57 self.device.wait()
58
59 return wrapper
60
61
62def requires_non_root(func):
63 def wrapper(self, *args):
64 was_root = self.device.shell(['id', '-un'])[0].strip() == 'root'
65 if was_root:
66 self.device.unroot()
67 self.device.wait()
68
69 try:
70 func(self, *args)
71 finally:
72 if was_root:
73 self.device.root()
74 self.device.wait()
75
76 return wrapper
77
78
Josh Gao191c1542015-12-09 11:26:11 -080079class DeviceTest(unittest.TestCase):
Josh Gao49ba5582020-03-26 19:33:25 -070080 device = adb.get_device()
Josh Gao191c1542015-12-09 11:26:11 -080081
82
Josh Gaoe4d66fc2020-02-04 12:32:43 -080083class AbbTest(DeviceTest):
84 def test_smoke(self):
Josh Gao8f834982020-05-19 20:12:52 -070085 abb = subprocess.run(['adb', 'abb'], capture_output=True)
86 cmd = subprocess.run(['adb', 'shell', 'cmd'], capture_output=True)
87
88 # abb squashes all failures to 1.
89 self.assertEqual(abb.returncode == 0, cmd.returncode == 0)
90 self.assertEqual(abb.stdout, cmd.stdout)
91 self.assertEqual(abb.stderr, cmd.stderr)
Josh Gaoe4d66fc2020-02-04 12:32:43 -080092
Josh Gao191c1542015-12-09 11:26:11 -080093class ForwardReverseTest(DeviceTest):
94 def _test_no_rebind(self, description, direction_list, direction,
95 direction_no_rebind, direction_remove_all):
96 msg = direction_list()
97 self.assertEqual('', msg.strip(),
98 description + ' list must be empty to run this test.')
99
100 # Use --no-rebind with no existing binding
101 direction_no_rebind('tcp:5566', 'tcp:6655')
102 msg = direction_list()
103 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
104
105 # Use --no-rebind with existing binding
106 with self.assertRaises(subprocess.CalledProcessError):
107 direction_no_rebind('tcp:5566', 'tcp:6677')
108 msg = direction_list()
109 self.assertFalse(re.search(r'tcp:5566.+tcp:6677', msg))
110 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
111
112 # Use the absence of --no-rebind with existing binding
113 direction('tcp:5566', 'tcp:6677')
114 msg = direction_list()
115 self.assertFalse(re.search(r'tcp:5566.+tcp:6655', msg))
116 self.assertTrue(re.search(r'tcp:5566.+tcp:6677', msg))
117
118 direction_remove_all()
119 msg = direction_list()
120 self.assertEqual('', msg.strip())
121
122 def test_forward_no_rebind(self):
123 self._test_no_rebind('forward', self.device.forward_list,
124 self.device.forward, self.device.forward_no_rebind,
125 self.device.forward_remove_all)
126
127 def test_reverse_no_rebind(self):
128 self._test_no_rebind('reverse', self.device.reverse_list,
129 self.device.reverse, self.device.reverse_no_rebind,
130 self.device.reverse_remove_all)
131
132 def test_forward(self):
133 msg = self.device.forward_list()
134 self.assertEqual('', msg.strip(),
135 'Forwarding list must be empty to run this test.')
136 self.device.forward('tcp:5566', 'tcp:6655')
137 msg = self.device.forward_list()
138 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
139 self.device.forward('tcp:7788', 'tcp:8877')
140 msg = self.device.forward_list()
141 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
142 self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
143 self.device.forward_remove('tcp:5566')
144 msg = self.device.forward_list()
145 self.assertFalse(re.search(r'tcp:5566.+tcp:6655', msg))
146 self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
147 self.device.forward_remove_all()
148 msg = self.device.forward_list()
149 self.assertEqual('', msg.strip())
150
Josh Gao727b07b2019-09-13 00:12:26 +0800151 def test_forward_old_protocol(self):
152 serialno = subprocess.check_output(self.device.adb_cmd + ['get-serialno']).strip()
153
154 msg = self.device.forward_list()
155 self.assertEqual('', msg.strip(),
156 'Forwarding list must be empty to run this test.')
157
158 s = socket.create_connection(("localhost", 5037))
159 service = b"host-serial:%s:forward:tcp:5566;tcp:6655" % serialno
160 cmd = b"%04x%s" % (len(service), service)
161 s.sendall(cmd)
162
163 msg = self.device.forward_list()
164 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
165
166 self.device.forward_remove_all()
167 msg = self.device.forward_list()
168 self.assertEqual('', msg.strip())
169
David Purselleaae97e2016-04-07 11:25:48 -0700170 def test_forward_tcp_port_0(self):
171 self.assertEqual('', self.device.forward_list().strip(),
172 'Forwarding list must be empty to run this test.')
173
174 try:
175 # If resolving TCP port 0 is supported, `adb forward` will print
176 # the actual port number.
177 port = self.device.forward('tcp:0', 'tcp:8888').strip()
178 if not port:
179 raise unittest.SkipTest('Forwarding tcp:0 is not available.')
180
181 self.assertTrue(re.search(r'tcp:{}.+tcp:8888'.format(port),
182 self.device.forward_list()))
183 finally:
184 self.device.forward_remove_all()
185
Josh Gao191c1542015-12-09 11:26:11 -0800186 def test_reverse(self):
187 msg = self.device.reverse_list()
188 self.assertEqual('', msg.strip(),
189 'Reverse forwarding list must be empty to run this test.')
190 self.device.reverse('tcp:5566', 'tcp:6655')
191 msg = self.device.reverse_list()
192 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
193 self.device.reverse('tcp:7788', 'tcp:8877')
194 msg = self.device.reverse_list()
195 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
196 self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
197 self.device.reverse_remove('tcp:5566')
198 msg = self.device.reverse_list()
199 self.assertFalse(re.search(r'tcp:5566.+tcp:6655', msg))
200 self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
201 self.device.reverse_remove_all()
202 msg = self.device.reverse_list()
203 self.assertEqual('', msg.strip())
204
David Purselleaae97e2016-04-07 11:25:48 -0700205 def test_reverse_tcp_port_0(self):
206 self.assertEqual('', self.device.reverse_list().strip(),
207 'Reverse list must be empty to run this test.')
208
209 try:
210 # If resolving TCP port 0 is supported, `adb reverse` will print
211 # the actual port number.
212 port = self.device.reverse('tcp:0', 'tcp:8888').strip()
213 if not port:
214 raise unittest.SkipTest('Reversing tcp:0 is not available.')
215
216 self.assertTrue(re.search(r'tcp:{}.+tcp:8888'.format(port),
217 self.device.reverse_list()))
218 finally:
219 self.device.reverse_remove_all()
220
Josh Gao191c1542015-12-09 11:26:11 -0800221 def test_forward_reverse_echo(self):
222 """Send data through adb forward and read it back via adb reverse"""
223 forward_port = 12345
224 reverse_port = forward_port + 1
Josh Gao255c5c82016-03-03 14:49:02 -0800225 forward_spec = 'tcp:' + str(forward_port)
226 reverse_spec = 'tcp:' + str(reverse_port)
Josh Gao191c1542015-12-09 11:26:11 -0800227 forward_setup = False
228 reverse_setup = False
229
230 try:
231 # listen on localhost:forward_port, connect to remote:forward_port
232 self.device.forward(forward_spec, forward_spec)
233 forward_setup = True
234 # listen on remote:forward_port, connect to localhost:reverse_port
235 self.device.reverse(forward_spec, reverse_spec)
236 reverse_setup = True
237
238 listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
239 with contextlib.closing(listener):
240 # Use SO_REUSEADDR so that subsequent runs of the test can grab
241 # the port even if it is in TIME_WAIT.
242 listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
243
244 # Listen on localhost:reverse_port before connecting to
245 # localhost:forward_port because that will cause adb to connect
246 # back to localhost:reverse_port.
247 listener.bind(('127.0.0.1', reverse_port))
248 listener.listen(4)
249
250 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
251 with contextlib.closing(client):
252 # Connect to the listener.
253 client.connect(('127.0.0.1', forward_port))
254
255 # Accept the client connection.
256 accepted_connection, addr = listener.accept()
257 with contextlib.closing(accepted_connection) as server:
Josh Gao93dee962020-02-06 17:52:38 -0800258 data = b'hello'
Josh Gao191c1542015-12-09 11:26:11 -0800259
260 # Send data into the port setup by adb forward.
261 client.sendall(data)
262 # Explicitly close() so that server gets EOF.
263 client.close()
264
265 # Verify that the data came back via adb reverse.
Josh Gao93dee962020-02-06 17:52:38 -0800266 self.assertEqual(data, server.makefile().read().encode("utf8"))
Josh Gao191c1542015-12-09 11:26:11 -0800267 finally:
268 if reverse_setup:
269 self.device.reverse_remove(forward_spec)
270 if forward_setup:
271 self.device.forward_remove(forward_spec)
272
273
274class ShellTest(DeviceTest):
275 def _interactive_shell(self, shell_args, input):
276 """Runs an interactive adb shell.
277
278 Args:
279 shell_args: List of string arguments to `adb shell`.
Josh Gao93dee962020-02-06 17:52:38 -0800280 input: bytes input to send to the interactive shell.
Josh Gao191c1542015-12-09 11:26:11 -0800281
282 Returns:
283 The remote exit code.
284
285 Raises:
286 unittest.SkipTest: The device doesn't support exit codes.
287 """
David Pursellcf467412016-04-26 13:25:57 -0700288 if not self.device.has_shell_protocol():
Josh Gao191c1542015-12-09 11:26:11 -0800289 raise unittest.SkipTest('exit codes are unavailable on this device')
290
291 proc = subprocess.Popen(
292 self.device.adb_cmd + ['shell'] + shell_args,
293 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
294 stderr=subprocess.PIPE)
295 # Closing host-side stdin doesn't trigger a PTY shell to exit so we need
296 # to explicitly add an exit command to close the session from the device
297 # side, plus the necessary newline to complete the interactive command.
Josh Gao93dee962020-02-06 17:52:38 -0800298 proc.communicate(input + b'; exit\n')
Josh Gao191c1542015-12-09 11:26:11 -0800299 return proc.returncode
300
301 def test_cat(self):
302 """Check that we can at least cat a file."""
303 out = self.device.shell(['cat', '/proc/uptime'])[0].strip()
304 elements = out.split()
305 self.assertEqual(len(elements), 2)
306
307 uptime, idle = elements
308 self.assertGreater(float(uptime), 0.0)
309 self.assertGreater(float(idle), 0.0)
310
311 def test_throws_on_failure(self):
312 self.assertRaises(adb.ShellError, self.device.shell, ['false'])
313
314 def test_output_not_stripped(self):
315 out = self.device.shell(['echo', 'foo'])[0]
316 self.assertEqual(out, 'foo' + self.device.linesep)
317
Josh Gaoa019f782017-06-16 15:34:34 -0700318 def test_shell_command_length(self):
319 # Devices that have shell_v2 should be able to handle long commands.
320 if self.device.has_shell_protocol():
321 rc, out, err = self.device.shell_nocheck(['echo', 'x' * 16384])
322 self.assertEqual(rc, 0)
323 self.assertTrue(out == ('x' * 16384 + '\n'))
324
Josh Gao191c1542015-12-09 11:26:11 -0800325 def test_shell_nocheck_failure(self):
326 rc, out, _ = self.device.shell_nocheck(['false'])
327 self.assertNotEqual(rc, 0)
328 self.assertEqual(out, '')
329
330 def test_shell_nocheck_output_not_stripped(self):
331 rc, out, _ = self.device.shell_nocheck(['echo', 'foo'])
332 self.assertEqual(rc, 0)
333 self.assertEqual(out, 'foo' + self.device.linesep)
334
335 def test_can_distinguish_tricky_results(self):
336 # If result checking on ADB shell is naively implemented as
337 # `adb shell <cmd>; echo $?`, we would be unable to distinguish the
338 # output from the result for a cmd of `echo -n 1`.
339 rc, out, _ = self.device.shell_nocheck(['echo', '-n', '1'])
340 self.assertEqual(rc, 0)
341 self.assertEqual(out, '1')
342
343 def test_line_endings(self):
344 """Ensure that line ending translation is not happening in the pty.
345
346 Bug: http://b/19735063
347 """
348 output = self.device.shell(['uname'])[0]
349 self.assertEqual(output, 'Linux' + self.device.linesep)
350
351 def test_pty_logic(self):
352 """Tests that a PTY is allocated when it should be.
353
Elliott Hughescabfa112016-10-19 14:47:11 -0700354 PTY allocation behavior should match ssh.
Josh Gao191c1542015-12-09 11:26:11 -0800355 """
Josh Gao191c1542015-12-09 11:26:11 -0800356 def check_pty(args):
357 """Checks adb shell PTY allocation.
358
359 Tests |args| for terminal and non-terminal stdin.
360
361 Args:
362 args: -Tt args in a list (e.g. ['-t', '-t']).
363
364 Returns:
365 A tuple (<terminal>, <non-terminal>). True indicates
366 the corresponding shell allocated a remote PTY.
367 """
368 test_cmd = self.device.adb_cmd + ['shell'] + args + ['[ -t 0 ]']
369
370 terminal = subprocess.Popen(
371 test_cmd, stdin=None,
372 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
373 terminal.communicate()
374
375 non_terminal = subprocess.Popen(
376 test_cmd, stdin=subprocess.PIPE,
377 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
378 non_terminal.communicate()
379
380 return (terminal.returncode == 0, non_terminal.returncode == 0)
381
382 # -T: never allocate PTY.
383 self.assertEqual((False, False), check_pty(['-T']))
384
Elliott Hughescabfa112016-10-19 14:47:11 -0700385 # These tests require a new device.
386 if self.device.has_shell_protocol() and os.isatty(sys.stdin.fileno()):
387 # No args: PTY only if stdin is a terminal and shell is interactive,
388 # which is difficult to reliably test from a script.
389 self.assertEqual((False, False), check_pty([]))
Josh Gao191c1542015-12-09 11:26:11 -0800390
Elliott Hughescabfa112016-10-19 14:47:11 -0700391 # -t: PTY if stdin is a terminal.
392 self.assertEqual((True, False), check_pty(['-t']))
Josh Gao191c1542015-12-09 11:26:11 -0800393
394 # -t -t: always allocate PTY.
395 self.assertEqual((True, True), check_pty(['-t', '-t']))
396
Elliott Hughescabfa112016-10-19 14:47:11 -0700397 # -tt: always allocate PTY, POSIX style (http://b/32216152).
398 self.assertEqual((True, True), check_pty(['-tt']))
399
400 # -ttt: ssh has weird even/odd behavior with multiple -t flags, but
401 # we follow the man page instead.
402 self.assertEqual((True, True), check_pty(['-ttt']))
403
404 # -ttx: -x and -tt aren't incompatible (though -Tx would be an error).
405 self.assertEqual((True, True), check_pty(['-ttx']))
406
407 # -Ttt: -tt cancels out -T.
408 self.assertEqual((True, True), check_pty(['-Ttt']))
409
410 # -ttT: -T cancels out -tt.
411 self.assertEqual((False, False), check_pty(['-ttT']))
412
Josh Gao191c1542015-12-09 11:26:11 -0800413 def test_shell_protocol(self):
414 """Tests the shell protocol on the device.
415
416 If the device supports shell protocol, this gives us the ability
417 to separate stdout/stderr and return the exit code directly.
418
419 Bug: http://b/19734861
420 """
David Pursellcf467412016-04-26 13:25:57 -0700421 if not self.device.has_shell_protocol():
Josh Gao191c1542015-12-09 11:26:11 -0800422 raise unittest.SkipTest('shell protocol unsupported on this device')
423
424 # Shell protocol should be used by default.
425 result = self.device.shell_nocheck(
426 shlex.split('echo foo; echo bar >&2; exit 17'))
427 self.assertEqual(17, result[0])
428 self.assertEqual('foo' + self.device.linesep, result[1])
429 self.assertEqual('bar' + self.device.linesep, result[2])
430
Josh Gao93dee962020-02-06 17:52:38 -0800431 self.assertEqual(17, self._interactive_shell([], b'exit 17'))
Josh Gao191c1542015-12-09 11:26:11 -0800432
433 # -x flag should disable shell protocol.
434 result = self.device.shell_nocheck(
435 shlex.split('-x echo foo; echo bar >&2; exit 17'))
436 self.assertEqual(0, result[0])
437 self.assertEqual('foo{0}bar{0}'.format(self.device.linesep), result[1])
438 self.assertEqual('', result[2])
439
Josh Gao93dee962020-02-06 17:52:38 -0800440 self.assertEqual(0, self._interactive_shell(['-x'], b'exit 17'))
Josh Gao191c1542015-12-09 11:26:11 -0800441
442 def test_non_interactive_sigint(self):
443 """Tests that SIGINT in a non-interactive shell kills the process.
444
445 This requires the shell protocol in order to detect the broken
446 pipe; raw data transfer mode will only see the break once the
447 subprocess tries to read or write.
448
449 Bug: http://b/23825725
450 """
David Pursellcf467412016-04-26 13:25:57 -0700451 if not self.device.has_shell_protocol():
Josh Gao191c1542015-12-09 11:26:11 -0800452 raise unittest.SkipTest('shell protocol unsupported on this device')
453
454 # Start a long-running process.
455 sleep_proc = subprocess.Popen(
456 self.device.adb_cmd + shlex.split('shell echo $$; sleep 60'),
457 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
458 stderr=subprocess.STDOUT)
Josh Gao93dee962020-02-06 17:52:38 -0800459 remote_pid = sleep_proc.stdout.readline().strip().decode("utf8")
Josh Gao191c1542015-12-09 11:26:11 -0800460 self.assertIsNone(sleep_proc.returncode, 'subprocess terminated early')
461 proc_query = shlex.split('ps {0} | grep {0}'.format(remote_pid))
462
463 # Verify that the process is running, send signal, verify it stopped.
464 self.device.shell(proc_query)
465 os.kill(sleep_proc.pid, signal.SIGINT)
466 sleep_proc.communicate()
Josh Gaoe76b9f32016-10-21 12:40:42 -0700467
468 # It can take some time for the process to receive the signal and die.
469 end_time = time.time() + 3
470 while self.device.shell_nocheck(proc_query)[0] != 1:
471 self.assertFalse(time.time() > end_time,
472 'subprocess failed to terminate in time')
Josh Gao191c1542015-12-09 11:26:11 -0800473
474 def test_non_interactive_stdin(self):
475 """Tests that non-interactive shells send stdin."""
David Pursellcf467412016-04-26 13:25:57 -0700476 if not self.device.has_shell_protocol():
Josh Gao191c1542015-12-09 11:26:11 -0800477 raise unittest.SkipTest('non-interactive stdin unsupported '
478 'on this device')
479
480 # Test both small and large inputs.
Josh Gao93dee962020-02-06 17:52:38 -0800481 small_input = b'foo'
482 characters = [c.encode("utf8") for c in string.ascii_letters + string.digits]
483 large_input = b'\n'.join(characters)
484
Josh Gao191c1542015-12-09 11:26:11 -0800485
486 for input in (small_input, large_input):
487 proc = subprocess.Popen(self.device.adb_cmd + ['shell', 'cat'],
488 stdin=subprocess.PIPE,
489 stdout=subprocess.PIPE,
490 stderr=subprocess.PIPE)
491 stdout, stderr = proc.communicate(input)
492 self.assertEqual(input.splitlines(), stdout.splitlines())
Josh Gao93dee962020-02-06 17:52:38 -0800493 self.assertEqual(b'', stderr)
Josh Gao191c1542015-12-09 11:26:11 -0800494
Josh Gaofe50bb72016-06-22 18:27:22 -0700495 def test_sighup(self):
496 """Ensure that SIGHUP gets sent upon non-interactive ctrl-c"""
497 log_path = "/data/local/tmp/adb_signal_test.log"
498
499 # Clear the output file.
500 self.device.shell_nocheck(["echo", ">", log_path])
501
502 script = """
503 trap "echo SIGINT > {path}; exit 0" SIGINT
504 trap "echo SIGHUP > {path}; exit 0" SIGHUP
505 echo Waiting
Josh Gao470622f2016-10-21 13:17:32 -0700506 read
Josh Gaofe50bb72016-06-22 18:27:22 -0700507 """.format(path=log_path)
508
509 script = ";".join([x.strip() for x in script.strip().splitlines()])
510
Josh Gao470622f2016-10-21 13:17:32 -0700511 process = self.device.shell_popen([script], kill_atexit=False,
512 stdin=subprocess.PIPE,
513 stdout=subprocess.PIPE)
Josh Gaofe50bb72016-06-22 18:27:22 -0700514
Josh Gao93dee962020-02-06 17:52:38 -0800515 self.assertEqual(b"Waiting\n", process.stdout.readline())
Josh Gaofe50bb72016-06-22 18:27:22 -0700516 process.send_signal(signal.SIGINT)
517 process.wait()
518
519 # Waiting for the local adb to finish is insufficient, since it hangs
520 # up immediately.
Josh Gao470622f2016-10-21 13:17:32 -0700521 time.sleep(1)
Josh Gaofe50bb72016-06-22 18:27:22 -0700522
523 stdout, _ = self.device.shell(["cat", log_path])
524 self.assertEqual(stdout.strip(), "SIGHUP")
525
Josh Gaoc970aef2018-03-19 15:35:11 -0700526 def test_exit_stress(self):
527 """Hammer `adb shell exit 42` with multiple threads."""
528 thread_count = 48
529 result = dict()
530 def hammer(thread_idx, thread_count, result):
531 success = True
532 for i in range(thread_idx, 240, thread_count):
533 ret = subprocess.call(['adb', 'shell', 'exit {}'.format(i)])
534 if ret != i % 256:
535 success = False
536 break
537 result[thread_idx] = success
538
539 threads = []
540 for i in range(thread_count):
541 thread = threading.Thread(target=hammer, args=(i, thread_count, result))
542 thread.start()
543 threads.append(thread)
544 for thread in threads:
545 thread.join()
Josh Gao93dee962020-02-06 17:52:38 -0800546 for i, success in result.items():
Josh Gaoc970aef2018-03-19 15:35:11 -0700547 self.assertTrue(success)
548
Josh Gao63da8e62019-12-16 17:13:51 -0800549 def disabled_test_parallel(self):
550 """Spawn a bunch of `adb shell` instances in parallel.
551
552 This was broken historically due to the use of select, which only works
553 for fds that are numerically less than 1024.
554
555 Bug: http://b/141955761"""
556
557 n_procs = 2048
558 procs = dict()
Josh Gao93dee962020-02-06 17:52:38 -0800559 for i in range(0, n_procs):
Josh Gao63da8e62019-12-16 17:13:51 -0800560 procs[i] = subprocess.Popen(
561 ['adb', 'shell', 'read foo; echo $foo; read rc; exit $rc'],
562 stdin=subprocess.PIPE,
563 stdout=subprocess.PIPE
564 )
565
Josh Gao93dee962020-02-06 17:52:38 -0800566 for i in range(0, n_procs):
Josh Gao63da8e62019-12-16 17:13:51 -0800567 procs[i].stdin.write("%d\n" % i)
568
Josh Gao93dee962020-02-06 17:52:38 -0800569 for i in range(0, n_procs):
Josh Gao63da8e62019-12-16 17:13:51 -0800570 response = procs[i].stdout.readline()
571 assert(response == "%d\n" % i)
572
Josh Gao93dee962020-02-06 17:52:38 -0800573 for i in range(0, n_procs):
Josh Gao63da8e62019-12-16 17:13:51 -0800574 procs[i].stdin.write("%d\n" % (i % 256))
575
Josh Gao93dee962020-02-06 17:52:38 -0800576 for i in range(0, n_procs):
Josh Gao63da8e62019-12-16 17:13:51 -0800577 assert(procs[i].wait() == i % 256)
578
Josh Gao191c1542015-12-09 11:26:11 -0800579
580class ArgumentEscapingTest(DeviceTest):
581 def test_shell_escaping(self):
582 """Make sure that argument escaping is somewhat sane."""
583
584 # http://b/19734868
585 # Note that this actually matches ssh(1)'s behavior --- it's
586 # converted to `sh -c echo hello; echo world` which sh interprets
587 # as `sh -c echo` (with an argument to that shell of "hello"),
588 # and then `echo world` back in the first shell.
589 result = self.device.shell(
590 shlex.split("sh -c 'echo hello; echo world'"))[0]
591 result = result.splitlines()
592 self.assertEqual(['', 'world'], result)
593 # If you really wanted "hello" and "world", here's what you'd do:
594 result = self.device.shell(
595 shlex.split(r'echo hello\;echo world'))[0].splitlines()
596 self.assertEqual(['hello', 'world'], result)
597
598 # http://b/15479704
599 result = self.device.shell(shlex.split("'true && echo t'"))[0].strip()
600 self.assertEqual('t', result)
601 result = self.device.shell(
602 shlex.split("sh -c 'true && echo t'"))[0].strip()
603 self.assertEqual('t', result)
604
605 # http://b/20564385
606 result = self.device.shell(shlex.split('FOO=a BAR=b echo t'))[0].strip()
607 self.assertEqual('t', result)
608 result = self.device.shell(
609 shlex.split(r'echo -n 123\;uname'))[0].strip()
610 self.assertEqual('123Linux', result)
611
612 def test_install_argument_escaping(self):
613 """Make sure that install argument escaping works."""
614 # http://b/20323053, http://b/3090932.
Josh Gao93dee962020-02-06 17:52:38 -0800615 for file_suffix in (b'-text;ls;1.apk', b"-Live Hold'em.apk"):
Josh Gao191c1542015-12-09 11:26:11 -0800616 tf = tempfile.NamedTemporaryFile('wb', suffix=file_suffix,
617 delete=False)
618 tf.close()
619
620 # Installing bogus .apks fails if the device supports exit codes.
621 try:
Josh Gao93dee962020-02-06 17:52:38 -0800622 output = self.device.install(tf.name.decode("utf8"))
Josh Gao191c1542015-12-09 11:26:11 -0800623 except subprocess.CalledProcessError as e:
624 output = e.output
625
626 self.assertIn(file_suffix, output)
627 os.remove(tf.name)
628
629
630class RootUnrootTest(DeviceTest):
631 def _test_root(self):
632 message = self.device.root()
633 if 'adbd cannot run as root in production builds' in message:
634 return
635 self.device.wait()
636 self.assertEqual('root', self.device.shell(['id', '-un'])[0].strip())
637
638 def _test_unroot(self):
639 self.device.unroot()
640 self.device.wait()
641 self.assertEqual('shell', self.device.shell(['id', '-un'])[0].strip())
642
643 def test_root_unroot(self):
644 """Make sure that adb root and adb unroot work, using id(1)."""
645 if self.device.get_prop('ro.debuggable') != '1':
646 raise unittest.SkipTest('requires rootable build')
647
648 original_user = self.device.shell(['id', '-un'])[0].strip()
649 try:
650 if original_user == 'root':
651 self._test_unroot()
652 self._test_root()
653 elif original_user == 'shell':
654 self._test_root()
655 self._test_unroot()
656 finally:
657 if original_user == 'root':
658 self.device.root()
659 else:
660 self.device.unroot()
661 self.device.wait()
662
663
664class TcpIpTest(DeviceTest):
665 def test_tcpip_failure_raises(self):
666 """adb tcpip requires a port.
667
668 Bug: http://b/22636927
669 """
670 self.assertRaises(
671 subprocess.CalledProcessError, self.device.tcpip, '')
672 self.assertRaises(
673 subprocess.CalledProcessError, self.device.tcpip, 'foo')
674
675
676class SystemPropertiesTest(DeviceTest):
677 def test_get_prop(self):
678 self.assertEqual(self.device.get_prop('init.svc.adbd'), 'running')
679
680 @requires_root
681 def test_set_prop(self):
682 prop_name = 'foo.bar'
683 self.device.shell(['setprop', prop_name, '""'])
684
685 self.device.set_prop(prop_name, 'qux')
686 self.assertEqual(
687 self.device.shell(['getprop', prop_name])[0].strip(), 'qux')
688
689
690def compute_md5(string):
691 hsh = hashlib.md5()
692 hsh.update(string)
693 return hsh.hexdigest()
694
695
696def get_md5_prog(device):
697 """Older platforms (pre-L) had the name md5 rather than md5sum."""
698 try:
699 device.shell(['md5sum', '/proc/uptime'])
700 return 'md5sum'
701 except adb.ShellError:
702 return 'md5'
703
704
705class HostFile(object):
706 def __init__(self, handle, checksum):
707 self.handle = handle
708 self.checksum = checksum
709 self.full_path = handle.name
710 self.base_name = os.path.basename(self.full_path)
711
712
713class DeviceFile(object):
714 def __init__(self, checksum, full_path):
715 self.checksum = checksum
716 self.full_path = full_path
717 self.base_name = posixpath.basename(self.full_path)
718
719
720def make_random_host_files(in_dir, num_files):
721 min_size = 1 * (1 << 10)
722 max_size = 16 * (1 << 10)
723
724 files = []
Josh Gao93dee962020-02-06 17:52:38 -0800725 for _ in range(num_files):
Josh Gao191c1542015-12-09 11:26:11 -0800726 file_handle = tempfile.NamedTemporaryFile(dir=in_dir, delete=False)
727
728 size = random.randrange(min_size, max_size, 1024)
729 rand_str = os.urandom(size)
730 file_handle.write(rand_str)
731 file_handle.flush()
732 file_handle.close()
733
734 md5 = compute_md5(rand_str)
735 files.append(HostFile(file_handle, md5))
736 return files
737
738
739def make_random_device_files(device, in_dir, num_files, prefix='device_tmpfile'):
740 min_size = 1 * (1 << 10)
741 max_size = 16 * (1 << 10)
742
743 files = []
Josh Gao93dee962020-02-06 17:52:38 -0800744 for file_num in range(num_files):
Josh Gao191c1542015-12-09 11:26:11 -0800745 size = random.randrange(min_size, max_size, 1024)
746
747 base_name = prefix + str(file_num)
748 full_path = posixpath.join(in_dir, base_name)
749
750 device.shell(['dd', 'if=/dev/urandom', 'of={}'.format(full_path),
751 'bs={}'.format(size), 'count=1'])
752 dev_md5, _ = device.shell([get_md5_prog(device), full_path])[0].split()
753
754 files.append(DeviceFile(dev_md5, full_path))
755 return files
756
757
Josh Gao49ba5582020-03-26 19:33:25 -0700758class FileOperationsTest:
759 class Base(DeviceTest):
760 SCRATCH_DIR = '/data/local/tmp'
761 DEVICE_TEMP_FILE = SCRATCH_DIR + '/adb_test_file'
762 DEVICE_TEMP_DIR = SCRATCH_DIR + '/adb_test_dir'
Josh Gao191c1542015-12-09 11:26:11 -0800763
Josh Gao49ba5582020-03-26 19:33:25 -0700764 def setUp(self):
765 self.previous_env = os.environ.get("ADB_COMPRESSION")
766 os.environ["ADB_COMPRESSION"] = self.compression
Josh Gao191c1542015-12-09 11:26:11 -0800767
Josh Gao49ba5582020-03-26 19:33:25 -0700768 def tearDown(self):
769 if self.previous_env is None:
770 del os.environ["ADB_COMPRESSION"]
771 else:
772 os.environ["ADB_COMPRESSION"] = self.previous_env
Josh Gao191c1542015-12-09 11:26:11 -0800773
Josh Gao49ba5582020-03-26 19:33:25 -0700774 def _verify_remote(self, checksum, remote_path):
775 dev_md5, _ = self.device.shell([get_md5_prog(self.device),
776 remote_path])[0].split()
777 self.assertEqual(checksum, dev_md5)
Josh Gao191c1542015-12-09 11:26:11 -0800778
Josh Gao49ba5582020-03-26 19:33:25 -0700779 def _verify_local(self, checksum, local_path):
780 with open(local_path, 'rb') as host_file:
781 host_md5 = compute_md5(host_file.read())
782 self.assertEqual(host_md5, checksum)
Josh Gao191c1542015-12-09 11:26:11 -0800783
Josh Gao49ba5582020-03-26 19:33:25 -0700784 def test_push(self):
785 """Push a randomly generated file to specified device."""
786 kbytes = 512
787 tmp = tempfile.NamedTemporaryFile(mode='wb', delete=False)
788 rand_str = os.urandom(1024 * kbytes)
789 tmp.write(rand_str)
790 tmp.close()
Josh Gao191c1542015-12-09 11:26:11 -0800791
Josh Gao49ba5582020-03-26 19:33:25 -0700792 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
793 self.device.push(local=tmp.name, remote=self.DEVICE_TEMP_FILE)
Josh Gao191c1542015-12-09 11:26:11 -0800794
Josh Gao49ba5582020-03-26 19:33:25 -0700795 self._verify_remote(compute_md5(rand_str), self.DEVICE_TEMP_FILE)
796 self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
Josh Gao191c1542015-12-09 11:26:11 -0800797
Josh Gao49ba5582020-03-26 19:33:25 -0700798 os.remove(tmp.name)
Josh Gao191c1542015-12-09 11:26:11 -0800799
Josh Gao49ba5582020-03-26 19:33:25 -0700800 def test_push_dir(self):
801 """Push a randomly generated directory of files to the device."""
Josh Gao191c1542015-12-09 11:26:11 -0800802 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
Josh Gao49ba5582020-03-26 19:33:25 -0700803 self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
Josh Gao191c1542015-12-09 11:26:11 -0800804
Josh Gao49ba5582020-03-26 19:33:25 -0700805 try:
806 host_dir = tempfile.mkdtemp()
Josh Gao191c1542015-12-09 11:26:11 -0800807
Josh Gao49ba5582020-03-26 19:33:25 -0700808 # Make sure the temp directory isn't setuid, or else adb will complain.
809 os.chmod(host_dir, 0o700)
Josh Gao191c1542015-12-09 11:26:11 -0800810
Josh Gao49ba5582020-03-26 19:33:25 -0700811 # Create 32 random files.
812 temp_files = make_random_host_files(in_dir=host_dir, num_files=32)
813 self.device.push(host_dir, self.DEVICE_TEMP_DIR)
Josh Gao191c1542015-12-09 11:26:11 -0800814
Josh Gao49ba5582020-03-26 19:33:25 -0700815 for temp_file in temp_files:
816 remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
817 os.path.basename(host_dir),
818 temp_file.base_name)
819 self._verify_remote(temp_file.checksum, remote_path)
820 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
821 finally:
822 if host_dir is not None:
823 shutil.rmtree(host_dir)
Josh Gao191c1542015-12-09 11:26:11 -0800824
Josh Gao49ba5582020-03-26 19:33:25 -0700825 def disabled_test_push_empty(self):
826 """Push an empty directory to the device."""
Josh Gao191c1542015-12-09 11:26:11 -0800827 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
Josh Gao49ba5582020-03-26 19:33:25 -0700828 self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
Josh Gao191c1542015-12-09 11:26:11 -0800829
Josh Gao49ba5582020-03-26 19:33:25 -0700830 try:
831 host_dir = tempfile.mkdtemp()
Josh Gao94dc19f2016-09-14 16:13:50 -0700832
Josh Gao49ba5582020-03-26 19:33:25 -0700833 # Make sure the temp directory isn't setuid, or else adb will complain.
834 os.chmod(host_dir, 0o700)
Josh Gao94dc19f2016-09-14 16:13:50 -0700835
Josh Gao49ba5582020-03-26 19:33:25 -0700836 # Create an empty directory.
837 empty_dir_path = os.path.join(host_dir, 'empty')
838 os.mkdir(empty_dir_path);
Josh Gao94dc19f2016-09-14 16:13:50 -0700839
Josh Gao49ba5582020-03-26 19:33:25 -0700840 self.device.push(empty_dir_path, self.DEVICE_TEMP_DIR)
Josh Gao94dc19f2016-09-14 16:13:50 -0700841
Josh Gao49ba5582020-03-26 19:33:25 -0700842 remote_path = os.path.join(self.DEVICE_TEMP_DIR, "empty")
843 test_empty_cmd = ["[", "-d", remote_path, "]"]
844 rc, _, _ = self.device.shell_nocheck(test_empty_cmd)
845
846 self.assertEqual(rc, 0)
847 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
848 finally:
849 if host_dir is not None:
850 shutil.rmtree(host_dir)
851
852 @unittest.skipIf(sys.platform == "win32", "symlinks require elevated privileges on windows")
853 def test_push_symlink(self):
854 """Push a symlink.
855
856 Bug: http://b/31491920
857 """
858 try:
859 host_dir = tempfile.mkdtemp()
860
861 # Make sure the temp directory isn't setuid, or else adb will
862 # complain.
863 os.chmod(host_dir, 0o700)
864
865 with open(os.path.join(host_dir, 'foo'), 'w') as f:
866 f.write('foo')
867
868 symlink_path = os.path.join(host_dir, 'symlink')
869 os.symlink('foo', symlink_path)
870
871 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
872 self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
873 self.device.push(symlink_path, self.DEVICE_TEMP_DIR)
874 rc, out, _ = self.device.shell_nocheck(
875 ['cat', posixpath.join(self.DEVICE_TEMP_DIR, 'symlink')])
876 self.assertEqual(0, rc)
877 self.assertEqual(out.strip(), 'foo')
878 finally:
879 if host_dir is not None:
880 shutil.rmtree(host_dir)
881
882 def test_multiple_push(self):
883 """Push multiple files to the device in one adb push command.
884
885 Bug: http://b/25324823
886 """
Josh Gao94dc19f2016-09-14 16:13:50 -0700887
888 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
889 self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
Josh Gao94dc19f2016-09-14 16:13:50 -0700890
Josh Gaoafcdcd72016-02-19 15:55:55 -0800891 try:
Josh Gao49ba5582020-03-26 19:33:25 -0700892 host_dir = tempfile.mkdtemp()
893
894 # Create some random files and a subdirectory containing more files.
895 temp_files = make_random_host_files(in_dir=host_dir, num_files=4)
896
897 subdir = os.path.join(host_dir, 'subdir')
898 os.mkdir(subdir)
899 subdir_temp_files = make_random_host_files(in_dir=subdir,
900 num_files=4)
901
902 paths = [x.full_path for x in temp_files]
903 paths.append(subdir)
904 self.device._simple_call(['push'] + paths + [self.DEVICE_TEMP_DIR])
905
906 for temp_file in temp_files:
907 remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
908 temp_file.base_name)
909 self._verify_remote(temp_file.checksum, remote_path)
910
911 for subdir_temp_file in subdir_temp_files:
912 remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
913 # BROKEN: http://b/25394682
914 # 'subdir';
915 temp_file.base_name)
916 self._verify_remote(temp_file.checksum, remote_path)
917
918
919 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
920 finally:
921 if host_dir is not None:
922 shutil.rmtree(host_dir)
923
924 @requires_non_root
925 def test_push_error_reporting(self):
926 """Make sure that errors that occur while pushing a file get reported
927
928 Bug: http://b/26816782
929 """
930 with tempfile.NamedTemporaryFile() as tmp_file:
931 tmp_file.write(b'\0' * 1024 * 1024)
932 tmp_file.flush()
933 try:
934 self.device.push(local=tmp_file.name, remote='/system/')
935 self.fail('push should not have succeeded')
936 except subprocess.CalledProcessError as e:
937 output = e.output
938
939 self.assertTrue(b'Permission denied' in output or
940 b'Read-only file system' in output)
941
942 @requires_non_root
943 def test_push_directory_creation(self):
944 """Regression test for directory creation.
945
946 Bug: http://b/110953234
947 """
948 with tempfile.NamedTemporaryFile() as tmp_file:
949 tmp_file.write(b'\0' * 1024 * 1024)
950 tmp_file.flush()
951 remote_path = self.DEVICE_TEMP_DIR + '/test_push_directory_creation'
952 self.device.shell(['rm', '-rf', remote_path])
953
954 remote_path += '/filename'
955 self.device.push(local=tmp_file.name, remote=remote_path)
956
957 def disabled_test_push_multiple_slash_root(self):
958 """Regression test for pushing to //data/local/tmp.
959
960 Bug: http://b/141311284
961
962 Disabled because this broken on the adbd side as well: b/141943968
963 """
964 with tempfile.NamedTemporaryFile() as tmp_file:
965 tmp_file.write('\0' * 1024 * 1024)
966 tmp_file.flush()
967 remote_path = '/' + self.DEVICE_TEMP_DIR + '/test_push_multiple_slash_root'
968 self.device.shell(['rm', '-rf', remote_path])
969 self.device.push(local=tmp_file.name, remote=remote_path)
970
971 def _test_pull(self, remote_file, checksum):
972 tmp_write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
973 tmp_write.close()
974 self.device.pull(remote=remote_file, local=tmp_write.name)
975 with open(tmp_write.name, 'rb') as tmp_read:
976 host_contents = tmp_read.read()
977 host_md5 = compute_md5(host_contents)
978 self.assertEqual(checksum, host_md5)
979 os.remove(tmp_write.name)
980
981 @requires_non_root
982 def test_pull_error_reporting(self):
983 self.device.shell(['touch', self.DEVICE_TEMP_FILE])
984 self.device.shell(['chmod', 'a-rwx', self.DEVICE_TEMP_FILE])
985
986 try:
987 output = self.device.pull(remote=self.DEVICE_TEMP_FILE, local='x')
Josh Gaoafcdcd72016-02-19 15:55:55 -0800988 except subprocess.CalledProcessError as e:
989 output = e.output
990
Josh Gao49ba5582020-03-26 19:33:25 -0700991 self.assertIn(b'Permission denied', output)
Josh Gao191c1542015-12-09 11:26:11 -0800992
Josh Gao49ba5582020-03-26 19:33:25 -0700993 self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
Josh Gao4c0078d2018-06-28 18:43:19 -0700994
Josh Gao49ba5582020-03-26 19:33:25 -0700995 def test_pull(self):
996 """Pull a randomly generated file from specified device."""
997 kbytes = 512
998 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
999 cmd = ['dd', 'if=/dev/urandom',
1000 'of={}'.format(self.DEVICE_TEMP_FILE), 'bs=1024',
1001 'count={}'.format(kbytes)]
1002 self.device.shell(cmd)
1003 dev_md5, _ = self.device.shell(
1004 [get_md5_prog(self.device), self.DEVICE_TEMP_FILE])[0].split()
1005 self._test_pull(self.DEVICE_TEMP_FILE, dev_md5)
1006 self.device.shell_nocheck(['rm', self.DEVICE_TEMP_FILE])
Josh Gao4c0078d2018-06-28 18:43:19 -07001007
Josh Gao49ba5582020-03-26 19:33:25 -07001008 def test_pull_dir(self):
1009 """Pull a randomly generated directory of files from the device."""
1010 try:
1011 host_dir = tempfile.mkdtemp()
Josh Gao4c0078d2018-06-28 18:43:19 -07001012
Josh Gao49ba5582020-03-26 19:33:25 -07001013 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1014 self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
Josh Gao76b64ba2019-09-26 01:49:56 +08001015
Josh Gao49ba5582020-03-26 19:33:25 -07001016 # Populate device directory with random files.
1017 temp_files = make_random_device_files(
1018 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
Josh Gaoea3f43c2019-10-01 14:14:07 -07001019
Josh Gao49ba5582020-03-26 19:33:25 -07001020 self.device.pull(remote=self.DEVICE_TEMP_DIR, local=host_dir)
Josh Gao76b64ba2019-09-26 01:49:56 +08001021
Josh Gao49ba5582020-03-26 19:33:25 -07001022 for temp_file in temp_files:
1023 host_path = os.path.join(
1024 host_dir, posixpath.basename(self.DEVICE_TEMP_DIR),
1025 temp_file.base_name)
1026 self._verify_local(temp_file.checksum, host_path)
Josh Gao191c1542015-12-09 11:26:11 -08001027
Josh Gao49ba5582020-03-26 19:33:25 -07001028 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1029 finally:
1030 if host_dir is not None:
1031 shutil.rmtree(host_dir)
Josh Gao191c1542015-12-09 11:26:11 -08001032
Josh Gao49ba5582020-03-26 19:33:25 -07001033 def test_pull_dir_symlink(self):
1034 """Pull a directory into a symlink to a directory.
Josh Gao191c1542015-12-09 11:26:11 -08001035
Josh Gao49ba5582020-03-26 19:33:25 -07001036 Bug: http://b/27362811
1037 """
1038 if os.name != 'posix':
1039 raise unittest.SkipTest('requires POSIX')
Josh Gao191c1542015-12-09 11:26:11 -08001040
Josh Gao49ba5582020-03-26 19:33:25 -07001041 try:
1042 host_dir = tempfile.mkdtemp()
1043 real_dir = os.path.join(host_dir, 'dir')
1044 symlink = os.path.join(host_dir, 'symlink')
1045 os.mkdir(real_dir)
1046 os.symlink(real_dir, symlink)
Josh Gao191c1542015-12-09 11:26:11 -08001047
Josh Gao49ba5582020-03-26 19:33:25 -07001048 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1049 self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
Josh Gao191c1542015-12-09 11:26:11 -08001050
Josh Gao49ba5582020-03-26 19:33:25 -07001051 # Populate device directory with random files.
1052 temp_files = make_random_device_files(
1053 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
Josh Gao191c1542015-12-09 11:26:11 -08001054
Josh Gao49ba5582020-03-26 19:33:25 -07001055 self.device.pull(remote=self.DEVICE_TEMP_DIR, local=symlink)
Josh Gao191c1542015-12-09 11:26:11 -08001056
Josh Gao49ba5582020-03-26 19:33:25 -07001057 for temp_file in temp_files:
1058 host_path = os.path.join(
1059 real_dir, posixpath.basename(self.DEVICE_TEMP_DIR),
1060 temp_file.base_name)
1061 self._verify_local(temp_file.checksum, host_path)
Josh Gao191c1542015-12-09 11:26:11 -08001062
Josh Gao49ba5582020-03-26 19:33:25 -07001063 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1064 finally:
1065 if host_dir is not None:
1066 shutil.rmtree(host_dir)
Josh Gao191c1542015-12-09 11:26:11 -08001067
Josh Gao49ba5582020-03-26 19:33:25 -07001068 def test_pull_dir_symlink_collision(self):
1069 """Pull a directory into a colliding symlink to directory."""
1070 if os.name != 'posix':
1071 raise unittest.SkipTest('requires POSIX')
1072
1073 try:
1074 host_dir = tempfile.mkdtemp()
1075 real_dir = os.path.join(host_dir, 'real')
1076 tmp_dirname = os.path.basename(self.DEVICE_TEMP_DIR)
1077 symlink = os.path.join(host_dir, tmp_dirname)
1078 os.mkdir(real_dir)
1079 os.symlink(real_dir, symlink)
1080
1081 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1082 self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
1083
1084 # Populate device directory with random files.
1085 temp_files = make_random_device_files(
1086 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
1087
1088 self.device.pull(remote=self.DEVICE_TEMP_DIR, local=host_dir)
1089
1090 for temp_file in temp_files:
1091 host_path = os.path.join(real_dir, temp_file.base_name)
1092 self._verify_local(temp_file.checksum, host_path)
1093
1094 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1095 finally:
1096 if host_dir is not None:
1097 shutil.rmtree(host_dir)
1098
1099 def test_pull_dir_nonexistent(self):
1100 """Pull a directory of files from the device to a nonexistent path."""
1101 try:
1102 host_dir = tempfile.mkdtemp()
1103 dest_dir = os.path.join(host_dir, 'dest')
1104
1105 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1106 self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
1107
1108 # Populate device directory with random files.
1109 temp_files = make_random_device_files(
1110 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
1111
1112 self.device.pull(remote=self.DEVICE_TEMP_DIR, local=dest_dir)
1113
1114 for temp_file in temp_files:
1115 host_path = os.path.join(dest_dir, temp_file.base_name)
1116 self._verify_local(temp_file.checksum, host_path)
1117
1118 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1119 finally:
1120 if host_dir is not None:
1121 shutil.rmtree(host_dir)
1122
1123 # selinux prevents adbd from accessing symlinks on /data/local/tmp.
1124 def disabled_test_pull_symlink_dir(self):
1125 """Pull a symlink to a directory of symlinks to files."""
1126 try:
1127 host_dir = tempfile.mkdtemp()
1128
1129 remote_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'contents')
1130 remote_links = posixpath.join(self.DEVICE_TEMP_DIR, 'links')
1131 remote_symlink = posixpath.join(self.DEVICE_TEMP_DIR, 'symlink')
1132
1133 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1134 self.device.shell(['mkdir', '-p', remote_dir, remote_links])
1135 self.device.shell(['ln', '-s', remote_links, remote_symlink])
1136
1137 # Populate device directory with random files.
1138 temp_files = make_random_device_files(
1139 self.device, in_dir=remote_dir, num_files=32)
1140
1141 for temp_file in temp_files:
1142 self.device.shell(
1143 ['ln', '-s', '../contents/{}'.format(temp_file.base_name),
1144 posixpath.join(remote_links, temp_file.base_name)])
1145
1146 self.device.pull(remote=remote_symlink, local=host_dir)
1147
1148 for temp_file in temp_files:
1149 host_path = os.path.join(
1150 host_dir, 'symlink', temp_file.base_name)
1151 self._verify_local(temp_file.checksum, host_path)
1152
1153 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1154 finally:
1155 if host_dir is not None:
1156 shutil.rmtree(host_dir)
1157
1158 def test_pull_empty(self):
1159 """Pull a directory containing an empty directory from the device."""
1160 try:
1161 host_dir = tempfile.mkdtemp()
1162
1163 remote_empty_path = posixpath.join(self.DEVICE_TEMP_DIR, 'empty')
1164 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1165 self.device.shell(['mkdir', '-p', remote_empty_path])
1166
1167 self.device.pull(remote=remote_empty_path, local=host_dir)
1168 self.assertTrue(os.path.isdir(os.path.join(host_dir, 'empty')))
1169 finally:
1170 if host_dir is not None:
1171 shutil.rmtree(host_dir)
1172
1173 def test_multiple_pull(self):
1174 """Pull a randomly generated directory of files from the device."""
1175
1176 try:
1177 host_dir = tempfile.mkdtemp()
1178
1179 subdir = posixpath.join(self.DEVICE_TEMP_DIR, 'subdir')
1180 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1181 self.device.shell(['mkdir', '-p', subdir])
1182
1183 # Create some random files and a subdirectory containing more files.
1184 temp_files = make_random_device_files(
1185 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=4)
1186
1187 subdir_temp_files = make_random_device_files(
1188 self.device, in_dir=subdir, num_files=4, prefix='subdir_')
1189
1190 paths = [x.full_path for x in temp_files]
1191 paths.append(subdir)
1192 self.device._simple_call(['pull'] + paths + [host_dir])
1193
1194 for temp_file in temp_files:
1195 local_path = os.path.join(host_dir, temp_file.base_name)
1196 self._verify_local(temp_file.checksum, local_path)
1197
1198 for subdir_temp_file in subdir_temp_files:
1199 local_path = os.path.join(host_dir,
1200 'subdir',
1201 subdir_temp_file.base_name)
1202 self._verify_local(subdir_temp_file.checksum, local_path)
1203
1204 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1205 finally:
1206 if host_dir is not None:
1207 shutil.rmtree(host_dir)
1208
1209 def verify_sync(self, device, temp_files, device_dir):
1210 """Verifies that a list of temp files was synced to the device."""
1211 # Confirm that every file on the device mirrors that on the host.
Josh Gao191c1542015-12-09 11:26:11 -08001212 for temp_file in temp_files:
Josh Gao49ba5582020-03-26 19:33:25 -07001213 device_full_path = posixpath.join(
1214 device_dir, temp_file.base_name)
1215 dev_md5, _ = device.shell(
1216 [get_md5_prog(self.device), device_full_path])[0].split()
1217 self.assertEqual(temp_file.checksum, dev_md5)
Josh Gao191c1542015-12-09 11:26:11 -08001218
Josh Gao49ba5582020-03-26 19:33:25 -07001219 def test_sync(self):
1220 """Sync a host directory to the data partition."""
Josh Gao191c1542015-12-09 11:26:11 -08001221
Josh Gao49ba5582020-03-26 19:33:25 -07001222 try:
1223 base_dir = tempfile.mkdtemp()
Josh Gao1e611a32016-02-26 13:26:55 -08001224
Josh Gao49ba5582020-03-26 19:33:25 -07001225 # Create mirror device directory hierarchy within base_dir.
1226 full_dir_path = base_dir + self.DEVICE_TEMP_DIR
1227 os.makedirs(full_dir_path)
Josh Gao1e611a32016-02-26 13:26:55 -08001228
Josh Gao49ba5582020-03-26 19:33:25 -07001229 # Create 32 random files within the host mirror.
1230 temp_files = make_random_host_files(
1231 in_dir=full_dir_path, num_files=32)
Josh Gao1e611a32016-02-26 13:26:55 -08001232
Josh Gao49ba5582020-03-26 19:33:25 -07001233 # Clean up any stale files on the device.
1234 device = adb.get_device() # pylint: disable=no-member
1235 device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
Josh Gao1e611a32016-02-26 13:26:55 -08001236
Josh Gao49ba5582020-03-26 19:33:25 -07001237 old_product_out = os.environ.get('ANDROID_PRODUCT_OUT')
1238 os.environ['ANDROID_PRODUCT_OUT'] = base_dir
1239 device.sync('data')
1240 if old_product_out is None:
1241 del os.environ['ANDROID_PRODUCT_OUT']
1242 else:
1243 os.environ['ANDROID_PRODUCT_OUT'] = old_product_out
Josh Gao1e611a32016-02-26 13:26:55 -08001244
Josh Gao49ba5582020-03-26 19:33:25 -07001245 self.verify_sync(device, temp_files, self.DEVICE_TEMP_DIR)
Josh Gao1e611a32016-02-26 13:26:55 -08001246
Josh Gao49ba5582020-03-26 19:33:25 -07001247 #self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1248 finally:
1249 if base_dir is not None:
1250 shutil.rmtree(base_dir)
Josh Gao1e611a32016-02-26 13:26:55 -08001251
Josh Gao49ba5582020-03-26 19:33:25 -07001252 def test_push_sync(self):
1253 """Sync a host directory to a specific path."""
Josh Gao1e611a32016-02-26 13:26:55 -08001254
Josh Gao49ba5582020-03-26 19:33:25 -07001255 try:
1256 temp_dir = tempfile.mkdtemp()
1257 temp_files = make_random_host_files(in_dir=temp_dir, num_files=32)
Josh Gao1e611a32016-02-26 13:26:55 -08001258
Josh Gao49ba5582020-03-26 19:33:25 -07001259 device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'sync_src_dst')
Josh Gao1e611a32016-02-26 13:26:55 -08001260
Josh Gao49ba5582020-03-26 19:33:25 -07001261 # Clean up any stale files on the device.
1262 device = adb.get_device() # pylint: disable=no-member
1263 device.shell(['rm', '-rf', device_dir])
Josh Gao1e611a32016-02-26 13:26:55 -08001264
Josh Gao49ba5582020-03-26 19:33:25 -07001265 device.push(temp_dir, device_dir, sync=True)
Josh Gao1e611a32016-02-26 13:26:55 -08001266
Josh Gao49ba5582020-03-26 19:33:25 -07001267 self.verify_sync(device, temp_files, device_dir)
Josh Gao1e611a32016-02-26 13:26:55 -08001268
Josh Gao49ba5582020-03-26 19:33:25 -07001269 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1270 finally:
1271 if temp_dir is not None:
1272 shutil.rmtree(temp_dir)
Josh Gao1e611a32016-02-26 13:26:55 -08001273
Josh Gao1b65ebe2020-09-08 17:40:22 -07001274 def test_push_sync_multiple(self):
1275 """Sync multiple host directories to a specific path."""
1276
1277 try:
1278 temp_dir = tempfile.mkdtemp()
1279 temp_files = make_random_host_files(in_dir=temp_dir, num_files=32)
1280
1281 device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'sync_src_dst')
1282
1283 # Clean up any stale files on the device.
1284 device = adb.get_device() # pylint: disable=no-member
1285 device.shell(['rm', '-rf', device_dir])
1286 device.shell(['mkdir', '-p', device_dir])
1287
1288 host_paths = [os.path.join(temp_dir, x.base_name) for x in temp_files]
1289 device.push(host_paths, device_dir, sync=True)
1290
1291 self.verify_sync(device, temp_files, device_dir)
1292
1293 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1294 finally:
1295 if temp_dir is not None:
1296 shutil.rmtree(temp_dir)
1297
1298
Josh Gao5949fcc2020-03-30 23:25:16 -07001299 def test_push_dry_run_nonexistent_file(self):
1300 """Push with dry run."""
1301
1302 for file_size in [8, 1024 * 1024]:
1303 try:
1304 device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'push_dry_run')
1305 device_file = posixpath.join(device_dir, 'file')
1306
1307 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1308 self.device.shell(['mkdir', '-p', device_dir])
1309
1310 host_dir = tempfile.mkdtemp()
1311 host_file = posixpath.join(host_dir, 'file')
1312
1313 with open(host_file, "w") as f:
1314 f.write('x' * file_size)
1315
1316 self.device._simple_call(['push', '-n', host_file, device_file])
1317 rc, _, _ = self.device.shell_nocheck(['[', '-e', device_file, ']'])
1318 self.assertNotEqual(0, rc)
1319
1320 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1321 finally:
1322 if host_dir is not None:
1323 shutil.rmtree(host_dir)
1324
1325 def test_push_dry_run_existent_file(self):
1326 """Push with dry run."""
1327
1328 for file_size in [8, 1024 * 1024]:
1329 try:
1330 device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'push_dry_run')
1331 device_file = posixpath.join(device_dir, 'file')
1332
1333 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1334 self.device.shell(['mkdir', '-p', device_dir])
1335 self.device.shell(['echo', 'foo', '>', device_file])
1336
1337 host_dir = tempfile.mkdtemp()
1338 host_file = posixpath.join(host_dir, 'file')
1339
1340 with open(host_file, "w") as f:
1341 f.write('x' * file_size)
1342
1343 self.device._simple_call(['push', '-n', host_file, device_file])
1344 stdout, stderr = self.device.shell(['cat', device_file])
1345 self.assertEqual(stdout.strip(), "foo")
1346
1347 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1348 finally:
1349 if host_dir is not None:
1350 shutil.rmtree(host_dir)
1351
Josh Gao49ba5582020-03-26 19:33:25 -07001352 def test_unicode_paths(self):
1353 """Ensure that we can support non-ASCII paths, even on Windows."""
1354 name = u'로보카 폴리'
Josh Gao1e611a32016-02-26 13:26:55 -08001355
Josh Gao49ba5582020-03-26 19:33:25 -07001356 self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
1357 remote_path = u'/data/local/tmp/adb-test-{}'.format(name)
Josh Gao89ec3a82016-03-02 16:00:02 -08001358
Josh Gao49ba5582020-03-26 19:33:25 -07001359 ## push.
1360 tf = tempfile.NamedTemporaryFile('wb', suffix=name, delete=False)
1361 tf.close()
1362 self.device.push(tf.name, remote_path)
1363 os.remove(tf.name)
1364 self.assertFalse(os.path.exists(tf.name))
Josh Gao89ec3a82016-03-02 16:00:02 -08001365
Josh Gao49ba5582020-03-26 19:33:25 -07001366 # Verify that the device ended up with the expected UTF-8 path
1367 output = self.device.shell(
1368 ['ls', '/data/local/tmp/adb-test-*'])[0].strip()
1369 self.assertEqual(remote_path, output)
Josh Gao89ec3a82016-03-02 16:00:02 -08001370
Josh Gao49ba5582020-03-26 19:33:25 -07001371 # pull.
1372 self.device.pull(remote_path, tf.name)
1373 self.assertTrue(os.path.exists(tf.name))
1374 os.remove(tf.name)
1375 self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
Josh Gao89ec3a82016-03-02 16:00:02 -08001376
Josh Gao89ec3a82016-03-02 16:00:02 -08001377
Josh Gao49ba5582020-03-26 19:33:25 -07001378class FileOperationsTestUncompressed(FileOperationsTest.Base):
1379 compression = "none"
Josh Gao89ec3a82016-03-02 16:00:02 -08001380
Josh Gaof2642242015-12-09 14:03:30 -08001381
Josh Gao49ba5582020-03-26 19:33:25 -07001382class FileOperationsTestBrotli(FileOperationsTest.Base):
1383 compression = "brotli"
Josh Gao191c1542015-12-09 11:26:11 -08001384
1385
Josh Gaoec44d352020-03-26 22:02:03 -07001386class FileOperationsTestLZ4(FileOperationsTest.Base):
1387 compression = "lz4"
1388
1389
Josh Gao317d3e12020-05-27 17:52:52 -07001390class FileOperationsTestZstd(FileOperationsTest.Base):
1391 compression = "zstd"
1392
1393
Yabin Cuib5e11412017-03-10 16:01:01 -08001394class DeviceOfflineTest(DeviceTest):
1395 def _get_device_state(self, serialno):
1396 output = subprocess.check_output(self.device.adb_cmd + ['devices'])
1397 for line in output.split('\n'):
1398 m = re.match('(\S+)\s+(\S+)', line)
1399 if m and m.group(1) == serialno:
1400 return m.group(2)
1401 return None
1402
Josh Gao33d14b82017-09-13 14:51:23 -07001403 def disabled_test_killed_when_pushing_a_large_file(self):
Yabin Cuib5e11412017-03-10 16:01:01 -08001404 """
1405 While running adb push with a large file, kill adb server.
1406 Occasionally the device becomes offline. Because the device is still
1407 reading data without realizing that the adb server has been restarted.
1408 Test if we can bring the device online automatically now.
1409 http://b/32952319
1410 """
1411 serialno = subprocess.check_output(self.device.adb_cmd + ['get-serialno']).strip()
1412 # 1. Push a large file
1413 file_path = 'tmp_large_file'
1414 try:
1415 fh = open(file_path, 'w')
1416 fh.write('\0' * (100 * 1024 * 1024))
1417 fh.close()
1418 subproc = subprocess.Popen(self.device.adb_cmd + ['push', file_path, '/data/local/tmp'])
1419 time.sleep(0.1)
1420 # 2. Kill the adb server
1421 subprocess.check_call(self.device.adb_cmd + ['kill-server'])
1422 subproc.terminate()
1423 finally:
1424 try:
1425 os.unlink(file_path)
1426 except:
1427 pass
1428 # 3. See if the device still exist.
1429 # Sleep to wait for the adb server exit.
1430 time.sleep(0.5)
1431 # 4. The device should be online
1432 self.assertEqual(self._get_device_state(serialno), 'device')
1433
Josh Gao33d14b82017-09-13 14:51:23 -07001434 def disabled_test_killed_when_pulling_a_large_file(self):
Yabin Cuib5e11412017-03-10 16:01:01 -08001435 """
1436 While running adb pull with a large file, kill adb server.
1437 Occasionally the device can't be connected. Because the device is trying to
1438 send a message larger than what is expected by the adb server.
1439 Test if we can bring the device online automatically now.
1440 """
1441 serialno = subprocess.check_output(self.device.adb_cmd + ['get-serialno']).strip()
1442 file_path = 'tmp_large_file'
1443 try:
1444 # 1. Create a large file on device.
1445 self.device.shell(['dd', 'if=/dev/zero', 'of=/data/local/tmp/tmp_large_file',
1446 'bs=1000000', 'count=100'])
1447 # 2. Pull the large file on host.
1448 subproc = subprocess.Popen(self.device.adb_cmd +
1449 ['pull','/data/local/tmp/tmp_large_file', file_path])
1450 time.sleep(0.1)
1451 # 3. Kill the adb server
1452 subprocess.check_call(self.device.adb_cmd + ['kill-server'])
1453 subproc.terminate()
1454 finally:
1455 try:
1456 os.unlink(file_path)
1457 except:
1458 pass
1459 # 4. See if the device still exist.
1460 # Sleep to wait for the adb server exit.
1461 time.sleep(0.5)
1462 self.assertEqual(self._get_device_state(serialno), 'device')
1463
1464
Josh Gaoef3d3432017-05-02 15:01:09 -07001465 def test_packet_size_regression(self):
1466 """Test for http://b/37783561
1467
1468 Receiving packets of a length divisible by 512 but not 1024 resulted in
1469 the adb client waiting indefinitely for more input.
1470 """
1471 # The values that trigger things are 507 (512 - 5 bytes from shell protocol) + 1024*n
1472 # Probe some surrounding values as well, for the hell of it.
Josh Gao93dee962020-02-06 17:52:38 -08001473 for base in [512] + list(range(1024, 1024 * 16, 1024)):
Josh Gao2ea46522018-04-10 14:35:06 -07001474 for offset in [-6, -5, -4]:
1475 length = base + offset
1476 cmd = ['dd', 'if=/dev/zero', 'bs={}'.format(length), 'count=1', '2>/dev/null;'
1477 'echo', 'foo']
1478 rc, stdout, _ = self.device.shell_nocheck(cmd)
Josh Gaoef3d3432017-05-02 15:01:09 -07001479
Josh Gao2ea46522018-04-10 14:35:06 -07001480 self.assertEqual(0, rc)
Josh Gaoef3d3432017-05-02 15:01:09 -07001481
Josh Gao2ea46522018-04-10 14:35:06 -07001482 # Output should be '\0' * length, followed by "foo\n"
1483 self.assertEqual(length, len(stdout) - 4)
1484 self.assertEqual(stdout, "\0" * length + "foo\n")
Josh Gaoef3d3432017-05-02 15:01:09 -07001485
Josh Gao5d799cd2018-08-22 15:13:18 -07001486 def test_zero_packet(self):
1487 """Test for http://b/113070258
1488
1489 Make sure that we don't blow up when sending USB transfers that line up
1490 exactly with the USB packet size.
1491 """
1492
1493 local_port = int(self.device.forward("tcp:0", "tcp:12345"))
1494 try:
1495 for size in [512, 1024]:
1496 def listener():
1497 cmd = ["echo foo | nc -l -p 12345; echo done"]
1498 rc, stdout, stderr = self.device.shell_nocheck(cmd)
1499
1500 thread = threading.Thread(target=listener)
1501 thread.start()
1502
1503 # Wait a bit to let the shell command start.
1504 time.sleep(0.25)
1505
1506 sock = socket.create_connection(("localhost", local_port))
1507 with contextlib.closing(sock):
Josh Gao93dee962020-02-06 17:52:38 -08001508 bytesWritten = sock.send(b"a" * size)
Josh Gao5d799cd2018-08-22 15:13:18 -07001509 self.assertEqual(size, bytesWritten)
1510 readBytes = sock.recv(4096)
Josh Gao93dee962020-02-06 17:52:38 -08001511 self.assertEqual(b"foo\n", readBytes)
Josh Gao5d799cd2018-08-22 15:13:18 -07001512
1513 thread.join()
1514 finally:
1515 self.device.forward_remove("tcp:{}".format(local_port))
1516
Josh Gaoef3d3432017-05-02 15:01:09 -07001517
Josh Gao74b7ec72019-01-11 14:42:08 -08001518class SocketTest(DeviceTest):
1519 def test_socket_flush(self):
1520 """Test that we handle socket closure properly.
1521
1522 If we're done writing to a socket, closing before the other end has
1523 closed will send a TCP_RST if we have incoming data queued up, which
1524 may result in data that we've written being discarded.
1525
1526 Bug: http://b/74616284
1527 """
1528 s = socket.create_connection(("localhost", 5037))
1529
1530 def adb_length_prefixed(string):
1531 encoded = string.encode("utf8")
1532 result = b"%04x%s" % (len(encoded), encoded)
1533 return result
1534
1535 if "ANDROID_SERIAL" in os.environ:
1536 transport_string = "host:transport:" + os.environ["ANDROID_SERIAL"]
1537 else:
1538 transport_string = "host:transport-any"
1539
1540 s.sendall(adb_length_prefixed(transport_string))
1541 response = s.recv(4)
Josh Gao93dee962020-02-06 17:52:38 -08001542 self.assertEqual(b"OKAY", response)
Josh Gao74b7ec72019-01-11 14:42:08 -08001543
1544 shell_string = "shell:sleep 0.5; dd if=/dev/zero bs=1m count=1 status=none; echo foo"
1545 s.sendall(adb_length_prefixed(shell_string))
1546
1547 response = s.recv(4)
Josh Gao93dee962020-02-06 17:52:38 -08001548 self.assertEqual(b"OKAY", response)
Josh Gao74b7ec72019-01-11 14:42:08 -08001549
1550 # Spawn a thread that dumps garbage into the socket until failure.
1551 def spam():
1552 buf = b"\0" * 16384
1553 try:
1554 while True:
1555 s.sendall(buf)
1556 except Exception as ex:
1557 print(ex)
1558
1559 thread = threading.Thread(target=spam)
1560 thread.start()
1561
1562 time.sleep(1)
1563
1564 received = b""
1565 while True:
1566 read = s.recv(512)
1567 if len(read) == 0:
1568 break
1569 received += read
1570
Josh Gao93dee962020-02-06 17:52:38 -08001571 self.assertEqual(1024 * 1024 + len("foo\n"), len(received))
Josh Gao74b7ec72019-01-11 14:42:08 -08001572 thread.join()
1573
1574
Spencer Low69d8c392018-08-11 00:16:16 -07001575if sys.platform == "win32":
1576 # From https://stackoverflow.com/a/38749458
1577 import os
1578 import contextlib
1579 import msvcrt
1580 import ctypes
1581 from ctypes import wintypes
1582
1583 kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
1584
1585 GENERIC_READ = 0x80000000
1586 GENERIC_WRITE = 0x40000000
1587 FILE_SHARE_READ = 1
1588 FILE_SHARE_WRITE = 2
1589 CONSOLE_TEXTMODE_BUFFER = 1
1590 INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
1591 STD_OUTPUT_HANDLE = wintypes.DWORD(-11)
1592 STD_ERROR_HANDLE = wintypes.DWORD(-12)
1593
1594 def _check_zero(result, func, args):
1595 if not result:
1596 raise ctypes.WinError(ctypes.get_last_error())
1597 return args
1598
1599 def _check_invalid(result, func, args):
1600 if result == INVALID_HANDLE_VALUE:
1601 raise ctypes.WinError(ctypes.get_last_error())
1602 return args
1603
1604 if not hasattr(wintypes, 'LPDWORD'): # Python 2
1605 wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
1606 wintypes.PSMALL_RECT = ctypes.POINTER(wintypes.SMALL_RECT)
1607
1608 class COORD(ctypes.Structure):
1609 _fields_ = (('X', wintypes.SHORT),
1610 ('Y', wintypes.SHORT))
1611
1612 class CONSOLE_SCREEN_BUFFER_INFOEX(ctypes.Structure):
1613 _fields_ = (('cbSize', wintypes.ULONG),
1614 ('dwSize', COORD),
1615 ('dwCursorPosition', COORD),
1616 ('wAttributes', wintypes.WORD),
1617 ('srWindow', wintypes.SMALL_RECT),
1618 ('dwMaximumWindowSize', COORD),
1619 ('wPopupAttributes', wintypes.WORD),
1620 ('bFullscreenSupported', wintypes.BOOL),
1621 ('ColorTable', wintypes.DWORD * 16))
1622 def __init__(self, *args, **kwds):
1623 super(CONSOLE_SCREEN_BUFFER_INFOEX, self).__init__(
1624 *args, **kwds)
1625 self.cbSize = ctypes.sizeof(self)
1626
1627 PCONSOLE_SCREEN_BUFFER_INFOEX = ctypes.POINTER(
1628 CONSOLE_SCREEN_BUFFER_INFOEX)
1629 LPSECURITY_ATTRIBUTES = wintypes.LPVOID
1630
1631 kernel32.GetStdHandle.errcheck = _check_invalid
1632 kernel32.GetStdHandle.restype = wintypes.HANDLE
1633 kernel32.GetStdHandle.argtypes = (
1634 wintypes.DWORD,) # _In_ nStdHandle
1635
1636 kernel32.CreateConsoleScreenBuffer.errcheck = _check_invalid
1637 kernel32.CreateConsoleScreenBuffer.restype = wintypes.HANDLE
1638 kernel32.CreateConsoleScreenBuffer.argtypes = (
1639 wintypes.DWORD, # _In_ dwDesiredAccess
1640 wintypes.DWORD, # _In_ dwShareMode
1641 LPSECURITY_ATTRIBUTES, # _In_opt_ lpSecurityAttributes
1642 wintypes.DWORD, # _In_ dwFlags
1643 wintypes.LPVOID) # _Reserved_ lpScreenBufferData
1644
1645 kernel32.GetConsoleScreenBufferInfoEx.errcheck = _check_zero
1646 kernel32.GetConsoleScreenBufferInfoEx.argtypes = (
1647 wintypes.HANDLE, # _In_ hConsoleOutput
1648 PCONSOLE_SCREEN_BUFFER_INFOEX) # _Out_ lpConsoleScreenBufferInfo
1649
1650 kernel32.SetConsoleScreenBufferInfoEx.errcheck = _check_zero
1651 kernel32.SetConsoleScreenBufferInfoEx.argtypes = (
1652 wintypes.HANDLE, # _In_ hConsoleOutput
1653 PCONSOLE_SCREEN_BUFFER_INFOEX) # _In_ lpConsoleScreenBufferInfo
1654
1655 kernel32.SetConsoleWindowInfo.errcheck = _check_zero
1656 kernel32.SetConsoleWindowInfo.argtypes = (
1657 wintypes.HANDLE, # _In_ hConsoleOutput
1658 wintypes.BOOL, # _In_ bAbsolute
1659 wintypes.PSMALL_RECT) # _In_ lpConsoleWindow
1660
1661 kernel32.FillConsoleOutputCharacterW.errcheck = _check_zero
1662 kernel32.FillConsoleOutputCharacterW.argtypes = (
1663 wintypes.HANDLE, # _In_ hConsoleOutput
1664 wintypes.WCHAR, # _In_ cCharacter
1665 wintypes.DWORD, # _In_ nLength
1666 COORD, # _In_ dwWriteCoord
1667 wintypes.LPDWORD) # _Out_ lpNumberOfCharsWritten
1668
1669 kernel32.ReadConsoleOutputCharacterW.errcheck = _check_zero
1670 kernel32.ReadConsoleOutputCharacterW.argtypes = (
1671 wintypes.HANDLE, # _In_ hConsoleOutput
1672 wintypes.LPWSTR, # _Out_ lpCharacter
1673 wintypes.DWORD, # _In_ nLength
1674 COORD, # _In_ dwReadCoord
1675 wintypes.LPDWORD) # _Out_ lpNumberOfCharsRead
1676
1677 @contextlib.contextmanager
1678 def allocate_console():
1679 allocated = kernel32.AllocConsole()
1680 try:
1681 yield allocated
1682 finally:
1683 if allocated:
1684 kernel32.FreeConsole()
1685
1686 @contextlib.contextmanager
1687 def console_screen(ncols=None, nrows=None):
1688 info = CONSOLE_SCREEN_BUFFER_INFOEX()
1689 new_info = CONSOLE_SCREEN_BUFFER_INFOEX()
1690 nwritten = (wintypes.DWORD * 1)()
1691 hStdOut = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
1692 kernel32.GetConsoleScreenBufferInfoEx(
1693 hStdOut, ctypes.byref(info))
1694 if ncols is None:
1695 ncols = info.dwSize.X
1696 if nrows is None:
1697 nrows = info.dwSize.Y
1698 elif nrows > 9999:
1699 raise ValueError('nrows must be 9999 or less')
1700 fd_screen = None
1701 hScreen = kernel32.CreateConsoleScreenBuffer(
1702 GENERIC_READ | GENERIC_WRITE,
1703 FILE_SHARE_READ | FILE_SHARE_WRITE,
1704 None, CONSOLE_TEXTMODE_BUFFER, None)
1705 try:
1706 fd_screen = msvcrt.open_osfhandle(
1707 hScreen, os.O_RDWR | os.O_BINARY)
1708 kernel32.GetConsoleScreenBufferInfoEx(
1709 hScreen, ctypes.byref(new_info))
1710 new_info.dwSize = COORD(ncols, nrows)
1711 new_info.srWindow = wintypes.SMALL_RECT(
1712 Left=0, Top=0, Right=(ncols - 1),
1713 Bottom=(info.srWindow.Bottom - info.srWindow.Top))
1714 kernel32.SetConsoleScreenBufferInfoEx(
1715 hScreen, ctypes.byref(new_info))
1716 kernel32.SetConsoleWindowInfo(hScreen, True,
1717 ctypes.byref(new_info.srWindow))
1718 kernel32.FillConsoleOutputCharacterW(
1719 hScreen, u'\0', ncols * nrows, COORD(0,0), nwritten)
1720 kernel32.SetConsoleActiveScreenBuffer(hScreen)
1721 try:
1722 yield fd_screen
1723 finally:
1724 kernel32.SetConsoleScreenBufferInfoEx(
1725 hStdOut, ctypes.byref(info))
1726 kernel32.SetConsoleWindowInfo(hStdOut, True,
1727 ctypes.byref(info.srWindow))
1728 kernel32.SetConsoleActiveScreenBuffer(hStdOut)
1729 finally:
1730 if fd_screen is not None:
1731 os.close(fd_screen)
1732 else:
1733 kernel32.CloseHandle(hScreen)
1734
1735 def read_screen(fd):
1736 hScreen = msvcrt.get_osfhandle(fd)
1737 csbi = CONSOLE_SCREEN_BUFFER_INFOEX()
1738 kernel32.GetConsoleScreenBufferInfoEx(
1739 hScreen, ctypes.byref(csbi))
1740 ncols = csbi.dwSize.X
1741 pos = csbi.dwCursorPosition
1742 length = ncols * pos.Y + pos.X + 1
1743 buf = (ctypes.c_wchar * length)()
1744 n = (wintypes.DWORD * 1)()
1745 kernel32.ReadConsoleOutputCharacterW(
1746 hScreen, buf, length, COORD(0,0), n)
1747 lines = [buf[i:i+ncols].rstrip(u'\0')
1748 for i in range(0, n[0], ncols)]
1749 return u'\n'.join(lines)
1750
1751@unittest.skipUnless(sys.platform == "win32", "requires Windows")
1752class WindowsConsoleTest(DeviceTest):
1753 def test_unicode_output(self):
1754 """Test Unicode command line parameters and Unicode console window output.
1755
1756 Bug: https://issuetracker.google.com/issues/111972753
1757 """
1758 # If we don't have a console window, allocate one. This isn't necessary if we're already
1759 # being run from a console window, which is typical.
1760 with allocate_console() as allocated_console:
1761 # Create a temporary console buffer and switch to it. We could also pass a parameter of
1762 # ncols=len(unicode_string), but it causes the window to flash as it is resized and
1763 # likely unnecessary given the typical console window size.
1764 with console_screen(nrows=1000) as screen:
1765 unicode_string = u'로보카 폴리'
1766 # Run adb and allow it to detect that stdout is a console, not a pipe, by using
1767 # device.shell_popen() which does not use a pipe, unlike device.shell().
1768 process = self.device.shell_popen(['echo', '"' + unicode_string + '"'])
1769 process.wait()
1770 # Read what was written by adb to the temporary console buffer.
1771 console_output = read_screen(screen)
1772 self.assertEqual(unicode_string, console_output)
1773
1774
Josh Gao191c1542015-12-09 11:26:11 -08001775def main():
1776 random.seed(0)
1777 if len(adb.get_devices()) > 0:
1778 suite = unittest.TestLoader().loadTestsFromName(__name__)
1779 unittest.TextTestRunner(verbosity=3).run(suite)
1780 else:
1781 print('Test suite must be run with attached devices')
1782
1783
1784if __name__ == '__main__':
1785 main()