blob: 34f8fd9fa4601bd09e5613e12930f0ff349d0a7e [file] [log] [blame]
Josh Gao49e3c632015-12-09 11:26:11 -08001#!/usr/bin/env python
2# -*- 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 Gao160bf7e2018-03-19 15:35:11 -070034import threading
Josh Gao2eae66e2016-06-22 18:27:22 -070035import time
Josh Gao49e3c632015-12-09 11:26:11 -080036import unittest
37
Josh Gao49e3c632015-12-09 11:26:11 -080038import adb
39
Josh Gao49e3c632015-12-09 11:26:11 -080040def requires_root(func):
41 def wrapper(self, *args):
42 if self.device.get_prop('ro.debuggable') != '1':
43 raise unittest.SkipTest('requires rootable build')
44
45 was_root = self.device.shell(['id', '-un'])[0].strip() == 'root'
46 if not was_root:
47 self.device.root()
48 self.device.wait()
49
50 try:
51 func(self, *args)
52 finally:
53 if not was_root:
54 self.device.unroot()
55 self.device.wait()
56
57 return wrapper
58
59
60def requires_non_root(func):
61 def wrapper(self, *args):
62 was_root = self.device.shell(['id', '-un'])[0].strip() == 'root'
63 if was_root:
64 self.device.unroot()
65 self.device.wait()
66
67 try:
68 func(self, *args)
69 finally:
70 if was_root:
71 self.device.root()
72 self.device.wait()
73
74 return wrapper
75
76
Josh Gao49e3c632015-12-09 11:26:11 -080077class DeviceTest(unittest.TestCase):
78 def setUp(self):
79 self.device = adb.get_device()
80
81
82class ForwardReverseTest(DeviceTest):
83 def _test_no_rebind(self, description, direction_list, direction,
84 direction_no_rebind, direction_remove_all):
85 msg = direction_list()
86 self.assertEqual('', msg.strip(),
87 description + ' list must be empty to run this test.')
88
89 # Use --no-rebind with no existing binding
90 direction_no_rebind('tcp:5566', 'tcp:6655')
91 msg = direction_list()
92 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
93
94 # Use --no-rebind with existing binding
95 with self.assertRaises(subprocess.CalledProcessError):
96 direction_no_rebind('tcp:5566', 'tcp:6677')
97 msg = direction_list()
98 self.assertFalse(re.search(r'tcp:5566.+tcp:6677', msg))
99 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
100
101 # Use the absence of --no-rebind with existing binding
102 direction('tcp:5566', 'tcp:6677')
103 msg = direction_list()
104 self.assertFalse(re.search(r'tcp:5566.+tcp:6655', msg))
105 self.assertTrue(re.search(r'tcp:5566.+tcp:6677', msg))
106
107 direction_remove_all()
108 msg = direction_list()
109 self.assertEqual('', msg.strip())
110
111 def test_forward_no_rebind(self):
112 self._test_no_rebind('forward', self.device.forward_list,
113 self.device.forward, self.device.forward_no_rebind,
114 self.device.forward_remove_all)
115
116 def test_reverse_no_rebind(self):
117 self._test_no_rebind('reverse', self.device.reverse_list,
118 self.device.reverse, self.device.reverse_no_rebind,
119 self.device.reverse_remove_all)
120
121 def test_forward(self):
122 msg = self.device.forward_list()
123 self.assertEqual('', msg.strip(),
124 'Forwarding list must be empty to run this test.')
125 self.device.forward('tcp:5566', 'tcp:6655')
126 msg = self.device.forward_list()
127 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
128 self.device.forward('tcp:7788', 'tcp:8877')
129 msg = self.device.forward_list()
130 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
131 self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
132 self.device.forward_remove('tcp:5566')
133 msg = self.device.forward_list()
134 self.assertFalse(re.search(r'tcp:5566.+tcp:6655', msg))
135 self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
136 self.device.forward_remove_all()
137 msg = self.device.forward_list()
138 self.assertEqual('', msg.strip())
139
David Pursell19d0c232016-04-07 11:25:48 -0700140 def test_forward_tcp_port_0(self):
141 self.assertEqual('', self.device.forward_list().strip(),
142 'Forwarding list must be empty to run this test.')
143
144 try:
145 # If resolving TCP port 0 is supported, `adb forward` will print
146 # the actual port number.
147 port = self.device.forward('tcp:0', 'tcp:8888').strip()
148 if not port:
149 raise unittest.SkipTest('Forwarding tcp:0 is not available.')
150
151 self.assertTrue(re.search(r'tcp:{}.+tcp:8888'.format(port),
152 self.device.forward_list()))
153 finally:
154 self.device.forward_remove_all()
155
Josh Gao49e3c632015-12-09 11:26:11 -0800156 def test_reverse(self):
157 msg = self.device.reverse_list()
158 self.assertEqual('', msg.strip(),
159 'Reverse forwarding list must be empty to run this test.')
160 self.device.reverse('tcp:5566', 'tcp:6655')
161 msg = self.device.reverse_list()
162 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
163 self.device.reverse('tcp:7788', 'tcp:8877')
164 msg = self.device.reverse_list()
165 self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
166 self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
167 self.device.reverse_remove('tcp:5566')
168 msg = self.device.reverse_list()
169 self.assertFalse(re.search(r'tcp:5566.+tcp:6655', msg))
170 self.assertTrue(re.search(r'tcp:7788.+tcp:8877', msg))
171 self.device.reverse_remove_all()
172 msg = self.device.reverse_list()
173 self.assertEqual('', msg.strip())
174
David Pursell19d0c232016-04-07 11:25:48 -0700175 def test_reverse_tcp_port_0(self):
176 self.assertEqual('', self.device.reverse_list().strip(),
177 'Reverse list must be empty to run this test.')
178
179 try:
180 # If resolving TCP port 0 is supported, `adb reverse` will print
181 # the actual port number.
182 port = self.device.reverse('tcp:0', 'tcp:8888').strip()
183 if not port:
184 raise unittest.SkipTest('Reversing tcp:0 is not available.')
185
186 self.assertTrue(re.search(r'tcp:{}.+tcp:8888'.format(port),
187 self.device.reverse_list()))
188 finally:
189 self.device.reverse_remove_all()
190
Josh Gao49e3c632015-12-09 11:26:11 -0800191 def test_forward_reverse_echo(self):
192 """Send data through adb forward and read it back via adb reverse"""
193 forward_port = 12345
194 reverse_port = forward_port + 1
Josh Gao18f74202016-03-03 14:49:02 -0800195 forward_spec = 'tcp:' + str(forward_port)
196 reverse_spec = 'tcp:' + str(reverse_port)
Josh Gao49e3c632015-12-09 11:26:11 -0800197 forward_setup = False
198 reverse_setup = False
199
200 try:
201 # listen on localhost:forward_port, connect to remote:forward_port
202 self.device.forward(forward_spec, forward_spec)
203 forward_setup = True
204 # listen on remote:forward_port, connect to localhost:reverse_port
205 self.device.reverse(forward_spec, reverse_spec)
206 reverse_setup = True
207
208 listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
209 with contextlib.closing(listener):
210 # Use SO_REUSEADDR so that subsequent runs of the test can grab
211 # the port even if it is in TIME_WAIT.
212 listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
213
214 # Listen on localhost:reverse_port before connecting to
215 # localhost:forward_port because that will cause adb to connect
216 # back to localhost:reverse_port.
217 listener.bind(('127.0.0.1', reverse_port))
218 listener.listen(4)
219
220 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
221 with contextlib.closing(client):
222 # Connect to the listener.
223 client.connect(('127.0.0.1', forward_port))
224
225 # Accept the client connection.
226 accepted_connection, addr = listener.accept()
227 with contextlib.closing(accepted_connection) as server:
228 data = 'hello'
229
230 # Send data into the port setup by adb forward.
231 client.sendall(data)
232 # Explicitly close() so that server gets EOF.
233 client.close()
234
235 # Verify that the data came back via adb reverse.
236 self.assertEqual(data, server.makefile().read())
237 finally:
238 if reverse_setup:
239 self.device.reverse_remove(forward_spec)
240 if forward_setup:
241 self.device.forward_remove(forward_spec)
242
243
244class ShellTest(DeviceTest):
245 def _interactive_shell(self, shell_args, input):
246 """Runs an interactive adb shell.
247
248 Args:
249 shell_args: List of string arguments to `adb shell`.
250 input: String input to send to the interactive shell.
251
252 Returns:
253 The remote exit code.
254
255 Raises:
256 unittest.SkipTest: The device doesn't support exit codes.
257 """
David Pursell4b38af42016-04-26 13:25:57 -0700258 if not self.device.has_shell_protocol():
Josh Gao49e3c632015-12-09 11:26:11 -0800259 raise unittest.SkipTest('exit codes are unavailable on this device')
260
261 proc = subprocess.Popen(
262 self.device.adb_cmd + ['shell'] + shell_args,
263 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
264 stderr=subprocess.PIPE)
265 # Closing host-side stdin doesn't trigger a PTY shell to exit so we need
266 # to explicitly add an exit command to close the session from the device
267 # side, plus the necessary newline to complete the interactive command.
268 proc.communicate(input + '; exit\n')
269 return proc.returncode
270
271 def test_cat(self):
272 """Check that we can at least cat a file."""
273 out = self.device.shell(['cat', '/proc/uptime'])[0].strip()
274 elements = out.split()
275 self.assertEqual(len(elements), 2)
276
277 uptime, idle = elements
278 self.assertGreater(float(uptime), 0.0)
279 self.assertGreater(float(idle), 0.0)
280
281 def test_throws_on_failure(self):
282 self.assertRaises(adb.ShellError, self.device.shell, ['false'])
283
284 def test_output_not_stripped(self):
285 out = self.device.shell(['echo', 'foo'])[0]
286 self.assertEqual(out, 'foo' + self.device.linesep)
287
Josh Gao05012022017-06-16 15:34:34 -0700288 def test_shell_command_length(self):
289 # Devices that have shell_v2 should be able to handle long commands.
290 if self.device.has_shell_protocol():
291 rc, out, err = self.device.shell_nocheck(['echo', 'x' * 16384])
292 self.assertEqual(rc, 0)
293 self.assertTrue(out == ('x' * 16384 + '\n'))
294
Josh Gao49e3c632015-12-09 11:26:11 -0800295 def test_shell_nocheck_failure(self):
296 rc, out, _ = self.device.shell_nocheck(['false'])
297 self.assertNotEqual(rc, 0)
298 self.assertEqual(out, '')
299
300 def test_shell_nocheck_output_not_stripped(self):
301 rc, out, _ = self.device.shell_nocheck(['echo', 'foo'])
302 self.assertEqual(rc, 0)
303 self.assertEqual(out, 'foo' + self.device.linesep)
304
305 def test_can_distinguish_tricky_results(self):
306 # If result checking on ADB shell is naively implemented as
307 # `adb shell <cmd>; echo $?`, we would be unable to distinguish the
308 # output from the result for a cmd of `echo -n 1`.
309 rc, out, _ = self.device.shell_nocheck(['echo', '-n', '1'])
310 self.assertEqual(rc, 0)
311 self.assertEqual(out, '1')
312
313 def test_line_endings(self):
314 """Ensure that line ending translation is not happening in the pty.
315
316 Bug: http://b/19735063
317 """
318 output = self.device.shell(['uname'])[0]
319 self.assertEqual(output, 'Linux' + self.device.linesep)
320
321 def test_pty_logic(self):
322 """Tests that a PTY is allocated when it should be.
323
Elliott Hughes02e33782016-10-19 14:47:11 -0700324 PTY allocation behavior should match ssh.
Josh Gao49e3c632015-12-09 11:26:11 -0800325 """
Josh Gao49e3c632015-12-09 11:26:11 -0800326 def check_pty(args):
327 """Checks adb shell PTY allocation.
328
329 Tests |args| for terminal and non-terminal stdin.
330
331 Args:
332 args: -Tt args in a list (e.g. ['-t', '-t']).
333
334 Returns:
335 A tuple (<terminal>, <non-terminal>). True indicates
336 the corresponding shell allocated a remote PTY.
337 """
338 test_cmd = self.device.adb_cmd + ['shell'] + args + ['[ -t 0 ]']
339
340 terminal = subprocess.Popen(
341 test_cmd, stdin=None,
342 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
343 terminal.communicate()
344
345 non_terminal = subprocess.Popen(
346 test_cmd, stdin=subprocess.PIPE,
347 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
348 non_terminal.communicate()
349
350 return (terminal.returncode == 0, non_terminal.returncode == 0)
351
352 # -T: never allocate PTY.
353 self.assertEqual((False, False), check_pty(['-T']))
354
Elliott Hughes02e33782016-10-19 14:47:11 -0700355 # These tests require a new device.
356 if self.device.has_shell_protocol() and os.isatty(sys.stdin.fileno()):
357 # No args: PTY only if stdin is a terminal and shell is interactive,
358 # which is difficult to reliably test from a script.
359 self.assertEqual((False, False), check_pty([]))
Josh Gao49e3c632015-12-09 11:26:11 -0800360
Elliott Hughes02e33782016-10-19 14:47:11 -0700361 # -t: PTY if stdin is a terminal.
362 self.assertEqual((True, False), check_pty(['-t']))
Josh Gao49e3c632015-12-09 11:26:11 -0800363
364 # -t -t: always allocate PTY.
365 self.assertEqual((True, True), check_pty(['-t', '-t']))
366
Elliott Hughes02e33782016-10-19 14:47:11 -0700367 # -tt: always allocate PTY, POSIX style (http://b/32216152).
368 self.assertEqual((True, True), check_pty(['-tt']))
369
370 # -ttt: ssh has weird even/odd behavior with multiple -t flags, but
371 # we follow the man page instead.
372 self.assertEqual((True, True), check_pty(['-ttt']))
373
374 # -ttx: -x and -tt aren't incompatible (though -Tx would be an error).
375 self.assertEqual((True, True), check_pty(['-ttx']))
376
377 # -Ttt: -tt cancels out -T.
378 self.assertEqual((True, True), check_pty(['-Ttt']))
379
380 # -ttT: -T cancels out -tt.
381 self.assertEqual((False, False), check_pty(['-ttT']))
382
Josh Gao49e3c632015-12-09 11:26:11 -0800383 def test_shell_protocol(self):
384 """Tests the shell protocol on the device.
385
386 If the device supports shell protocol, this gives us the ability
387 to separate stdout/stderr and return the exit code directly.
388
389 Bug: http://b/19734861
390 """
David Pursell4b38af42016-04-26 13:25:57 -0700391 if not self.device.has_shell_protocol():
Josh Gao49e3c632015-12-09 11:26:11 -0800392 raise unittest.SkipTest('shell protocol unsupported on this device')
393
394 # Shell protocol should be used by default.
395 result = self.device.shell_nocheck(
396 shlex.split('echo foo; echo bar >&2; exit 17'))
397 self.assertEqual(17, result[0])
398 self.assertEqual('foo' + self.device.linesep, result[1])
399 self.assertEqual('bar' + self.device.linesep, result[2])
400
401 self.assertEqual(17, self._interactive_shell([], 'exit 17'))
402
403 # -x flag should disable shell protocol.
404 result = self.device.shell_nocheck(
405 shlex.split('-x echo foo; echo bar >&2; exit 17'))
406 self.assertEqual(0, result[0])
407 self.assertEqual('foo{0}bar{0}'.format(self.device.linesep), result[1])
408 self.assertEqual('', result[2])
409
410 self.assertEqual(0, self._interactive_shell(['-x'], 'exit 17'))
411
412 def test_non_interactive_sigint(self):
413 """Tests that SIGINT in a non-interactive shell kills the process.
414
415 This requires the shell protocol in order to detect the broken
416 pipe; raw data transfer mode will only see the break once the
417 subprocess tries to read or write.
418
419 Bug: http://b/23825725
420 """
David Pursell4b38af42016-04-26 13:25:57 -0700421 if not self.device.has_shell_protocol():
Josh Gao49e3c632015-12-09 11:26:11 -0800422 raise unittest.SkipTest('shell protocol unsupported on this device')
423
424 # Start a long-running process.
425 sleep_proc = subprocess.Popen(
426 self.device.adb_cmd + shlex.split('shell echo $$; sleep 60'),
427 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
428 stderr=subprocess.STDOUT)
429 remote_pid = sleep_proc.stdout.readline().strip()
430 self.assertIsNone(sleep_proc.returncode, 'subprocess terminated early')
431 proc_query = shlex.split('ps {0} | grep {0}'.format(remote_pid))
432
433 # Verify that the process is running, send signal, verify it stopped.
434 self.device.shell(proc_query)
435 os.kill(sleep_proc.pid, signal.SIGINT)
436 sleep_proc.communicate()
Josh Gao76ffdac2016-10-21 12:40:42 -0700437
438 # It can take some time for the process to receive the signal and die.
439 end_time = time.time() + 3
440 while self.device.shell_nocheck(proc_query)[0] != 1:
441 self.assertFalse(time.time() > end_time,
442 'subprocess failed to terminate in time')
Josh Gao49e3c632015-12-09 11:26:11 -0800443
444 def test_non_interactive_stdin(self):
445 """Tests that non-interactive shells send stdin."""
David Pursell4b38af42016-04-26 13:25:57 -0700446 if not self.device.has_shell_protocol():
Josh Gao49e3c632015-12-09 11:26:11 -0800447 raise unittest.SkipTest('non-interactive stdin unsupported '
448 'on this device')
449
450 # Test both small and large inputs.
451 small_input = 'foo'
452 large_input = '\n'.join(c * 100 for c in (string.ascii_letters +
453 string.digits))
454
455 for input in (small_input, large_input):
456 proc = subprocess.Popen(self.device.adb_cmd + ['shell', 'cat'],
457 stdin=subprocess.PIPE,
458 stdout=subprocess.PIPE,
459 stderr=subprocess.PIPE)
460 stdout, stderr = proc.communicate(input)
461 self.assertEqual(input.splitlines(), stdout.splitlines())
462 self.assertEqual('', stderr)
463
Josh Gao2eae66e2016-06-22 18:27:22 -0700464 def test_sighup(self):
465 """Ensure that SIGHUP gets sent upon non-interactive ctrl-c"""
466 log_path = "/data/local/tmp/adb_signal_test.log"
467
468 # Clear the output file.
469 self.device.shell_nocheck(["echo", ">", log_path])
470
471 script = """
472 trap "echo SIGINT > {path}; exit 0" SIGINT
473 trap "echo SIGHUP > {path}; exit 0" SIGHUP
474 echo Waiting
Josh Gao6a8ce062016-10-21 13:17:32 -0700475 read
Josh Gao2eae66e2016-06-22 18:27:22 -0700476 """.format(path=log_path)
477
478 script = ";".join([x.strip() for x in script.strip().splitlines()])
479
Josh Gao6a8ce062016-10-21 13:17:32 -0700480 process = self.device.shell_popen([script], kill_atexit=False,
481 stdin=subprocess.PIPE,
482 stdout=subprocess.PIPE)
Josh Gao2eae66e2016-06-22 18:27:22 -0700483
484 self.assertEqual("Waiting\n", process.stdout.readline())
485 process.send_signal(signal.SIGINT)
486 process.wait()
487
488 # Waiting for the local adb to finish is insufficient, since it hangs
489 # up immediately.
Josh Gao6a8ce062016-10-21 13:17:32 -0700490 time.sleep(1)
Josh Gao2eae66e2016-06-22 18:27:22 -0700491
492 stdout, _ = self.device.shell(["cat", log_path])
493 self.assertEqual(stdout.strip(), "SIGHUP")
494
Josh Gao160bf7e2018-03-19 15:35:11 -0700495 def test_exit_stress(self):
496 """Hammer `adb shell exit 42` with multiple threads."""
497 thread_count = 48
498 result = dict()
499 def hammer(thread_idx, thread_count, result):
500 success = True
501 for i in range(thread_idx, 240, thread_count):
502 ret = subprocess.call(['adb', 'shell', 'exit {}'.format(i)])
503 if ret != i % 256:
504 success = False
505 break
506 result[thread_idx] = success
507
508 threads = []
509 for i in range(thread_count):
510 thread = threading.Thread(target=hammer, args=(i, thread_count, result))
511 thread.start()
512 threads.append(thread)
513 for thread in threads:
514 thread.join()
515 for i, success in result.iteritems():
516 self.assertTrue(success)
517
Josh Gao49e3c632015-12-09 11:26:11 -0800518
519class ArgumentEscapingTest(DeviceTest):
520 def test_shell_escaping(self):
521 """Make sure that argument escaping is somewhat sane."""
522
523 # http://b/19734868
524 # Note that this actually matches ssh(1)'s behavior --- it's
525 # converted to `sh -c echo hello; echo world` which sh interprets
526 # as `sh -c echo` (with an argument to that shell of "hello"),
527 # and then `echo world` back in the first shell.
528 result = self.device.shell(
529 shlex.split("sh -c 'echo hello; echo world'"))[0]
530 result = result.splitlines()
531 self.assertEqual(['', 'world'], result)
532 # If you really wanted "hello" and "world", here's what you'd do:
533 result = self.device.shell(
534 shlex.split(r'echo hello\;echo world'))[0].splitlines()
535 self.assertEqual(['hello', 'world'], result)
536
537 # http://b/15479704
538 result = self.device.shell(shlex.split("'true && echo t'"))[0].strip()
539 self.assertEqual('t', result)
540 result = self.device.shell(
541 shlex.split("sh -c 'true && echo t'"))[0].strip()
542 self.assertEqual('t', result)
543
544 # http://b/20564385
545 result = self.device.shell(shlex.split('FOO=a BAR=b echo t'))[0].strip()
546 self.assertEqual('t', result)
547 result = self.device.shell(
548 shlex.split(r'echo -n 123\;uname'))[0].strip()
549 self.assertEqual('123Linux', result)
550
551 def test_install_argument_escaping(self):
552 """Make sure that install argument escaping works."""
553 # http://b/20323053, http://b/3090932.
554 for file_suffix in ('-text;ls;1.apk', "-Live Hold'em.apk"):
555 tf = tempfile.NamedTemporaryFile('wb', suffix=file_suffix,
556 delete=False)
557 tf.close()
558
559 # Installing bogus .apks fails if the device supports exit codes.
560 try:
561 output = self.device.install(tf.name)
562 except subprocess.CalledProcessError as e:
563 output = e.output
564
565 self.assertIn(file_suffix, output)
566 os.remove(tf.name)
567
568
569class RootUnrootTest(DeviceTest):
570 def _test_root(self):
571 message = self.device.root()
572 if 'adbd cannot run as root in production builds' in message:
573 return
574 self.device.wait()
575 self.assertEqual('root', self.device.shell(['id', '-un'])[0].strip())
576
577 def _test_unroot(self):
578 self.device.unroot()
579 self.device.wait()
580 self.assertEqual('shell', self.device.shell(['id', '-un'])[0].strip())
581
582 def test_root_unroot(self):
583 """Make sure that adb root and adb unroot work, using id(1)."""
584 if self.device.get_prop('ro.debuggable') != '1':
585 raise unittest.SkipTest('requires rootable build')
586
587 original_user = self.device.shell(['id', '-un'])[0].strip()
588 try:
589 if original_user == 'root':
590 self._test_unroot()
591 self._test_root()
592 elif original_user == 'shell':
593 self._test_root()
594 self._test_unroot()
595 finally:
596 if original_user == 'root':
597 self.device.root()
598 else:
599 self.device.unroot()
600 self.device.wait()
601
602
603class TcpIpTest(DeviceTest):
604 def test_tcpip_failure_raises(self):
605 """adb tcpip requires a port.
606
607 Bug: http://b/22636927
608 """
609 self.assertRaises(
610 subprocess.CalledProcessError, self.device.tcpip, '')
611 self.assertRaises(
612 subprocess.CalledProcessError, self.device.tcpip, 'foo')
613
614
615class SystemPropertiesTest(DeviceTest):
616 def test_get_prop(self):
617 self.assertEqual(self.device.get_prop('init.svc.adbd'), 'running')
618
619 @requires_root
620 def test_set_prop(self):
621 prop_name = 'foo.bar'
622 self.device.shell(['setprop', prop_name, '""'])
623
624 self.device.set_prop(prop_name, 'qux')
625 self.assertEqual(
626 self.device.shell(['getprop', prop_name])[0].strip(), 'qux')
627
628
629def compute_md5(string):
630 hsh = hashlib.md5()
631 hsh.update(string)
632 return hsh.hexdigest()
633
634
635def get_md5_prog(device):
636 """Older platforms (pre-L) had the name md5 rather than md5sum."""
637 try:
638 device.shell(['md5sum', '/proc/uptime'])
639 return 'md5sum'
640 except adb.ShellError:
641 return 'md5'
642
643
644class HostFile(object):
645 def __init__(self, handle, checksum):
646 self.handle = handle
647 self.checksum = checksum
648 self.full_path = handle.name
649 self.base_name = os.path.basename(self.full_path)
650
651
652class DeviceFile(object):
653 def __init__(self, checksum, full_path):
654 self.checksum = checksum
655 self.full_path = full_path
656 self.base_name = posixpath.basename(self.full_path)
657
658
659def make_random_host_files(in_dir, num_files):
660 min_size = 1 * (1 << 10)
661 max_size = 16 * (1 << 10)
662
663 files = []
664 for _ in xrange(num_files):
665 file_handle = tempfile.NamedTemporaryFile(dir=in_dir, delete=False)
666
667 size = random.randrange(min_size, max_size, 1024)
668 rand_str = os.urandom(size)
669 file_handle.write(rand_str)
670 file_handle.flush()
671 file_handle.close()
672
673 md5 = compute_md5(rand_str)
674 files.append(HostFile(file_handle, md5))
675 return files
676
677
678def make_random_device_files(device, in_dir, num_files, prefix='device_tmpfile'):
679 min_size = 1 * (1 << 10)
680 max_size = 16 * (1 << 10)
681
682 files = []
683 for file_num in xrange(num_files):
684 size = random.randrange(min_size, max_size, 1024)
685
686 base_name = prefix + str(file_num)
687 full_path = posixpath.join(in_dir, base_name)
688
689 device.shell(['dd', 'if=/dev/urandom', 'of={}'.format(full_path),
690 'bs={}'.format(size), 'count=1'])
691 dev_md5, _ = device.shell([get_md5_prog(device), full_path])[0].split()
692
693 files.append(DeviceFile(dev_md5, full_path))
694 return files
695
696
697class FileOperationsTest(DeviceTest):
698 SCRATCH_DIR = '/data/local/tmp'
699 DEVICE_TEMP_FILE = SCRATCH_DIR + '/adb_test_file'
700 DEVICE_TEMP_DIR = SCRATCH_DIR + '/adb_test_dir'
701
702 def _verify_remote(self, checksum, remote_path):
703 dev_md5, _ = self.device.shell([get_md5_prog(self.device),
704 remote_path])[0].split()
705 self.assertEqual(checksum, dev_md5)
706
707 def _verify_local(self, checksum, local_path):
708 with open(local_path, 'rb') as host_file:
709 host_md5 = compute_md5(host_file.read())
710 self.assertEqual(host_md5, checksum)
711
712 def test_push(self):
713 """Push a randomly generated file to specified device."""
714 kbytes = 512
715 tmp = tempfile.NamedTemporaryFile(mode='wb', delete=False)
716 rand_str = os.urandom(1024 * kbytes)
717 tmp.write(rand_str)
718 tmp.close()
719
720 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
721 self.device.push(local=tmp.name, remote=self.DEVICE_TEMP_FILE)
722
723 self._verify_remote(compute_md5(rand_str), self.DEVICE_TEMP_FILE)
724 self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
725
726 os.remove(tmp.name)
727
728 def test_push_dir(self):
729 """Push a randomly generated directory of files to the device."""
730 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
731 self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
732
733 try:
734 host_dir = tempfile.mkdtemp()
735
736 # Make sure the temp directory isn't setuid, or else adb will complain.
737 os.chmod(host_dir, 0o700)
738
739 # Create 32 random files.
740 temp_files = make_random_host_files(in_dir=host_dir, num_files=32)
741 self.device.push(host_dir, self.DEVICE_TEMP_DIR)
742
743 for temp_file in temp_files:
744 remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
745 os.path.basename(host_dir),
746 temp_file.base_name)
747 self._verify_remote(temp_file.checksum, remote_path)
748 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
749 finally:
750 if host_dir is not None:
751 shutil.rmtree(host_dir)
752
Josh Gao281aab72018-10-22 13:00:05 -0700753 def disabled_test_push_empty(self):
Josh Gao290ccaf2018-09-21 13:40:16 -0700754 """Push an empty directory to the device."""
Josh Gao49e3c632015-12-09 11:26:11 -0800755 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
756 self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
757
758 try:
759 host_dir = tempfile.mkdtemp()
760
761 # Make sure the temp directory isn't setuid, or else adb will complain.
762 os.chmod(host_dir, 0o700)
763
764 # Create an empty directory.
Josh Gaoc117edb2018-08-08 14:25:41 -0700765 empty_dir_path = os.path.join(host_dir, 'empty')
766 os.mkdir(empty_dir_path);
Josh Gao49e3c632015-12-09 11:26:11 -0800767
Josh Gaoc117edb2018-08-08 14:25:41 -0700768 self.device.push(empty_dir_path, self.DEVICE_TEMP_DIR)
Josh Gao49e3c632015-12-09 11:26:11 -0800769
Josh Gao290ccaf2018-09-21 13:40:16 -0700770 remote_path = os.path.join(self.DEVICE_TEMP_DIR, "empty")
771 test_empty_cmd = ["[", "-d", remote_path, "]"]
Josh Gao49e3c632015-12-09 11:26:11 -0800772 rc, _, _ = self.device.shell_nocheck(test_empty_cmd)
Josh Gao290ccaf2018-09-21 13:40:16 -0700773
Josh Gao49e3c632015-12-09 11:26:11 -0800774 self.assertEqual(rc, 0)
775 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
776 finally:
777 if host_dir is not None:
778 shutil.rmtree(host_dir)
779
Josh Gao1deea102016-09-14 16:13:50 -0700780 @unittest.skipIf(sys.platform == "win32", "symlinks require elevated privileges on windows")
781 def test_push_symlink(self):
782 """Push a symlink.
783
784 Bug: http://b/31491920
785 """
786 try:
787 host_dir = tempfile.mkdtemp()
788
789 # Make sure the temp directory isn't setuid, or else adb will
790 # complain.
791 os.chmod(host_dir, 0o700)
792
793 with open(os.path.join(host_dir, 'foo'), 'w') as f:
794 f.write('foo')
795
796 symlink_path = os.path.join(host_dir, 'symlink')
797 os.symlink('foo', symlink_path)
798
799 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
800 self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
801 self.device.push(symlink_path, self.DEVICE_TEMP_DIR)
802 rc, out, _ = self.device.shell_nocheck(
803 ['cat', posixpath.join(self.DEVICE_TEMP_DIR, 'symlink')])
804 self.assertEqual(0, rc)
805 self.assertEqual(out.strip(), 'foo')
806 finally:
807 if host_dir is not None:
808 shutil.rmtree(host_dir)
809
Josh Gao49e3c632015-12-09 11:26:11 -0800810 def test_multiple_push(self):
811 """Push multiple files to the device in one adb push command.
812
813 Bug: http://b/25324823
814 """
815
816 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
817 self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
818
819 try:
820 host_dir = tempfile.mkdtemp()
821
822 # Create some random files and a subdirectory containing more files.
823 temp_files = make_random_host_files(in_dir=host_dir, num_files=4)
824
Josh Gao18f74202016-03-03 14:49:02 -0800825 subdir = os.path.join(host_dir, 'subdir')
Josh Gao49e3c632015-12-09 11:26:11 -0800826 os.mkdir(subdir)
827 subdir_temp_files = make_random_host_files(in_dir=subdir,
828 num_files=4)
829
830 paths = map(lambda temp_file: temp_file.full_path, temp_files)
831 paths.append(subdir)
832 self.device._simple_call(['push'] + paths + [self.DEVICE_TEMP_DIR])
833
834 for temp_file in temp_files:
835 remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
836 temp_file.base_name)
837 self._verify_remote(temp_file.checksum, remote_path)
838
839 for subdir_temp_file in subdir_temp_files:
840 remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
841 # BROKEN: http://b/25394682
Josh Gao18f74202016-03-03 14:49:02 -0800842 # 'subdir';
Josh Gao49e3c632015-12-09 11:26:11 -0800843 temp_file.base_name)
844 self._verify_remote(temp_file.checksum, remote_path)
845
846
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
Josh Gaoa53abe72016-02-19 15:55:55 -0800852 @requires_non_root
853 def test_push_error_reporting(self):
854 """Make sure that errors that occur while pushing a file get reported
855
856 Bug: http://b/26816782
857 """
858 with tempfile.NamedTemporaryFile() as tmp_file:
859 tmp_file.write('\0' * 1024 * 1024)
860 tmp_file.flush()
861 try:
862 self.device.push(local=tmp_file.name, remote='/system/')
Josh Gao18f74202016-03-03 14:49:02 -0800863 self.fail('push should not have succeeded')
Josh Gaoa53abe72016-02-19 15:55:55 -0800864 except subprocess.CalledProcessError as e:
865 output = e.output
866
Josh Gaocb6497a2016-11-18 15:31:11 -0800867 self.assertTrue('Permission denied' in output or
868 'Read-only file system' in output)
Josh Gao49e3c632015-12-09 11:26:11 -0800869
Josh Gaof9671172018-06-28 18:43:19 -0700870 @requires_non_root
871 def test_push_directory_creation(self):
872 """Regression test for directory creation.
873
874 Bug: http://b/110953234
875 """
876 with tempfile.NamedTemporaryFile() as tmp_file:
877 tmp_file.write('\0' * 1024 * 1024)
878 tmp_file.flush()
879 remote_path = self.DEVICE_TEMP_DIR + '/test_push_directory_creation'
880 self.device.shell(['rm', '-rf', remote_path])
881
882 remote_path += '/filename'
883 self.device.push(local=tmp_file.name, remote=remote_path)
884
Josh Gao49e3c632015-12-09 11:26:11 -0800885 def _test_pull(self, remote_file, checksum):
886 tmp_write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
887 tmp_write.close()
888 self.device.pull(remote=remote_file, local=tmp_write.name)
889 with open(tmp_write.name, 'rb') as tmp_read:
890 host_contents = tmp_read.read()
891 host_md5 = compute_md5(host_contents)
892 self.assertEqual(checksum, host_md5)
893 os.remove(tmp_write.name)
894
895 @requires_non_root
896 def test_pull_error_reporting(self):
897 self.device.shell(['touch', self.DEVICE_TEMP_FILE])
898 self.device.shell(['chmod', 'a-rwx', self.DEVICE_TEMP_FILE])
899
900 try:
901 output = self.device.pull(remote=self.DEVICE_TEMP_FILE, local='x')
902 except subprocess.CalledProcessError as e:
903 output = e.output
904
905 self.assertIn('Permission denied', output)
906
907 self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
908
909 def test_pull(self):
910 """Pull a randomly generated file from specified device."""
911 kbytes = 512
912 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
913 cmd = ['dd', 'if=/dev/urandom',
914 'of={}'.format(self.DEVICE_TEMP_FILE), 'bs=1024',
915 'count={}'.format(kbytes)]
916 self.device.shell(cmd)
917 dev_md5, _ = self.device.shell(
918 [get_md5_prog(self.device), self.DEVICE_TEMP_FILE])[0].split()
919 self._test_pull(self.DEVICE_TEMP_FILE, dev_md5)
920 self.device.shell_nocheck(['rm', self.DEVICE_TEMP_FILE])
921
922 def test_pull_dir(self):
923 """Pull a randomly generated directory of files from the device."""
924 try:
925 host_dir = tempfile.mkdtemp()
926
927 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
928 self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
929
930 # Populate device directory with random files.
931 temp_files = make_random_device_files(
932 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
933
934 self.device.pull(remote=self.DEVICE_TEMP_DIR, local=host_dir)
935
936 for temp_file in temp_files:
Josh Gao38752792015-12-09 14:20:23 -0800937 host_path = os.path.join(
938 host_dir, posixpath.basename(self.DEVICE_TEMP_DIR),
939 temp_file.base_name)
940 self._verify_local(temp_file.checksum, host_path)
Josh Gao49e3c632015-12-09 11:26:11 -0800941
942 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
943 finally:
944 if host_dir is not None:
945 shutil.rmtree(host_dir)
946
Josh Gao49726bc2016-02-26 13:26:55 -0800947 def test_pull_dir_symlink(self):
948 """Pull a directory into a symlink to a directory.
949
950 Bug: http://b/27362811
951 """
Josh Gao18f74202016-03-03 14:49:02 -0800952 if os.name != 'posix':
Josh Gao49726bc2016-02-26 13:26:55 -0800953 raise unittest.SkipTest('requires POSIX')
954
955 try:
956 host_dir = tempfile.mkdtemp()
Josh Gao18f74202016-03-03 14:49:02 -0800957 real_dir = os.path.join(host_dir, 'dir')
958 symlink = os.path.join(host_dir, 'symlink')
Josh Gao49726bc2016-02-26 13:26:55 -0800959 os.mkdir(real_dir)
960 os.symlink(real_dir, symlink)
961
962 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
963 self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
964
965 # Populate device directory with random files.
966 temp_files = make_random_device_files(
967 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
968
969 self.device.pull(remote=self.DEVICE_TEMP_DIR, local=symlink)
970
971 for temp_file in temp_files:
972 host_path = os.path.join(
973 real_dir, posixpath.basename(self.DEVICE_TEMP_DIR),
974 temp_file.base_name)
975 self._verify_local(temp_file.checksum, host_path)
976
977 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
978 finally:
979 if host_dir is not None:
980 shutil.rmtree(host_dir)
981
982 def test_pull_dir_symlink_collision(self):
983 """Pull a directory into a colliding symlink to directory."""
Josh Gao18f74202016-03-03 14:49:02 -0800984 if os.name != 'posix':
Josh Gao49726bc2016-02-26 13:26:55 -0800985 raise unittest.SkipTest('requires POSIX')
986
987 try:
988 host_dir = tempfile.mkdtemp()
Josh Gao18f74202016-03-03 14:49:02 -0800989 real_dir = os.path.join(host_dir, 'real')
Josh Gao49726bc2016-02-26 13:26:55 -0800990 tmp_dirname = os.path.basename(self.DEVICE_TEMP_DIR)
991 symlink = os.path.join(host_dir, tmp_dirname)
992 os.mkdir(real_dir)
993 os.symlink(real_dir, symlink)
994
995 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
996 self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
997
998 # Populate device directory with random files.
999 temp_files = make_random_device_files(
1000 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
1001
1002 self.device.pull(remote=self.DEVICE_TEMP_DIR, local=host_dir)
1003
1004 for temp_file in temp_files:
1005 host_path = os.path.join(real_dir, temp_file.base_name)
1006 self._verify_local(temp_file.checksum, host_path)
1007
1008 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1009 finally:
1010 if host_dir is not None:
1011 shutil.rmtree(host_dir)
1012
Josh Gaoa842b382016-03-02 16:00:02 -08001013 def test_pull_dir_nonexistent(self):
1014 """Pull a directory of files from the device to a nonexistent path."""
1015 try:
1016 host_dir = tempfile.mkdtemp()
1017 dest_dir = os.path.join(host_dir, 'dest')
1018
1019 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1020 self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
1021
1022 # Populate device directory with random files.
1023 temp_files = make_random_device_files(
1024 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
1025
1026 self.device.pull(remote=self.DEVICE_TEMP_DIR, local=dest_dir)
1027
1028 for temp_file in temp_files:
1029 host_path = os.path.join(dest_dir, temp_file.base_name)
1030 self._verify_local(temp_file.checksum, host_path)
1031
1032 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1033 finally:
1034 if host_dir is not None:
1035 shutil.rmtree(host_dir)
1036
Josh Gaofcd0ffb2018-08-08 14:26:03 -07001037 # selinux prevents adbd from accessing symlinks on /data/local/tmp.
1038 def disabled_test_pull_symlink_dir(self):
Josh Gaod9a2fd62015-12-09 14:03:30 -08001039 """Pull a symlink to a directory of symlinks to files."""
1040 try:
1041 host_dir = tempfile.mkdtemp()
1042
1043 remote_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'contents')
1044 remote_links = posixpath.join(self.DEVICE_TEMP_DIR, 'links')
1045 remote_symlink = posixpath.join(self.DEVICE_TEMP_DIR, 'symlink')
1046
1047 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1048 self.device.shell(['mkdir', '-p', remote_dir, remote_links])
1049 self.device.shell(['ln', '-s', remote_links, remote_symlink])
1050
1051 # Populate device directory with random files.
1052 temp_files = make_random_device_files(
1053 self.device, in_dir=remote_dir, num_files=32)
1054
1055 for temp_file in temp_files:
1056 self.device.shell(
1057 ['ln', '-s', '../contents/{}'.format(temp_file.base_name),
1058 posixpath.join(remote_links, temp_file.base_name)])
1059
1060 self.device.pull(remote=remote_symlink, local=host_dir)
1061
1062 for temp_file in temp_files:
1063 host_path = os.path.join(
1064 host_dir, 'symlink', temp_file.base_name)
1065 self._verify_local(temp_file.checksum, host_path)
1066
1067 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1068 finally:
1069 if host_dir is not None:
1070 shutil.rmtree(host_dir)
1071
Josh Gao49e3c632015-12-09 11:26:11 -08001072 def test_pull_empty(self):
1073 """Pull a directory containing an empty directory from the device."""
1074 try:
1075 host_dir = tempfile.mkdtemp()
1076
1077 remote_empty_path = posixpath.join(self.DEVICE_TEMP_DIR, 'empty')
1078 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1079 self.device.shell(['mkdir', '-p', remote_empty_path])
1080
1081 self.device.pull(remote=remote_empty_path, local=host_dir)
1082 self.assertTrue(os.path.isdir(os.path.join(host_dir, 'empty')))
1083 finally:
1084 if host_dir is not None:
1085 shutil.rmtree(host_dir)
1086
1087 def test_multiple_pull(self):
1088 """Pull a randomly generated directory of files from the device."""
1089
1090 try:
1091 host_dir = tempfile.mkdtemp()
1092
Josh Gao18f74202016-03-03 14:49:02 -08001093 subdir = posixpath.join(self.DEVICE_TEMP_DIR, 'subdir')
Josh Gao49e3c632015-12-09 11:26:11 -08001094 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1095 self.device.shell(['mkdir', '-p', subdir])
1096
1097 # Create some random files and a subdirectory containing more files.
1098 temp_files = make_random_device_files(
1099 self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=4)
1100
1101 subdir_temp_files = make_random_device_files(
1102 self.device, in_dir=subdir, num_files=4, prefix='subdir_')
1103
1104 paths = map(lambda temp_file: temp_file.full_path, temp_files)
1105 paths.append(subdir)
1106 self.device._simple_call(['pull'] + paths + [host_dir])
1107
1108 for temp_file in temp_files:
1109 local_path = os.path.join(host_dir, temp_file.base_name)
1110 self._verify_local(temp_file.checksum, local_path)
1111
1112 for subdir_temp_file in subdir_temp_files:
1113 local_path = os.path.join(host_dir,
Josh Gao18f74202016-03-03 14:49:02 -08001114 'subdir',
Josh Gao49e3c632015-12-09 11:26:11 -08001115 subdir_temp_file.base_name)
1116 self._verify_local(subdir_temp_file.checksum, local_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
Dan Albert8449e062017-05-18 22:56:48 -07001123 def verify_sync(self, device, temp_files, device_dir):
1124 """Verifies that a list of temp files was synced to the device."""
1125 # Confirm that every file on the device mirrors that on the host.
1126 for temp_file in temp_files:
1127 device_full_path = posixpath.join(
1128 device_dir, temp_file.base_name)
1129 dev_md5, _ = device.shell(
1130 [get_md5_prog(self.device), device_full_path])[0].split()
1131 self.assertEqual(temp_file.checksum, dev_md5)
1132
Josh Gao49e3c632015-12-09 11:26:11 -08001133 def test_sync(self):
Dan Albert8449e062017-05-18 22:56:48 -07001134 """Sync a host directory to the data partition."""
Josh Gao49e3c632015-12-09 11:26:11 -08001135
1136 try:
1137 base_dir = tempfile.mkdtemp()
1138
1139 # Create mirror device directory hierarchy within base_dir.
1140 full_dir_path = base_dir + self.DEVICE_TEMP_DIR
1141 os.makedirs(full_dir_path)
1142
1143 # Create 32 random files within the host mirror.
Dan Albert8449e062017-05-18 22:56:48 -07001144 temp_files = make_random_host_files(
1145 in_dir=full_dir_path, num_files=32)
Josh Gao49e3c632015-12-09 11:26:11 -08001146
Dan Albert8449e062017-05-18 22:56:48 -07001147 # Clean up any stale files on the device.
Dan Albertb29a57b2017-05-18 13:52:45 -07001148 device = adb.get_device() # pylint: disable=no-member
Josh Gao49e3c632015-12-09 11:26:11 -08001149 device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1150
Dan Albertb29a57b2017-05-18 13:52:45 -07001151 old_product_out = os.environ.get('ANDROID_PRODUCT_OUT')
1152 os.environ['ANDROID_PRODUCT_OUT'] = base_dir
Josh Gao49e3c632015-12-09 11:26:11 -08001153 device.sync('data')
Dan Albertb29a57b2017-05-18 13:52:45 -07001154 if old_product_out is None:
1155 del os.environ['ANDROID_PRODUCT_OUT']
1156 else:
1157 os.environ['ANDROID_PRODUCT_OUT'] = old_product_out
Josh Gao49e3c632015-12-09 11:26:11 -08001158
Dan Albert8449e062017-05-18 22:56:48 -07001159 self.verify_sync(device, temp_files, self.DEVICE_TEMP_DIR)
Josh Gao49e3c632015-12-09 11:26:11 -08001160
Dan Albert8449e062017-05-18 22:56:48 -07001161 #self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
Josh Gao49e3c632015-12-09 11:26:11 -08001162 finally:
1163 if base_dir is not None:
1164 shutil.rmtree(base_dir)
1165
Dan Albert8449e062017-05-18 22:56:48 -07001166 def test_push_sync(self):
1167 """Sync a host directory to a specific path."""
1168
1169 try:
1170 temp_dir = tempfile.mkdtemp()
1171 temp_files = make_random_host_files(in_dir=temp_dir, num_files=32)
1172
1173 device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'sync_src_dst')
1174
1175 # Clean up any stale files on the device.
1176 device = adb.get_device() # pylint: disable=no-member
1177 device.shell(['rm', '-rf', device_dir])
1178
1179 device.push(temp_dir, device_dir, sync=True)
1180
1181 self.verify_sync(device, temp_files, device_dir)
1182
1183 self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
1184 finally:
1185 if temp_dir is not None:
1186 shutil.rmtree(temp_dir)
1187
Josh Gao49e3c632015-12-09 11:26:11 -08001188 def test_unicode_paths(self):
1189 """Ensure that we can support non-ASCII paths, even on Windows."""
1190 name = u'로보카 폴리'
1191
1192 self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
1193 remote_path = u'/data/local/tmp/adb-test-{}'.format(name)
1194
1195 ## push.
1196 tf = tempfile.NamedTemporaryFile('wb', suffix=name, delete=False)
1197 tf.close()
1198 self.device.push(tf.name, remote_path)
1199 os.remove(tf.name)
1200 self.assertFalse(os.path.exists(tf.name))
1201
1202 # Verify that the device ended up with the expected UTF-8 path
1203 output = self.device.shell(
1204 ['ls', '/data/local/tmp/adb-test-*'])[0].strip()
Josh Gao4f8504e2018-03-19 16:09:05 -07001205 self.assertEqual(remote_path, output)
Josh Gao49e3c632015-12-09 11:26:11 -08001206
1207 # pull.
1208 self.device.pull(remote_path, tf.name)
1209 self.assertTrue(os.path.exists(tf.name))
1210 os.remove(tf.name)
1211 self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
1212
1213
Yabin Cui3cf1b362017-03-10 16:01:01 -08001214class DeviceOfflineTest(DeviceTest):
1215 def _get_device_state(self, serialno):
1216 output = subprocess.check_output(self.device.adb_cmd + ['devices'])
1217 for line in output.split('\n'):
1218 m = re.match('(\S+)\s+(\S+)', line)
1219 if m and m.group(1) == serialno:
1220 return m.group(2)
1221 return None
1222
Josh Gao6e0ed552017-09-13 14:51:23 -07001223 def disabled_test_killed_when_pushing_a_large_file(self):
Yabin Cui3cf1b362017-03-10 16:01:01 -08001224 """
1225 While running adb push with a large file, kill adb server.
1226 Occasionally the device becomes offline. Because the device is still
1227 reading data without realizing that the adb server has been restarted.
1228 Test if we can bring the device online automatically now.
1229 http://b/32952319
1230 """
1231 serialno = subprocess.check_output(self.device.adb_cmd + ['get-serialno']).strip()
1232 # 1. Push a large file
1233 file_path = 'tmp_large_file'
1234 try:
1235 fh = open(file_path, 'w')
1236 fh.write('\0' * (100 * 1024 * 1024))
1237 fh.close()
1238 subproc = subprocess.Popen(self.device.adb_cmd + ['push', file_path, '/data/local/tmp'])
1239 time.sleep(0.1)
1240 # 2. Kill the adb server
1241 subprocess.check_call(self.device.adb_cmd + ['kill-server'])
1242 subproc.terminate()
1243 finally:
1244 try:
1245 os.unlink(file_path)
1246 except:
1247 pass
1248 # 3. See if the device still exist.
1249 # Sleep to wait for the adb server exit.
1250 time.sleep(0.5)
1251 # 4. The device should be online
1252 self.assertEqual(self._get_device_state(serialno), 'device')
1253
Josh Gao6e0ed552017-09-13 14:51:23 -07001254 def disabled_test_killed_when_pulling_a_large_file(self):
Yabin Cui3cf1b362017-03-10 16:01:01 -08001255 """
1256 While running adb pull with a large file, kill adb server.
1257 Occasionally the device can't be connected. Because the device is trying to
1258 send a message larger than what is expected by the adb server.
1259 Test if we can bring the device online automatically now.
1260 """
1261 serialno = subprocess.check_output(self.device.adb_cmd + ['get-serialno']).strip()
1262 file_path = 'tmp_large_file'
1263 try:
1264 # 1. Create a large file on device.
1265 self.device.shell(['dd', 'if=/dev/zero', 'of=/data/local/tmp/tmp_large_file',
1266 'bs=1000000', 'count=100'])
1267 # 2. Pull the large file on host.
1268 subproc = subprocess.Popen(self.device.adb_cmd +
1269 ['pull','/data/local/tmp/tmp_large_file', file_path])
1270 time.sleep(0.1)
1271 # 3. Kill the adb server
1272 subprocess.check_call(self.device.adb_cmd + ['kill-server'])
1273 subproc.terminate()
1274 finally:
1275 try:
1276 os.unlink(file_path)
1277 except:
1278 pass
1279 # 4. See if the device still exist.
1280 # Sleep to wait for the adb server exit.
1281 time.sleep(0.5)
1282 self.assertEqual(self._get_device_state(serialno), 'device')
1283
1284
Josh Gao3734cf02017-05-02 15:01:09 -07001285 def test_packet_size_regression(self):
1286 """Test for http://b/37783561
1287
1288 Receiving packets of a length divisible by 512 but not 1024 resulted in
1289 the adb client waiting indefinitely for more input.
1290 """
1291 # The values that trigger things are 507 (512 - 5 bytes from shell protocol) + 1024*n
1292 # Probe some surrounding values as well, for the hell of it.
Josh Gaoc7f2d192018-04-10 14:35:06 -07001293 for base in [512] + range(1024, 1024 * 16, 1024):
1294 for offset in [-6, -5, -4]:
1295 length = base + offset
1296 cmd = ['dd', 'if=/dev/zero', 'bs={}'.format(length), 'count=1', '2>/dev/null;'
1297 'echo', 'foo']
1298 rc, stdout, _ = self.device.shell_nocheck(cmd)
Josh Gao3734cf02017-05-02 15:01:09 -07001299
Josh Gaoc7f2d192018-04-10 14:35:06 -07001300 self.assertEqual(0, rc)
Josh Gao3734cf02017-05-02 15:01:09 -07001301
Josh Gaoc7f2d192018-04-10 14:35:06 -07001302 # Output should be '\0' * length, followed by "foo\n"
1303 self.assertEqual(length, len(stdout) - 4)
1304 self.assertEqual(stdout, "\0" * length + "foo\n")
Josh Gao3734cf02017-05-02 15:01:09 -07001305
Josh Gao9fae8762018-08-22 15:13:18 -07001306 def test_zero_packet(self):
1307 """Test for http://b/113070258
1308
1309 Make sure that we don't blow up when sending USB transfers that line up
1310 exactly with the USB packet size.
1311 """
1312
1313 local_port = int(self.device.forward("tcp:0", "tcp:12345"))
1314 try:
1315 for size in [512, 1024]:
1316 def listener():
1317 cmd = ["echo foo | nc -l -p 12345; echo done"]
1318 rc, stdout, stderr = self.device.shell_nocheck(cmd)
1319
1320 thread = threading.Thread(target=listener)
1321 thread.start()
1322
1323 # Wait a bit to let the shell command start.
1324 time.sleep(0.25)
1325
1326 sock = socket.create_connection(("localhost", local_port))
1327 with contextlib.closing(sock):
1328 bytesWritten = sock.send("a" * size)
1329 self.assertEqual(size, bytesWritten)
1330 readBytes = sock.recv(4096)
1331 self.assertEqual("foo\n", readBytes)
1332
1333 thread.join()
1334 finally:
1335 self.device.forward_remove("tcp:{}".format(local_port))
1336
Josh Gao3734cf02017-05-02 15:01:09 -07001337
Spencer Low35a47db2018-08-11 00:16:16 -07001338if sys.platform == "win32":
1339 # From https://stackoverflow.com/a/38749458
1340 import os
1341 import contextlib
1342 import msvcrt
1343 import ctypes
1344 from ctypes import wintypes
1345
1346 kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
1347
1348 GENERIC_READ = 0x80000000
1349 GENERIC_WRITE = 0x40000000
1350 FILE_SHARE_READ = 1
1351 FILE_SHARE_WRITE = 2
1352 CONSOLE_TEXTMODE_BUFFER = 1
1353 INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
1354 STD_OUTPUT_HANDLE = wintypes.DWORD(-11)
1355 STD_ERROR_HANDLE = wintypes.DWORD(-12)
1356
1357 def _check_zero(result, func, args):
1358 if not result:
1359 raise ctypes.WinError(ctypes.get_last_error())
1360 return args
1361
1362 def _check_invalid(result, func, args):
1363 if result == INVALID_HANDLE_VALUE:
1364 raise ctypes.WinError(ctypes.get_last_error())
1365 return args
1366
1367 if not hasattr(wintypes, 'LPDWORD'): # Python 2
1368 wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
1369 wintypes.PSMALL_RECT = ctypes.POINTER(wintypes.SMALL_RECT)
1370
1371 class COORD(ctypes.Structure):
1372 _fields_ = (('X', wintypes.SHORT),
1373 ('Y', wintypes.SHORT))
1374
1375 class CONSOLE_SCREEN_BUFFER_INFOEX(ctypes.Structure):
1376 _fields_ = (('cbSize', wintypes.ULONG),
1377 ('dwSize', COORD),
1378 ('dwCursorPosition', COORD),
1379 ('wAttributes', wintypes.WORD),
1380 ('srWindow', wintypes.SMALL_RECT),
1381 ('dwMaximumWindowSize', COORD),
1382 ('wPopupAttributes', wintypes.WORD),
1383 ('bFullscreenSupported', wintypes.BOOL),
1384 ('ColorTable', wintypes.DWORD * 16))
1385 def __init__(self, *args, **kwds):
1386 super(CONSOLE_SCREEN_BUFFER_INFOEX, self).__init__(
1387 *args, **kwds)
1388 self.cbSize = ctypes.sizeof(self)
1389
1390 PCONSOLE_SCREEN_BUFFER_INFOEX = ctypes.POINTER(
1391 CONSOLE_SCREEN_BUFFER_INFOEX)
1392 LPSECURITY_ATTRIBUTES = wintypes.LPVOID
1393
1394 kernel32.GetStdHandle.errcheck = _check_invalid
1395 kernel32.GetStdHandle.restype = wintypes.HANDLE
1396 kernel32.GetStdHandle.argtypes = (
1397 wintypes.DWORD,) # _In_ nStdHandle
1398
1399 kernel32.CreateConsoleScreenBuffer.errcheck = _check_invalid
1400 kernel32.CreateConsoleScreenBuffer.restype = wintypes.HANDLE
1401 kernel32.CreateConsoleScreenBuffer.argtypes = (
1402 wintypes.DWORD, # _In_ dwDesiredAccess
1403 wintypes.DWORD, # _In_ dwShareMode
1404 LPSECURITY_ATTRIBUTES, # _In_opt_ lpSecurityAttributes
1405 wintypes.DWORD, # _In_ dwFlags
1406 wintypes.LPVOID) # _Reserved_ lpScreenBufferData
1407
1408 kernel32.GetConsoleScreenBufferInfoEx.errcheck = _check_zero
1409 kernel32.GetConsoleScreenBufferInfoEx.argtypes = (
1410 wintypes.HANDLE, # _In_ hConsoleOutput
1411 PCONSOLE_SCREEN_BUFFER_INFOEX) # _Out_ lpConsoleScreenBufferInfo
1412
1413 kernel32.SetConsoleScreenBufferInfoEx.errcheck = _check_zero
1414 kernel32.SetConsoleScreenBufferInfoEx.argtypes = (
1415 wintypes.HANDLE, # _In_ hConsoleOutput
1416 PCONSOLE_SCREEN_BUFFER_INFOEX) # _In_ lpConsoleScreenBufferInfo
1417
1418 kernel32.SetConsoleWindowInfo.errcheck = _check_zero
1419 kernel32.SetConsoleWindowInfo.argtypes = (
1420 wintypes.HANDLE, # _In_ hConsoleOutput
1421 wintypes.BOOL, # _In_ bAbsolute
1422 wintypes.PSMALL_RECT) # _In_ lpConsoleWindow
1423
1424 kernel32.FillConsoleOutputCharacterW.errcheck = _check_zero
1425 kernel32.FillConsoleOutputCharacterW.argtypes = (
1426 wintypes.HANDLE, # _In_ hConsoleOutput
1427 wintypes.WCHAR, # _In_ cCharacter
1428 wintypes.DWORD, # _In_ nLength
1429 COORD, # _In_ dwWriteCoord
1430 wintypes.LPDWORD) # _Out_ lpNumberOfCharsWritten
1431
1432 kernel32.ReadConsoleOutputCharacterW.errcheck = _check_zero
1433 kernel32.ReadConsoleOutputCharacterW.argtypes = (
1434 wintypes.HANDLE, # _In_ hConsoleOutput
1435 wintypes.LPWSTR, # _Out_ lpCharacter
1436 wintypes.DWORD, # _In_ nLength
1437 COORD, # _In_ dwReadCoord
1438 wintypes.LPDWORD) # _Out_ lpNumberOfCharsRead
1439
1440 @contextlib.contextmanager
1441 def allocate_console():
1442 allocated = kernel32.AllocConsole()
1443 try:
1444 yield allocated
1445 finally:
1446 if allocated:
1447 kernel32.FreeConsole()
1448
1449 @contextlib.contextmanager
1450 def console_screen(ncols=None, nrows=None):
1451 info = CONSOLE_SCREEN_BUFFER_INFOEX()
1452 new_info = CONSOLE_SCREEN_BUFFER_INFOEX()
1453 nwritten = (wintypes.DWORD * 1)()
1454 hStdOut = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
1455 kernel32.GetConsoleScreenBufferInfoEx(
1456 hStdOut, ctypes.byref(info))
1457 if ncols is None:
1458 ncols = info.dwSize.X
1459 if nrows is None:
1460 nrows = info.dwSize.Y
1461 elif nrows > 9999:
1462 raise ValueError('nrows must be 9999 or less')
1463 fd_screen = None
1464 hScreen = kernel32.CreateConsoleScreenBuffer(
1465 GENERIC_READ | GENERIC_WRITE,
1466 FILE_SHARE_READ | FILE_SHARE_WRITE,
1467 None, CONSOLE_TEXTMODE_BUFFER, None)
1468 try:
1469 fd_screen = msvcrt.open_osfhandle(
1470 hScreen, os.O_RDWR | os.O_BINARY)
1471 kernel32.GetConsoleScreenBufferInfoEx(
1472 hScreen, ctypes.byref(new_info))
1473 new_info.dwSize = COORD(ncols, nrows)
1474 new_info.srWindow = wintypes.SMALL_RECT(
1475 Left=0, Top=0, Right=(ncols - 1),
1476 Bottom=(info.srWindow.Bottom - info.srWindow.Top))
1477 kernel32.SetConsoleScreenBufferInfoEx(
1478 hScreen, ctypes.byref(new_info))
1479 kernel32.SetConsoleWindowInfo(hScreen, True,
1480 ctypes.byref(new_info.srWindow))
1481 kernel32.FillConsoleOutputCharacterW(
1482 hScreen, u'\0', ncols * nrows, COORD(0,0), nwritten)
1483 kernel32.SetConsoleActiveScreenBuffer(hScreen)
1484 try:
1485 yield fd_screen
1486 finally:
1487 kernel32.SetConsoleScreenBufferInfoEx(
1488 hStdOut, ctypes.byref(info))
1489 kernel32.SetConsoleWindowInfo(hStdOut, True,
1490 ctypes.byref(info.srWindow))
1491 kernel32.SetConsoleActiveScreenBuffer(hStdOut)
1492 finally:
1493 if fd_screen is not None:
1494 os.close(fd_screen)
1495 else:
1496 kernel32.CloseHandle(hScreen)
1497
1498 def read_screen(fd):
1499 hScreen = msvcrt.get_osfhandle(fd)
1500 csbi = CONSOLE_SCREEN_BUFFER_INFOEX()
1501 kernel32.GetConsoleScreenBufferInfoEx(
1502 hScreen, ctypes.byref(csbi))
1503 ncols = csbi.dwSize.X
1504 pos = csbi.dwCursorPosition
1505 length = ncols * pos.Y + pos.X + 1
1506 buf = (ctypes.c_wchar * length)()
1507 n = (wintypes.DWORD * 1)()
1508 kernel32.ReadConsoleOutputCharacterW(
1509 hScreen, buf, length, COORD(0,0), n)
1510 lines = [buf[i:i+ncols].rstrip(u'\0')
1511 for i in range(0, n[0], ncols)]
1512 return u'\n'.join(lines)
1513
1514@unittest.skipUnless(sys.platform == "win32", "requires Windows")
1515class WindowsConsoleTest(DeviceTest):
1516 def test_unicode_output(self):
1517 """Test Unicode command line parameters and Unicode console window output.
1518
1519 Bug: https://issuetracker.google.com/issues/111972753
1520 """
1521 # If we don't have a console window, allocate one. This isn't necessary if we're already
1522 # being run from a console window, which is typical.
1523 with allocate_console() as allocated_console:
1524 # Create a temporary console buffer and switch to it. We could also pass a parameter of
1525 # ncols=len(unicode_string), but it causes the window to flash as it is resized and
1526 # likely unnecessary given the typical console window size.
1527 with console_screen(nrows=1000) as screen:
1528 unicode_string = u'로보카 폴리'
1529 # Run adb and allow it to detect that stdout is a console, not a pipe, by using
1530 # device.shell_popen() which does not use a pipe, unlike device.shell().
1531 process = self.device.shell_popen(['echo', '"' + unicode_string + '"'])
1532 process.wait()
1533 # Read what was written by adb to the temporary console buffer.
1534 console_output = read_screen(screen)
1535 self.assertEqual(unicode_string, console_output)
1536
1537
Josh Gao49e3c632015-12-09 11:26:11 -08001538def main():
1539 random.seed(0)
1540 if len(adb.get_devices()) > 0:
1541 suite = unittest.TestLoader().loadTestsFromName(__name__)
1542 unittest.TextTestRunner(verbosity=3).run(suite)
1543 else:
1544 print('Test suite must be run with attached devices')
1545
1546
1547if __name__ == '__main__':
1548 main()