blob: e2e0f74462a3f3860123ed45ca1dab49fc4b547f [file] [log] [blame]
Josh Gao4218d852020-02-06 17:52:38 -08001#!/usr/bin/env python3
Josh Gao49e3c632015-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
Fabien Sanglard3d155b62023-11-30 14:52:40 -080022import io
Josh Gao49e3c632015-12-09 11:26:11 -080023import os
24import posixpath
25import random
26import re
27import shlex
28import shutil
29import signal
30import socket
31import string
32import subprocess
33import sys
34import tempfile
Josh Gao160bf7e2018-03-19 15:35:11 -070035import threading
Josh Gao2eae66e2016-06-22 18:27:22 -070036import time
Josh Gao49e3c632015-12-09 11:26:11 -080037import unittest
38
Fabien Sanglard3d155b62023-11-30 14:52:40 -080039import proto.devices_pb2 as proto_devices
40
Josh Gao18f7a5c2019-01-11 14:42:08 -080041from datetime import datetime
42
Josh Gao49e3c632015-12-09 11:26:11 -080043import adb
44
Josh Gao49e3c632015-12-09 11:26:11 -080045def requires_non_root(func):
46 def wrapper(self, *args):
47 was_root = self.device.shell(['id', '-un'])[0].strip() == 'root'
48 if was_root:
49 self.device.unroot()
50 self.device.wait()
51
52 try:
53 func(self, *args)
54 finally:
55 if was_root:
56 self.device.root()
57 self.device.wait()
58
59 return wrapper
60
61
Josh Gao49e3c632015-12-09 11:26:11 -080062class DeviceTest(unittest.TestCase):
Josh Gaobfcd8ff2020-03-26 19:33:25 -070063 device = adb.get_device()
Josh Gao49e3c632015-12-09 11:26:11 -080064
65
Josh Gao32e903d2020-02-04 12:32:43 -080066class AbbTest(DeviceTest):
67 def test_smoke(self):
Josh Gaodfa7ba62020-05-19 20:12:52 -070068 abb = subprocess.run(['adb', 'abb'], capture_output=True)
69 cmd = subprocess.run(['adb', 'shell', 'cmd'], capture_output=True)
70
71 # abb squashes all failures to 1.
72 self.assertEqual(abb.returncode == 0, cmd.returncode == 0)
73 self.assertEqual(abb.stdout, cmd.stdout)
74 self.assertEqual(abb.stderr, cmd.stderr)
Josh Gao32e903d2020-02-04 12:32:43 -080075
Josh Gao49e3c632015-12-09 11:26:11 -080076class ForwardReverseTest(DeviceTest):
77 def _test_no_rebind(self, description, direction_list, direction,
78 direction_no_rebind, direction_remove_all):
79 msg = direction_list()
80 self.assertEqual('', msg.strip(),
81 description + ' list must be empty to run this test.')
82
83 # Use --no-rebind with no existing binding
84 direction_no_rebind('tcp:5566', 'tcp:6655')
85 msg = direction_list()
86 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
87
88 # Use --no-rebind with existing binding
89 with self.assertRaises(subprocess.CalledProcessError):
90 direction_no_rebind('tcp:5566', 'tcp:6677')
91 msg = direction_list()
92 self.assertFalse(re.search(r'tcp:5566.+tcp:6677', msg))
93 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
94
95 # Use the absence of --no-rebind with existing binding
96 direction('tcp:5566', 'tcp:6677')
97 msg = direction_list()
98 self.assertFalse(re.search(r'tcp:5566.+tcp:6655', msg))
99 self.assertTrue(re.search(r'tcp:5566.+tcp:6677', msg))
100
101 direction_remove_all()
102 msg = direction_list()
103 self.assertEqual('', msg.strip())
104
105 def test_forward_no_rebind(self):
106 self._test_no_rebind('forward', self.device.forward_list,
107 self.device.forward, self.device.forward_no_rebind,
108 self.device.forward_remove_all)
109
110 def test_reverse_no_rebind(self):
111 self._test_no_rebind('reverse', self.device.reverse_list,
112 self.device.reverse, self.device.reverse_no_rebind,
113 self.device.reverse_remove_all)
114
115 def test_forward(self):
116 msg = self.device.forward_list()
117 self.assertEqual('', msg.strip(),
118 'Forwarding list must be empty to run this test.')
119 self.device.forward('tcp:5566', 'tcp:6655')
120 msg = self.device.forward_list()
121 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
122 self.device.forward('tcp:7788', 'tcp:8877')
123 msg = self.device.forward_list()
124 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
125 self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
126 self.device.forward_remove('tcp:5566')
127 msg = self.device.forward_list()
128 self.assertFalse(re.search(r'tcp:5566.+tcp:6655', msg))
129 self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
130 self.device.forward_remove_all()
131 msg = self.device.forward_list()
132 self.assertEqual('', msg.strip())
133
Josh Gao07790752019-09-13 00:12:26 +0800134 def test_forward_old_protocol(self):
135 serialno = subprocess.check_output(self.device.adb_cmd + ['get-serialno']).strip()
136
137 msg = self.device.forward_list()
138 self.assertEqual('', msg.strip(),
139 'Forwarding list must be empty to run this test.')
140
141 s = socket.create_connection(("localhost", 5037))
142 service = b"host-serial:%s:forward:tcp:5566;tcp:6655" % serialno
143 cmd = b"%04x%s" % (len(service), service)
144 s.sendall(cmd)
145
146 msg = self.device.forward_list()
147 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
148
149 self.device.forward_remove_all()
150 msg = self.device.forward_list()
151 self.assertEqual('', msg.strip())
152
David Pursell19d0c232016-04-07 11:25:48 -0700153 def test_forward_tcp_port_0(self):
154 self.assertEqual('', self.device.forward_list().strip(),
155 'Forwarding list must be empty to run this test.')
156
157 try:
158 # If resolving TCP port 0 is supported, `adb forward` will print
159 # the actual port number.
160 port = self.device.forward('tcp:0', 'tcp:8888').strip()
161 if not port:
162 raise unittest.SkipTest('Forwarding tcp:0 is not available.')
163
164 self.assertTrue(re.search(r'tcp:{}.+tcp:8888'.format(port),
165 self.device.forward_list()))
166 finally:
167 self.device.forward_remove_all()
168
Josh Gao49e3c632015-12-09 11:26:11 -0800169 def test_reverse(self):
170 msg = self.device.reverse_list()
171 self.assertEqual('', msg.strip(),
172 'Reverse forwarding list must be empty to run this test.')
173 self.device.reverse('tcp:5566', 'tcp:6655')
174 msg = self.device.reverse_list()
175 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
176 self.device.reverse('tcp:7788', 'tcp:8877')
177 msg = self.device.reverse_list()
178 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
179 self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
180 self.device.reverse_remove('tcp:5566')
181 msg = self.device.reverse_list()
182 self.assertFalse(re.search(r'tcp:5566.+tcp:6655', msg))
183 self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
184 self.device.reverse_remove_all()
185 msg = self.device.reverse_list()
186 self.assertEqual('', msg.strip())
187
David Pursell19d0c232016-04-07 11:25:48 -0700188 def test_reverse_tcp_port_0(self):
189 self.assertEqual('', self.device.reverse_list().strip(),
190 'Reverse list must be empty to run this test.')
191
192 try:
193 # If resolving TCP port 0 is supported, `adb reverse` will print
194 # the actual port number.
195 port = self.device.reverse('tcp:0', 'tcp:8888').strip()
196 if not port:
197 raise unittest.SkipTest('Reversing tcp:0 is not available.')
198
199 self.assertTrue(re.search(r'tcp:{}.+tcp:8888'.format(port),
200 self.device.reverse_list()))
201 finally:
202 self.device.reverse_remove_all()
203
Josh Gao49e3c632015-12-09 11:26:11 -0800204 def test_forward_reverse_echo(self):
205 """Send data through adb forward and read it back via adb reverse"""
206 forward_port = 12345
207 reverse_port = forward_port + 1
Josh Gao18f74202016-03-03 14:49:02 -0800208 forward_spec = 'tcp:' + str(forward_port)
209 reverse_spec = 'tcp:' + str(reverse_port)
Josh Gao49e3c632015-12-09 11:26:11 -0800210 forward_setup = False
211 reverse_setup = False
212
213 try:
214 # listen on localhost:forward_port, connect to remote:forward_port
215 self.device.forward(forward_spec, forward_spec)
216 forward_setup = True
217 # listen on remote:forward_port, connect to localhost:reverse_port
218 self.device.reverse(forward_spec, reverse_spec)
219 reverse_setup = True
220
221 listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
222 with contextlib.closing(listener):
223 # Use SO_REUSEADDR so that subsequent runs of the test can grab
224 # the port even if it is in TIME_WAIT.
225 listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
226
227 # Listen on localhost:reverse_port before connecting to
228 # localhost:forward_port because that will cause adb to connect
229 # back to localhost:reverse_port.
230 listener.bind(('127.0.0.1', reverse_port))
231 listener.listen(4)
232
233 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
234 with contextlib.closing(client):
235 # Connect to the listener.
236 client.connect(('127.0.0.1', forward_port))
237
238 # Accept the client connection.
239 accepted_connection, addr = listener.accept()
240 with contextlib.closing(accepted_connection) as server:
Josh Gao4218d852020-02-06 17:52:38 -0800241 data = b'hello'
Josh Gao49e3c632015-12-09 11:26:11 -0800242
243 # Send data into the port setup by adb forward.
244 client.sendall(data)
245 # Explicitly close() so that server gets EOF.
246 client.close()
247
248 # Verify that the data came back via adb reverse.
Josh Gao4218d852020-02-06 17:52:38 -0800249 self.assertEqual(data, server.makefile().read().encode("utf8"))
Josh Gao49e3c632015-12-09 11:26:11 -0800250 finally:
251 if reverse_setup:
252 self.device.reverse_remove(forward_spec)
253 if forward_setup:
254 self.device.forward_remove(forward_spec)
255
256
257class ShellTest(DeviceTest):
258 def _interactive_shell(self, shell_args, input):
259 """Runs an interactive adb shell.
260
261 Args:
262 shell_args: List of string arguments to `adb shell`.
Josh Gao4218d852020-02-06 17:52:38 -0800263 input: bytes input to send to the interactive shell.
Josh Gao49e3c632015-12-09 11:26:11 -0800264
265 Returns:
266 The remote exit code.
267
268 Raises:
269 unittest.SkipTest: The device doesn't support exit codes.
270 """
David Pursell4b38af42016-04-26 13:25:57 -0700271 if not self.device.has_shell_protocol():
Josh Gao49e3c632015-12-09 11:26:11 -0800272 raise unittest.SkipTest('exit codes are unavailable on this device')
273
274 proc = subprocess.Popen(
275 self.device.adb_cmd + ['shell'] + shell_args,
276 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
277 stderr=subprocess.PIPE)
278 # Closing host-side stdin doesn't trigger a PTY shell to exit so we need
279 # to explicitly add an exit command to close the session from the device
280 # side, plus the necessary newline to complete the interactive command.
Josh Gao4218d852020-02-06 17:52:38 -0800281 proc.communicate(input + b'; exit\n')
Josh Gao49e3c632015-12-09 11:26:11 -0800282 return proc.returncode
283
284 def test_cat(self):
285 """Check that we can at least cat a file."""
286 out = self.device.shell(['cat', '/proc/uptime'])[0].strip()
287 elements = out.split()
288 self.assertEqual(len(elements), 2)
289
290 uptime, idle = elements
291 self.assertGreater(float(uptime), 0.0)
292 self.assertGreater(float(idle), 0.0)
293
294 def test_throws_on_failure(self):
295 self.assertRaises(adb.ShellError, self.device.shell, ['false'])
296
297 def test_output_not_stripped(self):
298 out = self.device.shell(['echo', 'foo'])[0]
299 self.assertEqual(out, 'foo' + self.device.linesep)
300
Josh Gao05012022017-06-16 15:34:34 -0700301 def test_shell_command_length(self):
302 # Devices that have shell_v2 should be able to handle long commands.
303 if self.device.has_shell_protocol():
304 rc, out, err = self.device.shell_nocheck(['echo', 'x' * 16384])
305 self.assertEqual(rc, 0)
306 self.assertTrue(out == ('x' * 16384 + '\n'))
307
Josh Gao49e3c632015-12-09 11:26:11 -0800308 def test_shell_nocheck_failure(self):
309 rc, out, _ = self.device.shell_nocheck(['false'])
310 self.assertNotEqual(rc, 0)
311 self.assertEqual(out, '')
312
313 def test_shell_nocheck_output_not_stripped(self):
314 rc, out, _ = self.device.shell_nocheck(['echo', 'foo'])
315 self.assertEqual(rc, 0)
316 self.assertEqual(out, 'foo' + self.device.linesep)
317
318 def test_can_distinguish_tricky_results(self):
319 # If result checking on ADB shell is naively implemented as
320 # `adb shell <cmd>; echo $?`, we would be unable to distinguish the
321 # output from the result for a cmd of `echo -n 1`.
322 rc, out, _ = self.device.shell_nocheck(['echo', '-n', '1'])
323 self.assertEqual(rc, 0)
324 self.assertEqual(out, '1')
325
326 def test_line_endings(self):
327 """Ensure that line ending translation is not happening in the pty.
328
329 Bug: http://b/19735063
330 """
331 output = self.device.shell(['uname'])[0]
332 self.assertEqual(output, 'Linux' + self.device.linesep)
333
334 def test_pty_logic(self):
335 """Tests that a PTY is allocated when it should be.
336
Elliott Hughes02e33782016-10-19 14:47:11 -0700337 PTY allocation behavior should match ssh.
Josh Gao49e3c632015-12-09 11:26:11 -0800338 """
Josh Gao49e3c632015-12-09 11:26:11 -0800339 def check_pty(args):
340 """Checks adb shell PTY allocation.
341
342 Tests |args| for terminal and non-terminal stdin.
343
344 Args:
345 args: -Tt args in a list (e.g. ['-t', '-t']).
346
347 Returns:
348 A tuple (<terminal>, <non-terminal>). True indicates
349 the corresponding shell allocated a remote PTY.
350 """
351 test_cmd = self.device.adb_cmd + ['shell'] + args + ['[ -t 0 ]']
352
353 terminal = subprocess.Popen(
354 test_cmd, stdin=None,
355 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
356 terminal.communicate()
357
358 non_terminal = subprocess.Popen(
359 test_cmd, stdin=subprocess.PIPE,
360 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
361 non_terminal.communicate()
362
363 return (terminal.returncode == 0, non_terminal.returncode == 0)
364
365 # -T: never allocate PTY.
366 self.assertEqual((False, False), check_pty(['-T']))
367
Elliott Hughes02e33782016-10-19 14:47:11 -0700368 # These tests require a new device.
369 if self.device.has_shell_protocol() and os.isatty(sys.stdin.fileno()):
370 # No args: PTY only if stdin is a terminal and shell is interactive,
371 # which is difficult to reliably test from a script.
372 self.assertEqual((False, False), check_pty([]))
Josh Gao49e3c632015-12-09 11:26:11 -0800373
Elliott Hughes02e33782016-10-19 14:47:11 -0700374 # -t: PTY if stdin is a terminal.
375 self.assertEqual((True, False), check_pty(['-t']))
Josh Gao49e3c632015-12-09 11:26:11 -0800376
377 # -t -t: always allocate PTY.
378 self.assertEqual((True, True), check_pty(['-t', '-t']))
379
Elliott Hughes02e33782016-10-19 14:47:11 -0700380 # -tt: always allocate PTY, POSIX style (http://b/32216152).
381 self.assertEqual((True, True), check_pty(['-tt']))
382
383 # -ttt: ssh has weird even/odd behavior with multiple -t flags, but
384 # we follow the man page instead.
385 self.assertEqual((True, True), check_pty(['-ttt']))
386
387 # -ttx: -x and -tt aren't incompatible (though -Tx would be an error).
388 self.assertEqual((True, True), check_pty(['-ttx']))
389
390 # -Ttt: -tt cancels out -T.
391 self.assertEqual((True, True), check_pty(['-Ttt']))
392
393 # -ttT: -T cancels out -tt.
394 self.assertEqual((False, False), check_pty(['-ttT']))
395
Josh Gao49e3c632015-12-09 11:26:11 -0800396 def test_shell_protocol(self):
397 """Tests the shell protocol on the device.
398
399 If the device supports shell protocol, this gives us the ability
400 to separate stdout/stderr and return the exit code directly.
401
402 Bug: http://b/19734861
403 """
David Pursell4b38af42016-04-26 13:25:57 -0700404 if not self.device.has_shell_protocol():
Josh Gao49e3c632015-12-09 11:26:11 -0800405 raise unittest.SkipTest('shell protocol unsupported on this device')
406
407 # Shell protocol should be used by default.
408 result = self.device.shell_nocheck(
409 shlex.split('echo foo; echo bar >&2; exit 17'))
410 self.assertEqual(17, result[0])
411 self.assertEqual('foo' + self.device.linesep, result[1])
412 self.assertEqual('bar' + self.device.linesep, result[2])
413
Josh Gao4218d852020-02-06 17:52:38 -0800414 self.assertEqual(17, self._interactive_shell([], b'exit 17'))
Josh Gao49e3c632015-12-09 11:26:11 -0800415
416 # -x flag should disable shell protocol.
417 result = self.device.shell_nocheck(
418 shlex.split('-x echo foo; echo bar >&2; exit 17'))
419 self.assertEqual(0, result[0])
420 self.assertEqual('foo{0}bar{0}'.format(self.device.linesep), result[1])
421 self.assertEqual('', result[2])
422
Josh Gao4218d852020-02-06 17:52:38 -0800423 self.assertEqual(0, self._interactive_shell(['-x'], b'exit 17'))
Josh Gao49e3c632015-12-09 11:26:11 -0800424
425 def test_non_interactive_sigint(self):
426 """Tests that SIGINT in a non-interactive shell kills the process.
427
428 This requires the shell protocol in order to detect the broken
429 pipe; raw data transfer mode will only see the break once the
430 subprocess tries to read or write.
431
432 Bug: http://b/23825725
433 """
David Pursell4b38af42016-04-26 13:25:57 -0700434 if not self.device.has_shell_protocol():
Josh Gao49e3c632015-12-09 11:26:11 -0800435 raise unittest.SkipTest('shell protocol unsupported on this device')
436
437 # Start a long-running process.
438 sleep_proc = subprocess.Popen(
439 self.device.adb_cmd + shlex.split('shell echo $$; sleep 60'),
440 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
441 stderr=subprocess.STDOUT)
Josh Gao4218d852020-02-06 17:52:38 -0800442 remote_pid = sleep_proc.stdout.readline().strip().decode("utf8")
Josh Gao49e3c632015-12-09 11:26:11 -0800443 self.assertIsNone(sleep_proc.returncode, 'subprocess terminated early')
444 proc_query = shlex.split('ps {0} | grep {0}'.format(remote_pid))
445
446 # Verify that the process is running, send signal, verify it stopped.
447 self.device.shell(proc_query)
448 os.kill(sleep_proc.pid, signal.SIGINT)
449 sleep_proc.communicate()
Josh Gao76ffdac2016-10-21 12:40:42 -0700450
451 # It can take some time for the process to receive the signal and die.
452 end_time = time.time() + 3
453 while self.device.shell_nocheck(proc_query)[0] != 1:
454 self.assertFalse(time.time() > end_time,
455 'subprocess failed to terminate in time')
Josh Gao49e3c632015-12-09 11:26:11 -0800456
457 def test_non_interactive_stdin(self):
458 """Tests that non-interactive shells send stdin."""
David Pursell4b38af42016-04-26 13:25:57 -0700459 if not self.device.has_shell_protocol():
Josh Gao49e3c632015-12-09 11:26:11 -0800460 raise unittest.SkipTest('non-interactive stdin unsupported '
461 'on this device')
462
463 # Test both small and large inputs.
Josh Gao4218d852020-02-06 17:52:38 -0800464 small_input = b'foo'
465 characters = [c.encode("utf8") for c in string.ascii_letters + string.digits]
466 large_input = b'\n'.join(characters)
467
Josh Gao49e3c632015-12-09 11:26:11 -0800468
469 for input in (small_input, large_input):
470 proc = subprocess.Popen(self.device.adb_cmd + ['shell', 'cat'],
471 stdin=subprocess.PIPE,
472 stdout=subprocess.PIPE,
473 stderr=subprocess.PIPE)
474 stdout, stderr = proc.communicate(input)
475 self.assertEqual(input.splitlines(), stdout.splitlines())
Josh Gao4218d852020-02-06 17:52:38 -0800476 self.assertEqual(b'', stderr)
Josh Gao49e3c632015-12-09 11:26:11 -0800477
Josh Gao2eae66e2016-06-22 18:27:22 -0700478 def test_sighup(self):
479 """Ensure that SIGHUP gets sent upon non-interactive ctrl-c"""
480 log_path = "/data/local/tmp/adb_signal_test.log"
481
482 # Clear the output file.
483 self.device.shell_nocheck(["echo", ">", log_path])
484
485 script = """
486 trap "echo SIGINT > {path}; exit 0" SIGINT
487 trap "echo SIGHUP > {path}; exit 0" SIGHUP
488 echo Waiting
Josh Gao6a8ce062016-10-21 13:17:32 -0700489 read
Josh Gao2eae66e2016-06-22 18:27:22 -0700490 """.format(path=log_path)
491
492 script = ";".join([x.strip() for x in script.strip().splitlines()])
493
Josh Gao6a8ce062016-10-21 13:17:32 -0700494 process = self.device.shell_popen([script], kill_atexit=False,
495 stdin=subprocess.PIPE,
496 stdout=subprocess.PIPE)
Josh Gao2eae66e2016-06-22 18:27:22 -0700497
Josh Gao4218d852020-02-06 17:52:38 -0800498 self.assertEqual(b"Waiting\n", process.stdout.readline())
Josh Gao2eae66e2016-06-22 18:27:22 -0700499 process.send_signal(signal.SIGINT)
500 process.wait()
501
502 # Waiting for the local adb to finish is insufficient, since it hangs
503 # up immediately.
Josh Gao6a8ce062016-10-21 13:17:32 -0700504 time.sleep(1)
Josh Gao2eae66e2016-06-22 18:27:22 -0700505
506 stdout, _ = self.device.shell(["cat", log_path])
507 self.assertEqual(stdout.strip(), "SIGHUP")
508
Elliott Hughes2d065592022-04-12 14:00:36 -0700509 # Temporarily disabled because it seems to cause later instability.
510 # http://b/228114748
511 def disabled_test_exit_stress(self):
Josh Gao160bf7e2018-03-19 15:35:11 -0700512 """Hammer `adb shell exit 42` with multiple threads."""
513 thread_count = 48
514 result = dict()
515 def hammer(thread_idx, thread_count, result):
516 success = True
517 for i in range(thread_idx, 240, thread_count):
518 ret = subprocess.call(['adb', 'shell', 'exit {}'.format(i)])
519 if ret != i % 256:
520 success = False
521 break
522 result[thread_idx] = success
523
524 threads = []
525 for i in range(thread_count):
526 thread = threading.Thread(target=hammer, args=(i, thread_count, result))
527 thread.start()
528 threads.append(thread)
529 for thread in threads:
530 thread.join()
Josh Gao4218d852020-02-06 17:52:38 -0800531 for i, success in result.items():
Josh Gao160bf7e2018-03-19 15:35:11 -0700532 self.assertTrue(success)
533
Josh Gaoa7e4b452019-12-16 17:13:51 -0800534 def disabled_test_parallel(self):
535 """Spawn a bunch of `adb shell` instances in parallel.
536
537 This was broken historically due to the use of select, which only works
538 for fds that are numerically less than 1024.
539
540 Bug: http://b/141955761"""
541
542 n_procs = 2048
543 procs = dict()
Josh Gao4218d852020-02-06 17:52:38 -0800544 for i in range(0, n_procs):
Josh Gaoa7e4b452019-12-16 17:13:51 -0800545 procs[i] = subprocess.Popen(
546 ['adb', 'shell', 'read foo; echo $foo; read rc; exit $rc'],
547 stdin=subprocess.PIPE,
548 stdout=subprocess.PIPE
549 )
550
Josh Gao4218d852020-02-06 17:52:38 -0800551 for i in range(0, n_procs):
Josh Gaoa7e4b452019-12-16 17:13:51 -0800552 procs[i].stdin.write("%d\n" % i)
553
Josh Gao4218d852020-02-06 17:52:38 -0800554 for i in range(0, n_procs):
Josh Gaoa7e4b452019-12-16 17:13:51 -0800555 response = procs[i].stdout.readline()
556 assert(response == "%d\n" % i)
557
Josh Gao4218d852020-02-06 17:52:38 -0800558 for i in range(0, n_procs):
Josh Gaoa7e4b452019-12-16 17:13:51 -0800559 procs[i].stdin.write("%d\n" % (i % 256))
560
Josh Gao4218d852020-02-06 17:52:38 -0800561 for i in range(0, n_procs):
Josh Gaoa7e4b452019-12-16 17:13:51 -0800562 assert(procs[i].wait() == i % 256)
563
Josh Gao49e3c632015-12-09 11:26:11 -0800564
565class ArgumentEscapingTest(DeviceTest):
566 def test_shell_escaping(self):
567 """Make sure that argument escaping is somewhat sane."""
568
569 # http://b/19734868
570 # Note that this actually matches ssh(1)'s behavior --- it's
571 # converted to `sh -c echo hello; echo world` which sh interprets
572 # as `sh -c echo` (with an argument to that shell of "hello"),
573 # and then `echo world` back in the first shell.
574 result = self.device.shell(
575 shlex.split("sh -c 'echo hello; echo world'"))[0]
576 result = result.splitlines()
577 self.assertEqual(['', 'world'], result)
578 # If you really wanted "hello" and "world", here's what you'd do:
579 result = self.device.shell(
580 shlex.split(r'echo hello\;echo world'))[0].splitlines()
581 self.assertEqual(['hello', 'world'], result)
582
583 # http://b/15479704
584 result = self.device.shell(shlex.split("'true && echo t'"))[0].strip()
585 self.assertEqual('t', result)
586 result = self.device.shell(
587 shlex.split("sh -c 'true && echo t'"))[0].strip()
588 self.assertEqual('t', result)
589
590 # http://b/20564385
591 result = self.device.shell(shlex.split('FOO=a BAR=b echo t'))[0].strip()
592 self.assertEqual('t', result)
593 result = self.device.shell(
594 shlex.split(r'echo -n 123\;uname'))[0].strip()
595 self.assertEqual('123Linux', result)
596
597 def test_install_argument_escaping(self):
598 """Make sure that install argument escaping works."""
599 # http://b/20323053, http://b/3090932.
Josh Gao4218d852020-02-06 17:52:38 -0800600 for file_suffix in (b'-text;ls;1.apk', b"-Live Hold'em.apk"):
Josh Gao49e3c632015-12-09 11:26:11 -0800601 tf = tempfile.NamedTemporaryFile('wb', suffix=file_suffix,
602 delete=False)
603 tf.close()
604
605 # Installing bogus .apks fails if the device supports exit codes.
606 try:
Josh Gao4218d852020-02-06 17:52:38 -0800607 output = self.device.install(tf.name.decode("utf8"))
Josh Gao49e3c632015-12-09 11:26:11 -0800608 except subprocess.CalledProcessError as e:
609 output = e.output
610
611 self.assertIn(file_suffix, output)
612 os.remove(tf.name)
613
614
615class RootUnrootTest(DeviceTest):
616 def _test_root(self):
617 message = self.device.root()
618 if 'adbd cannot run as root in production builds' in message:
619 return
620 self.device.wait()
621 self.assertEqual('root', self.device.shell(['id', '-un'])[0].strip())
622
623 def _test_unroot(self):
624 self.device.unroot()
625 self.device.wait()
626 self.assertEqual('shell', self.device.shell(['id', '-un'])[0].strip())
627
628 def test_root_unroot(self):
629 """Make sure that adb root and adb unroot work, using id(1)."""
630 if self.device.get_prop('ro.debuggable') != '1':
631 raise unittest.SkipTest('requires rootable build')
632
633 original_user = self.device.shell(['id', '-un'])[0].strip()
634 try:
635 if original_user == 'root':
636 self._test_unroot()
637 self._test_root()
638 elif original_user == 'shell':
639 self._test_root()
640 self._test_unroot()
641 finally:
642 if original_user == 'root':
643 self.device.root()
644 else:
645 self.device.unroot()
646 self.device.wait()
647
648
649class TcpIpTest(DeviceTest):
650 def test_tcpip_failure_raises(self):
651 """adb tcpip requires a port.
652
653 Bug: http://b/22636927
654 """
655 self.assertRaises(
656 subprocess.CalledProcessError, self.device.tcpip, '')
657 self.assertRaises(
658 subprocess.CalledProcessError, self.device.tcpip, 'foo')
659
660
661class SystemPropertiesTest(DeviceTest):
662 def test_get_prop(self):
663 self.assertEqual(self.device.get_prop('init.svc.adbd'), 'running')
664
Josh Gao49e3c632015-12-09 11:26:11 -0800665 def test_set_prop(self):
Shaju Mathew5c1347b2022-10-21 03:43:57 +0000666 # debug.* prop does not require root privileges
667 prop_name = 'debug.foo'
Josh Gao49e3c632015-12-09 11:26:11 -0800668 self.device.shell(['setprop', prop_name, '""'])
669
Shaju Mathew5c1347b2022-10-21 03:43:57 +0000670 val = random.random()
671 self.device.set_prop(prop_name, str(val))
Josh Gao49e3c632015-12-09 11:26:11 -0800672 self.assertEqual(
Shaju Mathew5c1347b2022-10-21 03:43:57 +0000673 self.device.shell(['getprop', prop_name])[0].strip(), str(val))
Josh Gao49e3c632015-12-09 11:26:11 -0800674
675
676def compute_md5(string):
677 hsh = hashlib.md5()
678 hsh.update(string)
679 return hsh.hexdigest()
680
681
682def get_md5_prog(device):
683 """Older platforms (pre-L) had the name md5 rather than md5sum."""
684 try:
685 device.shell(['md5sum', '/proc/uptime'])
686 return 'md5sum'
687 except adb.ShellError:
688 return 'md5'
689
690
691class HostFile(object):
692 def __init__(self, handle, checksum):
693 self.handle = handle
694 self.checksum = checksum
695 self.full_path = handle.name
696 self.base_name = os.path.basename(self.full_path)
697
698
699class DeviceFile(object):
700 def __init__(self, checksum, full_path):
701 self.checksum = checksum
702 self.full_path = full_path
703 self.base_name = posixpath.basename(self.full_path)
704
705
706def make_random_host_files(in_dir, num_files):
707 min_size = 1 * (1 << 10)
708 max_size = 16 * (1 << 10)
709
710 files = []
Josh Gao4218d852020-02-06 17:52:38 -0800711 for _ in range(num_files):
Josh Gao49e3c632015-12-09 11:26:11 -0800712 file_handle = tempfile.NamedTemporaryFile(dir=in_dir, delete=False)
713
714 size = random.randrange(min_size, max_size, 1024)
715 rand_str = os.urandom(size)
716 file_handle.write(rand_str)
717 file_handle.flush()
718 file_handle.close()
719
720 md5 = compute_md5(rand_str)
721 files.append(HostFile(file_handle, md5))
722 return files
723
724
725def make_random_device_files(device, in_dir, num_files, prefix='device_tmpfile'):
726 min_size = 1 * (1 << 10)
727 max_size = 16 * (1 << 10)
728
729 files = []
Josh Gao4218d852020-02-06 17:52:38 -0800730 for file_num in range(num_files):
Josh Gao49e3c632015-12-09 11:26:11 -0800731 size = random.randrange(min_size, max_size, 1024)
732
733 base_name = prefix + str(file_num)
734 full_path = posixpath.join(in_dir, base_name)
735
736 device.shell(['dd', 'if=/dev/urandom', 'of={}'.format(full_path),
737 'bs={}'.format(size), 'count=1'])
738 dev_md5, _ = device.shell([get_md5_prog(device), full_path])[0].split()
739
740 files.append(DeviceFile(dev_md5, full_path))
741 return files
742
743
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700744class FileOperationsTest:
745 class Base(DeviceTest):
746 SCRATCH_DIR = '/data/local/tmp'
747 DEVICE_TEMP_FILE = SCRATCH_DIR + '/adb_test_file'
748 DEVICE_TEMP_DIR = SCRATCH_DIR + '/adb_test_dir'
Josh Gao49e3c632015-12-09 11:26:11 -0800749
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700750 def setUp(self):
751 self.previous_env = os.environ.get("ADB_COMPRESSION")
752 os.environ["ADB_COMPRESSION"] = self.compression
Josh Gao49e3c632015-12-09 11:26:11 -0800753
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700754 def tearDown(self):
755 if self.previous_env is None:
756 del os.environ["ADB_COMPRESSION"]
757 else:
758 os.environ["ADB_COMPRESSION"] = self.previous_env
Josh Gao49e3c632015-12-09 11:26:11 -0800759
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700760 def _verify_remote(self, checksum, remote_path):
761 dev_md5, _ = self.device.shell([get_md5_prog(self.device),
762 remote_path])[0].split()
763 self.assertEqual(checksum, dev_md5)
Josh Gao49e3c632015-12-09 11:26:11 -0800764
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700765 def _verify_local(self, checksum, local_path):
766 with open(local_path, 'rb') as host_file:
767 host_md5 = compute_md5(host_file.read())
768 self.assertEqual(host_md5, checksum)
Josh Gao49e3c632015-12-09 11:26:11 -0800769
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700770 def test_push(self):
771 """Push a randomly generated file to specified device."""
772 kbytes = 512
773 tmp = tempfile.NamedTemporaryFile(mode='wb', delete=False)
774 rand_str = os.urandom(1024 * kbytes)
775 tmp.write(rand_str)
776 tmp.close()
Josh Gao49e3c632015-12-09 11:26:11 -0800777
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700778 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
779 self.device.push(local=tmp.name, remote=self.DEVICE_TEMP_FILE)
Josh Gao49e3c632015-12-09 11:26:11 -0800780
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700781 self._verify_remote(compute_md5(rand_str), self.DEVICE_TEMP_FILE)
782 self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
Josh Gao49e3c632015-12-09 11:26:11 -0800783
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700784 os.remove(tmp.name)
Josh Gao49e3c632015-12-09 11:26:11 -0800785
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700786 def test_push_dir(self):
787 """Push a randomly generated directory of files to the device."""
Josh Gao49e3c632015-12-09 11:26:11 -0800788 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700789 self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
Josh Gao49e3c632015-12-09 11:26:11 -0800790
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700791 try:
792 host_dir = tempfile.mkdtemp()
Josh Gao49e3c632015-12-09 11:26:11 -0800793
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700794 # Make sure the temp directory isn't setuid, or else adb will complain.
795 os.chmod(host_dir, 0o700)
Josh Gao49e3c632015-12-09 11:26:11 -0800796
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700797 # Create 32 random files.
798 temp_files = make_random_host_files(in_dir=host_dir, num_files=32)
799 self.device.push(host_dir, self.DEVICE_TEMP_DIR)
Josh Gao49e3c632015-12-09 11:26:11 -0800800
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700801 for temp_file in temp_files:
802 remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
803 os.path.basename(host_dir),
804 temp_file.base_name)
805 self._verify_remote(temp_file.checksum, remote_path)
806 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
807 finally:
808 if host_dir is not None:
809 shutil.rmtree(host_dir)
Josh Gao49e3c632015-12-09 11:26:11 -0800810
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700811 def disabled_test_push_empty(self):
812 """Push an empty directory to the device."""
Josh Gao49e3c632015-12-09 11:26:11 -0800813 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700814 self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
Josh Gao49e3c632015-12-09 11:26:11 -0800815
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700816 try:
817 host_dir = tempfile.mkdtemp()
Josh Gao1deea102016-09-14 16:13:50 -0700818
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700819 # Make sure the temp directory isn't setuid, or else adb will complain.
820 os.chmod(host_dir, 0o700)
Josh Gao1deea102016-09-14 16:13:50 -0700821
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700822 # Create an empty directory.
823 empty_dir_path = os.path.join(host_dir, 'empty')
824 os.mkdir(empty_dir_path);
Josh Gao1deea102016-09-14 16:13:50 -0700825
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700826 self.device.push(empty_dir_path, self.DEVICE_TEMP_DIR)
Josh Gao1deea102016-09-14 16:13:50 -0700827
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700828 remote_path = os.path.join(self.DEVICE_TEMP_DIR, "empty")
829 test_empty_cmd = ["[", "-d", remote_path, "]"]
830 rc, _, _ = self.device.shell_nocheck(test_empty_cmd)
831
832 self.assertEqual(rc, 0)
833 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
834 finally:
835 if host_dir is not None:
836 shutil.rmtree(host_dir)
837
838 @unittest.skipIf(sys.platform == "win32", "symlinks require elevated privileges on windows")
839 def test_push_symlink(self):
840 """Push a symlink.
841
842 Bug: http://b/31491920
843 """
844 try:
845 host_dir = tempfile.mkdtemp()
846
847 # Make sure the temp directory isn't setuid, or else adb will
848 # complain.
849 os.chmod(host_dir, 0o700)
850
851 with open(os.path.join(host_dir, 'foo'), 'w') as f:
852 f.write('foo')
853
854 symlink_path = os.path.join(host_dir, 'symlink')
855 os.symlink('foo', symlink_path)
856
857 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
858 self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
859 self.device.push(symlink_path, self.DEVICE_TEMP_DIR)
860 rc, out, _ = self.device.shell_nocheck(
861 ['cat', posixpath.join(self.DEVICE_TEMP_DIR, 'symlink')])
862 self.assertEqual(0, rc)
863 self.assertEqual(out.strip(), 'foo')
864 finally:
865 if host_dir is not None:
866 shutil.rmtree(host_dir)
867
868 def test_multiple_push(self):
869 """Push multiple files to the device in one adb push command.
870
871 Bug: http://b/25324823
872 """
Josh Gao1deea102016-09-14 16:13:50 -0700873
874 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
875 self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
Josh Gao1deea102016-09-14 16:13:50 -0700876
Josh Gaoa53abe72016-02-19 15:55:55 -0800877 try:
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700878 host_dir = tempfile.mkdtemp()
879
880 # Create some random files and a subdirectory containing more files.
881 temp_files = make_random_host_files(in_dir=host_dir, num_files=4)
882
883 subdir = os.path.join(host_dir, 'subdir')
884 os.mkdir(subdir)
885 subdir_temp_files = make_random_host_files(in_dir=subdir,
886 num_files=4)
887
888 paths = [x.full_path for x in temp_files]
889 paths.append(subdir)
890 self.device._simple_call(['push'] + paths + [self.DEVICE_TEMP_DIR])
891
892 for temp_file in temp_files:
893 remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
894 temp_file.base_name)
895 self._verify_remote(temp_file.checksum, remote_path)
896
897 for subdir_temp_file in subdir_temp_files:
898 remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
899 # BROKEN: http://b/25394682
900 # 'subdir';
901 temp_file.base_name)
902 self._verify_remote(temp_file.checksum, remote_path)
903
904
905 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
906 finally:
907 if host_dir is not None:
908 shutil.rmtree(host_dir)
909
910 @requires_non_root
911 def test_push_error_reporting(self):
912 """Make sure that errors that occur while pushing a file get reported
913
914 Bug: http://b/26816782
915 """
916 with tempfile.NamedTemporaryFile() as tmp_file:
917 tmp_file.write(b'\0' * 1024 * 1024)
918 tmp_file.flush()
919 try:
920 self.device.push(local=tmp_file.name, remote='/system/')
921 self.fail('push should not have succeeded')
922 except subprocess.CalledProcessError as e:
923 output = e.output
924
925 self.assertTrue(b'Permission denied' in output or
926 b'Read-only file system' in output)
927
928 @requires_non_root
929 def test_push_directory_creation(self):
930 """Regression test for directory creation.
931
932 Bug: http://b/110953234
933 """
934 with tempfile.NamedTemporaryFile() as tmp_file:
935 tmp_file.write(b'\0' * 1024 * 1024)
936 tmp_file.flush()
937 remote_path = self.DEVICE_TEMP_DIR + '/test_push_directory_creation'
938 self.device.shell(['rm', '-rf', remote_path])
939
940 remote_path += '/filename'
941 self.device.push(local=tmp_file.name, remote=remote_path)
942
943 def disabled_test_push_multiple_slash_root(self):
944 """Regression test for pushing to //data/local/tmp.
945
946 Bug: http://b/141311284
947
948 Disabled because this broken on the adbd side as well: b/141943968
949 """
950 with tempfile.NamedTemporaryFile() as tmp_file:
Shaju Mathew4c9fefa2022-10-01 04:23:17 +0000951 tmp_file.write(b'\0' * 1024 * 1024)
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700952 tmp_file.flush()
953 remote_path = '/' + self.DEVICE_TEMP_DIR + '/test_push_multiple_slash_root'
954 self.device.shell(['rm', '-rf', remote_path])
955 self.device.push(local=tmp_file.name, remote=remote_path)
956
957 def _test_pull(self, remote_file, checksum):
958 tmp_write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
959 tmp_write.close()
960 self.device.pull(remote=remote_file, local=tmp_write.name)
961 with open(tmp_write.name, 'rb') as tmp_read:
962 host_contents = tmp_read.read()
963 host_md5 = compute_md5(host_contents)
964 self.assertEqual(checksum, host_md5)
965 os.remove(tmp_write.name)
966
967 @requires_non_root
968 def test_pull_error_reporting(self):
969 self.device.shell(['touch', self.DEVICE_TEMP_FILE])
970 self.device.shell(['chmod', 'a-rwx', self.DEVICE_TEMP_FILE])
971
972 try:
973 output = self.device.pull(remote=self.DEVICE_TEMP_FILE, local='x')
Josh Gaoa53abe72016-02-19 15:55:55 -0800974 except subprocess.CalledProcessError as e:
975 output = e.output
976
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700977 self.assertIn(b'Permission denied', output)
Josh Gao49e3c632015-12-09 11:26:11 -0800978
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700979 self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
Josh Gaof9671172018-06-28 18:43:19 -0700980
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700981 def test_pull(self):
982 """Pull a randomly generated file from specified device."""
983 kbytes = 512
984 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
985 cmd = ['dd', 'if=/dev/urandom',
986 'of={}'.format(self.DEVICE_TEMP_FILE), 'bs=1024',
987 'count={}'.format(kbytes)]
988 self.device.shell(cmd)
989 dev_md5, _ = self.device.shell(
990 [get_md5_prog(self.device), self.DEVICE_TEMP_FILE])[0].split()
991 self._test_pull(self.DEVICE_TEMP_FILE, dev_md5)
992 self.device.shell_nocheck(['rm', self.DEVICE_TEMP_FILE])
Josh Gaof9671172018-06-28 18:43:19 -0700993
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700994 def test_pull_dir(self):
995 """Pull a randomly generated directory of files from the device."""
996 try:
997 host_dir = tempfile.mkdtemp()
Josh Gaof9671172018-06-28 18:43:19 -0700998
Josh Gaobfcd8ff2020-03-26 19:33:25 -0700999 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1000 self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
Josh Gaoce6d3a52019-09-26 01:49:56 +08001001
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001002 # Populate device directory with random files.
1003 temp_files = make_random_device_files(
1004 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
Josh Gao1aab8982019-10-01 14:14:07 -07001005
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001006 self.device.pull(remote=self.DEVICE_TEMP_DIR, local=host_dir)
Josh Gaoce6d3a52019-09-26 01:49:56 +08001007
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001008 for temp_file in temp_files:
1009 host_path = os.path.join(
1010 host_dir, posixpath.basename(self.DEVICE_TEMP_DIR),
1011 temp_file.base_name)
1012 self._verify_local(temp_file.checksum, host_path)
Josh Gao49e3c632015-12-09 11:26:11 -08001013
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001014 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1015 finally:
1016 if host_dir is not None:
1017 shutil.rmtree(host_dir)
Josh Gao49e3c632015-12-09 11:26:11 -08001018
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001019 def test_pull_dir_symlink(self):
1020 """Pull a directory into a symlink to a directory.
Josh Gao49e3c632015-12-09 11:26:11 -08001021
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001022 Bug: http://b/27362811
1023 """
1024 if os.name != 'posix':
1025 raise unittest.SkipTest('requires POSIX')
Josh Gao49e3c632015-12-09 11:26:11 -08001026
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001027 try:
1028 host_dir = tempfile.mkdtemp()
1029 real_dir = os.path.join(host_dir, 'dir')
1030 symlink = os.path.join(host_dir, 'symlink')
1031 os.mkdir(real_dir)
1032 os.symlink(real_dir, symlink)
Josh Gao49e3c632015-12-09 11:26:11 -08001033
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001034 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1035 self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
Josh Gao49e3c632015-12-09 11:26:11 -08001036
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001037 # Populate device directory with random files.
1038 temp_files = make_random_device_files(
1039 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
Josh Gao49e3c632015-12-09 11:26:11 -08001040
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001041 self.device.pull(remote=self.DEVICE_TEMP_DIR, local=symlink)
Josh Gao49e3c632015-12-09 11:26:11 -08001042
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001043 for temp_file in temp_files:
1044 host_path = os.path.join(
1045 real_dir, posixpath.basename(self.DEVICE_TEMP_DIR),
1046 temp_file.base_name)
1047 self._verify_local(temp_file.checksum, host_path)
Josh Gao49e3c632015-12-09 11:26:11 -08001048
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001049 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1050 finally:
1051 if host_dir is not None:
1052 shutil.rmtree(host_dir)
Josh Gao49e3c632015-12-09 11:26:11 -08001053
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001054 def test_pull_dir_symlink_collision(self):
1055 """Pull a directory into a colliding symlink to directory."""
1056 if os.name != 'posix':
1057 raise unittest.SkipTest('requires POSIX')
1058
1059 try:
1060 host_dir = tempfile.mkdtemp()
1061 real_dir = os.path.join(host_dir, 'real')
1062 tmp_dirname = os.path.basename(self.DEVICE_TEMP_DIR)
1063 symlink = os.path.join(host_dir, tmp_dirname)
1064 os.mkdir(real_dir)
1065 os.symlink(real_dir, symlink)
1066
1067 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1068 self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
1069
1070 # Populate device directory with random files.
1071 temp_files = make_random_device_files(
1072 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
1073
1074 self.device.pull(remote=self.DEVICE_TEMP_DIR, local=host_dir)
1075
1076 for temp_file in temp_files:
1077 host_path = os.path.join(real_dir, temp_file.base_name)
1078 self._verify_local(temp_file.checksum, host_path)
1079
1080 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1081 finally:
1082 if host_dir is not None:
1083 shutil.rmtree(host_dir)
1084
1085 def test_pull_dir_nonexistent(self):
1086 """Pull a directory of files from the device to a nonexistent path."""
1087 try:
1088 host_dir = tempfile.mkdtemp()
1089 dest_dir = os.path.join(host_dir, 'dest')
1090
1091 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1092 self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
1093
1094 # Populate device directory with random files.
1095 temp_files = make_random_device_files(
1096 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
1097
1098 self.device.pull(remote=self.DEVICE_TEMP_DIR, local=dest_dir)
1099
1100 for temp_file in temp_files:
1101 host_path = os.path.join(dest_dir, temp_file.base_name)
1102 self._verify_local(temp_file.checksum, host_path)
1103
1104 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1105 finally:
1106 if host_dir is not None:
1107 shutil.rmtree(host_dir)
1108
1109 # selinux prevents adbd from accessing symlinks on /data/local/tmp.
1110 def disabled_test_pull_symlink_dir(self):
1111 """Pull a symlink to a directory of symlinks to files."""
1112 try:
1113 host_dir = tempfile.mkdtemp()
1114
1115 remote_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'contents')
1116 remote_links = posixpath.join(self.DEVICE_TEMP_DIR, 'links')
1117 remote_symlink = posixpath.join(self.DEVICE_TEMP_DIR, 'symlink')
1118
1119 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1120 self.device.shell(['mkdir', '-p', remote_dir, remote_links])
1121 self.device.shell(['ln', '-s', remote_links, remote_symlink])
1122
1123 # Populate device directory with random files.
1124 temp_files = make_random_device_files(
1125 self.device, in_dir=remote_dir, num_files=32)
1126
1127 for temp_file in temp_files:
1128 self.device.shell(
1129 ['ln', '-s', '../contents/{}'.format(temp_file.base_name),
1130 posixpath.join(remote_links, temp_file.base_name)])
1131
1132 self.device.pull(remote=remote_symlink, local=host_dir)
1133
1134 for temp_file in temp_files:
1135 host_path = os.path.join(
1136 host_dir, 'symlink', temp_file.base_name)
1137 self._verify_local(temp_file.checksum, host_path)
1138
1139 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1140 finally:
1141 if host_dir is not None:
1142 shutil.rmtree(host_dir)
1143
1144 def test_pull_empty(self):
1145 """Pull a directory containing an empty directory from the device."""
1146 try:
1147 host_dir = tempfile.mkdtemp()
1148
1149 remote_empty_path = posixpath.join(self.DEVICE_TEMP_DIR, 'empty')
1150 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1151 self.device.shell(['mkdir', '-p', remote_empty_path])
1152
1153 self.device.pull(remote=remote_empty_path, local=host_dir)
1154 self.assertTrue(os.path.isdir(os.path.join(host_dir, 'empty')))
1155 finally:
1156 if host_dir is not None:
1157 shutil.rmtree(host_dir)
1158
1159 def test_multiple_pull(self):
1160 """Pull a randomly generated directory of files from the device."""
1161
1162 try:
1163 host_dir = tempfile.mkdtemp()
1164
1165 subdir = posixpath.join(self.DEVICE_TEMP_DIR, 'subdir')
1166 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1167 self.device.shell(['mkdir', '-p', subdir])
1168
1169 # Create some random files and a subdirectory containing more files.
1170 temp_files = make_random_device_files(
1171 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=4)
1172
1173 subdir_temp_files = make_random_device_files(
1174 self.device, in_dir=subdir, num_files=4, prefix='subdir_')
1175
1176 paths = [x.full_path for x in temp_files]
1177 paths.append(subdir)
1178 self.device._simple_call(['pull'] + paths + [host_dir])
1179
1180 for temp_file in temp_files:
1181 local_path = os.path.join(host_dir, temp_file.base_name)
1182 self._verify_local(temp_file.checksum, local_path)
1183
1184 for subdir_temp_file in subdir_temp_files:
1185 local_path = os.path.join(host_dir,
1186 'subdir',
1187 subdir_temp_file.base_name)
1188 self._verify_local(subdir_temp_file.checksum, local_path)
1189
1190 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1191 finally:
1192 if host_dir is not None:
1193 shutil.rmtree(host_dir)
1194
1195 def verify_sync(self, device, temp_files, device_dir):
1196 """Verifies that a list of temp files was synced to the device."""
1197 # Confirm that every file on the device mirrors that on the host.
Josh Gao49e3c632015-12-09 11:26:11 -08001198 for temp_file in temp_files:
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001199 device_full_path = posixpath.join(
1200 device_dir, temp_file.base_name)
1201 dev_md5, _ = device.shell(
1202 [get_md5_prog(self.device), device_full_path])[0].split()
1203 self.assertEqual(temp_file.checksum, dev_md5)
Josh Gao49e3c632015-12-09 11:26:11 -08001204
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001205 def test_sync(self):
1206 """Sync a host directory to the data partition."""
Josh Gao49e3c632015-12-09 11:26:11 -08001207
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001208 try:
1209 base_dir = tempfile.mkdtemp()
Josh Gao49726bc2016-02-26 13:26:55 -08001210
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001211 # Create mirror device directory hierarchy within base_dir.
1212 full_dir_path = base_dir + self.DEVICE_TEMP_DIR
1213 os.makedirs(full_dir_path)
Josh Gao49726bc2016-02-26 13:26:55 -08001214
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001215 # Create 32 random files within the host mirror.
1216 temp_files = make_random_host_files(
1217 in_dir=full_dir_path, num_files=32)
Josh Gao49726bc2016-02-26 13:26:55 -08001218
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001219 # Clean up any stale files on the device.
1220 device = adb.get_device() # pylint: disable=no-member
1221 device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
Josh Gao49726bc2016-02-26 13:26:55 -08001222
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001223 old_product_out = os.environ.get('ANDROID_PRODUCT_OUT')
1224 os.environ['ANDROID_PRODUCT_OUT'] = base_dir
1225 device.sync('data')
1226 if old_product_out is None:
1227 del os.environ['ANDROID_PRODUCT_OUT']
1228 else:
1229 os.environ['ANDROID_PRODUCT_OUT'] = old_product_out
Josh Gao49726bc2016-02-26 13:26:55 -08001230
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001231 self.verify_sync(device, temp_files, self.DEVICE_TEMP_DIR)
Josh Gao49726bc2016-02-26 13:26:55 -08001232
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001233 #self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1234 finally:
1235 if base_dir is not None:
1236 shutil.rmtree(base_dir)
Josh Gao49726bc2016-02-26 13:26:55 -08001237
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001238 def test_push_sync(self):
1239 """Sync a host directory to a specific path."""
Josh Gao49726bc2016-02-26 13:26:55 -08001240
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001241 try:
1242 temp_dir = tempfile.mkdtemp()
1243 temp_files = make_random_host_files(in_dir=temp_dir, num_files=32)
Josh Gao49726bc2016-02-26 13:26:55 -08001244
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001245 device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'sync_src_dst')
Josh Gao49726bc2016-02-26 13:26:55 -08001246
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001247 # Clean up any stale files on the device.
1248 device = adb.get_device() # pylint: disable=no-member
1249 device.shell(['rm', '-rf', device_dir])
Josh Gao49726bc2016-02-26 13:26:55 -08001250
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001251 device.push(temp_dir, device_dir, sync=True)
Josh Gao49726bc2016-02-26 13:26:55 -08001252
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001253 self.verify_sync(device, temp_files, device_dir)
Josh Gao49726bc2016-02-26 13:26:55 -08001254
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001255 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1256 finally:
1257 if temp_dir is not None:
1258 shutil.rmtree(temp_dir)
Josh Gao49726bc2016-02-26 13:26:55 -08001259
Josh Gao89837af2020-09-08 17:40:22 -07001260 def test_push_sync_multiple(self):
1261 """Sync multiple host directories to a specific path."""
1262
1263 try:
1264 temp_dir = tempfile.mkdtemp()
1265 temp_files = make_random_host_files(in_dir=temp_dir, num_files=32)
1266
1267 device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'sync_src_dst')
1268
1269 # Clean up any stale files on the device.
1270 device = adb.get_device() # pylint: disable=no-member
1271 device.shell(['rm', '-rf', device_dir])
1272 device.shell(['mkdir', '-p', device_dir])
1273
1274 host_paths = [os.path.join(temp_dir, x.base_name) for x in temp_files]
1275 device.push(host_paths, device_dir, sync=True)
1276
1277 self.verify_sync(device, temp_files, device_dir)
1278
1279 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1280 finally:
1281 if temp_dir is not None:
1282 shutil.rmtree(temp_dir)
1283
1284
Josh Gao8a410a02020-03-30 23:25:16 -07001285 def test_push_dry_run_nonexistent_file(self):
1286 """Push with dry run."""
1287
1288 for file_size in [8, 1024 * 1024]:
1289 try:
1290 device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'push_dry_run')
1291 device_file = posixpath.join(device_dir, 'file')
1292
1293 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1294 self.device.shell(['mkdir', '-p', device_dir])
1295
1296 host_dir = tempfile.mkdtemp()
1297 host_file = posixpath.join(host_dir, 'file')
1298
1299 with open(host_file, "w") as f:
1300 f.write('x' * file_size)
1301
1302 self.device._simple_call(['push', '-n', host_file, device_file])
1303 rc, _, _ = self.device.shell_nocheck(['[', '-e', device_file, ']'])
1304 self.assertNotEqual(0, rc)
1305
1306 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1307 finally:
1308 if host_dir is not None:
1309 shutil.rmtree(host_dir)
1310
1311 def test_push_dry_run_existent_file(self):
1312 """Push with dry run."""
1313
1314 for file_size in [8, 1024 * 1024]:
1315 try:
Shaju Mathew4c9fefa2022-10-01 04:23:17 +00001316 host_dir = tempfile.mkdtemp()
Josh Gao8a410a02020-03-30 23:25:16 -07001317 device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'push_dry_run')
1318 device_file = posixpath.join(device_dir, 'file')
1319
1320 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1321 self.device.shell(['mkdir', '-p', device_dir])
1322 self.device.shell(['echo', 'foo', '>', device_file])
1323
Josh Gao8a410a02020-03-30 23:25:16 -07001324 host_file = posixpath.join(host_dir, 'file')
1325
1326 with open(host_file, "w") as f:
1327 f.write('x' * file_size)
1328
1329 self.device._simple_call(['push', '-n', host_file, device_file])
1330 stdout, stderr = self.device.shell(['cat', device_file])
1331 self.assertEqual(stdout.strip(), "foo")
1332
1333 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1334 finally:
1335 if host_dir is not None:
1336 shutil.rmtree(host_dir)
1337
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001338 def test_unicode_paths(self):
1339 """Ensure that we can support non-ASCII paths, even on Windows."""
1340 name = u'로보카 폴리'
Josh Gao49726bc2016-02-26 13:26:55 -08001341
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001342 self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
1343 remote_path = u'/data/local/tmp/adb-test-{}'.format(name)
Josh Gaoa842b382016-03-02 16:00:02 -08001344
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001345 ## push.
1346 tf = tempfile.NamedTemporaryFile('wb', suffix=name, delete=False)
1347 tf.close()
1348 self.device.push(tf.name, remote_path)
1349 os.remove(tf.name)
1350 self.assertFalse(os.path.exists(tf.name))
Josh Gaoa842b382016-03-02 16:00:02 -08001351
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001352 # Verify that the device ended up with the expected UTF-8 path
1353 output = self.device.shell(
1354 ['ls', '/data/local/tmp/adb-test-*'])[0].strip()
1355 self.assertEqual(remote_path, output)
Josh Gaoa842b382016-03-02 16:00:02 -08001356
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001357 # pull.
1358 self.device.pull(remote_path, tf.name)
1359 self.assertTrue(os.path.exists(tf.name))
1360 os.remove(tf.name)
1361 self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
Josh Gaoa842b382016-03-02 16:00:02 -08001362
Josh Gaoa842b382016-03-02 16:00:02 -08001363
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001364class FileOperationsTestUncompressed(FileOperationsTest.Base):
1365 compression = "none"
Josh Gaoa842b382016-03-02 16:00:02 -08001366
Josh Gaod9a2fd62015-12-09 14:03:30 -08001367
Josh Gaobfcd8ff2020-03-26 19:33:25 -07001368class FileOperationsTestBrotli(FileOperationsTest.Base):
1369 compression = "brotli"
Josh Gao49e3c632015-12-09 11:26:11 -08001370
1371
Josh Gaofb386cc2020-03-26 22:02:03 -07001372class FileOperationsTestLZ4(FileOperationsTest.Base):
1373 compression = "lz4"
1374
1375
Josh Gaobdebc9b2020-05-27 17:52:52 -07001376class FileOperationsTestZstd(FileOperationsTest.Base):
1377 compression = "zstd"
1378
1379
Yabin Cui3cf1b362017-03-10 16:01:01 -08001380class DeviceOfflineTest(DeviceTest):
1381 def _get_device_state(self, serialno):
1382 output = subprocess.check_output(self.device.adb_cmd + ['devices'])
1383 for line in output.split('\n'):
1384 m = re.match('(\S+)\s+(\S+)', line)
1385 if m and m.group(1) == serialno:
1386 return m.group(2)
1387 return None
1388
Josh Gao6e0ed552017-09-13 14:51:23 -07001389 def disabled_test_killed_when_pushing_a_large_file(self):
Yabin Cui3cf1b362017-03-10 16:01:01 -08001390 """
1391 While running adb push with a large file, kill adb server.
1392 Occasionally the device becomes offline. Because the device is still
1393 reading data without realizing that the adb server has been restarted.
1394 Test if we can bring the device online automatically now.
1395 http://b/32952319
1396 """
1397 serialno = subprocess.check_output(self.device.adb_cmd + ['get-serialno']).strip()
1398 # 1. Push a large file
1399 file_path = 'tmp_large_file'
1400 try:
1401 fh = open(file_path, 'w')
1402 fh.write('\0' * (100 * 1024 * 1024))
1403 fh.close()
1404 subproc = subprocess.Popen(self.device.adb_cmd + ['push', file_path, '/data/local/tmp'])
1405 time.sleep(0.1)
1406 # 2. Kill the adb server
1407 subprocess.check_call(self.device.adb_cmd + ['kill-server'])
1408 subproc.terminate()
1409 finally:
1410 try:
1411 os.unlink(file_path)
1412 except:
1413 pass
1414 # 3. See if the device still exist.
1415 # Sleep to wait for the adb server exit.
1416 time.sleep(0.5)
1417 # 4. The device should be online
1418 self.assertEqual(self._get_device_state(serialno), 'device')
1419
Josh Gao6e0ed552017-09-13 14:51:23 -07001420 def disabled_test_killed_when_pulling_a_large_file(self):
Yabin Cui3cf1b362017-03-10 16:01:01 -08001421 """
1422 While running adb pull with a large file, kill adb server.
1423 Occasionally the device can't be connected. Because the device is trying to
1424 send a message larger than what is expected by the adb server.
1425 Test if we can bring the device online automatically now.
1426 """
1427 serialno = subprocess.check_output(self.device.adb_cmd + ['get-serialno']).strip()
1428 file_path = 'tmp_large_file'
1429 try:
1430 # 1. Create a large file on device.
1431 self.device.shell(['dd', 'if=/dev/zero', 'of=/data/local/tmp/tmp_large_file',
1432 'bs=1000000', 'count=100'])
1433 # 2. Pull the large file on host.
1434 subproc = subprocess.Popen(self.device.adb_cmd +
1435 ['pull','/data/local/tmp/tmp_large_file', file_path])
1436 time.sleep(0.1)
1437 # 3. Kill the adb server
1438 subprocess.check_call(self.device.adb_cmd + ['kill-server'])
1439 subproc.terminate()
1440 finally:
1441 try:
1442 os.unlink(file_path)
1443 except:
1444 pass
1445 # 4. See if the device still exist.
1446 # Sleep to wait for the adb server exit.
1447 time.sleep(0.5)
1448 self.assertEqual(self._get_device_state(serialno), 'device')
1449
1450
Josh Gao3734cf02017-05-02 15:01:09 -07001451 def test_packet_size_regression(self):
1452 """Test for http://b/37783561
1453
1454 Receiving packets of a length divisible by 512 but not 1024 resulted in
1455 the adb client waiting indefinitely for more input.
1456 """
1457 # The values that trigger things are 507 (512 - 5 bytes from shell protocol) + 1024*n
1458 # Probe some surrounding values as well, for the hell of it.
Josh Gao4218d852020-02-06 17:52:38 -08001459 for base in [512] + list(range(1024, 1024 * 16, 1024)):
Josh Gaoc7f2d192018-04-10 14:35:06 -07001460 for offset in [-6, -5, -4]:
1461 length = base + offset
1462 cmd = ['dd', 'if=/dev/zero', 'bs={}'.format(length), 'count=1', '2>/dev/null;'
1463 'echo', 'foo']
1464 rc, stdout, _ = self.device.shell_nocheck(cmd)
Josh Gao3734cf02017-05-02 15:01:09 -07001465
Josh Gaoc7f2d192018-04-10 14:35:06 -07001466 self.assertEqual(0, rc)
Josh Gao3734cf02017-05-02 15:01:09 -07001467
Josh Gaoc7f2d192018-04-10 14:35:06 -07001468 # Output should be '\0' * length, followed by "foo\n"
1469 self.assertEqual(length, len(stdout) - 4)
1470 self.assertEqual(stdout, "\0" * length + "foo\n")
Josh Gao3734cf02017-05-02 15:01:09 -07001471
Josh Gao9fae8762018-08-22 15:13:18 -07001472 def test_zero_packet(self):
1473 """Test for http://b/113070258
1474
1475 Make sure that we don't blow up when sending USB transfers that line up
1476 exactly with the USB packet size.
1477 """
1478
1479 local_port = int(self.device.forward("tcp:0", "tcp:12345"))
1480 try:
1481 for size in [512, 1024]:
1482 def listener():
1483 cmd = ["echo foo | nc -l -p 12345; echo done"]
1484 rc, stdout, stderr = self.device.shell_nocheck(cmd)
1485
1486 thread = threading.Thread(target=listener)
1487 thread.start()
1488
1489 # Wait a bit to let the shell command start.
1490 time.sleep(0.25)
1491
1492 sock = socket.create_connection(("localhost", local_port))
1493 with contextlib.closing(sock):
Josh Gao4218d852020-02-06 17:52:38 -08001494 bytesWritten = sock.send(b"a" * size)
Josh Gao9fae8762018-08-22 15:13:18 -07001495 self.assertEqual(size, bytesWritten)
1496 readBytes = sock.recv(4096)
Josh Gao4218d852020-02-06 17:52:38 -08001497 self.assertEqual(b"foo\n", readBytes)
Josh Gao9fae8762018-08-22 15:13:18 -07001498
1499 thread.join()
1500 finally:
1501 self.device.forward_remove("tcp:{}".format(local_port))
1502
Josh Gao3734cf02017-05-02 15:01:09 -07001503
Josh Gao18f7a5c2019-01-11 14:42:08 -08001504class SocketTest(DeviceTest):
1505 def test_socket_flush(self):
1506 """Test that we handle socket closure properly.
1507
1508 If we're done writing to a socket, closing before the other end has
1509 closed will send a TCP_RST if we have incoming data queued up, which
1510 may result in data that we've written being discarded.
1511
1512 Bug: http://b/74616284
1513 """
1514 s = socket.create_connection(("localhost", 5037))
1515
1516 def adb_length_prefixed(string):
1517 encoded = string.encode("utf8")
1518 result = b"%04x%s" % (len(encoded), encoded)
1519 return result
1520
1521 if "ANDROID_SERIAL" in os.environ:
1522 transport_string = "host:transport:" + os.environ["ANDROID_SERIAL"]
1523 else:
1524 transport_string = "host:transport-any"
1525
1526 s.sendall(adb_length_prefixed(transport_string))
1527 response = s.recv(4)
Josh Gao4218d852020-02-06 17:52:38 -08001528 self.assertEqual(b"OKAY", response)
Josh Gao18f7a5c2019-01-11 14:42:08 -08001529
1530 shell_string = "shell:sleep 0.5; dd if=/dev/zero bs=1m count=1 status=none; echo foo"
1531 s.sendall(adb_length_prefixed(shell_string))
1532
1533 response = s.recv(4)
Josh Gao4218d852020-02-06 17:52:38 -08001534 self.assertEqual(b"OKAY", response)
Josh Gao18f7a5c2019-01-11 14:42:08 -08001535
1536 # Spawn a thread that dumps garbage into the socket until failure.
1537 def spam():
1538 buf = b"\0" * 16384
1539 try:
1540 while True:
1541 s.sendall(buf)
1542 except Exception as ex:
1543 print(ex)
1544
1545 thread = threading.Thread(target=spam)
1546 thread.start()
1547
1548 time.sleep(1)
1549
1550 received = b""
1551 while True:
1552 read = s.recv(512)
1553 if len(read) == 0:
1554 break
1555 received += read
1556
Josh Gao4218d852020-02-06 17:52:38 -08001557 self.assertEqual(1024 * 1024 + len("foo\n"), len(received))
Josh Gao18f7a5c2019-01-11 14:42:08 -08001558 thread.join()
1559
1560
Josh Gaoe83431c2021-05-20 18:49:31 -07001561class FramebufferTest(DeviceTest):
1562 def test_framebuffer(self):
1563 """Test that we get something from the framebuffer service."""
1564 output = subprocess.check_output(self.device.adb_cmd + ["raw", "framebuffer:"])
1565 self.assertFalse(len(output) == 0)
1566
1567
Spencer Low35a47db2018-08-11 00:16:16 -07001568if sys.platform == "win32":
1569 # From https://stackoverflow.com/a/38749458
1570 import os
1571 import contextlib
1572 import msvcrt
1573 import ctypes
1574 from ctypes import wintypes
1575
1576 kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
1577
1578 GENERIC_READ = 0x80000000
1579 GENERIC_WRITE = 0x40000000
1580 FILE_SHARE_READ = 1
1581 FILE_SHARE_WRITE = 2
1582 CONSOLE_TEXTMODE_BUFFER = 1
1583 INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
1584 STD_OUTPUT_HANDLE = wintypes.DWORD(-11)
1585 STD_ERROR_HANDLE = wintypes.DWORD(-12)
1586
1587 def _check_zero(result, func, args):
1588 if not result:
1589 raise ctypes.WinError(ctypes.get_last_error())
1590 return args
1591
1592 def _check_invalid(result, func, args):
1593 if result == INVALID_HANDLE_VALUE:
1594 raise ctypes.WinError(ctypes.get_last_error())
1595 return args
1596
1597 if not hasattr(wintypes, 'LPDWORD'): # Python 2
1598 wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
1599 wintypes.PSMALL_RECT = ctypes.POINTER(wintypes.SMALL_RECT)
1600
1601 class COORD(ctypes.Structure):
1602 _fields_ = (('X', wintypes.SHORT),
1603 ('Y', wintypes.SHORT))
1604
1605 class CONSOLE_SCREEN_BUFFER_INFOEX(ctypes.Structure):
1606 _fields_ = (('cbSize', wintypes.ULONG),
1607 ('dwSize', COORD),
1608 ('dwCursorPosition', COORD),
1609 ('wAttributes', wintypes.WORD),
1610 ('srWindow', wintypes.SMALL_RECT),
1611 ('dwMaximumWindowSize', COORD),
1612 ('wPopupAttributes', wintypes.WORD),
1613 ('bFullscreenSupported', wintypes.BOOL),
1614 ('ColorTable', wintypes.DWORD * 16))
1615 def __init__(self, *args, **kwds):
1616 super(CONSOLE_SCREEN_BUFFER_INFOEX, self).__init__(
1617 *args, **kwds)
1618 self.cbSize = ctypes.sizeof(self)
1619
1620 PCONSOLE_SCREEN_BUFFER_INFOEX = ctypes.POINTER(
1621 CONSOLE_SCREEN_BUFFER_INFOEX)
1622 LPSECURITY_ATTRIBUTES = wintypes.LPVOID
1623
1624 kernel32.GetStdHandle.errcheck = _check_invalid
1625 kernel32.GetStdHandle.restype = wintypes.HANDLE
1626 kernel32.GetStdHandle.argtypes = (
1627 wintypes.DWORD,) # _In_ nStdHandle
1628
1629 kernel32.CreateConsoleScreenBuffer.errcheck = _check_invalid
1630 kernel32.CreateConsoleScreenBuffer.restype = wintypes.HANDLE
1631 kernel32.CreateConsoleScreenBuffer.argtypes = (
1632 wintypes.DWORD, # _In_ dwDesiredAccess
1633 wintypes.DWORD, # _In_ dwShareMode
1634 LPSECURITY_ATTRIBUTES, # _In_opt_ lpSecurityAttributes
1635 wintypes.DWORD, # _In_ dwFlags
1636 wintypes.LPVOID) # _Reserved_ lpScreenBufferData
1637
1638 kernel32.GetConsoleScreenBufferInfoEx.errcheck = _check_zero
1639 kernel32.GetConsoleScreenBufferInfoEx.argtypes = (
1640 wintypes.HANDLE, # _In_ hConsoleOutput
1641 PCONSOLE_SCREEN_BUFFER_INFOEX) # _Out_ lpConsoleScreenBufferInfo
1642
1643 kernel32.SetConsoleScreenBufferInfoEx.errcheck = _check_zero
1644 kernel32.SetConsoleScreenBufferInfoEx.argtypes = (
1645 wintypes.HANDLE, # _In_ hConsoleOutput
1646 PCONSOLE_SCREEN_BUFFER_INFOEX) # _In_ lpConsoleScreenBufferInfo
1647
1648 kernel32.SetConsoleWindowInfo.errcheck = _check_zero
1649 kernel32.SetConsoleWindowInfo.argtypes = (
1650 wintypes.HANDLE, # _In_ hConsoleOutput
1651 wintypes.BOOL, # _In_ bAbsolute
1652 wintypes.PSMALL_RECT) # _In_ lpConsoleWindow
1653
1654 kernel32.FillConsoleOutputCharacterW.errcheck = _check_zero
1655 kernel32.FillConsoleOutputCharacterW.argtypes = (
1656 wintypes.HANDLE, # _In_ hConsoleOutput
1657 wintypes.WCHAR, # _In_ cCharacter
1658 wintypes.DWORD, # _In_ nLength
1659 COORD, # _In_ dwWriteCoord
1660 wintypes.LPDWORD) # _Out_ lpNumberOfCharsWritten
1661
1662 kernel32.ReadConsoleOutputCharacterW.errcheck = _check_zero
1663 kernel32.ReadConsoleOutputCharacterW.argtypes = (
1664 wintypes.HANDLE, # _In_ hConsoleOutput
1665 wintypes.LPWSTR, # _Out_ lpCharacter
1666 wintypes.DWORD, # _In_ nLength
1667 COORD, # _In_ dwReadCoord
1668 wintypes.LPDWORD) # _Out_ lpNumberOfCharsRead
1669
1670 @contextlib.contextmanager
1671 def allocate_console():
1672 allocated = kernel32.AllocConsole()
1673 try:
1674 yield allocated
1675 finally:
1676 if allocated:
1677 kernel32.FreeConsole()
1678
1679 @contextlib.contextmanager
1680 def console_screen(ncols=None, nrows=None):
1681 info = CONSOLE_SCREEN_BUFFER_INFOEX()
1682 new_info = CONSOLE_SCREEN_BUFFER_INFOEX()
1683 nwritten = (wintypes.DWORD * 1)()
1684 hStdOut = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
1685 kernel32.GetConsoleScreenBufferInfoEx(
1686 hStdOut, ctypes.byref(info))
1687 if ncols is None:
1688 ncols = info.dwSize.X
1689 if nrows is None:
1690 nrows = info.dwSize.Y
1691 elif nrows > 9999:
1692 raise ValueError('nrows must be 9999 or less')
1693 fd_screen = None
1694 hScreen = kernel32.CreateConsoleScreenBuffer(
1695 GENERIC_READ | GENERIC_WRITE,
1696 FILE_SHARE_READ | FILE_SHARE_WRITE,
1697 None, CONSOLE_TEXTMODE_BUFFER, None)
1698 try:
1699 fd_screen = msvcrt.open_osfhandle(
1700 hScreen, os.O_RDWR | os.O_BINARY)
1701 kernel32.GetConsoleScreenBufferInfoEx(
1702 hScreen, ctypes.byref(new_info))
1703 new_info.dwSize = COORD(ncols, nrows)
1704 new_info.srWindow = wintypes.SMALL_RECT(
1705 Left=0, Top=0, Right=(ncols - 1),
1706 Bottom=(info.srWindow.Bottom - info.srWindow.Top))
1707 kernel32.SetConsoleScreenBufferInfoEx(
1708 hScreen, ctypes.byref(new_info))
1709 kernel32.SetConsoleWindowInfo(hScreen, True,
1710 ctypes.byref(new_info.srWindow))
1711 kernel32.FillConsoleOutputCharacterW(
1712 hScreen, u'\0', ncols * nrows, COORD(0,0), nwritten)
1713 kernel32.SetConsoleActiveScreenBuffer(hScreen)
1714 try:
1715 yield fd_screen
1716 finally:
1717 kernel32.SetConsoleScreenBufferInfoEx(
1718 hStdOut, ctypes.byref(info))
1719 kernel32.SetConsoleWindowInfo(hStdOut, True,
1720 ctypes.byref(info.srWindow))
1721 kernel32.SetConsoleActiveScreenBuffer(hStdOut)
1722 finally:
1723 if fd_screen is not None:
1724 os.close(fd_screen)
1725 else:
1726 kernel32.CloseHandle(hScreen)
1727
1728 def read_screen(fd):
1729 hScreen = msvcrt.get_osfhandle(fd)
1730 csbi = CONSOLE_SCREEN_BUFFER_INFOEX()
1731 kernel32.GetConsoleScreenBufferInfoEx(
1732 hScreen, ctypes.byref(csbi))
1733 ncols = csbi.dwSize.X
1734 pos = csbi.dwCursorPosition
1735 length = ncols * pos.Y + pos.X + 1
1736 buf = (ctypes.c_wchar * length)()
1737 n = (wintypes.DWORD * 1)()
1738 kernel32.ReadConsoleOutputCharacterW(
1739 hScreen, buf, length, COORD(0,0), n)
1740 lines = [buf[i:i+ncols].rstrip(u'\0')
1741 for i in range(0, n[0], ncols)]
1742 return u'\n'.join(lines)
1743
1744@unittest.skipUnless(sys.platform == "win32", "requires Windows")
1745class WindowsConsoleTest(DeviceTest):
1746 def test_unicode_output(self):
1747 """Test Unicode command line parameters and Unicode console window output.
1748
1749 Bug: https://issuetracker.google.com/issues/111972753
1750 """
1751 # If we don't have a console window, allocate one. This isn't necessary if we're already
1752 # being run from a console window, which is typical.
1753 with allocate_console() as allocated_console:
1754 # Create a temporary console buffer and switch to it. We could also pass a parameter of
1755 # ncols=len(unicode_string), but it causes the window to flash as it is resized and
1756 # likely unnecessary given the typical console window size.
1757 with console_screen(nrows=1000) as screen:
1758 unicode_string = u'로보카 폴리'
1759 # Run adb and allow it to detect that stdout is a console, not a pipe, by using
1760 # device.shell_popen() which does not use a pipe, unlike device.shell().
1761 process = self.device.shell_popen(['echo', '"' + unicode_string + '"'])
1762 process.wait()
1763 # Read what was written by adb to the temporary console buffer.
1764 console_output = read_screen(screen)
1765 self.assertEqual(unicode_string, console_output)
1766
Fabien Sanglard3d155b62023-11-30 14:52:40 -08001767class DevicesListing(DeviceTest):
1768
1769 serial = subprocess.check_output(['adb', 'get-serialno']).strip().decode("utf-8")
1770 # def get_serial(self):
1771 # return subprocess.check_output(self.device.adb_cmd + ['get-serialno']).strip().decode("utf-8")
1772
1773 def test_devices(self):
1774 proc = subprocess.Popen(['adb', 'devices'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1775 lines = list(map(lambda b: b.decode("utf-8"), proc.stdout.readlines()))
1776 self.assertEqual(len(lines), 3)
1777 line = lines[1]
1778 self.assertTrue(self.serial in line)
1779 self.assertFalse("{" in line)
1780 self.assertFalse("}" in line)
1781 self.assertTrue("device" in line)
1782 self.assertFalse("product" in line)
1783 self.assertFalse("transport" in line)
1784
1785 def test_devices_l(self):
1786 proc = subprocess.Popen(['adb', 'devices', '-l'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1787 lines = list(map(lambda b: b.decode("utf-8"), proc.stdout.readlines()))
1788 self.assertEqual(len(lines), 3)
1789 line = lines[1]
1790 self.assertTrue(self.serial in line)
1791 self.assertFalse("{" in line)
1792 self.assertFalse("}" in line)
1793 self.assertTrue("device" in line)
1794 self.assertTrue("product" in line)
1795 self.assertTrue("transport" in line)
1796
1797 def test_track_devices(self):
1798 proc = subprocess.Popen(['adb', 'track-devices'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1799 reader = io.TextIOWrapper(proc.stdout, encoding='utf8')
1800 output_size = int(reader.read(4), 16)
1801 output = reader.read(output_size)
1802 self.assertFalse("{" in output)
1803 self.assertFalse("}" in output)
1804 self.assertTrue(self.serial in output)
1805 self.assertTrue("device" in output)
1806 self.assertFalse("product" in output)
1807 self.assertFalse("transport" in output)
1808
1809 def test_track_devices_l(self):
1810 proc = subprocess.Popen(['adb', 'track-devices', '-l'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1811 reader = io.TextIOWrapper(proc.stdout, encoding='utf8')
1812 output_size = int(reader.read(4), 16)
1813 output = reader.read(output_size)
1814 self.assertFalse("{" in output)
1815 self.assertFalse("}" in output)
1816 self.assertTrue(self.serial in output)
1817 self.assertTrue("device" in output)
1818 self.assertTrue("product" in output)
1819 self.assertTrue("transport" in output)
1820
1821 def test_track_devices_proto_text(self):
1822 proc = subprocess.Popen(['adb', 'track-devices', '--proto-text'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1823 reader = io.TextIOWrapper(proc.stdout, encoding='utf8')
1824 output_size = int(reader.read(4), 16)
1825 output = reader.read(output_size)
1826 self.assertTrue("{" in output)
1827 self.assertTrue("}" in output)
1828 self.assertTrue(self.serial in output)
1829 self.assertTrue("device" in output)
1830 self.assertTrue("product" in output)
1831 self.assertTrue("connection_type" in output)
1832
1833 def test_track_devices_proto_binary(self):
1834 proc = subprocess.Popen(['adb', 'track-devices', '--proto-binary'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
1835
1836 output_size = int(proc.stdout.read(4).decode("utf-8"), 16)
1837 proto = proc.stdout.read(output_size)
1838
1839 devices = proto_devices.Devices()
1840 devices.ParseFromString(proto)
1841
1842 device = devices.device[0]
1843 self.assertTrue(device.serial == self.serial)
1844 self.assertFalse(device.bus_address == "")
1845 self.assertFalse(device.product == "")
1846 self.assertFalse(device.model == "")
1847 self.assertFalse(device.device == "")
1848 self.assertTrue(device.negotiated_speed == int(device.negotiated_speed))
1849 self.assertTrue(device.max_speed == int(device.max_speed))
Fabien Sanglarde8d239f2023-12-15 11:48:09 -08001850 self.assertTrue(device.transport_id == int(device.transport_id))
Spencer Low35a47db2018-08-11 00:16:16 -07001851
Josh Gao49e3c632015-12-09 11:26:11 -08001852def main():
1853 random.seed(0)
Fabien Sanglard3d155b62023-11-30 14:52:40 -08001854 if len(adb.get_devices()) == 0:
Josh Gao49e3c632015-12-09 11:26:11 -08001855 print('Test suite must be run with attached devices')
Fabien Sanglard3d155b62023-11-30 14:52:40 -08001856 return
1857
1858 # Run only specific test if given on command-line e.g:
1859 # ./test_device.py ForwardReverseTest
1860 # ./test_device.py ForwardReverseTest.test_forward_no_rebind
1861 if len(sys.argv) == 2:
1862 test_name = "." + sys.argv[1]
1863 else:
1864 test_name = ""
1865
1866 suite = unittest.TestLoader().loadTestsFromName("__main__" + test_name)
1867 unittest.TextTestRunner(verbosity=3).run(suite)
Josh Gao49e3c632015-12-09 11:26:11 -08001868
1869
1870if __name__ == '__main__':
Fabien Sanglard3d155b62023-11-30 14:52:40 -08001871 main()