|
30 | 30 | import contextlib |
31 | 31 | import os |
32 | 32 | import re |
| 33 | +import shutil |
| 34 | +import subprocess as sp |
33 | 35 | import sys |
34 | 36 | import tempfile |
35 | 37 | import unittest |
36 | 38 |
|
37 | 39 | from .adb import ADBConnect |
38 | 40 | from .utils import AndroidUtils |
| 41 | +from .filesystem import AndroidFilesystem |
39 | 42 |
|
40 | 43 | SLOW_TESTS = False # Set to True to enable slot tests, False to skip them |
41 | 44 |
|
42 | 45 |
|
| 46 | +def get_script_relative_path(file_name: str) -> str: |
| 47 | + ''' |
| 48 | + Get the host path of a script relative file. |
| 49 | +
|
| 50 | + Args: |
| 51 | + file_name: The path of the file relative to this script. |
| 52 | +
|
| 53 | + Returns: |
| 54 | + The path of the file on disk. |
| 55 | + ''' |
| 56 | + dir_name = os.path.dirname(__file__) |
| 57 | + return os.path.join(dir_name, file_name) |
| 58 | + |
| 59 | + |
43 | 60 | @contextlib.contextmanager |
44 | 61 | def NamedTempFile(): # pylint: disable=invalid-name |
45 | 62 | ''' |
@@ -196,7 +213,7 @@ def test_util_device_list(self): |
196 | 213 | self.assertGreaterEqual(len(devices[1]), 0) |
197 | 214 |
|
198 | 215 |
|
199 | | -class AndroidTestDefaultDevice(unittest.TestCase): |
| 216 | +class AndroidTestDeviceUtil(unittest.TestCase): |
200 | 217 | ''' |
201 | 218 | This set of tests validates execution of device-level commands that |
202 | 219 | require adb to have a valid implicit default device connected. |
@@ -274,6 +291,167 @@ def test_util_device_model(self): |
274 | 291 | self.assertTrue(version[0]) |
275 | 292 | self.assertTrue(version[1]) |
276 | 293 |
|
| 294 | + def test_util_package_debuggable(self): |
| 295 | + ''' |
| 296 | + Test helper to get package debug status |
| 297 | + ''' |
| 298 | + conn = ADBConnect() |
| 299 | + |
| 300 | + # Fetch some packages that we can use |
| 301 | + all_packages = AndroidUtils.get_packages(conn, False, False) |
| 302 | + self.assertGreater(len(all_packages), 0) |
| 303 | + |
| 304 | + dbg_packages = AndroidUtils.get_packages(conn, True, False) |
| 305 | + self.assertGreater(len(dbg_packages), 0) |
| 306 | + |
| 307 | + ndbg_packages = list(set(all_packages) ^ set(dbg_packages)) |
| 308 | + self.assertGreater(len(ndbg_packages), 0) |
| 309 | + |
| 310 | + # Test the package |
| 311 | + is_debug = AndroidUtils.is_package_debuggable(conn, ndbg_packages[0]) |
| 312 | + self.assertFalse(is_debug) |
| 313 | + |
| 314 | + is_debug = AndroidUtils.is_package_debuggable(conn, dbg_packages[0]) |
| 315 | + self.assertTrue(is_debug) |
| 316 | + |
| 317 | + def test_util_package_bitness(self): |
| 318 | + ''' |
| 319 | + Test helper to get package ABI bitness. |
| 320 | + ''' |
| 321 | + conn = ADBConnect() |
| 322 | + |
| 323 | + # Fetch some packages that we can use |
| 324 | + packages = AndroidUtils.get_packages(conn, True, False) |
| 325 | + self.assertGreater(len(packages), 0) |
| 326 | + |
| 327 | + # Test the package |
| 328 | + is_32bit = AndroidUtils.is_package_32bit(conn, packages[0]) |
| 329 | + self.assertTrue(isinstance(is_32bit, bool)) |
| 330 | + |
| 331 | + def test_util_package_data_dir(self): |
| 332 | + ''' |
| 333 | + Test helper to get package data directory on the device filesystem. |
| 334 | + ''' |
| 335 | + conn = ADBConnect() |
| 336 | + |
| 337 | + # Fetch some packages that we can use |
| 338 | + packages = AndroidUtils.get_packages(conn, True, False) |
| 339 | + self.assertGreater(len(packages), 0) |
| 340 | + |
| 341 | + # Test the package |
| 342 | + data_dir = AndroidUtils.get_package_data_dir(conn, packages[0]) |
| 343 | + self.assertTrue(data_dir) |
| 344 | + |
| 345 | + |
| 346 | +class AndroidTestDeviceFilesystem(unittest.TestCase): |
| 347 | + ''' |
| 348 | + This set of tests validates execution of device-level filesystem operations |
| 349 | + that require adb to have a valid implicit default device connected. |
| 350 | + ''' |
| 351 | + |
| 352 | + HOST_DEST_DIR = 'x_test_tmp' |
| 353 | + |
| 354 | + def tearDown(self): |
| 355 | + ''' |
| 356 | + Post-test cleanup. |
| 357 | + ''' |
| 358 | + shutil.rmtree(self.HOST_DEST_DIR, True) |
| 359 | + |
| 360 | + def test_util_copy_to_device_tmp(self): |
| 361 | + ''' |
| 362 | + Test filesystem copy to device temp directory. |
| 363 | + ''' |
| 364 | + conn = ADBConnect() |
| 365 | + |
| 366 | + test_file = 'test_data.txt' |
| 367 | + test_path = get_script_relative_path(test_file) |
| 368 | + device_file = f'/data/local/tmp/{test_file}' |
| 369 | + |
| 370 | + # Push the file |
| 371 | + success = AndroidFilesystem.push_file_to_tmp(conn, test_path, False) |
| 372 | + self.assertTrue(success) |
| 373 | + |
| 374 | + # Validate it pushed OK |
| 375 | + data = conn.adb('shell', 'cat', device_file) |
| 376 | + self.assertEqual(data.strip(), 'test payload') |
| 377 | + |
| 378 | + # Cleanup |
| 379 | + success = AndroidFilesystem.delete_file_in_tmp(conn, test_file) |
| 380 | + self.assertTrue(success) |
| 381 | + |
| 382 | + def test_util_copy_to_device_tmp_exec(self): |
| 383 | + ''' |
| 384 | + Test filesystem copy executable payload to device temp directory. |
| 385 | + ''' |
| 386 | + conn = ADBConnect() |
| 387 | + |
| 388 | + test_file = 'test_data.sh' |
| 389 | + test_path = get_script_relative_path(test_file) |
| 390 | + device_file = f'/data/local/tmp/{test_file}' |
| 391 | + |
| 392 | + # Push the file with executable permissions |
| 393 | + success = AndroidFilesystem.push_file_to_tmp(conn, test_path, True) |
| 394 | + self.assertTrue(success) |
| 395 | + |
| 396 | + # Validate it pushed OK |
| 397 | + data = conn.adb('shell', device_file) |
| 398 | + self.assertEqual(data.strip(), 'test payload exec') |
| 399 | + |
| 400 | + # Cleanup |
| 401 | + success = AndroidFilesystem.delete_file_in_tmp(conn, test_file) |
| 402 | + self.assertTrue(success) |
| 403 | + |
| 404 | + def test_util_copy_from_device_keep(self): |
| 405 | + ''' |
| 406 | + Test filesystem copy executable payload from device temp directory. |
| 407 | + ''' |
| 408 | + conn = ADBConnect() |
| 409 | + |
| 410 | + test_file = 'test_data.txt' |
| 411 | + test_path = get_script_relative_path(test_file) |
| 412 | + |
| 413 | + # Push the file |
| 414 | + success = AndroidFilesystem.push_file_to_tmp(conn, test_path, False) |
| 415 | + self.assertTrue(success) |
| 416 | + |
| 417 | + # Copy the file without deletion |
| 418 | + success = AndroidFilesystem.pull_file_from_tmp( |
| 419 | + conn, test_file, self.HOST_DEST_DIR, False) |
| 420 | + self.assertTrue(success) |
| 421 | + |
| 422 | + # Cleanup |
| 423 | + success = AndroidFilesystem.delete_file_in_tmp(conn, test_file) |
| 424 | + self.assertTrue(success) |
| 425 | + |
| 426 | + def test_util_copy_from_device_delete(self): |
| 427 | + ''' |
| 428 | + Test filesystem copy executable payload from device temp directory. |
| 429 | + ''' |
| 430 | + conn = ADBConnect() |
| 431 | + |
| 432 | + test_file = 'test_data.txt' |
| 433 | + test_path = get_script_relative_path(test_file) |
| 434 | + |
| 435 | + device_path = f'/data/local/tmp/{test_file}' |
| 436 | + host_path = f'{self.HOST_DEST_DIR}/{test_file}' |
| 437 | + |
| 438 | + # Push the file |
| 439 | + success = AndroidFilesystem.push_file_to_tmp(conn, test_path, False) |
| 440 | + self.assertTrue(success) |
| 441 | + |
| 442 | + # Copy the file with deletion |
| 443 | + success = AndroidFilesystem.pull_file_from_tmp( |
| 444 | + conn, test_file, self.HOST_DEST_DIR, True) |
| 445 | + self.assertTrue(success) |
| 446 | + |
| 447 | + with open(host_path, 'r', encoding='utf-8') as handle: |
| 448 | + data = handle.read() |
| 449 | + self.assertEqual(data, 'test payload') |
| 450 | + |
| 451 | + # Check the file is deleted - this should fail |
| 452 | + with self.assertRaises(sp.CalledProcessError): |
| 453 | + conn.adb('shell', 'ls', device_path) |
| 454 | + |
277 | 455 |
|
278 | 456 | def main(): |
279 | 457 | ''' |
|
0 commit comments