mirror of
				https://github.com/ARM-software/devlib.git
				synced 2025-10-31 05:53:25 +00:00 
			
		
		
		
	target: tests: Address review comments on PR#667
PR#667: https://github.com/ARM-software/devlib/pull/667 - Implement a test module initializer with a tear down method in test/test_target.py - Make various cleanups in test/test_target.py - Improve structure of test/test_config.yml (previously target_configs.yaml) - Make docstrings Sphinx compatible - Make ``TargetRunner`` and its subclasses private - Cleanup tests/test_target.py - Replace print()'s with appropriate logging calls - Implement ``NOPTargetRunner`` class for simplifying tests - Improve Python v3.7 compatibility - Relax host machine type checking - Escape user input strings and more.. Signed-off-by: Metin Kaya <metin.kaya@arm.com>
This commit is contained in:
		| @@ -22,8 +22,6 @@ from devlib.target import ( | |||||||
|         ChromeOsTarget, |         ChromeOsTarget, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| from devlib.target_runner import QEMUTargetRunner |  | ||||||
|  |  | ||||||
| from devlib.host import ( | from devlib.host import ( | ||||||
|         PACKAGE_BIN_DIRECTORY, |         PACKAGE_BIN_DIRECTORY, | ||||||
|         LocalConnection, |         LocalConnection, | ||||||
|   | |||||||
| @@ -19,8 +19,6 @@ Target runner and related classes are implemented here. | |||||||
| 
 | 
 | ||||||
| import logging | import logging | ||||||
| import os | import os | ||||||
| import signal |  | ||||||
| import subprocess |  | ||||||
| import time | import time | ||||||
| from platform import machine | from platform import machine | ||||||
| 
 | 
 | ||||||
| @@ -37,20 +35,41 @@ class TargetRunner: | |||||||
|     It mainly aims to provide framework support for QEMU like target runners |     It mainly aims to provide framework support for QEMU like target runners | ||||||
|     (e.g., :class:`QEMUTargetRunner`). |     (e.g., :class:`QEMUTargetRunner`). | ||||||
| 
 | 
 | ||||||
|  |     :param target: Specifies type of target per :class:`Target` based classes. | ||||||
|  |     :type target: Target | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def __init__(self, | ||||||
|  |                  target): | ||||||
|  |         self.target = target | ||||||
|  | 
 | ||||||
|  |         self.logger = logging.getLogger(self.__class__.__name__) | ||||||
|  | 
 | ||||||
|  |     def __enter__(self): | ||||||
|  |         return self | ||||||
|  | 
 | ||||||
|  |     def __exit__(self, *_): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SubprocessTargetRunner(TargetRunner): | ||||||
|  |     """ | ||||||
|  |     Class for providing subprocess support to the target runners. | ||||||
|  | 
 | ||||||
|     :param runner_cmd: The command to start runner process (e.g., |     :param runner_cmd: The command to start runner process (e.g., | ||||||
|         ``qemu-system-aarch64 -kernel Image -append "console=ttyAMA0" ...``). |         ``qemu-system-aarch64 -kernel Image -append "console=ttyAMA0" ...``). | ||||||
|     :type runner_cmd: str |     :type runner_cmd: list(str) | ||||||
| 
 | 
 | ||||||
|     :param target: Specifies type of target per :class:`Target` based classes. |     :param target: Specifies type of target per :class:`Target` based classes. | ||||||
|     :type target: Target |     :type target: Target | ||||||
| 
 | 
 | ||||||
|     :param connect: Specifies if :class:`TargetRunner` should try to connect |     :param connect: Specifies if :class:`TargetRunner` should try to connect | ||||||
|         target after launching it, defaults to True. |         target after launching it, defaults to True. | ||||||
|     :type connect: bool, optional |     :type connect: bool or None | ||||||
| 
 | 
 | ||||||
|     :param boot_timeout: Timeout for target's being ready for SSH access in |     :param boot_timeout: Timeout for target's being ready for SSH access in | ||||||
|         seconds, defaults to 60. |         seconds, defaults to 60. | ||||||
|     :type boot_timeout: int, optional |     :type boot_timeout: int or None | ||||||
| 
 | 
 | ||||||
|     :raises HostError: if it cannot execute runner command successfully. |     :raises HostError: if it cannot execute runner command successfully. | ||||||
| 
 | 
 | ||||||
| @@ -62,15 +81,14 @@ class TargetRunner: | |||||||
|                  target, |                  target, | ||||||
|                  connect=True, |                  connect=True, | ||||||
|                  boot_timeout=60): |                  boot_timeout=60): | ||||||
|         self.boot_timeout = boot_timeout |         super().__init__(target=target) | ||||||
|         self.target = target |  | ||||||
| 
 | 
 | ||||||
|         self.logger = logging.getLogger(self.__class__.__name__) |         self.boot_timeout = boot_timeout | ||||||
| 
 | 
 | ||||||
|         self.logger.info('runner_cmd: %s', runner_cmd) |         self.logger.info('runner_cmd: %s', runner_cmd) | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|             self.runner_process = get_subprocess(list(runner_cmd.split())) |             self.runner_process = get_subprocess(runner_cmd) | ||||||
|         except Exception as ex: |         except Exception as ex: | ||||||
|             raise HostError(f'Error while running "{runner_cmd}": {ex}') from ex |             raise HostError(f'Error while running "{runner_cmd}": {ex}') from ex | ||||||
| 
 | 
 | ||||||
| @@ -78,20 +96,13 @@ class TargetRunner: | |||||||
|             self.wait_boot_complete() |             self.wait_boot_complete() | ||||||
| 
 | 
 | ||||||
|     def __enter__(self): |     def __enter__(self): | ||||||
|         """ |  | ||||||
|         Complementary method for contextmanager. |  | ||||||
| 
 |  | ||||||
|         :return: Self object. |  | ||||||
|         :rtype: TargetRunner |  | ||||||
|         """ |  | ||||||
| 
 |  | ||||||
|         return self |         return self | ||||||
| 
 | 
 | ||||||
|     def __exit__(self, *_): |     def __exit__(self, *_): | ||||||
|         """ |         """ | ||||||
|         Exit routine for contextmanager. |         Exit routine for contextmanager. | ||||||
| 
 | 
 | ||||||
|         Ensure :attr:`TargetRunner.runner_process` is terminated on exit. |         Ensure ``SubprocessTargetRunner.runner_process`` is terminated on exit. | ||||||
|         """ |         """ | ||||||
| 
 | 
 | ||||||
|         self.terminate() |         self.terminate() | ||||||
| @@ -99,7 +110,7 @@ class TargetRunner: | |||||||
|     def wait_boot_complete(self): |     def wait_boot_complete(self): | ||||||
|         """ |         """ | ||||||
|         Wait for target OS to finish boot up and become accessible over SSH in at most |         Wait for target OS to finish boot up and become accessible over SSH in at most | ||||||
|         :attr:`TargetRunner.boot_timeout` seconds. |         ``SubprocessTargetRunner.boot_timeout`` seconds. | ||||||
| 
 | 
 | ||||||
|         :raises TargetStableError: In case of timeout. |         :raises TargetStableError: In case of timeout. | ||||||
|         """ |         """ | ||||||
| @@ -109,10 +120,10 @@ class TargetRunner: | |||||||
|         while self.boot_timeout >= elapsed: |         while self.boot_timeout >= elapsed: | ||||||
|             try: |             try: | ||||||
|                 self.target.connect(timeout=self.boot_timeout - elapsed) |                 self.target.connect(timeout=self.boot_timeout - elapsed) | ||||||
|                 self.logger.info('Target is ready.') |                 self.logger.debug('Target is ready.') | ||||||
|                 return |                 return | ||||||
|             # pylint: disable=broad-except |             # pylint: disable=broad-except | ||||||
|             except BaseException as ex: |             except Exception as ex: | ||||||
|                 self.logger.info('Cannot connect target: %s', ex) |                 self.logger.info('Cannot connect target: %s', ex) | ||||||
| 
 | 
 | ||||||
|             time.sleep(1) |             time.sleep(1) | ||||||
| @@ -123,33 +134,41 @@ class TargetRunner: | |||||||
| 
 | 
 | ||||||
|     def terminate(self): |     def terminate(self): | ||||||
|         """ |         """ | ||||||
|         Terminate :attr:`TargetRunner.runner_process`. |         Terminate ``SubprocessTargetRunner.runner_process``. | ||||||
|         """ |         """ | ||||||
| 
 | 
 | ||||||
|         if self.runner_process is None: |         self.logger.debug('Killing target runner...') | ||||||
|             return |         self.runner_process.kill() | ||||||
| 
 |         self.runner_process.__exit__(None, None, None) | ||||||
|         try: |  | ||||||
|             self.runner_process.stdin.close() |  | ||||||
|             self.runner_process.stdout.close() |  | ||||||
|             self.runner_process.stderr.close() |  | ||||||
| 
 |  | ||||||
|             if self.runner_process.poll() is None: |  | ||||||
|                 self.logger.debug('Terminating target runner...') |  | ||||||
|                 os.killpg(self.runner_process.pid, signal.SIGTERM) |  | ||||||
|                 # Wait 3 seconds before killing the runner. |  | ||||||
|                 self.runner_process.wait(timeout=3) |  | ||||||
|         except subprocess.TimeoutExpired: |  | ||||||
|             self.logger.info('Killing target runner...') |  | ||||||
|             os.killpg(self.runner_process.pid, signal.SIGKILL) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class QEMUTargetRunner(TargetRunner): | class NOPTargetRunner(TargetRunner): | ||||||
|     """ |     """ | ||||||
|     Class for interacting with QEMU runners. |     Class for implementing a target runner which does nothing except providing .target attribute. | ||||||
| 
 | 
 | ||||||
|     :class:`QEMUTargetRunner` is a subclass of :class:`TargetRunner` which performs necessary |     :param target: Specifies type of target per :class:`Target` based classes. | ||||||
|     groundwork for launching a guest OS on QEMU. |     :type target: Target | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     def __init__(self, target): | ||||||
|  |         super().__init__(target=target) | ||||||
|  | 
 | ||||||
|  |     def __enter__(self): | ||||||
|  |         return self | ||||||
|  | 
 | ||||||
|  |     def __exit__(self, *_): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     def terminate(self): | ||||||
|  |         """ | ||||||
|  |         Nothing to terminate for NOP target runners. | ||||||
|  |         Defined to be compliant with other runners (e.g., ``SubprocessTargetRunner``). | ||||||
|  |         """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class QEMUTargetRunner(SubprocessTargetRunner): | ||||||
|  |     """ | ||||||
|  |     Class for preparing necessary groundwork for launching a guest OS on QEMU. | ||||||
| 
 | 
 | ||||||
|     :param qemu_settings: A dictionary which has QEMU related parameters. The full list |     :param qemu_settings: A dictionary which has QEMU related parameters. The full list | ||||||
|         of QEMU parameters is below: |         of QEMU parameters is below: | ||||||
| @@ -181,12 +200,11 @@ class QEMUTargetRunner(TargetRunner): | |||||||
|     :type qemu_settings: Dict |     :type qemu_settings: Dict | ||||||
| 
 | 
 | ||||||
|     :param connection_settings: the dictionary to store connection settings |     :param connection_settings: the dictionary to store connection settings | ||||||
|         of :attr:`Target.connection_settings`, defaults to None. |         of ``Target.connection_settings``, defaults to None. | ||||||
|     :type connection_settings: Dict, optional |     :type connection_settings: Dict or None | ||||||
| 
 | 
 | ||||||
|     :param make_target: Lambda function for creating :class:`Target` based |     :param make_target: Lambda function for creating :class:`Target` based object. | ||||||
|         object, defaults to :func:`lambda **kwargs: LinuxTarget(**kwargs)`. |     :type make_target: func or None | ||||||
|     :type make_target: func, optional |  | ||||||
| 
 | 
 | ||||||
|     :Variable positional arguments: Forwarded to :class:`TargetRunner`. |     :Variable positional arguments: Forwarded to :class:`TargetRunner`. | ||||||
| 
 | 
 | ||||||
| @@ -196,9 +214,9 @@ class QEMUTargetRunner(TargetRunner): | |||||||
|     def __init__(self, |     def __init__(self, | ||||||
|                  qemu_settings, |                  qemu_settings, | ||||||
|                  connection_settings=None, |                  connection_settings=None, | ||||||
|                  # pylint: disable=unnecessary-lambda |                  make_target=LinuxTarget, | ||||||
|                  make_target=lambda **kwargs: LinuxTarget(**kwargs), |  | ||||||
|                  **args): |                  **args): | ||||||
|  | 
 | ||||||
|         self.connection_settings = { |         self.connection_settings = { | ||||||
|             'host': '127.0.0.1', |             'host': '127.0.0.1', | ||||||
|             'port': 8022, |             'port': 8022, | ||||||
| @@ -206,62 +224,61 @@ class QEMUTargetRunner(TargetRunner): | |||||||
|             'password': 'root', |             'password': 'root', | ||||||
|             'strict_host_check': False, |             'strict_host_check': False, | ||||||
|         } |         } | ||||||
| 
 |         self.connection_settings = {**self.connection_settings, **(connection_settings or {})} | ||||||
|         if connection_settings is not None: |  | ||||||
|             self.connection_settings = self.connection_settings | connection_settings |  | ||||||
| 
 | 
 | ||||||
|         qemu_args = { |         qemu_args = { | ||||||
|             'kernel_image': '', |  | ||||||
|             'arch': 'aarch64', |             'arch': 'aarch64', | ||||||
|             'cpu_type': 'cortex-a72', |             'cpu_type': 'cortex-a72', | ||||||
|             'initrd_image': '', |  | ||||||
|             'mem_size': 512, |             'mem_size': 512, | ||||||
|             'num_cores': 2, |             'num_cores': 2, | ||||||
|             'num_threads': 2, |             'num_threads': 2, | ||||||
|             'cmdline': 'console=ttyAMA0', |             'cmdline': 'console=ttyAMA0', | ||||||
|             'enable_kvm': True, |             'enable_kvm': True, | ||||||
|         } |         } | ||||||
| 
 |         qemu_args = {**qemu_args, **qemu_settings} | ||||||
|         qemu_args = qemu_args | qemu_settings |  | ||||||
| 
 | 
 | ||||||
|         qemu_executable = f'qemu-system-{qemu_args["arch"]}' |         qemu_executable = f'qemu-system-{qemu_args["arch"]}' | ||||||
|         qemu_path = which(qemu_executable) |         qemu_path = which(qemu_executable) | ||||||
|         if qemu_path is None: |         if qemu_path is None: | ||||||
|             raise FileNotFoundError(f'Cannot find {qemu_executable} executable!') |             raise FileNotFoundError(f'Cannot find {qemu_executable} executable!') | ||||||
| 
 | 
 | ||||||
|         if not os.path.exists(qemu_args["kernel_image"]): |         if qemu_args.get("kernel_image"): | ||||||
|             raise FileNotFoundError(f'{qemu_args["kernel_image"]} does not exist!') |             if not os.path.exists(qemu_args["kernel_image"]): | ||||||
|  |                 raise FileNotFoundError(f'{qemu_args["kernel_image"]} does not exist!') | ||||||
|  |         else: | ||||||
|  |             raise KeyError('qemu_settings must have kernel_image!') | ||||||
| 
 | 
 | ||||||
|         # pylint: disable=consider-using-f-string |         qemu_cmd = [qemu_path, | ||||||
|         qemu_cmd = '''\ |                     '-kernel', qemu_args["kernel_image"], | ||||||
| {} -kernel {} -append "{}" -m {} -smp cores={},threads={} -netdev user,id=net0,hostfwd=tcp::{}-:22 \ |                     '-append', f"'{qemu_args['cmdline']}'", | ||||||
| -device virtio-net-pci,netdev=net0 --nographic\ |                     '-m', str(qemu_args["mem_size"]), | ||||||
| '''.format( |                     '-smp', f'cores={qemu_args["num_cores"]},threads={qemu_args["num_threads"]}', | ||||||
|             qemu_path, |                     '-netdev', f'user,id=net0,hostfwd=tcp::{self.connection_settings["port"]}-:22', | ||||||
|             qemu_args["kernel_image"], |                     '-device', 'virtio-net-pci,netdev=net0', | ||||||
|             qemu_args["cmdline"], |                     '--nographic', | ||||||
|             qemu_args["mem_size"], |                     ] | ||||||
|             qemu_args["num_cores"], |  | ||||||
|             qemu_args["num_threads"], |  | ||||||
|             self.connection_settings["port"], |  | ||||||
|         ) |  | ||||||
| 
 | 
 | ||||||
|         if qemu_args["initrd_image"]: |         if qemu_args.get("initrd_image"): | ||||||
|             if not os.path.exists(qemu_args["initrd_image"]): |             if not os.path.exists(qemu_args["initrd_image"]): | ||||||
|                 raise FileNotFoundError(f'{qemu_args["initrd_image"]} does not exist!') |                 raise FileNotFoundError(f'{qemu_args["initrd_image"]} does not exist!') | ||||||
| 
 | 
 | ||||||
|             qemu_cmd += f' -initrd {qemu_args["initrd_image"]}' |             qemu_cmd.extend(['-initrd', qemu_args["initrd_image"]]) | ||||||
| 
 | 
 | ||||||
|         if qemu_args["arch"] == machine(): |         if qemu_args["enable_kvm"]: | ||||||
|             if qemu_args["enable_kvm"]: |             # Enable KVM accelerator if host and guest architectures match. | ||||||
|                 qemu_cmd += ' --enable-kvm' |             # Comparison is done based on x86 for the sake of simplicity. | ||||||
|         else: |             if (qemu_args['arch'].startswith('x86') and machine().startswith('x86')) or ( | ||||||
|             qemu_cmd += f' -machine virt -cpu {qemu_args["cpu_type"]}' |                     qemu_args['arch'].startswith('x86') and machine().startswith('x86')): | ||||||
|  |                 qemu_cmd.append('--enable-kvm') | ||||||
| 
 | 
 | ||||||
|         self.target = make_target(connect=False, |         # qemu-system-x86_64 does not support -machine virt as of now. | ||||||
|                                   conn_cls=SshConnection, |         if not qemu_args['arch'].startswith('x86'): | ||||||
|                                   connection_settings=self.connection_settings) |             qemu_cmd.extend(['-machine', 'virt', '-cpu', qemu_args["cpu_type"]]) | ||||||
|  | 
 | ||||||
|  |         target = make_target(connect=False, | ||||||
|  |                              conn_cls=SshConnection, | ||||||
|  |                              connection_settings=self.connection_settings) | ||||||
| 
 | 
 | ||||||
|         super().__init__(runner_cmd=qemu_cmd, |         super().__init__(runner_cmd=qemu_cmd, | ||||||
|                          target=self.target, |                          target=target, | ||||||
|                          **args) |                          **args) | ||||||
| @@ -1131,14 +1131,14 @@ fi | |||||||
|         Creates temporary file/folder on target and deletes it once it's done. |         Creates temporary file/folder on target and deletes it once it's done. | ||||||
|  |  | ||||||
|         :param is_directory: Specifies if temporary object is a directory, defaults to True. |         :param is_directory: Specifies if temporary object is a directory, defaults to True. | ||||||
|         :type is_directory: bool, optional |         :type is_directory: bool or None | ||||||
|  |  | ||||||
|         :param directory: Temp object will be created under this directory, |         :param directory: Temp object will be created under this directory, | ||||||
|             defaults to :attr:`Target.working_directory`. |             defaults to ``Target.working_directory``. | ||||||
|         :type directory: str, optional |         :type directory: str or None | ||||||
|  |  | ||||||
|         :param prefix: Prefix of temp object's name, defaults to 'devlib-test'. |         :param prefix: Prefix of temp object's name. | ||||||
|         :type prefix: str, optional |         :type prefix: str or None | ||||||
|  |  | ||||||
|         :yield: Full path of temp object. |         :yield: Full path of temp object. | ||||||
|         :rtype: str |         :rtype: str | ||||||
| @@ -1147,7 +1147,7 @@ fi | |||||||
|         directory = directory or self.working_directory |         directory = directory or self.working_directory | ||||||
|         temp_obj = None |         temp_obj = None | ||||||
|         try: |         try: | ||||||
|             cmd = f'mktemp -p {directory} {prefix}-XXXXXX' |             cmd = f'mktemp -p {quote(directory)} {quote(prefix)}-XXXXXX' | ||||||
|             if is_directory: |             if is_directory: | ||||||
|                 cmd += ' -d' |                 cmd += ' -d' | ||||||
|  |  | ||||||
|   | |||||||
| @@ -392,10 +392,13 @@ class SshConnection(SshConnectionBase): | |||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         except BaseException: |         # pylint: disable=broad-except | ||||||
|             if self.client is not None: |         except BaseException as e: | ||||||
|                 self.client.close() |             try: | ||||||
|             raise |                 if self.client is not None: | ||||||
|  |                     self.client.close() | ||||||
|  |             finally: | ||||||
|  |                 raise e | ||||||
|  |  | ||||||
|     def _make_client(self): |     def _make_client(self): | ||||||
|         if self.strict_host_check: |         if self.strict_host_check: | ||||||
|   | |||||||
| @@ -1,5 +0,0 @@ | |||||||
| LocalLinuxTarget: |  | ||||||
|   entry-0: |  | ||||||
|     connection_settings: |  | ||||||
|       unrooted: True |  | ||||||
|  |  | ||||||
| @@ -1,39 +0,0 @@ | |||||||
| AndroidTarget: |  | ||||||
|   entry-0: |  | ||||||
|     timeout: 60 |  | ||||||
|     connection_settings: |  | ||||||
|       device: 'emulator-5554' |  | ||||||
|  |  | ||||||
| ChromeOsTarget: |  | ||||||
|   entry-0: |  | ||||||
|     connection_settings: |  | ||||||
|       device: 'emulator-5556' |  | ||||||
|       host: 'example.com' |  | ||||||
|       username: 'username' |  | ||||||
|       password: 'password' |  | ||||||
|  |  | ||||||
| LinuxTarget: |  | ||||||
|   entry-0: |  | ||||||
|     connection_settings: |  | ||||||
|       host: 'example.com' |  | ||||||
|       username: 'username' |  | ||||||
|       password: 'password' |  | ||||||
|  |  | ||||||
| LocalLinuxTarget: |  | ||||||
|   entry-0: |  | ||||||
|     connection_settings: |  | ||||||
|       unrooted: True |  | ||||||
|  |  | ||||||
| QEMUTargetRunner: |  | ||||||
|   entry-0: |  | ||||||
|     qemu_settings: |  | ||||||
|       kernel_image: '/path/to/devlib/tools/buildroot/buildroot-v2023.11.1-aarch64/output/images/Image' |  | ||||||
|  |  | ||||||
|   entry-1: |  | ||||||
|     connection_settings: |  | ||||||
|       port : 8023 |  | ||||||
|  |  | ||||||
|     qemu_settings: |  | ||||||
|       kernel_image: '/path/to/devlib/tools/buildroot/buildroot-v2023.11.1-x86_64/output/images/bzImage' |  | ||||||
|       arch: 'x86_64' |  | ||||||
|       cmdline: 'console=ttyS0' |  | ||||||
							
								
								
									
										5
									
								
								tests/test_config.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/test_config.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | target-configs: | ||||||
|  |   entry-0: | ||||||
|  |     LocalLinuxTarget: | ||||||
|  |       connection_settings: | ||||||
|  |         unrooted: True | ||||||
							
								
								
									
										40
									
								
								tests/test_config.yml.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								tests/test_config.yml.example
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | target-configs: | ||||||
|  |   entry-0: | ||||||
|  |     AndroidTarget: | ||||||
|  |       timeout: 60 | ||||||
|  |       connection_settings: | ||||||
|  |         device: 'emulator-5554' | ||||||
|  |  | ||||||
|  |   entry-1: | ||||||
|  |     ChromeOsTarget: | ||||||
|  |       connection_settings: | ||||||
|  |         device: 'emulator-5556' | ||||||
|  |         host: 'example.com' | ||||||
|  |         username: 'username' | ||||||
|  |         password: 'password' | ||||||
|  |  | ||||||
|  |   entry-2: | ||||||
|  |     LinuxTarget: | ||||||
|  |       connection_settings: | ||||||
|  |         host: 'example.com' | ||||||
|  |         username: 'username' | ||||||
|  |         password: 'password' | ||||||
|  |  | ||||||
|  |   entry-3: | ||||||
|  |     LocalLinuxTarget: | ||||||
|  |       connection_settings: | ||||||
|  |         unrooted: True | ||||||
|  |  | ||||||
|  |   entry-4: | ||||||
|  |     QEMUTargetRunner: | ||||||
|  |         qemu_settings: | ||||||
|  |           kernel_image: '/path/to/devlib/tools/buildroot/buildroot-v2023.11.1-aarch64/output/images/Image' | ||||||
|  |  | ||||||
|  |   entry-5: | ||||||
|  |     QEMUTargetRunner: | ||||||
|  |       connection_settings: | ||||||
|  |         port: 8023 | ||||||
|  |       qemu_settings: | ||||||
|  |         kernel_image: '/path/to/devlib/tools/buildroot/buildroot-v2023.11.1-x86_64/output/images/bzImage' | ||||||
|  |         arch: 'x86_64' | ||||||
|  |         cmdline: 'console=ttyS0' | ||||||
| @@ -14,136 +14,168 @@ | |||||||
| # limitations under the License. | # limitations under the License. | ||||||
| # | # | ||||||
|  |  | ||||||
| """Module for testing targets.""" | """ | ||||||
|  | Module for testing targets. | ||||||
|  |  | ||||||
|  | Sample run with log level is set to DEBUG (see | ||||||
|  | https://docs.pytest.org/en/7.1.x/how-to/logging.html#live-logs for logging details): | ||||||
|  |  | ||||||
|  | $ python -m pytest --log-cli-level DEBUG test_target.py | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import logging | ||||||
| import os | import os | ||||||
| from pprint import pp |  | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from devlib import AndroidTarget, ChromeOsTarget, LinuxTarget, LocalLinuxTarget, QEMUTargetRunner | from devlib import AndroidTarget, ChromeOsTarget, LinuxTarget, LocalLinuxTarget | ||||||
|  | from devlib._target_runner import NOPTargetRunner, QEMUTargetRunner | ||||||
| from devlib.utils.android import AdbConnection | from devlib.utils.android import AdbConnection | ||||||
| from devlib.utils.misc import load_struct_from_yaml | from devlib.utils.misc import load_struct_from_yaml | ||||||
|  |  | ||||||
|  |  | ||||||
| def build_targets(): | logger = logging.getLogger('test_target') | ||||||
|     """Read targets from a YAML formatted config file""" |  | ||||||
|  |  | ||||||
|     config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'target_configs.yaml') |  | ||||||
|  |  | ||||||
|     target_configs = load_struct_from_yaml(config_file) | def get_class_object(name): | ||||||
|     if target_configs is None: |     """ | ||||||
|  |     Get associated class object from string formatted class name | ||||||
|  |  | ||||||
|  |     :param name: Class name | ||||||
|  |     :type name: str | ||||||
|  |     :return: Class object | ||||||
|  |     :rtype: object or None | ||||||
|  |     """ | ||||||
|  |     if globals().get(name) is None: | ||||||
|  |         return None | ||||||
|  |  | ||||||
|  |     return globals()[name] if issubclass(globals()[name], object) else None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture(scope="module") | ||||||
|  | # pylint: disable=too-many-branches | ||||||
|  | def build_target_runners(): | ||||||
|  |     """Read targets from a YAML formatted config file and create runners for them""" | ||||||
|  |  | ||||||
|  |     logger.info("Initializing resources...") | ||||||
|  |  | ||||||
|  |     config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_config.yml') | ||||||
|  |  | ||||||
|  |     test_config = load_struct_from_yaml(config_file) | ||||||
|  |     if test_config is None: | ||||||
|         raise ValueError(f'{config_file} looks empty!') |         raise ValueError(f'{config_file} looks empty!') | ||||||
|  |  | ||||||
|     targets = [] |     target_configs = test_config.get('target-configs') | ||||||
|  |     if target_configs is None: | ||||||
|  |         raise ValueError('No targets found!') | ||||||
|  |  | ||||||
|     if target_configs.get('AndroidTarget') is not None: |     target_runners = [] | ||||||
|         print('> Android targets:') |  | ||||||
|         for entry in target_configs['AndroidTarget'].values(): |     for entry in target_configs.values(): | ||||||
|             pp(entry) |         key, target_info = entry.popitem() | ||||||
|  |  | ||||||
|  |         target_class = get_class_object(key) | ||||||
|  |         if target_class is AndroidTarget: | ||||||
|  |             logger.info('> Android target: %s', repr(target_info)) | ||||||
|             a_target = AndroidTarget( |             a_target = AndroidTarget( | ||||||
|                 connect=False, |                 connect=False, | ||||||
|                 connection_settings=entry['connection_settings'], |                 connection_settings=target_info['connection_settings'], | ||||||
|                 conn_cls=lambda **kwargs: AdbConnection(adb_as_root=True, **kwargs), |                 conn_cls=lambda **kwargs: AdbConnection(adb_as_root=True, **kwargs), | ||||||
|             ) |             ) | ||||||
|             a_target.connect(timeout=entry.get('timeout', 60)) |             a_target.connect(timeout=target_info.get('timeout', 60)) | ||||||
|             targets.append((a_target, None)) |             target_runners.append(NOPTargetRunner(a_target)) | ||||||
|  |  | ||||||
|     if target_configs.get('LinuxTarget') is not None: |         elif target_class is ChromeOsTarget: | ||||||
|         print('> Linux targets:') |             logger.info('> ChromeOS target: %s', repr(target_info)) | ||||||
|         for entry in target_configs['LinuxTarget'].values(): |  | ||||||
|             pp(entry) |  | ||||||
|             l_target = LinuxTarget(connection_settings=entry['connection_settings']) |  | ||||||
|             targets.append((l_target, None)) |  | ||||||
|  |  | ||||||
|     if target_configs.get('ChromeOsTarget') is not None: |  | ||||||
|         print('> ChromeOS targets:') |  | ||||||
|         for entry in target_configs['ChromeOsTarget'].values(): |  | ||||||
|             pp(entry) |  | ||||||
|             c_target = ChromeOsTarget( |             c_target = ChromeOsTarget( | ||||||
|                 connection_settings=entry['connection_settings'], |                 connection_settings=target_info['connection_settings'], | ||||||
|                 working_directory='/tmp/devlib-target', |                 working_directory='/tmp/devlib-target', | ||||||
|             ) |             ) | ||||||
|             targets.append((c_target, None)) |             target_runners.append(NOPTargetRunner(c_target)) | ||||||
|  |  | ||||||
|     if target_configs.get('LocalLinuxTarget') is not None: |         elif target_class is LinuxTarget: | ||||||
|         print('> LocalLinux targets:') |             logger.info('> Linux target: %s', repr(target_info)) | ||||||
|         for entry in target_configs['LocalLinuxTarget'].values(): |             l_target = LinuxTarget(connection_settings=target_info['connection_settings']) | ||||||
|             pp(entry) |             target_runners.append(NOPTargetRunner(l_target)) | ||||||
|             ll_target = LocalLinuxTarget(connection_settings=entry['connection_settings']) |  | ||||||
|             targets.append((ll_target, None)) |  | ||||||
|  |  | ||||||
|     if target_configs.get('QEMUTargetRunner') is not None: |         elif target_class is LocalLinuxTarget: | ||||||
|         print('> QEMU target runners:') |             logger.info('> LocalLinux target: %s', repr(target_info)) | ||||||
|         for entry in target_configs['QEMUTargetRunner'].values(): |             ll_target = LocalLinuxTarget(connection_settings=target_info['connection_settings']) | ||||||
|             pp(entry) |             target_runners.append(NOPTargetRunner(ll_target)) | ||||||
|             qemu_settings = entry.get('qemu_settings') and entry['qemu_settings'] |  | ||||||
|             connection_settings = entry.get( |         elif target_class is QEMUTargetRunner: | ||||||
|                 'connection_settings') and entry['connection_settings'] |             logger.info('> QEMU target runner: %s', repr(target_info)) | ||||||
|  |  | ||||||
|             qemu_runner = QEMUTargetRunner( |             qemu_runner = QEMUTargetRunner( | ||||||
|                 qemu_settings=qemu_settings, |                 qemu_settings=target_info.get('qemu_settings'), | ||||||
|                 connection_settings=connection_settings, |                 connection_settings=target_info.get('connection_settings'), | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|             if entry.get('ChromeOsTarget') is None: |             if target_info.get('ChromeOsTarget') is not None: | ||||||
|                 targets.append((qemu_runner.target, qemu_runner)) |                 # Leave termination of QEMU runner to ChromeOS target. | ||||||
|                 continue |                 target_runners.append(NOPTargetRunner(qemu_runner.target)) | ||||||
|  |  | ||||||
|             # Leave termination of QEMU runner to ChromeOS target. |                 logger.info('>> ChromeOS target: %s', repr(target_info["ChromeOsTarget"])) | ||||||
|             targets.append((qemu_runner.target, None)) |                 qemu_runner.target = ChromeOsTarget( | ||||||
|  |                     connection_settings={ | ||||||
|  |                         **target_info['ChromeOsTarget']['connection_settings'], | ||||||
|  |                         **qemu_runner.target.connection_settings, | ||||||
|  |                     }, | ||||||
|  |                     working_directory='/tmp/devlib-target', | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|             print('> ChromeOS targets:') |             target_runners.append(qemu_runner) | ||||||
|             pp(entry['ChromeOsTarget']) |  | ||||||
|             c_target = ChromeOsTarget( |  | ||||||
|                 connection_settings={ |  | ||||||
|                     **entry['ChromeOsTarget']['connection_settings'], |  | ||||||
|                     **qemu_runner.target.connection_settings, |  | ||||||
|                 }, |  | ||||||
|                 working_directory='/tmp/devlib-target', |  | ||||||
|             ) |  | ||||||
|             targets.append((c_target, qemu_runner)) |  | ||||||
|  |  | ||||||
|     return targets |         else: | ||||||
|  |             raise ValueError(f'Unknown target type {key}!') | ||||||
|  |  | ||||||
|  |     yield target_runners | ||||||
|  |  | ||||||
|  |     logger.info("Destroying resources...") | ||||||
|  |  | ||||||
|  |     for target_runner in target_runners: | ||||||
|  |         target = target_runner.target | ||||||
|  |  | ||||||
|  |         # TODO: Revisit per https://github.com/ARM-software/devlib/issues/680. | ||||||
|  |         logger.debug('Removing %s...', target.working_directory) | ||||||
|  |         target.remove(target.working_directory) | ||||||
|  |  | ||||||
|  |         target_runner.terminate() | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.parametrize("target, target_runner", build_targets()) | # pylint: disable=redefined-outer-name | ||||||
| def test_read_multiline_values(target, target_runner): | def test_read_multiline_values(build_target_runners): | ||||||
|     """ |     """ | ||||||
|     Test Target.read_tree_values_flat() |     Test Target.read_tree_values_flat() | ||||||
|  |  | ||||||
|     :param target: Type of target per :class:`Target` based classes. |     Runs tests around ``Target.read_tree_values_flat()`` for ``TargetRunner`` objects. | ||||||
|     :type target: Target |  | ||||||
|  |  | ||||||
|     :param target_runner: Target runner object to terminate target (if necessary). |  | ||||||
|     :type target: TargetRunner |  | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|  |     logger.info('Running test_read_multiline_values test...') | ||||||
|  |  | ||||||
|     data = { |     data = { | ||||||
|         'test1': '1', |         'test1': '1', | ||||||
|         'test2': '2\n\n', |         'test2': '2\n\n', | ||||||
|         'test3': '3\n\n4\n\n', |         'test3': '3\n\n4\n\n', | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     print(f'target={target.__class__.__name__} os={target.os} hostname={target.hostname}') |     target_runners = build_target_runners | ||||||
|  |     for target_runner in target_runners: | ||||||
|  |         target = target_runner.target | ||||||
|  |  | ||||||
|     with target.make_temp() as tempdir: |         logger.info('target=%s os=%s hostname=%s', | ||||||
|         print(f'Created {tempdir}.') |                     target.__class__.__name__, target.os, target.hostname) | ||||||
|  |  | ||||||
|         for key, value in data.items(): |         with target.make_temp() as tempdir: | ||||||
|             path = os.path.join(tempdir, key) |             logger.debug('Created %s.', tempdir) | ||||||
|             print(f'Writing {value!r} to {path}...') |  | ||||||
|             target.write_value(path, value, verify=False, |  | ||||||
|                                as_root=target.conn.connected_as_root) |  | ||||||
|  |  | ||||||
|         print('Reading values from target...') |             for key, value in data.items(): | ||||||
|         raw_result = target.read_tree_values_flat(tempdir) |                 path = os.path.join(tempdir, key) | ||||||
|         result = {os.path.basename(k): v for k, v in raw_result.items()} |                 logger.debug('Writing %s to %s...', repr(value), path) | ||||||
|  |                 target.write_value(path, value, verify=False, | ||||||
|  |                                    as_root=target.conn.connected_as_root) | ||||||
|  |  | ||||||
|     print(f'Removing {target.working_directory}...') |             logger.debug('Reading values from target...') | ||||||
|     target.remove(target.working_directory) |             raw_result = target.read_tree_values_flat(tempdir) | ||||||
|  |             result = {os.path.basename(k): v for k, v in raw_result.items()} | ||||||
|  |  | ||||||
|     if target_runner is not None: |         assert {k: v.strip() for k, v in data.items()} == result | ||||||
|         print('Terminating target runner...') |  | ||||||
|         target_runner.terminate() |  | ||||||
|  |  | ||||||
|     assert {k: v.strip() for k, v in data.items()} == result |  | ||||||
|   | |||||||
| @@ -1,43 +0,0 @@ | |||||||
| AndroidTarget: |  | ||||||
|   # Android-12, Pixel-6 |  | ||||||
|   entry-0: |  | ||||||
|     timeout: 60 |  | ||||||
|     connection_settings: |  | ||||||
|       device: 'emulator-5554' |  | ||||||
|  |  | ||||||
|   # Android-14, Pixel-6 |  | ||||||
|   entry-1: |  | ||||||
|     connection_settings: |  | ||||||
|       device: 'emulator-5556' |  | ||||||
|  |  | ||||||
|   # Android-13, Pixel tablet |  | ||||||
|   entry-2: |  | ||||||
|     connection_settings: |  | ||||||
|       device: 'emulator-5558' |  | ||||||
|  |  | ||||||
| LocalLinuxTarget: |  | ||||||
|   entry-0: |  | ||||||
|     connection_settings: |  | ||||||
|       unrooted: True |  | ||||||
|  |  | ||||||
| QEMUTargetRunner: |  | ||||||
|   entry-0: |  | ||||||
|     qemu_settings: |  | ||||||
|       kernel_image: '/devlib/tools/buildroot/buildroot-v2023.11.1-aarch64/output/images/Image' |  | ||||||
|  |  | ||||||
|     ChromeOsTarget: |  | ||||||
|       connection_settings: |  | ||||||
|         device: 'emulator-5558' |  | ||||||
|  |  | ||||||
|   entry-1: |  | ||||||
|     connection_settings: |  | ||||||
|       port: 8023 |  | ||||||
|  |  | ||||||
|     qemu_settings: |  | ||||||
|       kernel_image: '/devlib/tools/buildroot/buildroot-v2023.11.1-x86_64/output/images/bzImage' |  | ||||||
|       arch: 'x86_64' |  | ||||||
|       cmdline: 'console=ttyS0' |  | ||||||
|  |  | ||||||
|     ChromeOsTarget: |  | ||||||
|       connection_settings: |  | ||||||
|         device: 'emulator-5558' |  | ||||||
							
								
								
									
										46
									
								
								tools/docker/target_configs.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								tools/docker/target_configs.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | target-configs: | ||||||
|  |   entry-0: | ||||||
|  |     # Android-12, Pixel-6 | ||||||
|  |     AndroidTarget: | ||||||
|  |       timeout: 60 | ||||||
|  |       connection_settings: | ||||||
|  |         device: 'emulator-5554' | ||||||
|  |  | ||||||
|  |   entry-1: | ||||||
|  |     # Android-14, Pixel-6 | ||||||
|  |     AndroidTarget: | ||||||
|  |       connection_settings: | ||||||
|  |         device: 'emulator-5556' | ||||||
|  |  | ||||||
|  |   entry-2: | ||||||
|  |     # Android-13, Pixel tablet | ||||||
|  |     AndroidTarget: | ||||||
|  |       connection_settings: | ||||||
|  |         device: 'emulator-5558' | ||||||
|  |  | ||||||
|  |   entry-3: | ||||||
|  |     LocalLinuxTarget: | ||||||
|  |       connection_settings: | ||||||
|  |         unrooted: True | ||||||
|  |  | ||||||
|  |   entry-4: | ||||||
|  |     # aarch64 target | ||||||
|  |     QEMUTargetRunner: | ||||||
|  |       qemu_settings: | ||||||
|  |         kernel_image: '/devlib/tools/buildroot/buildroot-v2023.11.1-aarch64/output/images/Image' | ||||||
|  |       ChromeOsTarget: | ||||||
|  |         connection_settings: | ||||||
|  |           device: 'emulator-5558' | ||||||
|  |  | ||||||
|  |   entry-5: | ||||||
|  |     # x86_64 target | ||||||
|  |     QEMUTargetRunner: | ||||||
|  |       connection_settings: | ||||||
|  |         port: 8023 | ||||||
|  |       qemu_settings: | ||||||
|  |         kernel_image: '/devlib/tools/buildroot/buildroot-v2023.11.1-x86_64/output/images/bzImage' | ||||||
|  |         arch: 'x86_64' | ||||||
|  |         cmdline: 'console=ttyS0' | ||||||
|  |       ChromeOsTarget: | ||||||
|  |         connection_settings: | ||||||
|  |           device: 'emulator-5558' | ||||||
		Reference in New Issue
	
	Block a user