1
- # Copyright 2024 ARM Limited
1
+ # Copyright 2024-2025 ARM Limited
2
2
#
3
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
4
# you may not use this file except in compliance with the License.
20
20
import logging
21
21
import os
22
22
import time
23
+
23
24
from platform import machine
25
+ from typing import Optional , cast , Protocol , TYPE_CHECKING , TypedDict , Union
26
+ from typing_extensions import NotRequired , LiteralString
27
+ if TYPE_CHECKING :
28
+ from _typeshed import StrPath , BytesPath
29
+ from devlib .platform import Platform
30
+ else :
31
+ StrPath = str
32
+ BytesPath = bytes
24
33
25
34
from devlib .exception import (TargetStableError , HostError )
26
- from devlib .target import LinuxTarget
35
+ from devlib .target import LinuxTarget , Target
27
36
from devlib .utils .misc import get_subprocess , which
28
37
from devlib .utils .ssh import SshConnection
38
+ from devlib .utils .annotation_helpers import SubprocessCommand , SshUserConnectionSettings
29
39
30
40
31
41
class TargetRunner :
@@ -40,15 +50,22 @@ class TargetRunner:
40
50
"""
41
51
42
52
def __init__ (self ,
43
- target ) :
53
+ target : Target ) -> None :
44
54
self .target = target
45
-
46
55
self .logger = logging .getLogger (self .__class__ .__name__ )
47
56
48
57
def __enter__ (self ):
58
+ """
59
+ Enter the context for this runner.
60
+ :return: This runner instance.
61
+ :rtype: TargetRunner
62
+ """
49
63
return self
50
64
51
65
def __exit__ (self , * _ ):
66
+ """
67
+ Exit the context for this runner.
68
+ """
52
69
pass
53
70
54
71
@@ -77,20 +94,20 @@ class SubprocessTargetRunner(TargetRunner):
77
94
"""
78
95
79
96
def __init__ (self ,
80
- runner_cmd ,
81
- target ,
82
- connect = True ,
83
- boot_timeout = 60 ):
97
+ runner_cmd : SubprocessCommand ,
98
+ target : Target ,
99
+ connect : bool = True ,
100
+ boot_timeout : int = 60 ):
84
101
super ().__init__ (target = target )
85
102
86
- self .boot_timeout = boot_timeout
103
+ self .boot_timeout : int = boot_timeout
87
104
88
105
self .logger .info ('runner_cmd: %s' , runner_cmd )
89
106
90
107
try :
91
108
self .runner_process = get_subprocess (runner_cmd )
92
109
except Exception as ex :
93
- raise HostError (f'Error while running "{ runner_cmd } ": { ex } ' ) from ex
110
+ raise HostError (f'Error while running "{ runner_cmd !r } ": { ex } ' ) from ex
94
111
95
112
if connect :
96
113
self .wait_boot_complete ()
@@ -107,16 +124,16 @@ def __exit__(self, *_):
107
124
108
125
self .terminate ()
109
126
110
- def wait_boot_complete (self ):
127
+ def wait_boot_complete (self ) -> None :
111
128
"""
112
- Wait for target OS to finish boot up and become accessible over SSH in at most
113
- ``SubprocessTargetRunner. boot_timeout` ` seconds.
129
+ Wait for the target OS to finish booting and become accessible within
130
+ :attr:` boot_timeout` seconds.
114
131
115
- :raises TargetStableError: In case of timeout.
132
+ :raises TargetStableError: If the target is inaccessible after the timeout.
116
133
"""
117
134
118
135
start_time = time .time ()
119
- elapsed = 0
136
+ elapsed : float = 0. 0
120
137
while self .boot_timeout >= elapsed :
121
138
try :
122
139
self .target .connect (timeout = self .boot_timeout - elapsed )
@@ -132,9 +149,9 @@ def wait_boot_complete(self):
132
149
self .terminate ()
133
150
raise TargetStableError (f'Target is inaccessible for { self .boot_timeout } seconds!' )
134
151
135
- def terminate (self ):
152
+ def terminate (self ) -> None :
136
153
"""
137
- Terminate ``SubprocessTargetRunner.runner_process`` .
154
+ Terminate the subprocess associated with this runner .
138
155
"""
139
156
140
157
self .logger .debug ('Killing target runner...' )
@@ -150,7 +167,7 @@ class NOPTargetRunner(TargetRunner):
150
167
:type target: Target
151
168
"""
152
169
153
- def __init__ (self , target ) :
170
+ def __init__ (self , target : Target ) -> None :
154
171
super ().__init__ (target = target )
155
172
156
173
def __enter__ (self ):
@@ -159,11 +176,63 @@ def __enter__(self):
159
176
def __exit__ (self , * _ ):
160
177
pass
161
178
162
- def terminate (self ):
179
+ def terminate (self ) -> None :
163
180
"""
164
181
Nothing to terminate for NOP target runners.
165
182
Defined to be compliant with other runners (e.g., ``SubprocessTargetRunner``).
166
183
"""
184
+ pass
185
+
186
+
187
+ QEMUTargetUserSettings = TypedDict ("QEMUTargetUserSettings" , {
188
+ 'kernel_image' : str ,
189
+ 'arch' : NotRequired [str ],
190
+ 'cpu_type' : NotRequired [str ],
191
+ 'initrd_image' : str ,
192
+ 'mem_size' : NotRequired [int ],
193
+ 'num_cores' : NotRequired [int ],
194
+ 'num_threads' : NotRequired [int ],
195
+ 'cmdline' : NotRequired [str ],
196
+ 'enable_kvm' : NotRequired [bool ],
197
+ })
198
+
199
+ QEMUTargetRunnerSettings = TypedDict ("QEMUTargetRunnerSettings" , {
200
+ 'kernel_image' : str ,
201
+ 'arch' : str ,
202
+ 'cpu_type' : str ,
203
+ 'initrd_image' : str ,
204
+ 'mem_size' : int ,
205
+ 'num_cores' : int ,
206
+ 'num_threads' : int ,
207
+ 'cmdline' : str ,
208
+ 'enable_kvm' : bool ,
209
+ })
210
+
211
+
212
+ SshConnectionSettings = TypedDict ("SshConnectionSettings" , {
213
+ 'username' : str ,
214
+ 'password' : str ,
215
+ 'keyfile' : Optional [Union [LiteralString , StrPath , BytesPath ]],
216
+ 'host' : str ,
217
+ 'port' : int ,
218
+ 'timeout' : float ,
219
+ 'platform' : 'Platform' ,
220
+ 'sudo_cmd' : str ,
221
+ 'strict_host_check' : bool ,
222
+ 'use_scp' : bool ,
223
+ 'poll_transfers' : bool ,
224
+ 'start_transfer_poll_delay' : int ,
225
+ 'total_transfer_timeout' : int ,
226
+ 'transfer_poll_period' : int ,
227
+ })
228
+
229
+
230
+ class QEMUTargetRunnerTargetFactory (Protocol ):
231
+ """
232
+ Protocol for Lambda function for creating :class:`Target` based object.
233
+ """
234
+ def __call__ (self , * , connect : bool , conn_cls , connection_settings : SshConnectionSettings ) -> Target :
235
+ ...
167
236
168
237
169
238
class QEMUTargetRunner (SubprocessTargetRunner ):
@@ -177,7 +246,7 @@ class QEMUTargetRunner(SubprocessTargetRunner):
177
246
178
247
* ``arch``: Architecture type. Defaults to ``aarch64``.
179
248
180
- * ``cpu_types ``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by
249
+ * ``cpu_type ``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by
181
250
default. This parameter is valid for Arm architectures only.
182
251
183
252
* ``initrd_image``: This points to the location of initrd image (e.g.,
@@ -212,21 +281,25 @@ class QEMUTargetRunner(SubprocessTargetRunner):
212
281
"""
213
282
214
283
def __init__ (self ,
215
- qemu_settings ,
216
- connection_settings = None ,
217
- make_target = LinuxTarget ,
218
- ** args ):
284
+ qemu_settings : QEMUTargetUserSettings ,
285
+ connection_settings : Optional [ SshUserConnectionSettings ] = None ,
286
+ make_target : QEMUTargetRunnerTargetFactory = cast ( QEMUTargetRunnerTargetFactory , LinuxTarget ) ,
287
+ ** args ) -> None :
219
288
220
- self . connection_settings = {
289
+ default_connection_settings = {
221
290
'host' : '127.0.0.1' ,
222
291
'port' : 8022 ,
223
292
'username' : 'root' ,
224
293
'password' : 'root' ,
225
294
'strict_host_check' : False ,
226
295
}
227
- self .connection_settings = {** self .connection_settings , ** (connection_settings or {})}
228
296
229
- qemu_args = {
297
+ self .connection_settings : SshConnectionSettings = cast (SshConnectionSettings , {
298
+ ** default_connection_settings ,
299
+ ** (connection_settings or {})
300
+ })
301
+
302
+ qemu_default_args = {
230
303
'arch' : 'aarch64' ,
231
304
'cpu_type' : 'cortex-a72' ,
232
305
'mem_size' : 512 ,
@@ -235,7 +308,7 @@ def __init__(self,
235
308
'cmdline' : 'console=ttyAMA0' ,
236
309
'enable_kvm' : True ,
237
310
}
238
- qemu_args = {** qemu_args , ** qemu_settings }
311
+ qemu_args : QEMUTargetRunnerSettings = cast ( QEMUTargetRunnerSettings , {** qemu_default_args , ** qemu_settings })
239
312
240
313
qemu_executable = f'qemu-system-{ qemu_args ["arch" ]} '
241
314
qemu_path = which (qemu_executable )
0 commit comments