Skip to content

Commit dd87540

Browse files
plauricrestyled-commitshicklinkiel-apple
authored
Python test scripts: enable remote use of named pipe (#34950)
* initial commit * fix issue with the previous commit * edit change * Restyled by autopep8 * fix linter issues * Restyled by isort * Apply suggestions from code review Co-authored-by: William <[email protected]> * address code review feedback * Added documentation about the write_to_app_pipe command to the testing docs. * Restyled by prettier-markdown * Fixed typos. * Apply suggestions from code review Pleasing restyler. * Apply suggestions from code review Co-authored-by: Kiel Oleson <[email protected]> * Restyled by prettier-markdown * address code review comments * Restyled by autopep8 * fix bug introduced by the previous commit * Restyled by autopep8 * remove unused import * reset to 5359c7b. refactor the code to use a suggestion from the code review. * fix conflict * fix conflict * fix conflict * Restyled by autopep8 * Restyled by isort --------- Co-authored-by: Restyled.io <[email protected]> Co-authored-by: William <[email protected]> Co-authored-by: William Hicklin <[email protected]> Co-authored-by: Kiel Oleson <[email protected]>
1 parent 41be641 commit dd87540

15 files changed

+161
-152
lines changed

.github/.wordlist.txt

+1
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ DS
454454
duplicative
455455
DUT
456456
DUTS
457+
DUT's
457458
DV
458459
DVK
459460
dynload

docs/testing/python.md

+55
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,61 @@ Fabric admin for default controller:
497497
second_ctrl = fa.new_fabric_admin.NewController(nodeId=node_id)
498498
```
499499

500+
## Automating manual steps
501+
502+
Some test plans have manual steps that require the tester to manually change the
503+
state of the DUT. To run these tests in a CI environment, specific example apps
504+
can be built such that these manual steps can be achieved by Matter or
505+
named-pipe commands.
506+
507+
In the case that all the manual steps in a test script can be achieved just
508+
using Matter commands, you can check if the `PICS_SDK_CI_ONLY` PICS is set to
509+
decide if the test script should send the required Matter commands to perform
510+
the manual step.
511+
512+
```python
513+
self.is_ci = self.check_pics("PICS_SDK_CI_ONLY")
514+
```
515+
516+
In the case that a test script requires the use of named-pipe commands to
517+
achieve the manual steps, you can use the `write_to_app_pipe(command)` to send
518+
these commands. This command requires the test class to define a `self.app_pipe`
519+
string value with the name of the pipe. This depends on how the app is set up.
520+
521+
If the name of the pipe is dynamic and based on the app's PID, the following
522+
snippet can be added to the start of tests that use the `write_to_app_pipe`
523+
method.
524+
525+
```python
526+
app_pid = self.matter_test_config.app_pid
527+
if app_pid != 0:
528+
self.is_ci = true
529+
self.app_pipe = "/tmp/chip_<app name>_fifo_" + str(app_pid)
530+
```
531+
532+
This requires the test to be executed with the `--app-pid` flag set if the
533+
manual steps should be executed by the script. This flag sets the process ID of
534+
the DUT's matter application.
535+
536+
### Running on a separate machines
537+
538+
If the DUT and test script are running on different machines, the
539+
`write_to_app_pipe` method can send named-pipe commands to the DUT via ssh. This
540+
requires two additional environment variables:
541+
542+
- `LINUX_DUT_IP` sets the DUT's IP address
543+
- `LINUX_DUT_UNAME` sets the DUT's ssh username. If not set, this is assumed
544+
to be `root`.
545+
546+
The `write_to_app_pipe` also requires that ssh-keys are set up to access the DUT
547+
from the machine running the test script without a password. You can follow
548+
these steps to set this up:
549+
550+
1. If you do not have a key, create one using `ssh-keygen`.
551+
2. Authorize this key on the remote host: run `ssh-copy-id user@ip` once, using
552+
your password.
553+
3. From now on `ssh user@ip` will no longer ask for your password.
554+
500555
## Other support utilities
501556

502557
- `basic_composition_support`

src/python_testing/TC_OpstateCommon.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
# limitations under the License.
1616
#
1717

18-
import json
1918
import logging
2019
import queue
2120
import time
@@ -112,12 +111,7 @@ def init_test(self):
112111
asserts.fail("The --app-pid flag must be set when PICS_SDK_CI_ONLY is set")
113112
self.app_pipe = self.app_pipe + str(app_pid)
114113

115-
# Sends and out-of-band command to test-app
116-
def write_to_app_pipe(self, command):
117-
with open(self.app_pipe, "w") as app_pipe:
118-
app_pipe.write(command + "\n")
119-
120-
def send_raw_manual_or_pipe_command(self, command):
114+
def send_raw_manual_or_pipe_command(self, command: dict):
121115
if self.is_ci:
122116
self.write_to_app_pipe(command)
123117
time.sleep(0.1)
@@ -134,7 +128,7 @@ def send_manual_or_pipe_command(self, device: str, name: str, operation: str, pa
134128
if param is not None:
135129
command["Param"] = param
136130

137-
self.send_raw_manual_or_pipe_command(json.dumps(command))
131+
self.send_raw_manual_or_pipe_command(command)
138132

139133
async def send_cmd(self, endpoint, cmd, timedRequestTimeoutMs=None):
140134
logging.info(f"##### Command {cmd}")

src/python_testing/TC_RVCCLEANM_2_1.py

+1-10
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
# === END CI TEST ARGUMENTS ===
2929

3030
import logging
31-
from time import sleep
3231

3332
import chip.clusters as Clusters
3433
from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main, type_matches
@@ -65,14 +64,6 @@ async def send_run_change_to_mode_cmd(self, newMode) -> Clusters.Objects.RvcRunM
6564
"Unexpected return type for RVC Run Mode ChangeToMode")
6665
return ret
6766

68-
# Sends and out-of-band command to the rvc-app
69-
def write_to_app_pipe(self, command):
70-
with open(self.app_pipe, "w") as app_pipe:
71-
app_pipe.write(command + "\n")
72-
# Delay for pipe command to be processed (otherwise tests are flaky)
73-
# TODO(#31239): centralize pipe write logic and remove the need of sleep
74-
sleep(0.001)
75-
7667
def pics_TC_RVCCLEANM_2_1(self) -> list[str]:
7768
return ["RVCCLEANM.S"]
7869

@@ -107,7 +98,7 @@ async def test_TC_RVCCLEANM_2_1(self):
10798

10899
# Ensure that the device is in the correct state
109100
if self.is_ci:
110-
self.write_to_app_pipe('{"Name": "Reset"}')
101+
self.write_to_app_pipe({"Name": "Reset"})
111102

112103
self.print_step(2, "Read SupportedModes attribute")
113104
supported_modes = await self.read_mod_attribute_expect_success(endpoint=self.endpoint, attribute=attributes.SupportedModes)

src/python_testing/TC_RVCCLEANM_2_2.py

+1-10
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
# === END CI TEST ARGUMENTS ===
2929

3030
import enum
31-
from time import sleep
3231

3332
import chip.clusters as Clusters
3433
from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main, type_matches
@@ -94,14 +93,6 @@ def print_instruction(self, step_number, instruction):
9493
def pics_TC_RVCCLEANM_2_2(self) -> list[str]:
9594
return ["RVCCLEANM.S"]
9695

97-
# Sends and out-of-band command to the rvc-app
98-
def write_to_app_pipe(self, command):
99-
with open(self.app_pipe, "w") as app_pipe:
100-
app_pipe.write(command + "\n")
101-
# Delay for pipe command to be processed (otherwise tests are flaky)
102-
# TODO(#31239): centralize pipe write logic and remove the need of sleep
103-
sleep(0.001)
104-
10596
@async_test_body
10697
async def test_TC_RVCCLEANM_2_2(self):
10798
# TODO Replace 0x8000 with python object of RVCCLEAN FEATURE bit map when implemented
@@ -123,7 +114,7 @@ async def test_TC_RVCCLEANM_2_2(self):
123114

124115
# Ensure that the device is in the correct state
125116
if self.is_ci:
126-
self.write_to_app_pipe('{"Name": "Reset"}')
117+
self.write_to_app_pipe({"Name": "Reset"})
127118

128119
self.print_step(
129120
2, "Manually put the device in a state in which the RVC Run Mode cluster’s CurrentMode attribute is set to a mode without the Idle mode tag.")

src/python_testing/TC_RVCOPSTATE_2_1.py

+16-25
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
# === END CI TEST ARGUMENTS ===
2929

3030
import logging
31-
from time import sleep
3231

3332
import chip.clusters as Clusters
3433
from chip.clusters.Types import NullValue
@@ -72,14 +71,6 @@ async def send_pause_cmd(self) -> Clusters.Objects.RvcOperationalState.Commands.
7271
ret = await self.send_single_cmd(cmd=Clusters.Objects.RvcOperationalState.Commands.Pause(), endpoint=self.endpoint)
7372
return ret
7473

75-
# Sends and out-of-band command to the rvc-app
76-
def write_to_app_pipe(self, command):
77-
with open(self.app_pipe, "w") as app_pipe:
78-
app_pipe.write(command + "\n")
79-
# Allow some time for the command to take effect.
80-
# This removes the test flakyness which is very annoying for everyone in CI.
81-
sleep(0.001)
82-
8374
def TC_RVCOPSTATE_2_1(self) -> list[str]:
8475
return ["RVCOPSTATE.S"]
8576

@@ -100,7 +91,7 @@ async def test_TC_RVCOPSTATE_2_1(self):
10091

10192
# Ensure that the device is in the correct state
10293
if self.is_ci:
103-
self.write_to_app_pipe('{"Name": "Reset"}')
94+
self.write_to_app_pipe({"Name": "Reset"})
10495

10596
if self.check_pics("RVCOPSTATE.S.A0000"):
10697
self.print_step(2, "Read PhaseList attribute")
@@ -195,15 +186,15 @@ async def test_TC_RVCOPSTATE_2_1(self):
195186
test_step = "Manually put the device in the error state"
196187
self.print_step("6g", test_step)
197188
if self.is_ci:
198-
self.write_to_app_pipe('{"Name": "ErrorEvent", "Error": "UnableToStartOrResume"}')
189+
self.write_to_app_pipe({"Name": "ErrorEvent", "Error": "UnableToStartOrResume"})
199190
else:
200191
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
201192
await self.read_and_validate_opstate(step="6h", expected_state=Clusters.OperationalState.Enums.OperationalStateEnum.kError)
202193
if self.check_pics("RVCOPSTATE.S.M.ST_SEEKING_CHARGER"):
203194
test_step = "Manually put the device in the seeking charger state"
204195
self.print_step("6i", test_step)
205196
if self.is_ci:
206-
self.write_to_app_pipe('{"Name": "Reset"}')
197+
self.write_to_app_pipe({"Name": "Reset"})
207198
await self.send_run_change_to_mode_cmd(1)
208199
await self.send_run_change_to_mode_cmd(0)
209200
else:
@@ -213,15 +204,15 @@ async def test_TC_RVCOPSTATE_2_1(self):
213204
test_step = "Manually put the device in the charging state"
214205
self.print_step("6k", test_step)
215206
if self.is_ci:
216-
self.write_to_app_pipe('{"Name": "ChargerFound"}')
207+
self.write_to_app_pipe({"Name": "ChargerFound"})
217208
else:
218209
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
219210
await self.read_and_validate_opstate(step="6l", expected_state=Clusters.RvcOperationalState.Enums.OperationalStateEnum.kCharging)
220211
if self.check_pics("RVCOPSTATE.S.M.ST_DOCKED"):
221212
test_step = "Manually put the device in the docked state"
222213
self.print_step("6m", test_step)
223214
if self.is_ci:
224-
self.write_to_app_pipe('{"Name": "Charged"}')
215+
self.write_to_app_pipe({"Name": "Charged"})
225216
else:
226217
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
227218
await self.read_and_validate_opstate(step="6n", expected_state=Clusters.RvcOperationalState.Enums.OperationalStateEnum.kDocked)
@@ -254,87 +245,87 @@ async def test_TC_RVCOPSTATE_2_1(self):
254245
test_step = "Manually put the device in the unable to start or resume error state"
255246
self.print_step("7c", test_step)
256247
if self.is_ci:
257-
self.write_to_app_pipe('{"Name": "ErrorEvent", "Error": "UnableToStartOrResume"}')
248+
self.write_to_app_pipe({"Name": "ErrorEvent", "Error": "UnableToStartOrResume"})
258249
else:
259250
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
260251
await self.read_and_validate_operror(step="7d", expected_error=Clusters.OperationalState.Enums.ErrorStateEnum.kUnableToStartOrResume)
261252
if self.check_pics("RVCOPSTATE.S.M.ERR_UNABLE_TO_COMPLETE_OPERATION"):
262253
test_step = "Manually put the device in the unable to complete operation error state"
263254
self.print_step("7e", test_step)
264255
if self.is_ci:
265-
self.write_to_app_pipe('{"Name": "ErrorEvent", "Error": "UnableToCompleteOperation"}')
256+
self.write_to_app_pipe({"Name": "ErrorEvent", "Error": "UnableToCompleteOperation"})
266257
else:
267258
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
268259
await self.read_and_validate_operror(step="7f", expected_error=Clusters.OperationalState.Enums.ErrorStateEnum.kUnableToCompleteOperation)
269260
if self.check_pics("RVCOPSTATE.S.M.ERR_COMMAND_INVALID_IN_STATE"):
270261
test_step = "Manually put the device in the command invalid error state"
271262
self.print_step("7g", test_step)
272263
if self.is_ci:
273-
self.write_to_app_pipe('{"Name": "ErrorEvent", "Error": "CommandInvalidInState"}')
264+
self.write_to_app_pipe({"Name": "ErrorEvent", "Error": "CommandInvalidInState"})
274265
else:
275266
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
276267
await self.read_and_validate_operror(step="7h", expected_error=Clusters.OperationalState.Enums.ErrorStateEnum.kCommandInvalidInState)
277268
if self.check_pics("RVCOPSTATE.S.M.ERR_FAILED_TO_FIND_CHARGING_DOCK"):
278269
test_step = "Manually put the device in the failed to find dock error state"
279270
self.print_step("7i", test_step)
280271
if self.is_ci:
281-
self.write_to_app_pipe('{"Name": "ErrorEvent", "Error": "FailedToFindChargingDock"}')
272+
self.write_to_app_pipe({"Name": "ErrorEvent", "Error": "FailedToFindChargingDock"})
282273
else:
283274
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
284275
await self.read_and_validate_operror(step="7j", expected_error=Clusters.RvcOperationalState.Enums.ErrorStateEnum.kFailedToFindChargingDock)
285276
if self.check_pics("RVCOPSTATE.S.M.ERR_STUCK"):
286277
test_step = "Manually put the device in the stuck error state"
287278
self.print_step("7k", test_step)
288279
if self.is_ci:
289-
self.write_to_app_pipe('{"Name": "ErrorEvent", "Error": "Stuck"}')
280+
self.write_to_app_pipe({"Name": "ErrorEvent", "Error": "Stuck"})
290281
else:
291282
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
292283
await self.read_and_validate_operror(step="7l", expected_error=Clusters.RvcOperationalState.Enums.ErrorStateEnum.kStuck)
293284
if self.check_pics("RVCOPSTATE.S.M.ERR_DUST_BIN_MISSING"):
294285
test_step = "Manually put the device in the dust bin missing error state"
295286
self.print_step("7m", test_step)
296287
if self.is_ci:
297-
self.write_to_app_pipe('{"Name": "ErrorEvent", "Error": "DustBinMissing"}')
288+
self.write_to_app_pipe({"Name": "ErrorEvent", "Error": "DustBinMissing"})
298289
else:
299290
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
300291
await self.read_and_validate_operror(step="7n", expected_error=Clusters.RvcOperationalState.Enums.ErrorStateEnum.kDustBinMissing)
301292
if self.check_pics("RVCOPSTATE.S.M.ERR_DUST_BIN_FULL"):
302293
test_step = "Manually put the device in the dust bin full error state"
303294
self.print_step("7o", test_step)
304295
if self.is_ci:
305-
self.write_to_app_pipe('{"Name": "ErrorEvent", "Error": "DustBinFull"}')
296+
self.write_to_app_pipe({"Name": "ErrorEvent", "Error": "DustBinFull"})
306297
else:
307298
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
308299
await self.read_and_validate_operror(step="7p", expected_error=Clusters.RvcOperationalState.Enums.ErrorStateEnum.kDustBinFull)
309300
if self.check_pics("RVCOPSTATE.S.M.ERR_WATER_TANK_EMPTY"):
310301
test_step = "Manually put the device in the water tank empty error state"
311302
self.print_step("7q", test_step)
312303
if self.is_ci:
313-
self.write_to_app_pipe('{"Name": "ErrorEvent", "Error": "WaterTankEmpty"}')
304+
self.write_to_app_pipe({"Name": "ErrorEvent", "Error": "WaterTankEmpty"})
314305
else:
315306
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
316307
await self.read_and_validate_operror(step="7r", expected_error=Clusters.RvcOperationalState.Enums.ErrorStateEnum.kWaterTankEmpty)
317308
if self.check_pics("RVCOPSTATE.S.M.ERR_WATER_TANK_MISSING"):
318309
test_step = "Manually put the device in the water tank missing error state"
319310
self.print_step("7s", test_step)
320311
if self.is_ci:
321-
self.write_to_app_pipe('{"Name": "ErrorEvent", "Error": "WaterTankMissing"}')
312+
self.write_to_app_pipe({"Name": "ErrorEvent", "Error": "WaterTankMissing"})
322313
else:
323314
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
324315
await self.read_and_validate_operror(step="7t", expected_error=Clusters.RvcOperationalState.Enums.ErrorStateEnum.kWaterTankMissing)
325316
if self.check_pics("RVCOPSTATE.S.M.ERR_WATER_TANK_LID_OPEN"):
326317
test_step = "Manually put the device in the water tank lid open error state"
327318
self.print_step("7u", test_step)
328319
if self.is_ci:
329-
self.write_to_app_pipe('{"Name": "ErrorEvent", "Error": "WaterTankLidOpen"}')
320+
self.write_to_app_pipe({"Name": "ErrorEvent", "Error": "WaterTankLidOpen"})
330321
else:
331322
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
332323
await self.read_and_validate_operror(step="7v", expected_error=Clusters.RvcOperationalState.Enums.ErrorStateEnum.kWaterTankLidOpen)
333324
if self.check_pics("RVCOPSTATE.S.M.ERR_MOP_CLEANING_PAD_MISSING"):
334325
test_step = "Manually put the device in the mop cleaning pad missing error state"
335326
self.print_step("7w", test_step)
336327
if self.is_ci:
337-
self.write_to_app_pipe('{"Name": "ErrorEvent", "Error": "MopCleaningPadMissing"}')
328+
self.write_to_app_pipe({"Name": "ErrorEvent", "Error": "MopCleaningPadMissing"})
338329
else:
339330
self.wait_for_user_input(prompt_msg=f"{test_step}, and press Enter when done.\n")
340331
await self.read_and_validate_operror(step="7x", expected_error=Clusters.RvcOperationalState.Enums.ErrorStateEnum.kMopCleaningPadMissing)

0 commit comments

Comments
 (0)