14
14
# See the License for the specific language governing permissions and
15
15
# limitations under the License.
16
16
17
+ import contextlib
17
18
import datetime
18
19
import glob
19
20
import io
22
23
import os .path
23
24
import pathlib
24
25
import re
26
+ import select
25
27
import shlex
26
28
import sys
29
+ import threading
27
30
import time
31
+ import typing
28
32
29
33
import click
30
34
import coloredlogs
@@ -68,6 +72,23 @@ def process_test_script_output(line, is_stderr):
68
72
return process_chip_output (line , is_stderr , TAG_PROCESS_TEST )
69
73
70
74
75
+ def forward_fifo (path : str , f_out : typing .BinaryIO , stop_event : threading .Event ):
76
+ """Forward the content of a named pipe to a file-like object."""
77
+ if not os .path .exists (path ):
78
+ with contextlib .suppress (OSError ):
79
+ os .mkfifo (path )
80
+ with open (os .open (path , os .O_RDONLY | os .O_NONBLOCK ), 'rb' ) as f_in :
81
+ while not stop_event .is_set ():
82
+ if select .select ([f_in ], [], [], 0.5 )[0 ]:
83
+ line = f_in .readline ()
84
+ if not line :
85
+ break
86
+ f_out .write (line )
87
+ f_out .flush ()
88
+ with contextlib .suppress (OSError ):
89
+ os .unlink (path )
90
+
91
+
71
92
@click .command ()
72
93
@click .option ("--app" , type = click .Path (exists = True ), default = None ,
73
94
help = 'Path to local application to use, omit to use external apps.' )
@@ -79,6 +100,8 @@ def process_test_script_output(line, is_stderr):
79
100
help = 'The extra arguments passed to the device. Can use placeholders like {SCRIPT_BASE_NAME}' )
80
101
@click .option ("--app-ready-pattern" , type = str , default = None ,
81
102
help = 'Delay test script start until given regular expression pattern is found in the application output.' )
103
+ @click .option ("--app-stdin-pipe" , type = str , default = None ,
104
+ help = 'Path for a standard input redirection named pipe to be used by the test script.' )
82
105
@click .option ("--script" , type = click .Path (exists = True ), default = os .path .join (DEFAULT_CHIP_ROOT ,
83
106
'src' ,
84
107
'controller' ,
@@ -94,7 +117,8 @@ def process_test_script_output(line, is_stderr):
94
117
help = "Do not print output from passing tests. Use this flag in CI to keep GitHub log size manageable." )
95
118
@click .option ("--load-from-env" , default = None , help = "YAML file that contains values for environment variables." )
96
119
def main (app : str , factory_reset : bool , factory_reset_app_only : bool , app_args : str ,
97
- app_ready_pattern : str , script : str , script_args : str , script_gdb : bool , quiet : bool , load_from_env ):
120
+ app_ready_pattern : str , app_stdin_pipe : str , script : str , script_args : str ,
121
+ script_gdb : bool , quiet : bool , load_from_env ):
98
122
if load_from_env :
99
123
reader = MetadataReader (load_from_env )
100
124
runs = reader .parse_script (script )
@@ -106,6 +130,7 @@ def main(app: str, factory_reset: bool, factory_reset_app_only: bool, app_args:
106
130
app = app ,
107
131
app_args = app_args ,
108
132
app_ready_pattern = app_ready_pattern ,
133
+ app_stdin_pipe = app_stdin_pipe ,
109
134
script_args = script_args ,
110
135
script_gdb = script_gdb ,
111
136
)
@@ -128,11 +153,13 @@ def main(app: str, factory_reset: bool, factory_reset_app_only: bool, app_args:
128
153
for run in runs :
129
154
logging .info ("Executing %s %s" , run .py_script_path .split ('/' )[- 1 ], run .run )
130
155
main_impl (run .app , run .factory_reset , run .factory_reset_app_only , run .app_args or "" ,
131
- run .app_ready_pattern , run .py_script_path , run .script_args or "" , run .script_gdb , run .quiet )
156
+ run .app_ready_pattern , run .app_stdin_pipe , run .py_script_path ,
157
+ run .script_args or "" , run .script_gdb , run .quiet )
132
158
133
159
134
160
def main_impl (app : str , factory_reset : bool , factory_reset_app_only : bool , app_args : str ,
135
- app_ready_pattern : str , script : str , script_args : str , script_gdb : bool , quiet : bool ):
161
+ app_ready_pattern : str , app_stdin_pipe : str , script : str , script_args : str ,
162
+ script_gdb : bool , quiet : bool ):
136
163
137
164
app_args = app_args .replace ('{SCRIPT_BASE_NAME}' , os .path .splitext (os .path .basename (script ))[0 ])
138
165
script_args = script_args .replace ('{SCRIPT_BASE_NAME}' , os .path .splitext (os .path .basename (script ))[0 ])
@@ -154,6 +181,8 @@ def main_impl(app: str, factory_reset: bool, factory_reset_app_only: bool, app_a
154
181
pathlib .Path (match .group ("path" )).unlink (missing_ok = True )
155
182
156
183
app_process = None
184
+ app_stdin_forwarding_thread = None
185
+ app_stdin_forwarding_stop_event = threading .Event ()
157
186
app_exit_code = 0
158
187
app_pid = 0
159
188
@@ -172,7 +201,13 @@ def main_impl(app: str, factory_reset: bool, factory_reset_app_only: bool, app_a
172
201
f_stdout = stream_output ,
173
202
f_stderr = stream_output )
174
203
app_process .start (expected_output = app_ready_pattern , timeout = 30 )
175
- app_process .p .stdin .close ()
204
+ if app_stdin_pipe :
205
+ logging .info ("Forwarding stdin from '%s' to app" , app_stdin_pipe )
206
+ app_stdin_forwarding_thread = threading .Thread (
207
+ target = forward_fifo , args = (app_stdin_pipe , app_process .p .stdin , app_stdin_forwarding_stop_event ))
208
+ app_stdin_forwarding_thread .start ()
209
+ else :
210
+ app_process .p .stdin .close ()
176
211
app_pid = app_process .p .pid
177
212
178
213
script_command = [script , "--paa-trust-store-path" , os .path .join (DEFAULT_CHIP_ROOT , MATTER_DEVELOPMENT_PAA_ROOT_CERTS ),
@@ -204,6 +239,9 @@ def main_impl(app: str, factory_reset: bool, factory_reset_app_only: bool, app_a
204
239
205
240
if app_process :
206
241
logging .info ("Stopping app with SIGTERM" )
242
+ if app_stdin_forwarding_thread :
243
+ app_stdin_forwarding_stop_event .set ()
244
+ app_stdin_forwarding_thread .join ()
207
245
app_process .terminate ()
208
246
app_exit_code = app_process .returncode
209
247
0 commit comments