1- # Copyright 2024 ARM Limited
1+ # Copyright 2024-2025 ARM Limited
22#
33# Licensed under the Apache License, Version 2.0 (the "License");
44# you may not use this file except in compliance with the License.
1717Target runner and related classes are implemented here.
1818"""
1919
20- import logging
2120import os
2221import time
22+
2323from platform import machine
24+ from typing import Optional , cast , Protocol , TYPE_CHECKING , Union
25+ from typing_extensions import NotRequired , LiteralString , TypedDict
26+ from devlib .platform import Platform
27+ if TYPE_CHECKING :
28+ from _typeshed import StrPath , BytesPath
29+ else :
30+ StrPath = str
31+ BytesPath = bytes
2432
2533from devlib .exception import (TargetStableError , HostError )
26- from devlib .target import LinuxTarget
27- from devlib .utils .misc import get_subprocess , which
34+ from devlib .target import LinuxTarget , Target
35+ from devlib .utils .misc import get_subprocess , which , get_logger
2836from devlib .utils .ssh import SshConnection
37+ from devlib .utils .annotation_helpers import SubprocessCommand , SshUserConnectionSettings
2938
3039
3140class TargetRunner :
@@ -36,16 +45,14 @@ class TargetRunner:
3645 (e.g., :class:`QEMUTargetRunner`).
3746
3847 :param target: Specifies type of target per :class:`Target` based classes.
39- :type target: Target
4048 """
4149
4250 def __init__ (self ,
43- target ) :
51+ target : Target ) -> None :
4452 self .target = target
53+ self .logger = get_logger (self .__class__ .__name__ )
4554
46- self .logger = logging .getLogger (self .__class__ .__name__ )
47-
48- def __enter__ (self ):
55+ def __enter__ (self ) -> 'TargetRunner' :
4956 return self
5057
5158 def __exit__ (self , * _ ):
@@ -58,29 +65,25 @@ class SubprocessTargetRunner(TargetRunner):
5865
5966 :param runner_cmd: The command to start runner process (e.g.,
6067 ``qemu-system-aarch64 -kernel Image -append "console=ttyAMA0" ...``).
61- :type runner_cmd: list(str)
6268
6369 :param target: Specifies type of target per :class:`Target` based classes.
64- :type target: Target
6570
6671 :param connect: Specifies if :class:`TargetRunner` should try to connect
6772 target after launching it, defaults to True.
68- :type connect: bool or None
6973
7074 :param boot_timeout: Timeout for target's being ready for SSH access in
7175 seconds, defaults to 60.
72- :type boot_timeout: int or None
7376
7477 :raises HostError: if it cannot execute runner command successfully.
7578
7679 :raises TargetStableError: if Target is inaccessible.
7780 """
7881
7982 def __init__ (self ,
80- runner_cmd ,
81- target ,
82- connect = True ,
83- boot_timeout = 60 ):
83+ runner_cmd : SubprocessCommand ,
84+ target : Target ,
85+ connect : bool = True ,
86+ boot_timeout : int = 60 ):
8487 super ().__init__ (target = target )
8588
8689 self .boot_timeout = boot_timeout
@@ -90,7 +93,7 @@ def __init__(self,
9093 try :
9194 self .runner_process = get_subprocess (runner_cmd )
9295 except Exception as ex :
93- raise HostError (f'Error while running "{ runner_cmd } ": { ex } ' ) from ex
96+ raise HostError (f'Error while running "{ runner_cmd !r } ": { ex } ' ) from ex
9497
9598 if connect :
9699 self .wait_boot_complete ()
@@ -107,16 +110,16 @@ def __exit__(self, *_):
107110
108111 self .terminate ()
109112
110- def wait_boot_complete (self ):
113+ def wait_boot_complete (self ) -> None :
111114 """
112- Wait for target OS to finish boot up and become accessible over SSH in at most
113- ``SubprocessTargetRunner. boot_timeout` ` seconds.
115+ Wait for the target OS to finish booting and become accessible within
116+ :attr:` boot_timeout` seconds.
114117
115- :raises TargetStableError: In case of timeout.
118+ :raises TargetStableError: If the target is inaccessible after the timeout.
116119 """
117120
118121 start_time = time .time ()
119- elapsed = 0
122+ elapsed : float = 0. 0
120123 while self .boot_timeout >= elapsed :
121124 try :
122125 self .target .connect (timeout = self .boot_timeout - elapsed )
@@ -132,9 +135,9 @@ def wait_boot_complete(self):
132135 self .terminate ()
133136 raise TargetStableError (f'Target is inaccessible for { self .boot_timeout } seconds!' )
134137
135- def terminate (self ):
138+ def terminate (self ) -> None :
136139 """
137- Terminate ``SubprocessTargetRunner.runner_process`` .
140+ Terminate the subprocess associated with this runner .
138141 """
139142
140143 self .logger .debug ('Killing target runner...' )
@@ -147,10 +150,9 @@ class NOPTargetRunner(TargetRunner):
147150 Class for implementing a target runner which does nothing except providing .target attribute.
148151
149152 :param target: Specifies type of target per :class:`Target` based classes.
150- :type target: Target
151153 """
152154
153- def __init__ (self , target ) :
155+ def __init__ (self , target : Target ) -> None :
154156 super ().__init__ (target = target )
155157
156158 def __enter__ (self ):
@@ -159,11 +161,63 @@ def __enter__(self):
159161 def __exit__ (self , * _ ):
160162 pass
161163
162- def terminate (self ):
164+ def terminate (self ) -> None :
163165 """
164166 Nothing to terminate for NOP target runners.
165167 Defined to be compliant with other runners (e.g., ``SubprocessTargetRunner``).
166168 """
169+ pass
170+
171+
172+ class QEMUTargetUserSettings (TypedDict , total = False ):
173+ kernel_image : str
174+ arch : NotRequired [str ]
175+ cpu_type : NotRequired [str ]
176+ initrd_image : str
177+ mem_size : NotRequired [int ]
178+ num_cores : NotRequired [int ]
179+ num_threads : NotRequired [int ]
180+ cmdline : NotRequired [str ]
181+ enable_kvm : NotRequired [bool ]
182+
183+
184+ class QEMUTargetRunnerSettings (TypedDict ):
185+ kernel_image : str
186+ arch : str
187+ cpu_type : str
188+ initrd_image : str
189+ mem_size : int
190+ num_cores : int
191+ num_threads : int
192+ cmdline : str
193+ enable_kvm : bool
194+
195+
196+ # TODO - look into which params can be made NotRequired and Optional
197+ # TODO - use pydantic for dynamic type checking
198+ class SshConnectionSettings (TypedDict ):
199+ username : str
200+ password : str
201+ keyfile : Optional [Union [LiteralString , StrPath , BytesPath ]]
202+ host : str
203+ port : int
204+ timeout : float
205+ platform : 'Platform'
206+ sudo_cmd : str
207+ strict_host_check : bool
208+ use_scp : bool
209+ poll_transfers : bool
210+ start_transfer_poll_delay : int
211+ total_transfer_timeout : int
212+ transfer_poll_period : int
213+
214+
215+ class QEMUTargetRunnerTargetFactory (Protocol ):
216+ """
217+ Protocol for Lambda function for creating :class:`Target` based object.
218+ """
219+ def __call__ (self , * , connect : bool , conn_cls , connection_settings : SshConnectionSettings ) -> Target :
220+ ...
167221
168222
169223class QEMUTargetRunner (SubprocessTargetRunner ):
@@ -177,7 +231,7 @@ class QEMUTargetRunner(SubprocessTargetRunner):
177231
178232 * ``arch``: Architecture type. Defaults to ``aarch64``.
179233
180- * ``cpu_types ``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by
234+ * ``cpu_type ``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by
181235 default. This parameter is valid for Arm architectures only.
182236
183237 * ``initrd_image``: This points to the location of initrd image (e.g.,
@@ -197,36 +251,37 @@ class QEMUTargetRunner(SubprocessTargetRunner):
197251 * ``enable_kvm``: Specifies if KVM will be used as accelerator in QEMU or not.
198252 Enabled by default if host architecture matches with target's for improving
199253 QEMU performance.
200- :type qemu_settings: Dict
201254
202255 :param connection_settings: the dictionary to store connection settings
203256 of ``Target.connection_settings``, defaults to None.
204- :type connection_settings: Dict or None
205257
206258 :param make_target: Lambda function for creating :class:`Target` based object.
207- :type make_target: func or None
208259
209260 :Variable positional arguments: Forwarded to :class:`TargetRunner`.
210261
211262 :raises FileNotFoundError: if QEMU executable, kernel or initrd image cannot be found.
212263 """
213264
214265 def __init__ (self ,
215- qemu_settings ,
216- connection_settings = None ,
217- make_target = LinuxTarget ,
218- ** args ) :
266+ qemu_settings : QEMUTargetUserSettings ,
267+ connection_settings : Optional [ SshUserConnectionSettings ] = None ,
268+ make_target : QEMUTargetRunnerTargetFactory = cast ( QEMUTargetRunnerTargetFactory , LinuxTarget ) ,
269+ ** kwargs ) -> None :
219270
220- self . connection_settings = {
271+ default_connection_settings = {
221272 'host' : '127.0.0.1' ,
222273 'port' : 8022 ,
223274 'username' : 'root' ,
224275 'password' : 'root' ,
225276 'strict_host_check' : False ,
226277 }
227- self .connection_settings = {** self .connection_settings , ** (connection_settings or {})}
278+ # TODO - use pydantic for dynamic type checking. that can avoid casting and ensure runtime type compatibility
279+ self .connection_settings : SshConnectionSettings = cast (SshConnectionSettings , {
280+ ** default_connection_settings ,
281+ ** (connection_settings or {})
282+ })
228283
229- qemu_args = {
284+ qemu_default_args = {
230285 'arch' : 'aarch64' ,
231286 'cpu_type' : 'cortex-a72' ,
232287 'mem_size' : 512 ,
@@ -235,7 +290,8 @@ def __init__(self,
235290 'cmdline' : 'console=ttyAMA0' ,
236291 'enable_kvm' : True ,
237292 }
238- qemu_args = {** qemu_args , ** qemu_settings }
293+ # TODO - same as above, use pydantic.
294+ qemu_args : QEMUTargetRunnerSettings = cast (QEMUTargetRunnerSettings , {** qemu_default_args , ** qemu_settings })
239295
240296 qemu_executable = f'qemu-system-{ qemu_args ["arch" ]} '
241297 qemu_path = which (qemu_executable )
@@ -281,4 +337,4 @@ def __init__(self,
281337
282338 super ().__init__ (runner_cmd = qemu_cmd ,
283339 target = target ,
284- ** args )
340+ ** kwargs )
0 commit comments