From fb419de588ed23df1ee29e46181b92b099ec4d64 Mon Sep 17 00:00:00 2001 From: Bartosz Krystowski Date: Mon, 11 Aug 2025 10:31:11 +0200 Subject: [PATCH 01/35] Add dual host ffmpeg tests for various video formats and configurations --- tests/validation/mtl_engine/ffmpeg_app.py | 322 ++++++++++++++++++ .../validation/tests/dual/ffmpeg/__init__.py | 2 + .../dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg.py | 59 ++++ .../ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24.py | 55 +++ ...test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple.py | 64 ++++ .../dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp.py | 66 ++++ 6 files changed, 568 insertions(+) create mode 100755 tests/validation/tests/dual/ffmpeg/__init__.py create mode 100755 tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg.py create mode 100755 tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24.py create mode 100755 tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple.py create mode 100755 tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp.py diff --git a/tests/validation/mtl_engine/ffmpeg_app.py b/tests/validation/mtl_engine/ffmpeg_app.py index 9697fd134..c7fa7d57e 100755 --- a/tests/validation/mtl_engine/ffmpeg_app.py +++ b/tests/validation/mtl_engine/ffmpeg_app.py @@ -993,3 +993,325 @@ def decode_video_format_to_st20p(video_format: str) -> tuple: else: log_fail(f"Invalid video format: {video_format}") return None + + +def execute_dual_test( + test_time: int, + build: str, + tx_host, + rx_host, + type_: str, + video_format: str, + pg_format: str, + video_url: str, + output_format: str, + multiple_sessions: bool = False, + tx_is_ffmpeg: bool = True, + capture_cfg=None, +): + init_test_logging() + + case_id = os.environ.get("PYTEST_CURRENT_TEST", "ffmpeg_dual_test") + case_id = case_id[: case_id.rfind("(") - 1] if "(" in case_id else case_id + + tx_nic_port_list = tx_host.vfs + rx_nic_port_list = rx_host.vfs + video_size, fps = decode_video_format_16_9(video_format) + + match output_format: + case "yuv": + ffmpeg_rx_f_flag = "-f rawvideo" + case "h264": + ffmpeg_rx_f_flag = "-c:v libopenh264" + + if not multiple_sessions: + output_files = create_empty_output_files(output_format, 1, rx_host, build) + rx_cmd = ( + f"ffmpeg -p_port {rx_nic_port_list[0]} -p_sip {ip_dict['rx_interfaces']} " + f"-p_rx_ip {ip_dict['rx_sessions']} -udp_port 20000 -payload_type 112 " + f"-fps {fps} -pix_fmt yuv422p10le -video_size {video_size} " + f"-f mtl_st20p -i k {ffmpeg_rx_f_flag} {output_files[0]} -y" + ) + if tx_is_ffmpeg: + tx_cmd = ( + f"ffmpeg -stream_loop -1 -video_size {video_size} -f rawvideo -pix_fmt yuv422p10le " + f"-i {video_url} -filter:v fps={fps} -p_port {tx_nic_port_list[0]} " + f"-p_sip {ip_dict['tx_interfaces']} -p_tx_ip {ip_dict['tx_sessions']} " + f"-udp_port 20000 -payload_type 112 -f mtl_st20p -" + ) + else: + tx_config_file = generate_rxtxapp_tx_config( + tx_nic_port_list[0], video_format, video_url, tx_host, build, multiple_sessions + ) + tx_cmd = f"{RXTXAPP_PATH} --config_file {tx_config_file} --test_time {test_time}" + else: # multiple sessions + output_files = create_empty_output_files(output_format, 2, rx_host, build) + # Implementation for multiple sessions would go here + + logger.info(f"TX Command (host {tx_host.name}): {tx_cmd}") + logger.info(f"RX Command (host {rx_host.name}): {rx_cmd}") + log_to_file(f"TX Command (host {tx_host.name}): {tx_cmd}", tx_host, build) + log_to_file(f"RX Command (host {rx_host.name}): {rx_cmd}", rx_host, build) + + rx_proc = None + tx_proc = None + tcpdump_tx = prepare_tcpdump(capture_cfg, tx_host) + tcpdump_rx = prepare_tcpdump(capture_cfg, rx_host) + + try: + # Start RX first + rx_proc = run(rx_cmd, background=True, host=rx_host) + time.sleep(2) + + # Start TX + tx_proc = run(tx_cmd, background=True, host=tx_host) + + # Wait for test completion + time.sleep(test_time + 5) + + except Exception as e: + logger.error(f"Error during dual test execution: {e}") + log_to_file(f"Error during dual test execution: {e}", tx_host, build) + log_to_file(f"Error during dual test execution: {e}", rx_host, build) + raise + finally: + # Stop processes + if tx_proc: + run(f"pkill -f '{tx_cmd.split()[0]}'", host=tx_host) + if rx_proc: + run(f"pkill -f '{rx_cmd.split()[0]}'", host=rx_host) + + # Stop tcpdump if running + if tcpdump_tx: + tcpdump_tx.stop() + if tcpdump_rx: + tcpdump_rx.stop() + + time.sleep(2) + + passed = False + match output_format: + case "yuv": + passed = check_output_video_yuv(output_files[0], rx_host, build, video_url) + case "h264": + passed = check_output_video_h264( + output_files[0], video_size, rx_host, build, video_url + ) + + # Clean up output files after validation + try: + for output_file in output_files: + run(f"rm -f {output_file}", host=rx_host) + except Exception as e: + logger.warning(f"Failed to clean up output files: {e}") + + if not passed: + log_fail(f"Dual ffmpeg test failed for {video_format}") + + return passed + + +def execute_dual_test_rgb24( + test_time: int, + build: str, + tx_host, + rx_host, + type_: str, + video_format: str, + pg_format: str, + video_url: str, + capture_cfg=None, +): + """Execute dual host RGB24 ffmpeg test""" + # Initialize logging for this test + init_test_logging() + + tx_nic_port_list = tx_host.vfs + rx_nic_port_list = rx_host.vfs + video_size, fps = decode_video_format_16_9(video_format) + + logger.info(f"Creating RX config for dual RGB24 test with video_format: {video_format}") + log_to_file( + f"Creating RX config for dual RGB24 test with video_format: {video_format}", + rx_host, + build, + ) + + try: + rx_config_file = generate_rxtxapp_rx_config( + rx_nic_port_list[0], video_format, rx_host, build + ) + except Exception as e: + logger.error(f"Failed to create RX config: {e}") + log_to_file(f"Failed to create RX config: {e}", rx_host, build) + raise + + rx_cmd = f"{RXTXAPP_PATH} --config_file {rx_config_file} --test_time {test_time}" + tx_cmd = ( + f"ffmpeg -stream_loop -1 -video_size {video_size} -f rawvideo -pix_fmt rgb24 " + f"-i {video_url} -filter:v fps={fps} -p_port {tx_nic_port_list[0]} " + f"-p_sip {ip_dict['tx_interfaces']} -p_tx_ip {ip_dict['tx_sessions']} " + f"-udp_port 20000 -payload_type 112 -f mtl_st20p -" + ) + + logger.info(f"TX Command (host {tx_host.name}): {tx_cmd}") + logger.info(f"RX Command (host {rx_host.name}): {rx_cmd}") + log_to_file(f"TX Command (host {tx_host.name}): {tx_cmd}", tx_host, build) + log_to_file(f"RX Command (host {rx_host.name}): {rx_cmd}", rx_host, build) + + rx_proc = None + tx_proc = None + tcpdump_tx = prepare_tcpdump(capture_cfg, tx_host) + tcpdump_rx = prepare_tcpdump(capture_cfg, rx_host) + + try: + # Start RX first + rx_proc = run(rx_cmd, background=True, host=rx_host) + time.sleep(2) + + # Start TX + tx_proc = run(tx_cmd, background=True, host=tx_host) + + # Wait for test completion + time.sleep(test_time + 5) + + # Get RX output + rx_output = rx_proc.stdout_text if rx_proc else "" + + except Exception as e: + logger.error(f"Error during dual RGB24 test execution: {e}") + log_to_file(f"Error during dual RGB24 test execution: {e}", tx_host, build) + log_to_file(f"Error during dual RGB24 test execution: {e}", rx_host, build) + raise + finally: + # Stop processes + if tx_proc: + run(f"pkill -f '{tx_cmd.split()[0]}'", host=tx_host) + if rx_proc: + run(f"pkill -f '{rx_cmd.split()[0]}'", host=rx_host) + + # Stop tcpdump if running + if tcpdump_tx: + tcpdump_tx.stop() + if tcpdump_rx: + tcpdump_rx.stop() + + time.sleep(2) + + if not check_output_rgb24(rx_output, 1): + log_fail(f"Dual RGB24 ffmpeg test failed for {video_format}") + + time.sleep(5) + return True + + +def execute_dual_test_rgb24_multiple( + test_time: int, + build: str, + tx_host, + rx_host, + type_: str, + video_format_list: list, + pg_format: str, + video_url_list: list, + capture_cfg=None, +): + """Execute dual host RGB24 multiple sessions ffmpeg test""" + # Initialize logging for this test + init_test_logging() + + tx_nic_port_list = tx_host.vfs + rx_nic_port_list = rx_host.vfs + video_size_1, fps_1 = decode_video_format_16_9(video_format_list[0]) + video_size_2, fps_2 = decode_video_format_16_9(video_format_list[1]) + + logger.info( + f"Creating RX config for dual RGB24 multiple test with video_formats: {video_format_list}" + ) + log_to_file( + f"Creating RX config for dual RGB24 multiple test with video_formats: {video_format_list}", + rx_host, + build, + ) + + try: + rx_config_file = generate_rxtxapp_rx_config_multiple( + rx_nic_port_list[:2], video_format_list, rx_host, build, True + ) + except Exception as e: + logger.error(f"Failed to create RX config: {e}") + log_to_file(f"Failed to create RX config: {e}", rx_host, build) + raise + + rx_cmd = f"{RXTXAPP_PATH} --config_file {rx_config_file} --test_time {test_time}" + tx_1_cmd = ( + f"ffmpeg -stream_loop -1 -video_size {video_size_1} -f rawvideo -pix_fmt rgb24 " + f"-i {video_url_list[0]} -filter:v fps={fps_1} -p_port {tx_nic_port_list[0]} " + f"-p_sip {ip_dict_rgb24_multiple['p_sip_1']} " + f"-p_tx_ip {ip_dict_rgb24_multiple['p_tx_ip_1']} " + f"-udp_port 20000 -payload_type 112 -f mtl_st20p -" + ) + tx_2_cmd = ( + f"ffmpeg -stream_loop -1 -video_size {video_size_2} -f rawvideo -pix_fmt rgb24 " + f"-i {video_url_list[1]} -filter:v fps={fps_2} -p_port {tx_nic_port_list[1]} " + f"-p_sip {ip_dict_rgb24_multiple['p_sip_2']} " + f"-p_tx_ip {ip_dict_rgb24_multiple['p_tx_ip_2']} " + f"-udp_port 20000 -payload_type 112 -f mtl_st20p -" + ) + + logger.info(f"TX1 Command (host {tx_host.name}): {tx_1_cmd}") + logger.info(f"TX2 Command (host {tx_host.name}): {tx_2_cmd}") + logger.info(f"RX Command (host {rx_host.name}): {rx_cmd}") + log_to_file(f"TX1 Command (host {tx_host.name}): {tx_1_cmd}", tx_host, build) + log_to_file(f"TX2 Command (host {tx_host.name}): {tx_2_cmd}", tx_host, build) + log_to_file(f"RX Command (host {rx_host.name}): {rx_cmd}", rx_host, build) + + rx_proc = None + tx_1_proc = None + tx_2_proc = None + tcpdump_tx = prepare_tcpdump(capture_cfg, tx_host) + tcpdump_rx = prepare_tcpdump(capture_cfg, rx_host) + + try: + # Start RX first + rx_proc = run(rx_cmd, background=True, host=rx_host) + time.sleep(2) + + # Start TX processes + tx_1_proc = run(tx_1_cmd, background=True, host=tx_host) + time.sleep(1) + tx_2_proc = run(tx_2_cmd, background=True, host=tx_host) + + # Wait for test completion + time.sleep(test_time + 5) + + # Get RX output + rx_output = rx_proc.stdout_text if rx_proc else "" + + except Exception as e: + logger.error(f"Error during dual RGB24 multiple test execution: {e}") + log_to_file(f"Error during dual RGB24 multiple test execution: {e}", tx_host, build) + log_to_file(f"Error during dual RGB24 multiple test execution: {e}", rx_host, build) + raise + finally: + # Stop processes + if tx_1_proc: + run(f"pkill -f '{tx_1_cmd.split()[0]}'", host=tx_host) + if tx_2_proc: + run(f"pkill -f '{tx_2_cmd.split()[0]}'", host=tx_host) + if rx_proc: + run(f"pkill -f '{rx_cmd.split()[0]}'", host=rx_host) + + # Stop tcpdump if running + if tcpdump_tx: + tcpdump_tx.stop() + if tcpdump_rx: + tcpdump_rx.stop() + + time.sleep(2) + + if not check_output_rgb24(rx_output, 2): + log_fail(f"Dual RGB24 multiple ffmpeg test failed for {video_format_list}") + + time.sleep(5) + return True diff --git a/tests/validation/tests/dual/ffmpeg/__init__.py b/tests/validation/tests/dual/ffmpeg/__init__.py new file mode 100755 index 000000000..7aacd98ac --- /dev/null +++ b/tests/validation/tests/dual/ffmpeg/__init__.py @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024-2025 Intel Corporation diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg.py new file mode 100755 index 000000000..283532cd0 --- /dev/null +++ b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg.py @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024-2025 Intel Corporation + +import os + +import pytest +from mtl_engine import ffmpeg_app +from mtl_engine.media_files import yuv_files + + +@pytest.mark.parametrize( + "video_format, test_time_multipler,", + [ + ("i1080p25", 2), + ("i1080p50", 2), + pytest.param("i1080p60", 4, marks=pytest.mark.smoke), + ("i2160p60", 6), + ], +) +@pytest.mark.parametrize("output_format", ["yuv", "h264"]) +def test_rx_ffmpeg_tx_ffmpeg_dual( + hosts, + test_time, + build, + media, + nic_port_list, + video_format, + test_time_multipler, + output_format, + test_config, + prepare_ramdisk, +): + # Get TX and RX hosts + host_list = list(hosts.values()) + if len(host_list) < 2: + pytest.skip("Dual tests require at least 2 hosts") + + tx_host = host_list[0] + rx_host = host_list[1] + + capture_cfg = dict(test_config.get("capture_cfg", {})) + capture_cfg["test_name"] = ( + f"test_rx_ffmpeg_tx_ffmpeg_dual_{video_format}_{output_format}" + ) + + video_file = yuv_files[video_format] + + ffmpeg_app.execute_dual_test( + test_time=test_time * test_time_multipler, + build=build, + tx_host=tx_host, + rx_host=rx_host, + type_="frame", + video_format=video_format, + pg_format=video_file["format"], + video_url=os.path.join(media, video_file["filename"]), + output_format=output_format, + capture_cfg=capture_cfg, + ) diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24.py new file mode 100755 index 000000000..b00749ed5 --- /dev/null +++ b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24.py @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024-2025 Intel Corporation + +import os + +import pytest +from mtl_engine import ffmpeg_app +from mtl_engine.media_files import yuv_files + + +@pytest.mark.parametrize( + "video_format, test_time_mutlipler", + [ + ("i1080p25", 2), + ("i1080p30", 2), + ("i1080p60", 4), + ("i2160p30", 4), + ("i2160p60", 6), + ], +) +def test_rx_ffmpeg_tx_ffmpeg_rgb24_dual( + hosts, + test_time, + build, + media, + nic_port_list, + video_format, + test_time_mutlipler, + test_config, + prepare_ramdisk, +): + # Get TX and RX hosts + host_list = list(hosts.values()) + if len(host_list) < 2: + pytest.skip("Dual tests require at least 2 hosts") + + tx_host = host_list[0] + rx_host = host_list[1] + + capture_cfg = dict(test_config.get("capture_cfg", {})) + capture_cfg["test_name"] = f"test_rx_ffmpeg_tx_ffmpeg_rgb24_dual_{video_format}" + + video_file = yuv_files[video_format] + + ffmpeg_app.execute_dual_test_rgb24( + test_time=test_time * test_time_mutlipler, + build=build, + tx_host=tx_host, + rx_host=rx_host, + type_="frame", + video_format=video_format, + pg_format=video_file["format"], + video_url=os.path.join(media, video_file["filename"]), + capture_cfg=capture_cfg, + ) diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple.py new file mode 100755 index 000000000..255fe1e06 --- /dev/null +++ b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple.py @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024-2025 Intel Corporation + +import os + +import pytest +from mtl_engine import ffmpeg_app +from mtl_engine.media_files import yuv_files + + +@pytest.mark.parametrize( + "video_format_1, video_format_2, test_time_mutlipler", + [ + ("i1080p25", "i1080p25", 4), + ("i1080p30", "i1080p30", 4), + ("i1080p60", "i1080p60", 8), + ("i1080p60", "i1080p50", 8), + ("i1080p50", "i1080p30", 6), + ("i1080p25", "i1080p50", 6), + ("i1080p25", "i1080p60", 6), + ], +) +def test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual( + hosts, + test_time, + build, + media, + nic_port_list, + video_format_1, + video_format_2, + test_time_mutlipler, + test_config, + prepare_ramdisk, +): + # Get TX and RX hosts + host_list = list(hosts.values()) + if len(host_list) < 2: + pytest.skip("Dual tests require at least 2 hosts") + + tx_host = host_list[0] + rx_host = host_list[1] + + capture_cfg = dict(test_config.get("capture_cfg", {})) + capture_cfg["test_name"] = ( + f"test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual_{video_format_1}_{video_format_2}" + ) + + video_file_1 = yuv_files[video_format_1] + video_file_2 = yuv_files[video_format_2] + + ffmpeg_app.execute_dual_test_rgb24_multiple( + test_time=test_time * test_time_mutlipler, + build=build, + tx_host=tx_host, + rx_host=rx_host, + type_="frame", + video_format_list=[video_format_1, video_format_2], + pg_format=video_file_1["format"], + video_url_list=[ + os.path.join(media, video_file_1["filename"]), + os.path.join(media, video_file_2["filename"]), + ], + capture_cfg=capture_cfg, + ) diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp.py new file mode 100755 index 000000000..07a5de176 --- /dev/null +++ b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp.py @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright(c) 2024-2025 Intel Corporation + +import os + +import pytest +from mtl_engine import ffmpeg_app +from mtl_engine.media_files import yuv_files + + +@pytest.mark.parametrize( + "video_format, multiple_sessions, test_time_multipler", + [ + ("i1080p25", False, 1), + ("i1080p30", False, 1), + ("i1080p60", False, 2), + ("i2160p25", False, 2), + ("i2160p30", False, 2), + ("i2160p60", False, 2), + ("i1080p25", True, 3), + ("i1080p30", True, 3), + ], +) +@pytest.mark.parametrize("output_format", ["yuv", "h264"]) +def test_rx_ffmpeg_tx_rxtxapp_dual( + hosts, + test_time, + build, + media, + nic_port_list, + video_format, + multiple_sessions, + test_time_multipler, + output_format, + test_config, + prepare_ramdisk, +): + # Get TX and RX hosts + host_list = list(hosts.values()) + if len(host_list) < 2: + pytest.skip("Dual tests require at least 2 hosts") + + tx_host = host_list[0] + rx_host = host_list[1] + + capture_cfg = dict(test_config.get("capture_cfg", {})) + capture_cfg["test_name"] = ( + f"test_rx_ffmpeg_tx_rxtxapp_dual_{video_format}_{output_format}_{multiple_sessions}_{test_time_multipler}" + ) + + video_file = yuv_files[video_format] + + ffmpeg_app.execute_dual_test( + test_time=test_time * test_time_multipler, + build=build, + tx_host=tx_host, + rx_host=rx_host, + type_="frame", + video_format=video_format, + pg_format=video_file["format"], + video_url=os.path.join(media, video_file["filename"]), + output_format=output_format, + multiple_sessions=multiple_sessions, + tx_is_ffmpeg=False, # This test uses RxTxApp for TX + capture_cfg=capture_cfg, + ) From ef780d099b3adcf13d88dbb7b2e118aaeec148fe Mon Sep 17 00:00:00 2001 From: Bartosz Krystowski Date: Tue, 12 Aug 2025 14:00:02 +0200 Subject: [PATCH 02/35] Fix dual basic tests --- tests/validation/mtl_engine/RxTxApp.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index 8524aabe2..423ebf7fc 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -1482,7 +1482,7 @@ def add_dual_interfaces( tx_config["interfaces"][0]["name"] = tx_nic_port_list[0] # Configure RX host interface only - rx_config["interfaces"][0]["name"] = rx_nic_port_list[1] + rx_config["interfaces"][0]["name"] = rx_nic_port_list[0] if test_mode == "unicast": tx_config["interfaces"][0]["ip"] = unicast_ip_dict["tx_interfaces"] @@ -1673,20 +1673,27 @@ def execute_dual_test( tx_config = config["tx_config"] rx_config = config["rx_config"] + tx_config_json = json.dumps(tx_config, indent=4) + rx_config_json = json.dumps(rx_config, indent=4) + # Log test start logger.info(f"Starting dual RxTxApp test: {get_case_id()}") log_to_file(f"Starting dual RxTxApp test: {get_case_id()}", tx_host, build) log_to_file(f"Starting dual RxTxApp test: {get_case_id()}", rx_host, build) - log_to_file(f"TX config: {json.dumps(tx_config, indent=4)}", tx_host, build) - log_to_file(f"RX config: {json.dumps(rx_config, indent=4)}", rx_host, build) + log_to_file(f"TX config: {tx_config_json}", tx_host, build) + log_to_file(f"RX config: {rx_config_json}", rx_host, build) # Prepare TX config + tx_config_file = f"{build}/tests/tx_config.json" tx_f = tx_host.connection.path(build, "tests", "tx_config.json") - tx_f.write_text(tx_config, encoding="utf-8") + tx_json_content = tx_config_json.replace('"', '\\"') + tx_f.write_text(tx_json_content) # Prepare RX config - rx_f = tx_host.connection.path(build, "tests", "rx_config.json") - rx_f.write_text(rx_config, encoding="utf-8") + rx_config_file = f"{build}/tests/rx_config.json" + rx_f = rx_host.connection.path(build, "tests", "rx_config.json") + rx_json_content = rx_config_json.replace('"', '\\"') + rx_f.write_text(rx_json_content) # Adjust test_time for high-res/fps/replicas if ( @@ -1710,8 +1717,8 @@ def execute_dual_test( if ptp: base_command += " --ptp" - tx_command = f"{base_command} --config_file {tx_config}" - rx_command = f"{base_command} --config_file {rx_config}" + tx_command = f"{base_command} --config_file {tx_config_file}" + rx_command = f"{base_command} --config_file {rx_config_file}" logger.info(f"TX Command: {tx_command}") logger.info(f"RX Command: {rx_command}") From 1e1cab7aa685d61068c90d7b188e351b479e2050 Mon Sep 17 00:00:00 2001 From: Bartosz Krystowski Date: Tue, 12 Aug 2025 21:31:02 +0200 Subject: [PATCH 03/35] Fix dual ffmpeg tests; Change test cases names; Adjust logging; --- tests/validation/mtl_engine/ffmpeg_app.py | 548 +++++++++++++----- .../validation/tests/dual/ffmpeg/__init__.py | 2 - ...eg.py => test_rx_ffmpeg_tx_ffmpeg_dual.py} | 8 +- ...=> test_rx_ffmpeg_tx_ffmpeg_rgb24_dual.py} | 10 +- ...x_ffmpeg_tx_ffmpeg_rgb24_multiple_dual.py} | 14 +- ...p.py => test_rx_ffmpeg_tx_rxtxapp_dual.py} | 0 6 files changed, 416 insertions(+), 166 deletions(-) rename tests/validation/tests/dual/ffmpeg/{test_rx_ffmpeg_tx_ffmpeg.py => test_rx_ffmpeg_tx_ffmpeg_dual.py} (90%) rename tests/validation/tests/dual/ffmpeg/{test_rx_ffmpeg_tx_ffmpeg_rgb24.py => test_rx_ffmpeg_tx_ffmpeg_rgb24_dual.py} (90%) rename tests/validation/tests/dual/ffmpeg/{test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple.py => test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual.py} (85%) rename tests/validation/tests/dual/ffmpeg/{test_rx_ffmpeg_tx_rxtxapp.py => test_rx_ffmpeg_tx_rxtxapp_dual.py} (100%) diff --git a/tests/validation/mtl_engine/ffmpeg_app.py b/tests/validation/mtl_engine/ffmpeg_app.py index c7fa7d57e..8ddbe9029 100755 --- a/tests/validation/mtl_engine/ffmpeg_app.py +++ b/tests/validation/mtl_engine/ffmpeg_app.py @@ -113,17 +113,21 @@ def execute_test( if not multiple_sessions: output_files = create_empty_output_files(output_format, 1, host, build) rx_cmd = ( - f"ffmpeg -p_port {nic_port_list[0]} -p_sip {ip_dict['rx_interfaces']} " - f"-p_rx_ip {ip_dict['rx_sessions']} -udp_port 20000 -payload_type 112 " - f"-fps {fps} -pix_fmt yuv422p10le -video_size {video_size} " - f"-f mtl_st20p -i k {ffmpeg_rx_f_flag} {output_files[0]} -y" + f"ffmpeg -p_port {nic_port_list[0]} " + f"-p_sip {ip_dict['rx_interfaces']} " + f"-p_rx_ip {ip_dict['rx_sessions']} -udp_port 20000 " + f"-payload_type 112 -fps {fps} -pix_fmt yuv422p10le " + f"-video_size {video_size} -f mtl_st20p -i k " + f"{ffmpeg_rx_f_flag} {output_files[0]} -y" ) if tx_is_ffmpeg: tx_cmd = ( - f"ffmpeg -video_size {video_size} -f rawvideo -pix_fmt yuv422p10le " - f"-i {video_url} -filter:v fps={fps} -p_port {nic_port_list[1]} " - f"-p_sip {ip_dict['tx_interfaces']} -p_tx_ip {ip_dict['tx_sessions']} " - f"-udp_port 20000 -payload_type 112 -f mtl_st20p -" + f"ffmpeg -video_size {video_size} -f rawvideo " + f"-pix_fmt yuv422p10le -i {video_url} " + f"-filter:v fps={fps} -p_port {nic_port_list[1]} " + f"-p_sip {ip_dict['tx_interfaces']} " + f"-p_tx_ip {ip_dict['tx_sessions']} -udp_port 20000 " + f"-payload_type 112 -f mtl_st20p -" ) else: # tx is rxtxapp tx_config_file = generate_rxtxapp_tx_config( @@ -134,21 +138,25 @@ def execute_test( output_files = create_empty_output_files(output_format, 2, host, build) rx_cmd = ( f"ffmpeg -p_sip {ip_dict['rx_interfaces']} " - f"-p_port {nic_port_list[0]} -p_rx_ip {ip_dict['rx_sessions']} " - f"-udp_port 20000 -payload_type 112 -fps {fps} -pix_fmt yuv422p10le " + f"-p_port {nic_port_list[0]} " + f"-p_rx_ip {ip_dict['rx_sessions']} -udp_port 20000 " + f"-payload_type 112 -fps {fps} -pix_fmt yuv422p10le " f"-video_size {video_size} -f mtl_st20p -i 1 " - f"-p_port {nic_port_list[0]} -p_rx_ip {ip_dict['rx_sessions']} " - f"-udp_port 20002 -payload_type 112 -fps {fps} -pix_fmt yuv422p10le " + f"-p_port {nic_port_list[0]} " + f"-p_rx_ip {ip_dict['rx_sessions']} -udp_port 20002 " + f"-payload_type 112 -fps {fps} -pix_fmt yuv422p10le " f"-video_size {video_size} -f mtl_st20p -i 2 " f"-map 0:0 {ffmpeg_rx_f_flag} {output_files[0]} -y " f"-map 1:0 {ffmpeg_rx_f_flag} {output_files[1]} -y" ) if tx_is_ffmpeg: tx_cmd = ( - f"ffmpeg -video_size {video_size} -f rawvideo -pix_fmt yuv422p10le " - f"-i {video_url} -filter:v fps={fps} -p_port {nic_port_list[1]} " - f"-p_sip {ip_dict['tx_interfaces']} -p_tx_ip {ip_dict['tx_sessions']} " - f"-udp_port 20000 -payload_type 112 -f mtl_st20p -" + f"ffmpeg -video_size {video_size} -f rawvideo " + f"-pix_fmt yuv422p10le -i {video_url} " + f"-filter:v fps={fps} -p_port {nic_port_list[1]} " + f"-p_sip {ip_dict['tx_interfaces']} " + f"-p_tx_ip {ip_dict['tx_sessions']} -udp_port 20000 " + f"-payload_type 112 -f mtl_st20p -" ) else: # tx is rxtxapp tx_config_file = generate_rxtxapp_tx_config( @@ -1009,21 +1017,20 @@ def execute_dual_test( tx_is_ffmpeg: bool = True, capture_cfg=None, ): + # Initialize logging for this test init_test_logging() - case_id = os.environ.get("PYTEST_CURRENT_TEST", "ffmpeg_dual_test") + case_id = os.environ.get("PYTEST_CURRENT_TEST", "ffmpeg_test") case_id = case_id[: case_id.rfind("(") - 1] if "(" in case_id else case_id tx_nic_port_list = tx_host.vfs rx_nic_port_list = rx_host.vfs video_size, fps = decode_video_format_16_9(video_format) - match output_format: case "yuv": ffmpeg_rx_f_flag = "-f rawvideo" case "h264": ffmpeg_rx_f_flag = "-c:v libopenh264" - if not multiple_sessions: output_files = create_empty_output_files(output_format, 1, rx_host, build) rx_cmd = ( @@ -1034,61 +1041,154 @@ def execute_dual_test( ) if tx_is_ffmpeg: tx_cmd = ( - f"ffmpeg -stream_loop -1 -video_size {video_size} -f rawvideo -pix_fmt yuv422p10le " + f"ffmpeg -video_size {video_size} -f rawvideo -pix_fmt yuv422p10le " f"-i {video_url} -filter:v fps={fps} -p_port {tx_nic_port_list[0]} " f"-p_sip {ip_dict['tx_interfaces']} -p_tx_ip {ip_dict['tx_sessions']} " f"-udp_port 20000 -payload_type 112 -f mtl_st20p -" ) - else: + else: # tx is rxtxapp tx_config_file = generate_rxtxapp_tx_config( - tx_nic_port_list[0], video_format, video_url, tx_host, build, multiple_sessions + tx_nic_port_list[0], video_format, video_url, tx_host, build ) - tx_cmd = f"{RXTXAPP_PATH} --config_file {tx_config_file} --test_time {test_time}" + tx_cmd = f"{RXTXAPP_PATH} --config_file {tx_config_file}" else: # multiple sessions output_files = create_empty_output_files(output_format, 2, rx_host, build) - # Implementation for multiple sessions would go here + rx_cmd = ( + f"ffmpeg -p_sip {ip_dict['rx_interfaces']} " + f"-p_port {rx_nic_port_list[0]} -p_rx_ip {ip_dict['rx_sessions']} " + f"-udp_port 20000 -payload_type 112 -fps {fps} -pix_fmt yuv422p10le " + f"-video_size {video_size} -f mtl_st20p -i 1 " + f"-p_port {rx_nic_port_list[0]} -p_rx_ip {ip_dict['rx_sessions']} " + f"-udp_port 20002 -payload_type 112 -fps {fps} -pix_fmt yuv422p10le " + f"-video_size {video_size} -f mtl_st20p -i 2 " + f"-map 0:0 {ffmpeg_rx_f_flag} {output_files[0]} -y " + f"-map 1:0 {ffmpeg_rx_f_flag} {output_files[1]} -y" + ) + if tx_is_ffmpeg: + tx_cmd = ( + f"ffmpeg -video_size {video_size} -f rawvideo -pix_fmt yuv422p10le " + f"-i {video_url} -filter:v fps={fps} -p_port {tx_nic_port_list[0]} " + f"-p_sip {ip_dict['tx_interfaces']} -p_tx_ip {ip_dict['tx_sessions']} " + f"-udp_port 20000 -payload_type 112 -f mtl_st20p -" + ) + else: # tx is rxtxapp + tx_config_file = generate_rxtxapp_tx_config( + tx_nic_port_list[0], video_format, video_url, tx_host, build, True + ) + tx_cmd = f"{RXTXAPP_PATH} --config_file {tx_config_file}" - logger.info(f"TX Command (host {tx_host.name}): {tx_cmd}") - logger.info(f"RX Command (host {rx_host.name}): {rx_cmd}") - log_to_file(f"TX Command (host {tx_host.name}): {tx_cmd}", tx_host, build) - log_to_file(f"RX Command (host {rx_host.name}): {rx_cmd}", rx_host, build) + logger.info(f"TX Host: {tx_host}") + logger.info(f"RX Host: {rx_host}") + logger.info(f"RX Command: {rx_cmd}") + logger.info(f"TX Command: {tx_cmd}") + log_to_file(f"TX Host: {tx_host}", rx_host, build) + log_to_file(f"RX Host: {rx_host}", rx_host, build) + log_to_file(f"RX Command: {rx_cmd}", rx_host, build) + log_to_file(f"TX Command: {tx_cmd}", tx_host, build) rx_proc = None tx_proc = None - tcpdump_tx = prepare_tcpdump(capture_cfg, tx_host) - tcpdump_rx = prepare_tcpdump(capture_cfg, rx_host) + # Use RX host for tcpdump capture + tcpdump = prepare_tcpdump(capture_cfg, rx_host) try: - # Start RX first - rx_proc = run(rx_cmd, background=True, host=rx_host) + # Start RX pipeline first on RX host + logger.info("Starting RX pipeline on RX host...") + rx_proc = run( + rx_cmd, + cwd=build, + timeout=test_time + 60, + testcmd=True, + host=rx_host, + background=True, + enable_sudo=True, + ) time.sleep(2) - - # Start TX - tx_proc = run(tx_cmd, background=True, host=tx_host) - - # Wait for test completion - time.sleep(test_time + 5) - + + # Start TX pipeline on TX host + logger.info("Starting TX pipeline on TX host...") + tx_proc = run( + tx_cmd, + cwd=build, + timeout=test_time + 60, + testcmd=True, + host=tx_host, + background=True, + enable_sudo=True, + ) + # Start tcpdump after pipelines are running + if tcpdump: + logger.info("Starting tcpdump capture...") + tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) + + # Let the test run for the specified duration + logger.info(f"Running test for {test_time} seconds...") + time.sleep(test_time) + + logger.info("Terminating processes...") + if tx_proc: + try: + tx_proc.terminate() + except Exception: + pass + if rx_proc: + try: + rx_proc.terminate() + except Exception: + pass + # Wait a bit for termination + time.sleep(2) + # Get output after processes have been terminated + try: + rx_output = f"RX Output:\n{rx_proc.stdout_text}" + log_to_file(rx_output, rx_host, build) + except Exception: + logger.info("Could not retrieve RX output") + try: + tx_output = f"TX Output:\n{tx_proc.stdout_text}" + log_to_file(tx_output, tx_host, build) + except Exception: + logger.info("Could not retrieve TX output") except Exception as e: - logger.error(f"Error during dual test execution: {e}") - log_to_file(f"Error during dual test execution: {e}", tx_host, build) - log_to_file(f"Error during dual test execution: {e}", rx_host, build) + log_fail(f"Error during test execution: {e}") + # Terminate processes immediately on error + if tx_proc: + try: + tx_proc.terminate() + except Exception: + pass + if rx_proc: + try: + rx_proc.terminate() + except Exception: + pass raise finally: - # Stop processes + # Ensure processes are terminated with force kill if needed if tx_proc: - run(f"pkill -f '{tx_cmd.split()[0]}'", host=tx_host) + try: + tx_proc.terminate() + tx_proc.wait(timeout=5) + except Exception: + try: + # Force kill if terminate didn't work + tx_proc.kill() + tx_proc.wait(timeout=5) + except Exception: + pass if rx_proc: - run(f"pkill -f '{rx_cmd.split()[0]}'", host=rx_host) - - # Stop tcpdump if running - if tcpdump_tx: - tcpdump_tx.stop() - if tcpdump_rx: - tcpdump_rx.stop() - - time.sleep(2) - + try: + rx_proc.terminate() + rx_proc.wait(timeout=5) + except Exception: + try: + # Force kill if terminate didn't work + rx_proc.kill() + rx_proc.wait(timeout=5) + except Exception: + pass + if tcpdump: + tcpdump.stop() passed = False match output_format: case "yuv": @@ -1097,17 +1197,15 @@ def execute_dual_test( passed = check_output_video_h264( output_files[0], video_size, rx_host, build, video_url ) - # Clean up output files after validation try: for output_file in output_files: run(f"rm -f {output_file}", host=rx_host) + logger.info(f"Removed output file: {output_file}") except Exception as e: - logger.warning(f"Failed to clean up output files: {e}") - + logger.info(f"Could not remove output files: {e}") if not passed: - log_fail(f"Dual ffmpeg test failed for {video_format}") - + log_fail("test failed") return passed @@ -1122,30 +1220,33 @@ def execute_dual_test_rgb24( video_url: str, capture_cfg=None, ): - """Execute dual host RGB24 ffmpeg test""" # Initialize logging for this test init_test_logging() + # Use separate NIC port lists for TX and RX hosts tx_nic_port_list = tx_host.vfs rx_nic_port_list = rx_host.vfs video_size, fps = decode_video_format_16_9(video_format) - logger.info(f"Creating RX config for dual RGB24 test with video_format: {video_format}") + logger.info(f"Creating RX config for RGB24 dual test with video_format: {video_format}") log_to_file( - f"Creating RX config for dual RGB24 test with video_format: {video_format}", + f"Creating RX config for RGB24 dual test with video_format: {video_format}", rx_host, build, ) - try: rx_config_file = generate_rxtxapp_rx_config( rx_nic_port_list[0], video_format, rx_host, build ) + logger.info(f"Successfully created RX config file: {rx_config_file}") + log_to_file( + f"Successfully created RX config file: {rx_config_file}", rx_host, build + ) except Exception as e: - logger.error(f"Failed to create RX config: {e}") - log_to_file(f"Failed to create RX config: {e}", rx_host, build) - raise - + log_fail(f"Failed to create RX config file: {e}") + log_to_file(f"Failed to create RX config file: {e}", rx_host, build) + return False + rx_cmd = f"{RXTXAPP_PATH} --config_file {rx_config_file} --test_time {test_time}" tx_cmd = ( f"ffmpeg -stream_loop -1 -video_size {video_size} -f rawvideo -pix_fmt rgb24 " @@ -1154,53 +1255,129 @@ def execute_dual_test_rgb24( f"-udp_port 20000 -payload_type 112 -f mtl_st20p -" ) - logger.info(f"TX Command (host {tx_host.name}): {tx_cmd}") - logger.info(f"RX Command (host {rx_host.name}): {rx_cmd}") - log_to_file(f"TX Command (host {tx_host.name}): {tx_cmd}", tx_host, build) - log_to_file(f"RX Command (host {rx_host.name}): {rx_cmd}", rx_host, build) + logger.info(f"TX Host: {tx_host}") + logger.info(f"RX Host: {rx_host}") + logger.info(f"RX Command: {rx_cmd}") + logger.info(f"TX Command: {tx_cmd}") + log_to_file(f"TX Host: {tx_host}", rx_host, build) + log_to_file(f"RX Host: {rx_host}", rx_host, build) + log_to_file(f"RX Command: {rx_cmd}", rx_host, build) + log_to_file(f"TX Command: {tx_cmd}", tx_host, build) rx_proc = None tx_proc = None - tcpdump_tx = prepare_tcpdump(capture_cfg, tx_host) - tcpdump_rx = prepare_tcpdump(capture_cfg, rx_host) + # Use RX host for tcpdump capture + tcpdump = prepare_tcpdump(capture_cfg, rx_host) try: - # Start RX first - rx_proc = run(rx_cmd, background=True, host=rx_host) - time.sleep(2) + # Start RX pipeline first on RX host + logger.info("Starting RX pipeline on RX host...") + rx_proc = run( + rx_cmd, + cwd=build, + timeout=test_time + 60, + testcmd=True, + host=rx_host, + background=True, + enable_sudo=True, + ) + time.sleep(5) - # Start TX - tx_proc = run(tx_cmd, background=True, host=tx_host) + # Start TX pipeline on TX host + logger.info("Starting TX pipeline on TX host...") + tx_proc = run( + tx_cmd, + cwd=build, + timeout=test_time + 60, + testcmd=True, + host=tx_host, + background=True, + enable_sudo=True, + ) - # Wait for test completion - time.sleep(test_time + 5) + # Start tcpdump after pipelines are running + if tcpdump: + logger.info("Starting tcpdump capture...") + tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) + + logger.info( + f"Waiting for RX process to complete (test_time: {test_time} seconds)..." + ) + rx_proc.wait() + logger.info("RX process completed") + + # Terminate TX process after RX completes + logger.info("Terminating TX process...") + if tx_proc: + try: + tx_proc.terminate() + tx_proc.wait(timeout=5) + logger.info("TX process terminated successfully") + except Exception: + try: + tx_proc.kill() + tx_proc.wait(timeout=5) + logger.info("TX process killed") + except Exception: + logger.info("Could not terminate TX process") - # Get RX output - rx_output = rx_proc.stdout_text if rx_proc else "" + rx_output = "" + try: + rx_output = rx_proc.stdout_text + log_to_file(f"RX Output:\n{rx_output}", rx_host, build) + logger.info("RX output captured successfully") + except Exception as e: + logger.info(f"Error retrieving RX output: {e}") + log_to_file(f"Error retrieving RX output: {e}", rx_host, build) + + try: + log_to_file(f"TX Output:\n{tx_proc.stdout_text}", tx_host, build) + logger.info("TX output captured successfully") + except Exception as e: + log_to_file(f"Error retrieving TX output: {e}", tx_host, build) except Exception as e: - logger.error(f"Error during dual RGB24 test execution: {e}") - log_to_file(f"Error during dual RGB24 test execution: {e}", tx_host, build) - log_to_file(f"Error during dual RGB24 test execution: {e}", rx_host, build) + log_fail(f"Error during test execution: {e}") + # Terminate processes immediately on error + if tx_proc: + try: + tx_proc.terminate() + except Exception: + pass + if rx_proc: + try: + rx_proc.terminate() + except Exception: + pass raise finally: - # Stop processes + # Final cleanup - ensure processes are terminated if tx_proc: - run(f"pkill -f '{tx_cmd.split()[0]}'", host=tx_host) + try: + tx_proc.terminate() + tx_proc.wait(timeout=3) + except Exception: + try: + tx_proc.kill() + tx_proc.wait(timeout=3) + except Exception: + pass if rx_proc: - run(f"pkill -f '{rx_cmd.split()[0]}'", host=rx_host) - - # Stop tcpdump if running - if tcpdump_tx: - tcpdump_tx.stop() - if tcpdump_rx: - tcpdump_rx.stop() - - time.sleep(2) - + try: + rx_proc.terminate() + rx_proc.wait(timeout=3) + except Exception: + try: + rx_proc.kill() + rx_proc.wait(timeout=3) + except Exception: + pass + if tcpdump: + tcpdump.stop() + if not check_output_rgb24(rx_output, 1): - log_fail(f"Dual RGB24 ffmpeg test failed for {video_format}") - + log_fail("rx video sessions failed") + return False time.sleep(5) return True @@ -1216,33 +1393,36 @@ def execute_dual_test_rgb24_multiple( video_url_list: list, capture_cfg=None, ): - """Execute dual host RGB24 multiple sessions ffmpeg test""" # Initialize logging for this test init_test_logging() + # Use separate NIC port lists for TX and RX hosts tx_nic_port_list = tx_host.vfs rx_nic_port_list = rx_host.vfs video_size_1, fps_1 = decode_video_format_16_9(video_format_list[0]) video_size_2, fps_2 = decode_video_format_16_9(video_format_list[1]) logger.info( - f"Creating RX config for dual RGB24 multiple test with video_formats: {video_format_list}" + f"Creating RX config for RGB24 multiple dual test with video_formats: {video_format_list}" ) log_to_file( - f"Creating RX config for dual RGB24 multiple test with video_formats: {video_format_list}", + f"Creating RX config for RGB24 multiple dual test with video_formats: {video_format_list}", rx_host, build, ) - try: rx_config_file = generate_rxtxapp_rx_config_multiple( rx_nic_port_list[:2], video_format_list, rx_host, build, True ) + logger.info(f"Successfully created RX config file: {rx_config_file}") + log_to_file( + f"Successfully created RX config file: {rx_config_file}", rx_host, build + ) except Exception as e: - logger.error(f"Failed to create RX config: {e}") - log_to_file(f"Failed to create RX config: {e}", rx_host, build) - raise - + log_fail(f"Failed to create RX config file: {e}") + log_to_file(f"Failed to create RX config file: {e}", rx_host, build) + return False + rx_cmd = f"{RXTXAPP_PATH} --config_file {rx_config_file} --test_time {test_time}" tx_1_cmd = ( f"ffmpeg -stream_loop -1 -video_size {video_size_1} -f rawvideo -pix_fmt rgb24 " @@ -1259,59 +1439,131 @@ def execute_dual_test_rgb24_multiple( f"-udp_port 20000 -payload_type 112 -f mtl_st20p -" ) - logger.info(f"TX1 Command (host {tx_host.name}): {tx_1_cmd}") - logger.info(f"TX2 Command (host {tx_host.name}): {tx_2_cmd}") - logger.info(f"RX Command (host {rx_host.name}): {rx_cmd}") - log_to_file(f"TX1 Command (host {tx_host.name}): {tx_1_cmd}", tx_host, build) - log_to_file(f"TX2 Command (host {tx_host.name}): {tx_2_cmd}", tx_host, build) - log_to_file(f"RX Command (host {rx_host.name}): {rx_cmd}", rx_host, build) + logger.info(f"TX Host: {tx_host}") + logger.info(f"RX Host: {rx_host}") + logger.info(f"RX Command: {rx_cmd}") + logger.info(f"TX1 Command: {tx_1_cmd}") + logger.info(f"TX2 Command: {tx_2_cmd}") + log_to_file(f"TX Host: {tx_host}", rx_host, build) + log_to_file(f"RX Host: {rx_host}", rx_host, build) + log_to_file(f"RX Command: {rx_cmd}", rx_host, build) + log_to_file(f"TX1 Command: {tx_1_cmd}", tx_host, build) + log_to_file(f"TX2 Command: {tx_2_cmd}", tx_host, build) rx_proc = None tx_1_proc = None tx_2_proc = None - tcpdump_tx = prepare_tcpdump(capture_cfg, tx_host) - tcpdump_rx = prepare_tcpdump(capture_cfg, rx_host) + # Use RX host for tcpdump capture + tcpdump = prepare_tcpdump(capture_cfg, rx_host) try: - # Start RX first - rx_proc = run(rx_cmd, background=True, host=rx_host) - time.sleep(2) + # Start RX pipeline first on RX host + logger.info("Starting RX pipeline on RX host...") + rx_proc = run( + rx_cmd, + cwd=build, + timeout=test_time + 60, + testcmd=True, + host=rx_host, + background=True, + enable_sudo=True, + ) + time.sleep(5) - # Start TX processes - tx_1_proc = run(tx_1_cmd, background=True, host=tx_host) - time.sleep(1) - tx_2_proc = run(tx_2_cmd, background=True, host=tx_host) + # Start TX pipelines on TX host + logger.info("Starting TX pipelines on TX host...") + tx_1_proc = run( + tx_1_cmd, + cwd=build, + timeout=test_time + 60, + testcmd=True, + host=tx_host, + background=True, + enable_sudo=True, + ) + tx_2_proc = run( + tx_2_cmd, + cwd=build, + timeout=test_time + 60, + testcmd=True, + host=tx_host, + background=True, + enable_sudo=True, + ) + + # Start tcpdump after pipelines are running + if tcpdump: + logger.info("Starting tcpdump capture...") + tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) + + logger.info(f"Waiting for RX process (test_time: {test_time} seconds)...") + rx_proc.wait() + logger.info("RX process completed") + + # Terminate TX processes after RX completes + logger.info("Terminating TX processes...") + for proc in [tx_1_proc, tx_2_proc]: + if proc: + try: + proc.terminate() + proc.wait(timeout=5) + logger.info("TX process terminated successfully") + except Exception: + try: + proc.kill() + proc.wait(timeout=5) + logger.info("TX process killed") + except Exception: + logger.info("Could not terminate TX process") - # Wait for test completion - time.sleep(test_time + 5) + rx_output = "" + try: + rx_output = rx_proc.stdout_text + log_to_file(f"RX Output:\n{rx_output}", rx_host, build) + logger.info("RX output captured successfully") + except Exception as e: + logger.info(f"Error retrieving RX output: {e}") + log_to_file(f"Error retrieving RX output: {e}", rx_host, build) - # Get RX output - rx_output = rx_proc.stdout_text if rx_proc else "" + try: + log_to_file(f"TX1 Output:\n{tx_1_proc.stdout_text}", tx_host, build) + logger.info("TX1 output captured successfully") + except Exception as e: + logger.info(f"Error retrieving TX1 output: {e}") + try: + log_to_file(f"TX2 Output:\n{tx_2_proc.stdout_text}", tx_host, build) + logger.info("TX2 output captured successfully") + except Exception as e: + logger.info(f"Error retrieving TX2 output: {e}") except Exception as e: - logger.error(f"Error during dual RGB24 multiple test execution: {e}") - log_to_file(f"Error during dual RGB24 multiple test execution: {e}", tx_host, build) - log_to_file(f"Error during dual RGB24 multiple test execution: {e}", rx_host, build) + log_fail(f"Error during test execution: {e}") + # Terminate processes immediately on error + for proc in [tx_1_proc, tx_2_proc, rx_proc]: + if proc: + try: + proc.terminate() + except Exception: + pass raise finally: - # Stop processes - if tx_1_proc: - run(f"pkill -f '{tx_1_cmd.split()[0]}'", host=tx_host) - if tx_2_proc: - run(f"pkill -f '{tx_2_cmd.split()[0]}'", host=tx_host) - if rx_proc: - run(f"pkill -f '{rx_cmd.split()[0]}'", host=rx_host) - - # Stop tcpdump if running - if tcpdump_tx: - tcpdump_tx.stop() - if tcpdump_rx: - tcpdump_rx.stop() - - time.sleep(2) - + # Final cleanup - ensure processes are terminated + for proc in [tx_1_proc, tx_2_proc, rx_proc]: + if proc: + try: + proc.terminate() + proc.wait(timeout=3) + except Exception: + try: + proc.kill() + proc.wait(timeout=3) + except Exception: + pass + if tcpdump: + tcpdump.stop() + if not check_output_rgb24(rx_output, 2): - log_fail(f"Dual RGB24 multiple ffmpeg test failed for {video_format_list}") - + log_fail("rx video session failed") + return False time.sleep(5) - return True + return True \ No newline at end of file diff --git a/tests/validation/tests/dual/ffmpeg/__init__.py b/tests/validation/tests/dual/ffmpeg/__init__.py index 7aacd98ac..e69de29bb 100755 --- a/tests/validation/tests/dual/ffmpeg/__init__.py +++ b/tests/validation/tests/dual/ffmpeg/__init__.py @@ -1,2 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright(c) 2024-2025 Intel Corporation diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py similarity index 90% rename from tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg.py rename to tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py index 283532cd0..9c37d4a00 100755 --- a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg.py +++ b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py @@ -11,10 +11,10 @@ @pytest.mark.parametrize( "video_format, test_time_multipler,", [ - ("i1080p25", 2), - ("i1080p50", 2), - pytest.param("i1080p60", 4, marks=pytest.mark.smoke), - ("i2160p60", 6), + ("i1080p25", 1), + ("i1080p50", 1), + ("i1080p60", 2), + ("i2160p60", 3), ], ) @pytest.mark.parametrize("output_format", ["yuv", "h264"]) diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_dual.py similarity index 90% rename from tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24.py rename to tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_dual.py index b00749ed5..278f0e018 100755 --- a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24.py +++ b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_dual.py @@ -11,11 +11,11 @@ @pytest.mark.parametrize( "video_format, test_time_mutlipler", [ - ("i1080p25", 2), - ("i1080p30", 2), - ("i1080p60", 4), - ("i2160p30", 4), - ("i2160p60", 6), + ("i1080p25", 1), + ("i1080p30", 1), + ("i1080p60", 2), + ("i2160p30", 2), + ("i2160p60", 3), ], ) def test_rx_ffmpeg_tx_ffmpeg_rgb24_dual( diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual.py similarity index 85% rename from tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple.py rename to tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual.py index 255fe1e06..748a79296 100755 --- a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple.py +++ b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual.py @@ -11,13 +11,13 @@ @pytest.mark.parametrize( "video_format_1, video_format_2, test_time_mutlipler", [ - ("i1080p25", "i1080p25", 4), - ("i1080p30", "i1080p30", 4), - ("i1080p60", "i1080p60", 8), - ("i1080p60", "i1080p50", 8), - ("i1080p50", "i1080p30", 6), - ("i1080p25", "i1080p50", 6), - ("i1080p25", "i1080p60", 6), + ("i1080p25", "i1080p25", 2), + ("i1080p30", "i1080p30", 2), + ("i1080p60", "i1080p60", 4), + ("i1080p60", "i1080p50", 4), + ("i1080p50", "i1080p30", 3), + ("i1080p25", "i1080p50", 3), + ("i1080p25", "i1080p60", 3), ], ) def test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual( diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp_dual.py similarity index 100% rename from tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp.py rename to tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp_dual.py From 9bac7a54993e77442bfd88e8c909ad4464678fa0 Mon Sep 17 00:00:00 2001 From: Bartosz Krystowski Date: Tue, 12 Aug 2025 21:55:40 +0200 Subject: [PATCH 04/35] Fix linter issues --- tests/validation/mtl_engine/ffmpeg_app.py | 42 ++++++++++--------- .../ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py | 2 +- .../test_rx_ffmpeg_tx_ffmpeg_rgb24_dual.py | 2 +- ...rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual.py | 2 +- .../ffmpeg/test_rx_ffmpeg_tx_rxtxapp_dual.py | 2 +- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/tests/validation/mtl_engine/ffmpeg_app.py b/tests/validation/mtl_engine/ffmpeg_app.py index 8ddbe9029..7d1b5d7e1 100755 --- a/tests/validation/mtl_engine/ffmpeg_app.py +++ b/tests/validation/mtl_engine/ffmpeg_app.py @@ -1222,13 +1222,15 @@ def execute_dual_test_rgb24( ): # Initialize logging for this test init_test_logging() - + # Use separate NIC port lists for TX and RX hosts tx_nic_port_list = tx_host.vfs rx_nic_port_list = rx_host.vfs video_size, fps = decode_video_format_16_9(video_format) - - logger.info(f"Creating RX config for RGB24 dual test with video_format: {video_format}") + + logger.info( + f"Creating RX config for RGB24 dual test with video_format: {video_format}" + ) log_to_file( f"Creating RX config for RGB24 dual test with video_format: {video_format}", rx_host, @@ -1246,7 +1248,7 @@ def execute_dual_test_rgb24( log_fail(f"Failed to create RX config file: {e}") log_to_file(f"Failed to create RX config file: {e}", rx_host, build) return False - + rx_cmd = f"{RXTXAPP_PATH} --config_file {rx_config_file} --test_time {test_time}" tx_cmd = ( f"ffmpeg -stream_loop -1 -video_size {video_size} -f rawvideo -pix_fmt rgb24 " @@ -1282,7 +1284,7 @@ def execute_dual_test_rgb24( enable_sudo=True, ) time.sleep(5) - + # Start TX pipeline on TX host logger.info("Starting TX pipeline on TX host...") tx_proc = run( @@ -1294,7 +1296,7 @@ def execute_dual_test_rgb24( background=True, enable_sudo=True, ) - + # Start tcpdump after pipelines are running if tcpdump: logger.info("Starting tcpdump capture...") @@ -1320,7 +1322,7 @@ def execute_dual_test_rgb24( logger.info("TX process killed") except Exception: logger.info("Could not terminate TX process") - + rx_output = "" try: rx_output = rx_proc.stdout_text @@ -1335,7 +1337,7 @@ def execute_dual_test_rgb24( logger.info("TX output captured successfully") except Exception as e: log_to_file(f"Error retrieving TX output: {e}", tx_host, build) - + except Exception as e: log_fail(f"Error during test execution: {e}") # Terminate processes immediately on error @@ -1374,7 +1376,7 @@ def execute_dual_test_rgb24( pass if tcpdump: tcpdump.stop() - + if not check_output_rgb24(rx_output, 1): log_fail("rx video sessions failed") return False @@ -1395,13 +1397,13 @@ def execute_dual_test_rgb24_multiple( ): # Initialize logging for this test init_test_logging() - - # Use separate NIC port lists for TX and RX hosts + + # Use separate NIC port lists for TX and RX hosts tx_nic_port_list = tx_host.vfs rx_nic_port_list = rx_host.vfs video_size_1, fps_1 = decode_video_format_16_9(video_format_list[0]) video_size_2, fps_2 = decode_video_format_16_9(video_format_list[1]) - + logger.info( f"Creating RX config for RGB24 multiple dual test with video_formats: {video_format_list}" ) @@ -1422,7 +1424,7 @@ def execute_dual_test_rgb24_multiple( log_fail(f"Failed to create RX config file: {e}") log_to_file(f"Failed to create RX config file: {e}", rx_host, build) return False - + rx_cmd = f"{RXTXAPP_PATH} --config_file {rx_config_file} --test_time {test_time}" tx_1_cmd = ( f"ffmpeg -stream_loop -1 -video_size {video_size_1} -f rawvideo -pix_fmt rgb24 " @@ -1469,7 +1471,7 @@ def execute_dual_test_rgb24_multiple( enable_sudo=True, ) time.sleep(5) - + # Start TX pipelines on TX host logger.info("Starting TX pipelines on TX host...") tx_1_proc = run( @@ -1490,7 +1492,7 @@ def execute_dual_test_rgb24_multiple( background=True, enable_sudo=True, ) - + # Start tcpdump after pipelines are running if tcpdump: logger.info("Starting tcpdump capture...") @@ -1515,7 +1517,7 @@ def execute_dual_test_rgb24_multiple( logger.info("TX process killed") except Exception: logger.info("Could not terminate TX process") - + rx_output = "" try: rx_output = rx_proc.stdout_text @@ -1524,13 +1526,13 @@ def execute_dual_test_rgb24_multiple( except Exception as e: logger.info(f"Error retrieving RX output: {e}") log_to_file(f"Error retrieving RX output: {e}", rx_host, build) - + try: log_to_file(f"TX1 Output:\n{tx_1_proc.stdout_text}", tx_host, build) logger.info("TX1 output captured successfully") except Exception as e: logger.info(f"Error retrieving TX1 output: {e}") - + try: log_to_file(f"TX2 Output:\n{tx_2_proc.stdout_text}", tx_host, build) logger.info("TX2 output captured successfully") @@ -1561,9 +1563,9 @@ def execute_dual_test_rgb24_multiple( pass if tcpdump: tcpdump.stop() - + if not check_output_rgb24(rx_output, 2): log_fail("rx video session failed") return False time.sleep(5) - return True \ No newline at end of file + return True diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py index 9c37d4a00..95d96cd4a 100755 --- a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py +++ b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py @@ -37,7 +37,7 @@ def test_rx_ffmpeg_tx_ffmpeg_dual( tx_host = host_list[0] rx_host = host_list[1] - + capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_rx_ffmpeg_tx_ffmpeg_dual_{video_format}_{output_format}" diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_dual.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_dual.py index 278f0e018..56ebfe24d 100755 --- a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_dual.py +++ b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_dual.py @@ -36,7 +36,7 @@ def test_rx_ffmpeg_tx_ffmpeg_rgb24_dual( tx_host = host_list[0] rx_host = host_list[1] - + capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_rx_ffmpeg_tx_ffmpeg_rgb24_dual_{video_format}" diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual.py index 748a79296..e831afd33 100755 --- a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual.py +++ b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual.py @@ -39,7 +39,7 @@ def test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual( tx_host = host_list[0] rx_host = host_list[1] - + capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_rx_ffmpeg_tx_ffmpeg_rgb24_multiple_dual_{video_format_1}_{video_format_2}" diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp_dual.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp_dual.py index 07a5de176..2ff984aae 100755 --- a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp_dual.py +++ b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_rxtxapp_dual.py @@ -42,7 +42,7 @@ def test_rx_ffmpeg_tx_rxtxapp_dual( tx_host = host_list[0] rx_host = host_list[1] - + capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_rx_ffmpeg_tx_rxtxapp_dual_{video_format}_{output_format}_{multiple_sessions}_{test_time_multipler}" From 442b4394983e4a7ab056bfdcbae03df09f56892d Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 13 Aug 2025 13:57:47 +0000 Subject: [PATCH 05/35] Add: netsniff-ng capturing capabilities based on already-created tcpdump capturing --- tests/validation/configs/test_config.yaml | 1 + tests/validation/create_pcap_file/netsniff.py | 115 ++++++++++++++++++ tests/validation/mtl_engine/RxTxApp.py | 63 ++++++++-- 3 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 tests/validation/create_pcap_file/netsniff.py diff --git a/tests/validation/configs/test_config.yaml b/tests/validation/configs/test_config.yaml index 45de3b956..11485a96b 100644 --- a/tests/validation/configs/test_config.yaml +++ b/tests/validation/configs/test_config.yaml @@ -3,6 +3,7 @@ mtl_path: . media_path: /mnt/media capture_cfg: enable: false + tool: netsniff-ng test_name: test_name pcap_dir: /home/ubuntu/pcap_files capture_time: 5 diff --git a/tests/validation/create_pcap_file/netsniff.py b/tests/validation/create_pcap_file/netsniff.py new file mode 100644 index 000000000..e8d467521 --- /dev/null +++ b/tests/validation/create_pcap_file/netsniff.py @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2025 Intel Corporation +import logging +import os + +from time import sleep + +from mfd_connect.exceptions import ConnectionCalledProcessError + +logger = logging.getLogger(__name__) + +STARTUP_WAIT = 2 # Default wait time after starting the process + +class NetsniffRecorder: + """ + Class to handle the recording of network traffic using netsniff-ng. + + Attributes: + host: Host object containing connection and network interfaces. + test_name (str): Name for the capture session (used for file naming). + pcap_dir (str): Directory to store the pcap files. + interface: Network interface to capture traffic on. + interface_index (int): Index of the network interface if not specified by interface. + silent (bool): Whether to run netsniff-ng in silent mode (no stdout) (default: True). + filter (str): Optional filter to apply to the capture. (default: None) + """ + + def __init__( + self, + host, + test_name: str, + pcap_dir: str, + interface = None, + interface_index: int = 0, + silent: bool = True, + filter: str | None = None, + ): + self.host = host + self.test_name = test_name + self.pcap_dir = pcap_dir + self.pcap_file = os.path.join(pcap_dir, f"{test_name}.pcap") + if interface is not None: + self.interface = interface + else: + self.interface = self.host.network_interfaces[interface_index].name + self.netsniff_process = None + self.silent = silent + self.filter = filter + + def start(self, startup_wait=STARTUP_WAIT): + """ + Starts the netsniff-ng + """ + if not self.netsniff_process or not self.netsniff_process.running: + connection = self.host.connection + try: + logger.debug(f"NETSNIFF INTERFACE NAME: {self.interface}") # TODO: Remove this debug log after testing + cmd = [ + "netsniff-ng", + "--silent" if self.silent else "", + "--in", + str(self.interface), # FIXME: It is not a proper interface name to be used here + "--out", + self.pcap_file, + if filter f"-f {self.filter}" else "", + ] + logger.info(f"Running command: {' '.join(cmd)}") + self.netsniff_process = connection.start_process( + " ".join(cmd), stderr_to_stdout=True + ) + logger.info( + f"PCAP file will be saved at: {os.path.abspath(self.pcap_file)}" + ) + + # Give netsniff-ng a moment to start and possibly error out + sleep(startup_wait) + + if not self.netsniff_process.running: + err = self.netsniff_process.stderr_text + logger.error(f"netsniff-ng failed to start. Error output:\n{err}") + return False + logger.info(f"netsniff-ng started with PID {self.netsniff_process.pid}.") + return True + except ConnectionCalledProcessError as e: + logger.error(f"Failed to start netsniff-ng: {e}") + return False + + + def capture(self, capture_time=20, startup_wait=2): + """ + Starts netsniff-ng, captures for capture_time seconds, then stops. + :param capture_time: Duration in seconds to capture packets. + :param startup_wait: Time in seconds to wait after starting netsniff-ng (default: 2) + """ + started = self.start(startup_wait=startup_wait) + if started: + logger.info(f"Capturing traffic for {capture_time} seconds...") + sleep(capture_time) + self.stop() + logger.info("Capture complete.") + else: + logger.error("netsniff-ng did not start; skipping capture.") + + + def stop(self): + """ + Stops all netsniff-ng processes on the host using pkill. + """ + connection = self.host.connection + try: + logger.info("Stopping netsniff-ng using pkill netsniff-ng...") + connection.execute_command("pkill netsniff-ng") + logger.info("netsniff-ng stopped (via pkill).") + except ConnectionCalledProcessError as e: + logger.error(f"Failed to stop netsniff-ng: {e}") diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index 8524aabe2..33b435d6f 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -10,6 +10,7 @@ import time from create_pcap_file.tcpdump import TcpDumpRecorder +from create_pcap_file.netsniff import NetsniffRecorder from mfd_connect import SSHConnection from . import rxtxapp_config @@ -103,10 +104,15 @@ def add_interfaces(config: dict, nic_port_list: list, test_mode: str) -> dict: def prepare_tcpdump(capture_cfg, host=None): """ - Prepare and (optionally) start TcpDumpRecorder if capture_cfg is enabled. - Returns the TcpDumpRecorder instance or None. + Prepare and TcpDumpRecorder if capture_cfg is enabled and tool is tcpdump. + + returns: TcpDumpRecorder instance or None. """ - if capture_cfg and capture_cfg.get("enable"): + if ( + capture_cfg + and capture_cfg.get("enable") + and capture_cfg.get("tool") == "tcpdump" + ): tcpdump = TcpDumpRecorder( host=host, test_name=capture_cfg.get("test_name", "capture"), @@ -114,6 +120,39 @@ def prepare_tcpdump(capture_cfg, host=None): interface=capture_cfg.get("interface"), ) return tcpdump + else: + logger.info( + "Not preparing tcpdump for capture as capturing not enabled " + "or tool is not tcpdump." + ) + return None + + +def prepare_netsniff(capture_cfg, host=None): + """ + Prepare and NetsniffRecorder if capture_cfg is enabled. + + returns: NetsniffRecorder instance or None. + """ + if ( + capture_cfg + and capture_cfg.get("enable") + and capture_cfg.get("tool" in ["netsniff", "netsniff-ng"]) + ): + netsniff = NetsniffRecorder( + host=host, + test_name=capture_cfg.get("test_name", "capture"), + pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), + interface=capture_cfg.get("interface"), + # TODO: Add filtering capability (src ... and dst ...) + # filter = ... + ) + return netsniff + else: + logger.info( + "Not preparing netsniff-ng for capture as capturing not enabled " + "or tool is not in [netsniff, netsniff-ng]." + ) return None @@ -567,8 +606,9 @@ def execute_test( log_to_file(f"RxTxApp Command: {command}", host, build) - # Prepare tcpdump recorder if capture is enabled in the test configuration. + # Prepare capturing programs if capture is enabled in the test configuration. tcpdump = prepare_tcpdump(capture_cfg, host) + netsniff = prepare_netsniff(capture_cfg, host) # For 4TX and 8k streams more timeout is needed timeout = test_time + 90 @@ -591,12 +631,17 @@ def execute_test( host=remote_host, ) - # Start tcpdump capture (blocking, so it captures during traffic) try: + # Start tcpdump capture (blocking, so it captures during traffic) if tcpdump: tcpdump.capture(capture_time=capture_cfg.get("capture_time", 0.5)) logger.info(f"Started tcpdump capture on host {host.name}") log_to_file("Started tcpdump capture", host, build) + # Start netsniff-ng capture (blocking, so it captures during traffic) + if netsniff: + netsniff.capture(capture_time=capture_cfg.get("capture_time", 0.5)) + logger.info(f"Started netsniff-ng capture on host {host.name}") + log_to_file("Started netsniff-ng capture", host, build) finally: cp.wait() @@ -760,7 +805,8 @@ def execute_perf_test( # Prepare tcpdump recorder if capture is enabled in the test configuration. tcpdump = prepare_tcpdump(capture_cfg, host) - background = tcpdump is not None + netsniff = prepare_netsniff(capture_cfg, host) + background = (tcpdump is not None) or (netsniff is not None) # For 4TX and 8k streams more timeout is needed # Also scale timeout with replica count for performance tests @@ -812,11 +858,14 @@ def execute_perf_test( background=background, ) - # Start tcpdump capture (blocking, so it captures during traffic) try: + # Start tcpdump capture (blocking, so it captures during traffic) if tcpdump: tcpdump.capture(capture_time=capture_cfg.get("capture_time", 0.5)) log_to_file("Started performance test tcpdump capture", host, build) + if netsniff: + netsniff.capture(capture_time=capture_cfg.get("capture_time", 0.5)) + log_to_file("Started performance test netsniff-ng capture", host, build) finally: cp.wait() From 57dffb1896bf0f61fff9bf9b32817fbdaa0b50e4 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 13 Aug 2025 14:05:50 +0000 Subject: [PATCH 06/35] Doc: Modifying comments about packet capture --- .../tests/single/kernel_socket/kernel_lo/test_kernel_lo.py | 2 +- .../kernel_socket/kernel_lo_st20p/test_kernel_lo_st20p.py | 2 +- .../kernel_socket/kernel_lo_st22p/test_kernel_lo_st22p.py | 2 +- .../kernel_socket/pmd_kernel/mixed/test_pmd_kernel_mixed.py | 2 +- .../kernel_socket/pmd_kernel/video/test_pmd_kernel_video.py | 2 +- .../tests/single/ptp/test_mode/mixed/test_mixed_format.py | 2 +- .../tests/single/ptp/test_mode/video/test_video_format.py | 2 +- .../tests/single/rss_mode/audio/test_rss_mode_audio.py | 2 +- .../tests/single/rss_mode/video/test_performance.py | 2 +- .../tests/single/rss_mode/video/test_rss_mode_video.py | 2 +- tests/validation/tests/single/rx_timing/mixed/test_mode.py | 2 +- .../validation/tests/single/rx_timing/video/test_replicas.py | 2 +- .../tests/single/rx_timing/video/test_video_format.py | 2 +- tests/validation/tests/single/st20p/format/test_format.py | 4 ++-- tests/validation/tests/single/st20p/fps/test_fps.py | 2 +- .../validation/tests/single/st20p/integrity/test_integrity.py | 2 +- .../validation/tests/single/st20p/interlace/test_interlace.py | 2 +- tests/validation/tests/single/st20p/pacing/test_pacing.py | 2 +- tests/validation/tests/single/st20p/packing/test_packing.py | 2 +- .../tests/single/st20p/resolutions/test_resolutions.py | 2 +- .../validation/tests/single/st20p/test_mode/test_multicast.py | 2 +- tests/validation/tests/single/st22p/codec/test_codec.py | 2 +- tests/validation/tests/single/st22p/format/test_format.py | 2 +- tests/validation/tests/single/st22p/fps/test_fps.py | 2 +- .../validation/tests/single/st22p/interlace/test_interlace.py | 2 +- tests/validation/tests/single/st22p/quality/test_quality.py | 2 +- tests/validation/tests/single/st22p/test_mode/test_unicast.py | 2 +- .../validation/tests/single/st30p/integrity/test_integrity.py | 2 +- .../tests/single/st30p/st30p_channel/test_st30p_channel.py | 2 +- .../tests/single/st30p/st30p_format/test_st30p_format.py | 2 +- .../tests/single/st30p/st30p_ptime/test_st30p_ptime.py | 2 +- .../tests/single/st30p/st30p_sampling/test_st30p_sampling.py | 2 +- .../validation/tests/single/st30p/test_mode/test_multicast.py | 2 +- tests/validation/tests/single/st41/dit/test_dit.py | 2 +- tests/validation/tests/single/st41/fps/test_fps.py | 2 +- tests/validation/tests/single/st41/k_bit/test_k_bit.py | 2 +- tests/validation/tests/single/st41/no_chain/test_no_chain.py | 2 +- .../tests/single/st41/payload_type/test_payload_type.py | 2 +- .../validation/tests/single/st41/type_mode/test_type_mode.py | 2 +- tests/validation/tests/single/virtio_user/test_virtio_user.py | 2 +- tests/validation/tests/single/xdp/test_mode/test_xdp_mode.py | 2 +- .../tests/single/xdp/test_standard/test_standard.py | 2 +- 42 files changed, 43 insertions(+), 43 deletions(-) diff --git a/tests/validation/tests/single/kernel_socket/kernel_lo/test_kernel_lo.py b/tests/validation/tests/single/kernel_socket/kernel_lo/test_kernel_lo.py index 7a145a595..8ecea9637 100755 --- a/tests/validation/tests/single/kernel_socket/kernel_lo/test_kernel_lo.py +++ b/tests/validation/tests/single/kernel_socket/kernel_lo/test_kernel_lo.py @@ -27,7 +27,7 @@ def test_kernello_mixed_format( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_kernel_lo_{test_mode}_{video_format}_replicas{replicas}" diff --git a/tests/validation/tests/single/kernel_socket/kernel_lo_st20p/test_kernel_lo_st20p.py b/tests/validation/tests/single/kernel_socket/kernel_lo_st20p/test_kernel_lo_st20p.py index a14f0f4d4..d6fadba81 100755 --- a/tests/validation/tests/single/kernel_socket/kernel_lo_st20p/test_kernel_lo_st20p.py +++ b/tests/validation/tests/single/kernel_socket/kernel_lo_st20p/test_kernel_lo_st20p.py @@ -24,7 +24,7 @@ def test_kernello_st20p_video_format( st20p_file = yuv_files_422p10le[file] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_kernel_lo_st20p_{test_mode}_{file}_replicas{replicas}" diff --git a/tests/validation/tests/single/kernel_socket/kernel_lo_st22p/test_kernel_lo_st22p.py b/tests/validation/tests/single/kernel_socket/kernel_lo_st22p/test_kernel_lo_st22p.py index 25065fe7a..0efacffb2 100755 --- a/tests/validation/tests/single/kernel_socket/kernel_lo_st22p/test_kernel_lo_st22p.py +++ b/tests/validation/tests/single/kernel_socket/kernel_lo_st22p/test_kernel_lo_st22p.py @@ -25,7 +25,7 @@ def test_kernello_st22p_video_format( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_kernel_lo_st22p_{test_mode}_{file}_replicas{replicas}" diff --git a/tests/validation/tests/single/kernel_socket/pmd_kernel/mixed/test_pmd_kernel_mixed.py b/tests/validation/tests/single/kernel_socket/pmd_kernel/mixed/test_pmd_kernel_mixed.py index aefec6487..223dfe91e 100755 --- a/tests/validation/tests/single/kernel_socket/pmd_kernel/mixed/test_pmd_kernel_mixed.py +++ b/tests/validation/tests/single/kernel_socket/pmd_kernel/mixed/test_pmd_kernel_mixed.py @@ -28,7 +28,7 @@ def test_pmd_kernel_mixed_format( # rxtxapp.check_and_bind_interface(["0000:38:00.0","0000:38:00.1"], "pmd") # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_pmd_kernel_mixed_{test_mode}_{video_format}_replicas{replicas}" diff --git a/tests/validation/tests/single/kernel_socket/pmd_kernel/video/test_pmd_kernel_video.py b/tests/validation/tests/single/kernel_socket/pmd_kernel/video/test_pmd_kernel_video.py index 3d7ddcf6d..ecee36994 100755 --- a/tests/validation/tests/single/kernel_socket/pmd_kernel/video/test_pmd_kernel_video.py +++ b/tests/validation/tests/single/kernel_socket/pmd_kernel/video/test_pmd_kernel_video.py @@ -28,7 +28,7 @@ def test_pmd_kernel_video_format( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_pmd_kernel_video_{test_mode}_{video_format}_replicas{replicas}" diff --git a/tests/validation/tests/single/ptp/test_mode/mixed/test_mixed_format.py b/tests/validation/tests/single/ptp/test_mode/mixed/test_mixed_format.py index 81a2df086..4d586cd25 100755 --- a/tests/validation/tests/single/ptp/test_mode/mixed/test_mixed_format.py +++ b/tests/validation/tests/single/ptp/test_mode/mixed/test_mixed_format.py @@ -29,7 +29,7 @@ def test_ptp_mixed_format( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_ptp_mixed_format_{test_mode}_{video_format}" diff --git a/tests/validation/tests/single/ptp/test_mode/video/test_video_format.py b/tests/validation/tests/single/ptp/test_mode/video/test_video_format.py index 2bd8b410a..9d3a18a61 100755 --- a/tests/validation/tests/single/ptp/test_mode/video/test_video_format.py +++ b/tests/validation/tests/single/ptp/test_mode/video/test_video_format.py @@ -31,7 +31,7 @@ def test_ptp_video_format( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_ptp_video_format_{test_mode}_{video_format}_replicas{replicas}" diff --git a/tests/validation/tests/single/rss_mode/audio/test_rss_mode_audio.py b/tests/validation/tests/single/rss_mode/audio/test_rss_mode_audio.py index aa363a3d6..6bbc01160 100755 --- a/tests/validation/tests/single/rss_mode/audio/test_rss_mode_audio.py +++ b/tests/validation/tests/single/rss_mode/audio/test_rss_mode_audio.py @@ -24,7 +24,7 @@ def test_rss_mode_audio( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_rss_mode_audio_{audio_format}_{rss_mode}" diff --git a/tests/validation/tests/single/rss_mode/video/test_performance.py b/tests/validation/tests/single/rss_mode/video/test_performance.py index 683efbbbf..990aa0ad2 100755 --- a/tests/validation/tests/single/rss_mode/video/test_performance.py +++ b/tests/validation/tests/single/rss_mode/video/test_performance.py @@ -29,7 +29,7 @@ def test_rss_mode_video_performance( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_rss_mode_video_performance_{video_format}_{rss_mode}" diff --git a/tests/validation/tests/single/rss_mode/video/test_rss_mode_video.py b/tests/validation/tests/single/rss_mode/video/test_rss_mode_video.py index 2386cfc32..4519e0c14 100755 --- a/tests/validation/tests/single/rss_mode/video/test_rss_mode_video.py +++ b/tests/validation/tests/single/rss_mode/video/test_rss_mode_video.py @@ -24,7 +24,7 @@ def test_rss_mode_video( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_rss_mode_video_{video_format}_{rss_mode}" diff --git a/tests/validation/tests/single/rx_timing/mixed/test_mode.py b/tests/validation/tests/single/rx_timing/mixed/test_mode.py index b6d4981c1..15a0f74a5 100755 --- a/tests/validation/tests/single/rx_timing/mixed/test_mode.py +++ b/tests/validation/tests/single/rx_timing/mixed/test_mode.py @@ -24,7 +24,7 @@ def test_rx_timing_mode( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_rx_timing_mode_{test_mode}" diff --git a/tests/validation/tests/single/rx_timing/video/test_replicas.py b/tests/validation/tests/single/rx_timing/video/test_replicas.py index 3783273c5..5b1531623 100755 --- a/tests/validation/tests/single/rx_timing/video/test_replicas.py +++ b/tests/validation/tests/single/rx_timing/video/test_replicas.py @@ -19,7 +19,7 @@ def test_rx_timing_video_replicas( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = "test_rx_timing_video_replicas" diff --git a/tests/validation/tests/single/rx_timing/video/test_video_format.py b/tests/validation/tests/single/rx_timing/video/test_video_format.py index ca96e6a9f..0fd4149fd 100755 --- a/tests/validation/tests/single/rx_timing/video/test_video_format.py +++ b/tests/validation/tests/single/rx_timing/video/test_video_format.py @@ -33,7 +33,7 @@ def test_rx_timing_video_video_format( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_rx_timing_video_video_format_{video_format}" diff --git a/tests/validation/tests/single/st20p/format/test_format.py b/tests/validation/tests/single/st20p/format/test_format.py index ad350a707..e4aa7da58 100644 --- a/tests/validation/tests/single/st20p/format/test_format.py +++ b/tests/validation/tests/single/st20p/format/test_format.py @@ -25,7 +25,7 @@ def test_422p10le( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_format_{file}" # Set a unique pcap file name @@ -182,7 +182,7 @@ def test_formats( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_format_formats_{format}" # Set a unique pcap file name diff --git a/tests/validation/tests/single/st20p/fps/test_fps.py b/tests/validation/tests/single/st20p/fps/test_fps.py index 34cc73aa7..032d90d19 100644 --- a/tests/validation/tests/single/st20p/fps/test_fps.py +++ b/tests/validation/tests/single/st20p/fps/test_fps.py @@ -39,7 +39,7 @@ def test_fps( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_fps_{file}_{fps}" # Set a unique pcap file name diff --git a/tests/validation/tests/single/st20p/integrity/test_integrity.py b/tests/validation/tests/single/st20p/integrity/test_integrity.py index 87d61fe3c..722bb92fb 100644 --- a/tests/validation/tests/single/st20p/integrity/test_integrity.py +++ b/tests/validation/tests/single/st20p/integrity/test_integrity.py @@ -44,7 +44,7 @@ def test_integrity( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) # Set a unique pcap file name capture_cfg["test_name"] = ( diff --git a/tests/validation/tests/single/st20p/interlace/test_interlace.py b/tests/validation/tests/single/st20p/interlace/test_interlace.py index 3d86b0ff6..d1d3e5328 100755 --- a/tests/validation/tests/single/st20p/interlace/test_interlace.py +++ b/tests/validation/tests/single/st20p/interlace/test_interlace.py @@ -15,7 +15,7 @@ def test_interlace( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name # capture_time: 15 capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_interlace_{file}" # Set a unique pcap file name diff --git a/tests/validation/tests/single/st20p/pacing/test_pacing.py b/tests/validation/tests/single/st20p/pacing/test_pacing.py index ce8e57bec..bcf3ecce7 100755 --- a/tests/validation/tests/single/st20p/pacing/test_pacing.py +++ b/tests/validation/tests/single/st20p/pacing/test_pacing.py @@ -24,7 +24,7 @@ def test_pacing( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_pacing_{file}_{pacing}" # Set a unique pcap file name diff --git a/tests/validation/tests/single/st20p/packing/test_packing.py b/tests/validation/tests/single/st20p/packing/test_packing.py index 81c7b67d5..c8ce72472 100755 --- a/tests/validation/tests/single/st20p/packing/test_packing.py +++ b/tests/validation/tests/single/st20p/packing/test_packing.py @@ -24,7 +24,7 @@ def test_packing( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_packing_{file}_{packing}" # Set a unique pcap file name diff --git a/tests/validation/tests/single/st20p/resolutions/test_resolutions.py b/tests/validation/tests/single/st20p/resolutions/test_resolutions.py index 993f475cf..4e79164bd 100755 --- a/tests/validation/tests/single/st20p/resolutions/test_resolutions.py +++ b/tests/validation/tests/single/st20p/resolutions/test_resolutions.py @@ -33,7 +33,7 @@ def test_resolutions( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_resolutions_{file}" # Set a unique pcap file name diff --git a/tests/validation/tests/single/st20p/test_mode/test_multicast.py b/tests/validation/tests/single/st20p/test_mode/test_multicast.py index bd4e0d6cf..6a4ac984b 100755 --- a/tests/validation/tests/single/st20p/test_mode/test_multicast.py +++ b/tests/validation/tests/single/st20p/test_mode/test_multicast.py @@ -22,7 +22,7 @@ def test_multicast( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_multicast_{file}" # Set a unique pcap file name diff --git a/tests/validation/tests/single/st22p/codec/test_codec.py b/tests/validation/tests/single/st22p/codec/test_codec.py index d1326015a..1f0a142cb 100755 --- a/tests/validation/tests/single/st22p/codec/test_codec.py +++ b/tests/validation/tests/single/st22p/codec/test_codec.py @@ -16,7 +16,7 @@ def test_codec( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_codec_{codec}_Penguin_1080p" diff --git a/tests/validation/tests/single/st22p/format/test_format.py b/tests/validation/tests/single/st22p/format/test_format.py index c9b827750..0df1e4b2d 100755 --- a/tests/validation/tests/single/st22p/format/test_format.py +++ b/tests/validation/tests/single/st22p/format/test_format.py @@ -22,7 +22,7 @@ def test_format( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_format_{format}" diff --git a/tests/validation/tests/single/st22p/fps/test_fps.py b/tests/validation/tests/single/st22p/fps/test_fps.py index c57a77bec..d0c551799 100644 --- a/tests/validation/tests/single/st22p/fps/test_fps.py +++ b/tests/validation/tests/single/st22p/fps/test_fps.py @@ -28,7 +28,7 @@ def test_fps( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_fps_{codec}_Penguin_1080p_{fps}" diff --git a/tests/validation/tests/single/st22p/interlace/test_interlace.py b/tests/validation/tests/single/st22p/interlace/test_interlace.py index 001522eba..6e642534a 100755 --- a/tests/validation/tests/single/st22p/interlace/test_interlace.py +++ b/tests/validation/tests/single/st22p/interlace/test_interlace.py @@ -16,7 +16,7 @@ def test_interlace( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_interlace_{format}" diff --git a/tests/validation/tests/single/st22p/quality/test_quality.py b/tests/validation/tests/single/st22p/quality/test_quality.py index fc5770e63..c2326018b 100755 --- a/tests/validation/tests/single/st22p/quality/test_quality.py +++ b/tests/validation/tests/single/st22p/quality/test_quality.py @@ -16,7 +16,7 @@ def test_quality( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_quality_{quality}_Penguin_1080p" diff --git a/tests/validation/tests/single/st22p/test_mode/test_unicast.py b/tests/validation/tests/single/st22p/test_mode/test_unicast.py index f3902a46c..0c079b96c 100755 --- a/tests/validation/tests/single/st22p/test_mode/test_unicast.py +++ b/tests/validation/tests/single/st22p/test_mode/test_unicast.py @@ -13,7 +13,7 @@ def test_unicast( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = "test_unicast_Penguin_1080p" diff --git a/tests/validation/tests/single/st30p/integrity/test_integrity.py b/tests/validation/tests/single/st30p/integrity/test_integrity.py index a7a9b2480..d0a1627d7 100644 --- a/tests/validation/tests/single/st30p/integrity/test_integrity.py +++ b/tests/validation/tests/single/st30p/integrity/test_integrity.py @@ -36,7 +36,7 @@ def test_integrity( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_integrity_{audio_format}" # Set a unique pcap file name diff --git a/tests/validation/tests/single/st30p/st30p_channel/test_st30p_channel.py b/tests/validation/tests/single/st30p/st30p_channel/test_st30p_channel.py index 299fc2b4e..bff1b63df 100755 --- a/tests/validation/tests/single/st30p/st30p_channel/test_st30p_channel.py +++ b/tests/validation/tests/single/st30p/st30p_channel/test_st30p_channel.py @@ -30,7 +30,7 @@ def test_st30p_channel( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_st30p_channel_{audio_format}_{audio_channel}" # e.g., test_st30p_channel_PCM8_M diff --git a/tests/validation/tests/single/st30p/st30p_format/test_st30p_format.py b/tests/validation/tests/single/st30p/st30p_format/test_st30p_format.py index 59363b1a7..73b7ff244 100755 --- a/tests/validation/tests/single/st30p/st30p_format/test_st30p_format.py +++ b/tests/validation/tests/single/st30p/st30p_format/test_st30p_format.py @@ -24,7 +24,7 @@ def test_st30p_format( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_st30p_format_{audio_format}" # Set a unique pcap file name diff --git a/tests/validation/tests/single/st30p/st30p_ptime/test_st30p_ptime.py b/tests/validation/tests/single/st30p/st30p_ptime/test_st30p_ptime.py index 66cfad979..8b3a3c3e5 100755 --- a/tests/validation/tests/single/st30p/st30p_ptime/test_st30p_ptime.py +++ b/tests/validation/tests/single/st30p/st30p_ptime/test_st30p_ptime.py @@ -25,7 +25,7 @@ def test_st30p_ptime( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_st30p_ptime_{audio_format}" # Set a unique pcap file name diff --git a/tests/validation/tests/single/st30p/st30p_sampling/test_st30p_sampling.py b/tests/validation/tests/single/st30p/st30p_sampling/test_st30p_sampling.py index b101e08e4..e5cd1c68b 100755 --- a/tests/validation/tests/single/st30p/st30p_sampling/test_st30p_sampling.py +++ b/tests/validation/tests/single/st30p/st30p_sampling/test_st30p_sampling.py @@ -25,7 +25,7 @@ def test_st30p_sampling( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_st30p_sampling_{audio_format}" # Set a unique pcap file name diff --git a/tests/validation/tests/single/st30p/test_mode/test_multicast.py b/tests/validation/tests/single/st30p/test_mode/test_multicast.py index 555e9d066..a48a33e3d 100755 --- a/tests/validation/tests/single/st30p/test_mode/test_multicast.py +++ b/tests/validation/tests/single/st30p/test_mode/test_multicast.py @@ -23,7 +23,7 @@ def test_multicast( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_multicast_{audio_format}" # Set a unique pcap file name diff --git a/tests/validation/tests/single/st41/dit/test_dit.py b/tests/validation/tests/single/st41/dit/test_dit.py index 2eb47b376..a7fe2be98 100644 --- a/tests/validation/tests/single/st41/dit/test_dit.py +++ b/tests/validation/tests/single/st41/dit/test_dit.py @@ -36,7 +36,7 @@ def test_dit( st41_file = st41_files["st41_p29_long_file"]["filename"] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_dit_{dit}" diff --git a/tests/validation/tests/single/st41/fps/test_fps.py b/tests/validation/tests/single/st41/fps/test_fps.py index cea768700..c2819fc1b 100644 --- a/tests/validation/tests/single/st41/fps/test_fps.py +++ b/tests/validation/tests/single/st41/fps/test_fps.py @@ -47,7 +47,7 @@ def test_fps( k_bit = k_bit_mapping["k0"] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_fps_st41_{fps}" diff --git a/tests/validation/tests/single/st41/k_bit/test_k_bit.py b/tests/validation/tests/single/st41/k_bit/test_k_bit.py index deb2dfc95..4220b95d1 100644 --- a/tests/validation/tests/single/st41/k_bit/test_k_bit.py +++ b/tests/validation/tests/single/st41/k_bit/test_k_bit.py @@ -36,7 +36,7 @@ def test_k_bit( st41_file = st41_files["st41_p29_long_file"]["filename"] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_k_bit_{k_bit}" diff --git a/tests/validation/tests/single/st41/no_chain/test_no_chain.py b/tests/validation/tests/single/st41/no_chain/test_no_chain.py index a404345df..f7700a7e1 100644 --- a/tests/validation/tests/single/st41/no_chain/test_no_chain.py +++ b/tests/validation/tests/single/st41/no_chain/test_no_chain.py @@ -43,7 +43,7 @@ def test_no_chain( st41_file = st41_files["st41_p29_long_file"]["filename"] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_no_chain_{type_mode}" diff --git a/tests/validation/tests/single/st41/payload_type/test_payload_type.py b/tests/validation/tests/single/st41/payload_type/test_payload_type.py index e3cdcb729..9d1965590 100644 --- a/tests/validation/tests/single/st41/payload_type/test_payload_type.py +++ b/tests/validation/tests/single/st41/payload_type/test_payload_type.py @@ -51,7 +51,7 @@ def test_payload_type( k_bit = k_bit_mapping["k0"] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_payload_type_{payload_type}_{type_mode}" diff --git a/tests/validation/tests/single/st41/type_mode/test_type_mode.py b/tests/validation/tests/single/st41/type_mode/test_type_mode.py index 547b3ffd1..dcf4a655c 100644 --- a/tests/validation/tests/single/st41/type_mode/test_type_mode.py +++ b/tests/validation/tests/single/st41/type_mode/test_type_mode.py @@ -45,7 +45,7 @@ def test_type_mode( dit = dit_mapping["dit0"] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = f"test_type_mode_{test_mode}_{type_mode}" diff --git a/tests/validation/tests/single/virtio_user/test_virtio_user.py b/tests/validation/tests/single/virtio_user/test_virtio_user.py index debbb891e..e41095dda 100755 --- a/tests/validation/tests/single/virtio_user/test_virtio_user.py +++ b/tests/validation/tests/single/virtio_user/test_virtio_user.py @@ -33,7 +33,7 @@ def test_virtio_user( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_virtio_user_multicast_{video_format}_replicas{replicas}" diff --git a/tests/validation/tests/single/xdp/test_mode/test_xdp_mode.py b/tests/validation/tests/single/xdp/test_mode/test_xdp_mode.py index a5133617f..77af9fcc2 100755 --- a/tests/validation/tests/single/xdp/test_mode/test_xdp_mode.py +++ b/tests/validation/tests/single/xdp/test_mode/test_xdp_mode.py @@ -27,7 +27,7 @@ def test_xdp_mode( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_xdp_mode_{test_mode}_{video_format}_replicas{replicas}" diff --git a/tests/validation/tests/single/xdp/test_standard/test_standard.py b/tests/validation/tests/single/xdp/test_standard/test_standard.py index 0a25f2f6e..24422d857 100755 --- a/tests/validation/tests/single/xdp/test_standard/test_standard.py +++ b/tests/validation/tests/single/xdp/test_standard/test_standard.py @@ -29,7 +29,7 @@ def test_xdp_standard( host = list(hosts.values())[0] # Get capture configuration from test_config.yaml - # This controls whether tcpdump capture is enabled, where to store the pcap, etc. + # Collect packet capture configuration and assign test_name capture_cfg = dict(test_config.get("capture_cfg", {})) capture_cfg["test_name"] = ( f"test_xdp_standard_{standard_mode}_{test_mode}_{video_format}_replicas{replicas}" From bda5c5a1d4bcde73df196fb068e14ccc085baae4 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 13 Aug 2025 14:16:35 +0000 Subject: [PATCH 07/35] Add: netsniff-ng can be executed in all tests where tcpdump capture was added --- tests/validation/mtl_engine/GstreamerApp.py | 10 ++++++-- tests/validation/mtl_engine/ffmpeg_app.py | 26 +++++++++++++++++---- tests/validation/mtl_engine/udp_app.py | 11 ++++++--- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/tests/validation/mtl_engine/GstreamerApp.py b/tests/validation/mtl_engine/GstreamerApp.py index 764304386..ecfef2a69 100755 --- a/tests/validation/mtl_engine/GstreamerApp.py +++ b/tests/validation/mtl_engine/GstreamerApp.py @@ -6,7 +6,7 @@ import os import time -from mtl_engine.RxTxApp import prepare_tcpdump +from mtl_engine.RxTxApp import prepare_tcpdump, prepare_netsniff from .execute import log_fail, run @@ -331,6 +331,7 @@ def execute_test( tx_process = None rx_process = None tcpdump = prepare_tcpdump(capture_cfg, host) + netsniff = prepare_netsniff(capture_cfg, host) try: if tx_first: @@ -383,10 +384,13 @@ def execute_test( background=True, enable_sudo=True, ) - # --- Start tcpdump after pipelines are running --- + # --- Start packet capture after pipelines are running --- if tcpdump: logger.info("Starting tcpdump capture...") tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) + if netsniff: + logger.info("Starting netsniff-ng capture...") + netsniff.capture(capture_time=capture_cfg.get("capture_time", test_time)) # Let the test run for the specified duration logger.info(f"Running test for {test_time} seconds...") @@ -444,6 +448,8 @@ def execute_test( pass if tcpdump: tcpdump.stop() + if netsniff: + netsniff.stop() # Compare files for validation file_compare = compare_files(input_file, output_file) diff --git a/tests/validation/mtl_engine/ffmpeg_app.py b/tests/validation/mtl_engine/ffmpeg_app.py index 9697fd134..4225c8d90 100755 --- a/tests/validation/mtl_engine/ffmpeg_app.py +++ b/tests/validation/mtl_engine/ffmpeg_app.py @@ -9,7 +9,7 @@ import time from mfd_connect import SSHConnection -from mtl_engine.RxTxApp import prepare_tcpdump +from mtl_engine.RxTxApp import prepare_tcpdump, prepare_netsniff from . import rxtxapp_config from .execute import log_fail, run @@ -164,6 +164,7 @@ def execute_test( rx_proc = None tx_proc = None tcpdump = prepare_tcpdump(capture_cfg, host) + netsniff = prepare_netsniff(capture_cfg, host) try: # Start RX pipeline first @@ -190,10 +191,13 @@ def execute_test( background=True, enable_sudo=True, ) - # Start tcpdump after pipelines are running + # Start packet capture when pipelines are running if tcpdump: logger.info("Starting tcpdump capture...") tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) + if netsniff: + logger.info("Starting netsniff-ng capture...") + netsniff.capture(capture_time=capture_cfg.get("capture_time", test_time)) # Let the test run for the specified duration logger.info(f"Running test for {test_time} seconds...") @@ -263,6 +267,8 @@ def execute_test( pass if tcpdump: tcpdump.stop() + if netsniff: + netsniff.stop() passed = False match output_format: case "yuv": @@ -331,6 +337,7 @@ def execute_test_rgb24( rx_proc = None tx_proc = None tcpdump = prepare_tcpdump(capture_cfg, host) + netsniff = prepare_netsniff(capture_cfg, host) try: # Start RX pipeline first @@ -356,10 +363,13 @@ def execute_test_rgb24( background=True, enable_sudo=True, ) - # Start tcpdump after pipelines are running + # Start packet capture when pipelines are running if tcpdump: logger.info("Starting tcpdump capture...") tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) + if netsniff: + logger.info("Starting netsniff-ng capture...") + netsniff.capture(capture_time=capture_cfg.get("capture_time", test_time)) logger.info( f"Waiting for RX process to complete (test_time: {test_time} seconds)..." @@ -437,6 +447,8 @@ def execute_test_rgb24( pass if tcpdump: tcpdump.stop() + if netsniff: + netsniff.stop() if not check_output_rgb24(rx_output, 1): log_fail("rx video sessions failed") return False @@ -506,6 +518,7 @@ def execute_test_rgb24_multiple( tx_1_proc = None tx_2_proc = None tcpdump = prepare_tcpdump(capture_cfg, host) + netsniff = prepare_netsniff(capture_cfg, host) try: rx_proc = run( @@ -538,10 +551,13 @@ def execute_test_rgb24_multiple( background=True, enable_sudo=True, ) - # Start tcpdump after pipelines are running + # Start packet capture when pipelines are running if tcpdump: logger.info("Starting tcpdump capture...") tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) + if netsniff: + logger.info("Starting netsniff-ng capture...") + netsniff.capture(capture_time=capture_cfg.get("capture_time", test_time)) logger.info(f"Waiting for RX process (test_time: {test_time} seconds)...") rx_proc.wait() @@ -611,6 +627,8 @@ def execute_test_rgb24_multiple( pass if tcpdump: tcpdump.stop() + if netsniff: + netsniff.stop() if not check_output_rgb24(rx_output, 2): log_fail("rx video session failed") return False diff --git a/tests/validation/mtl_engine/udp_app.py b/tests/validation/mtl_engine/udp_app.py index 7e573465d..4495fd533 100644 --- a/tests/validation/mtl_engine/udp_app.py +++ b/tests/validation/mtl_engine/udp_app.py @@ -7,7 +7,7 @@ import re from mtl_engine import udp_app_config -from mtl_engine.RxTxApp import prepare_tcpdump +from mtl_engine.RxTxApp import prepare_tcpdump, prepare_netsniff from .const import LOG_FOLDER from .execute import call, log_fail, wait @@ -63,20 +63,25 @@ def execute_test_sample( server_command = f"./build/app/UfdServerSample --sessions_cnt {sessions_cnt}" tcpdump = prepare_tcpdump(capture_cfg, host) + netsniff = prepare_netsniff(capture_cfg, host) client_proc = call(client_command, build, test_time, sigint=True, env=client_env) server_proc = call(server_command, build, test_time, sigint=True, env=server_env) try: - # Start tcpdump capture after traffic is flowing + # Start packet capture when traffic is flowing if tcpdump: tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) + if netsniff: + netsniff.capture(capture_time=capture_cfg.get("capture_time", test_time)) # Wait for both processes to finish wait(client_proc) wait(server_proc) finally: if tcpdump: tcpdump.stop() + if netsniff: + netsniff.stop() if not check_received_packets(client_proc.output) or not check_received_packets( server_proc.output @@ -137,7 +142,7 @@ def execute_test_librist( receive_proc = call(receive_command, build, test_time, sigint=True, env=receive_env) try: - # Start tcpdump capture after traffic is flowing + # Start packet capture when traffic is flowing if tcpdump: tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) # Wait for both processes to finish From 7a31926c012d2a998290194a97371acfd60e0311 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 13 Aug 2025 15:01:24 +0000 Subject: [PATCH 08/35] Lint: applying isort recommendations --- tests/validation/create_pcap_file/netsniff.py | 1 - tests/validation/mtl_engine/GstreamerApp.py | 2 +- tests/validation/mtl_engine/RxTxApp.py | 5 +++-- tests/validation/mtl_engine/ffmpeg_app.py | 3 ++- tests/validation/mtl_engine/udp_app.py | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/validation/create_pcap_file/netsniff.py b/tests/validation/create_pcap_file/netsniff.py index e8d467521..5c93b1efd 100644 --- a/tests/validation/create_pcap_file/netsniff.py +++ b/tests/validation/create_pcap_file/netsniff.py @@ -2,7 +2,6 @@ # Copyright 2025 Intel Corporation import logging import os - from time import sleep from mfd_connect.exceptions import ConnectionCalledProcessError diff --git a/tests/validation/mtl_engine/GstreamerApp.py b/tests/validation/mtl_engine/GstreamerApp.py index ecfef2a69..f40704fc0 100755 --- a/tests/validation/mtl_engine/GstreamerApp.py +++ b/tests/validation/mtl_engine/GstreamerApp.py @@ -6,7 +6,7 @@ import os import time -from mtl_engine.RxTxApp import prepare_tcpdump, prepare_netsniff +from mtl_engine.RxTxApp import prepare_netsniff, prepare_tcpdump from .execute import log_fail, run diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index 33b435d6f..b05582a68 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -9,10 +9,11 @@ import sys import time -from create_pcap_file.tcpdump import TcpDumpRecorder -from create_pcap_file.netsniff import NetsniffRecorder from mfd_connect import SSHConnection +from create_pcap_file.netsniff import NetsniffRecorder +from create_pcap_file.tcpdump import TcpDumpRecorder + from . import rxtxapp_config from .execute import log_fail, run diff --git a/tests/validation/mtl_engine/ffmpeg_app.py b/tests/validation/mtl_engine/ffmpeg_app.py index 4225c8d90..a4e6343b7 100755 --- a/tests/validation/mtl_engine/ffmpeg_app.py +++ b/tests/validation/mtl_engine/ffmpeg_app.py @@ -9,7 +9,8 @@ import time from mfd_connect import SSHConnection -from mtl_engine.RxTxApp import prepare_tcpdump, prepare_netsniff + +from mtl_engine.RxTxApp import prepare_netsniff, prepare_tcpdump from . import rxtxapp_config from .execute import log_fail, run diff --git a/tests/validation/mtl_engine/udp_app.py b/tests/validation/mtl_engine/udp_app.py index 4495fd533..a759dd9c2 100644 --- a/tests/validation/mtl_engine/udp_app.py +++ b/tests/validation/mtl_engine/udp_app.py @@ -7,7 +7,7 @@ import re from mtl_engine import udp_app_config -from mtl_engine.RxTxApp import prepare_tcpdump, prepare_netsniff +from mtl_engine.RxTxApp import prepare_netsniff, prepare_tcpdump from .const import LOG_FOLDER from .execute import call, log_fail, wait From d6fcab137a5dda18e640771564ab8b1d3adad2c0 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 13 Aug 2025 15:04:05 +0000 Subject: [PATCH 09/35] Fix: improper order in if-else oneliner --- tests/validation/create_pcap_file/netsniff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/validation/create_pcap_file/netsniff.py b/tests/validation/create_pcap_file/netsniff.py index 5c93b1efd..d77bf1385 100644 --- a/tests/validation/create_pcap_file/netsniff.py +++ b/tests/validation/create_pcap_file/netsniff.py @@ -61,7 +61,7 @@ def start(self, startup_wait=STARTUP_WAIT): str(self.interface), # FIXME: It is not a proper interface name to be used here "--out", self.pcap_file, - if filter f"-f {self.filter}" else "", + f"-f {self.filter}" if filter else "", ] logger.info(f"Running command: {' '.join(cmd)}") self.netsniff_process = connection.start_process( From 565f4cb68a498a8f949ba9d0e6224f8d40a5d876 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Mon, 18 Aug 2025 14:18:46 +0000 Subject: [PATCH 10/35] Test: Fixing the netsniff-ng preparation --- tests/validation/mtl_engine/RxTxApp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index b2690f288..0d6992cef 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -135,10 +135,11 @@ def prepare_netsniff(capture_cfg, host=None): returns: NetsniffRecorder instance or None. """ + breakpoint() if ( capture_cfg and capture_cfg.get("enable") - and capture_cfg.get("tool" in ["netsniff", "netsniff-ng"]) + and capture_cfg.get("tool") in ["netsniff", "netsniff-ng"] ): netsniff = NetsniffRecorder( host=host, From ccb33619c48ddc4aa7d899672377773903c8451c Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Mon, 18 Aug 2025 14:20:40 +0000 Subject: [PATCH 11/35] Test: Add netsniff-ng to dual-node tests --- tests/validation/mtl_engine/ffmpeg_app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/validation/mtl_engine/ffmpeg_app.py b/tests/validation/mtl_engine/ffmpeg_app.py index a0c02dc3b..0dfb4a809 100755 --- a/tests/validation/mtl_engine/ffmpeg_app.py +++ b/tests/validation/mtl_engine/ffmpeg_app.py @@ -1109,6 +1109,7 @@ def execute_dual_test( tx_proc = None # Use RX host for tcpdump capture tcpdump = prepare_tcpdump(capture_cfg, rx_host) + netsniff = prepare_netsniff(capture_cfg, rx_host) try: # Start RX pipeline first on RX host @@ -1139,6 +1140,9 @@ def execute_dual_test( if tcpdump: logger.info("Starting tcpdump capture...") tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) + if netsniff: + logger.info("Starting netsniff capture...") + netsniff.capture(capture_time=capture_cfg.get("capture_time", test_time)) # Let the test run for the specified duration logger.info(f"Running test for {test_time} seconds...") @@ -1208,6 +1212,8 @@ def execute_dual_test( pass if tcpdump: tcpdump.stop() + if netsniff: + netsniff.stop() passed = False match output_format: case "yuv": From 4f6754f0e2ede050aa07b91f804a58a80cf2f174 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Mon, 18 Aug 2025 16:13:18 +0000 Subject: [PATCH 12/35] Removing unnecessary ands from comments; Removing unnecessarily added breakpoint --- tests/validation/mtl_engine/RxTxApp.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index 0d6992cef..62f9573c0 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -105,7 +105,7 @@ def add_interfaces(config: dict, nic_port_list: list, test_mode: str) -> dict: def prepare_tcpdump(capture_cfg, host=None): """ - Prepare and TcpDumpRecorder if capture_cfg is enabled and tool is tcpdump. + Prepare TcpDumpRecorder if capture_cfg is enabled and tool is tcpdump. returns: TcpDumpRecorder instance or None. """ @@ -131,11 +131,10 @@ def prepare_tcpdump(capture_cfg, host=None): def prepare_netsniff(capture_cfg, host=None): """ - Prepare and NetsniffRecorder if capture_cfg is enabled. + Prepare NetsniffRecorder if capture_cfg is enabled. returns: NetsniffRecorder instance or None. """ - breakpoint() if ( capture_cfg and capture_cfg.get("enable") From f4631c0e1ea19a37c6c808642b9ff026b8992ba6 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Tue, 19 Aug 2025 09:23:41 +0000 Subject: [PATCH 13/35] filter -> self.filter so no -f None is present in netsniff-ng's command --- tests/validation/create_pcap_file/netsniff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/validation/create_pcap_file/netsniff.py b/tests/validation/create_pcap_file/netsniff.py index d77bf1385..be40b3088 100644 --- a/tests/validation/create_pcap_file/netsniff.py +++ b/tests/validation/create_pcap_file/netsniff.py @@ -61,7 +61,7 @@ def start(self, startup_wait=STARTUP_WAIT): str(self.interface), # FIXME: It is not a proper interface name to be used here "--out", self.pcap_file, - f"-f {self.filter}" if filter else "", + f"-f {self.filter}" if self.filter else "", ] logger.info(f"Running command: {' '.join(cmd)}") self.netsniff_process = connection.start_process( From e964bd373719811b1a42cd9b33d1b35ad559a8e6 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Tue, 19 Aug 2025 09:57:21 +0000 Subject: [PATCH 14/35] Adding missing quotation marks around filter, removing unnecessary debug and FIXME comment --- tests/validation/create_pcap_file/netsniff.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/validation/create_pcap_file/netsniff.py b/tests/validation/create_pcap_file/netsniff.py index be40b3088..140bea3af 100644 --- a/tests/validation/create_pcap_file/netsniff.py +++ b/tests/validation/create_pcap_file/netsniff.py @@ -53,15 +53,14 @@ def start(self, startup_wait=STARTUP_WAIT): if not self.netsniff_process or not self.netsniff_process.running: connection = self.host.connection try: - logger.debug(f"NETSNIFF INTERFACE NAME: {self.interface}") # TODO: Remove this debug log after testing cmd = [ "netsniff-ng", "--silent" if self.silent else "", "--in", - str(self.interface), # FIXME: It is not a proper interface name to be used here + str(self.interface), "--out", self.pcap_file, - f"-f {self.filter}" if self.filter else "", + f"-f \"{self.filter}\"" if self.filter else "", ] logger.info(f"Running command: {' '.join(cmd)}") self.netsniff_process = connection.start_process( From 46ddf17e8d93584bcdf7539ca6aeafeeef4cd722 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Tue, 19 Aug 2025 10:06:07 +0000 Subject: [PATCH 15/35] Add filtering to netsniff-ng, so only the test streams are captured, instead of all traffic --- tests/validation/mtl_engine/RxTxApp.py | 19 ++++++++++++--- tests/validation/mtl_engine/ffmpeg_app.py | 29 +++++++++++++++++++---- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index 62f9573c0..2894a0e6c 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -129,7 +129,12 @@ def prepare_tcpdump(capture_cfg, host=None): return None -def prepare_netsniff(capture_cfg, host=None): +def prepare_netsniff( + capture_cfg, + host=None, + src_ip: str|None = None, + dst_ip: str|None = None + ): """ Prepare NetsniffRecorder if capture_cfg is enabled. @@ -140,13 +145,21 @@ def prepare_netsniff(capture_cfg, host=None): and capture_cfg.get("enable") and capture_cfg.get("tool") in ["netsniff", "netsniff-ng"] ): + # Filtering + capture_filter = "" + if src_ip: + capture_filter += f"src {src_ip}" + if dst_ip and not capture_filter == "": + capture_filter += f"and dst {dst_ip}" + elif dst_ip: + capture_filter += f"dst {dst_ip}" + # Class prep netsniff = NetsniffRecorder( host=host, test_name=capture_cfg.get("test_name", "capture"), pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), interface=capture_cfg.get("interface"), - # TODO: Add filtering capability (src ... and dst ...) - # filter = ... + filter = capture_filter if capture_filter != "" else None, # Avoid forcing an empty filter ) return netsniff else: diff --git a/tests/validation/mtl_engine/ffmpeg_app.py b/tests/validation/mtl_engine/ffmpeg_app.py index 0dfb4a809..e50f13ac4 100755 --- a/tests/validation/mtl_engine/ffmpeg_app.py +++ b/tests/validation/mtl_engine/ffmpeg_app.py @@ -173,8 +173,12 @@ def execute_test( rx_proc = None tx_proc = None tcpdump = prepare_tcpdump(capture_cfg, host) - netsniff = prepare_netsniff(capture_cfg, host) - + netsniff = prepare_netsniff( + capture_cfg, + host, + src_ip = str(ip_dict['tx_interfaces']), + dst_ip = str(ip_dict['tx_sessions']) + ) try: # Start RX pipeline first logger.info("Starting RX pipeline...") @@ -346,7 +350,12 @@ def execute_test_rgb24( rx_proc = None tx_proc = None tcpdump = prepare_tcpdump(capture_cfg, host) - netsniff = prepare_netsniff(capture_cfg, host) + netsniff = prepare_netsniff( + capture_cfg, + host, + src_ip = str(ip_dict['tx_interfaces']), + dst_ip = str(ip_dict['tx_sessions']) + ) try: # Start RX pipeline first @@ -527,7 +536,12 @@ def execute_test_rgb24_multiple( tx_1_proc = None tx_2_proc = None tcpdump = prepare_tcpdump(capture_cfg, host) - netsniff = prepare_netsniff(capture_cfg, host) + netsniff = prepare_netsniff( + capture_cfg, + host, + src_ip = f"({ip_dict_rgb24_multiple['p_sip_1']} or {ip_dict_rgb24_multiple['p_sip_2']})", + dst_ip = f"({ip_dict_rgb24_multiple['p_tx_ip_1']} or {ip_dict_rgb24_multiple['p_tx_ip_2']})" + ) try: rx_proc = run( @@ -1109,7 +1123,12 @@ def execute_dual_test( tx_proc = None # Use RX host for tcpdump capture tcpdump = prepare_tcpdump(capture_cfg, rx_host) - netsniff = prepare_netsniff(capture_cfg, rx_host) + netsniff = prepare_netsniff( + capture_cfg, + rx_host, + src_ip = str(ip_dict['tx_interfaces']), + dst_ip = str(ip_dict['tx_sessions']) + ) try: # Start RX pipeline first on RX host From 1d2b2f384dbac79c443ebe72d4207e5f121bdc31 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Tue, 19 Aug 2025 10:18:33 +0000 Subject: [PATCH 16/35] Adding datetime into generated filename --- tests/validation/create_pcap_file/netsniff.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/validation/create_pcap_file/netsniff.py b/tests/validation/create_pcap_file/netsniff.py index 140bea3af..d794397d5 100644 --- a/tests/validation/create_pcap_file/netsniff.py +++ b/tests/validation/create_pcap_file/netsniff.py @@ -2,6 +2,7 @@ # Copyright 2025 Intel Corporation import logging import os +from datetime import datetime from time import sleep from mfd_connect.exceptions import ConnectionCalledProcessError @@ -37,7 +38,7 @@ def __init__( self.host = host self.test_name = test_name self.pcap_dir = pcap_dir - self.pcap_file = os.path.join(pcap_dir, f"{test_name}.pcap") + self.pcap_file = os.path.join(pcap_dir, f"{test_name}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.pcap") if interface is not None: self.interface = interface else: From 76545e261ac7ac046066d00c0ab3c18a28784318 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 20 Aug 2025 06:24:20 +0000 Subject: [PATCH 17/35] Fixing lack of space in netsniff-ng filter --- tests/validation/mtl_engine/RxTxApp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index 2894a0e6c..8353a31d1 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -150,7 +150,7 @@ def prepare_netsniff( if src_ip: capture_filter += f"src {src_ip}" if dst_ip and not capture_filter == "": - capture_filter += f"and dst {dst_ip}" + capture_filter += f" and dst {dst_ip}" elif dst_ip: capture_filter += f"dst {dst_ip}" # Class prep From 01bad5b56c86d71c4a0b304253e4e0c389484f34 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 20 Aug 2025 08:52:20 +0000 Subject: [PATCH 18/35] filter -> capture_filter to avoid shadowing over Python's filter function --- tests/validation/create_pcap_file/netsniff.py | 8 ++++---- tests/validation/mtl_engine/RxTxApp.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/validation/create_pcap_file/netsniff.py b/tests/validation/create_pcap_file/netsniff.py index d794397d5..a86486c0c 100644 --- a/tests/validation/create_pcap_file/netsniff.py +++ b/tests/validation/create_pcap_file/netsniff.py @@ -22,7 +22,7 @@ class NetsniffRecorder: interface: Network interface to capture traffic on. interface_index (int): Index of the network interface if not specified by interface. silent (bool): Whether to run netsniff-ng in silent mode (no stdout) (default: True). - filter (str): Optional filter to apply to the capture. (default: None) + capture_filter (str): Optional filter to apply to the capture. (default: None) """ def __init__( @@ -33,7 +33,7 @@ def __init__( interface = None, interface_index: int = 0, silent: bool = True, - filter: str | None = None, + capture_filter: str | None = None, ): self.host = host self.test_name = test_name @@ -45,7 +45,7 @@ def __init__( self.interface = self.host.network_interfaces[interface_index].name self.netsniff_process = None self.silent = silent - self.filter = filter + self.capture_filter = capture_filter def start(self, startup_wait=STARTUP_WAIT): """ @@ -61,7 +61,7 @@ def start(self, startup_wait=STARTUP_WAIT): str(self.interface), "--out", self.pcap_file, - f"-f \"{self.filter}\"" if self.filter else "", + f"-f \"{self.capture_filter}\"" if self.capture_filter else "", ] logger.info(f"Running command: {' '.join(cmd)}") self.netsniff_process = connection.start_process( diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index 8353a31d1..b023913c6 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -159,7 +159,7 @@ def prepare_netsniff( test_name=capture_cfg.get("test_name", "capture"), pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), interface=capture_cfg.get("interface"), - filter = capture_filter if capture_filter != "" else None, # Avoid forcing an empty filter + capture_filter = capture_filter if capture_filter != "" else None, # Avoid forcing an empty filter ) return netsniff else: From a77c7bdd753ae5b09c15d39e8819dcfd6ca702b9 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 20 Aug 2025 14:19:53 +0000 Subject: [PATCH 19/35] Documentation for netsniff installation --- .../create_pcap_file/netsniff_readme.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/validation/create_pcap_file/netsniff_readme.md diff --git a/tests/validation/create_pcap_file/netsniff_readme.md b/tests/validation/create_pcap_file/netsniff_readme.md new file mode 100644 index 000000000..8c2921bf0 --- /dev/null +++ b/tests/validation/create_pcap_file/netsniff_readme.md @@ -0,0 +1,40 @@ +# Automated packet capture using Netsniff-ng + +## Downloading and installing the tool + +> **Note:** At the moment, there is no automation present for netsniff-ng tool installation on the capturing machine. It must be installed on the system manually before running the test. If the capture fails, the test continues without the packet capture. + +There is an official instruction on how to download the tool present on the [tool's website](http://netsniff-ng.org/). It can also be useful for finding mirrors of the repositories. Below, only the Github repository is considered as a source. + +```shell +NETSNIFF_REPO="https://github.com/netsniff-ng/netsniff-ng" +NETSNIFF_VERSION="v0.6.9" +``` + +### Download the repository + +1. Clone the repository +2. Change directory to the cloned repository +3. Checkout on the proper release branch + +```shell +git clone "${NETSNIFF_REPO}" netsniff-ng # 1. +cd netsniff-ng # 2. +git checkout "${VERSION}" # 3. +``` + +Alternatively: + +1. Download the release package +2. Unpack the package + +```shell +wget "${NETSNIFF_REPO}/archive/refs/tags/${NETSNIFF_VERSION}.tar.gz" # 1. +tar xvz "${NETSNIFF_VERSION}" # 2. +``` + +### Install the repository + +> **Note:** The [document](https://github.com/netsniff-ng/netsniff-ng/blob/main/INSTALL) is also available online (main branch) + +1. Use the INSTALL document from repository root to install the tool From 48bec152407895d219b8097a436b098c1f653004 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Fri, 22 Aug 2025 09:59:10 +0000 Subject: [PATCH 20/35] Adding usage docs --- .../validation/create_pcap_file/netsniff_readme.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/validation/create_pcap_file/netsniff_readme.md b/tests/validation/create_pcap_file/netsniff_readme.md index 8c2921bf0..11dac9723 100644 --- a/tests/validation/create_pcap_file/netsniff_readme.md +++ b/tests/validation/create_pcap_file/netsniff_readme.md @@ -38,3 +38,16 @@ tar xvz "${NETSNIFF_VERSION}" # 2. > **Note:** The [document](https://github.com/netsniff-ng/netsniff-ng/blob/main/INSTALL) is also available online (main branch) 1. Use the INSTALL document from repository root to install the tool + + +## Usage + +After the tool is installed on the capturing machine, it can be used within the test code. + +In order to create and receive a `NetsniffRecorder` class instance, that can be used to start and stop the packet capture, use the `prepare_netsniff()` function. It takes the capture config (`capture_cfg`), host (`host`) and optional filters for source and destination IP addresses (`src_ip` and `dst_ip` respectively). The filtering IPs should be used to avoid capturing unrelated traffic during the test. + +To start the capture, call `start()` function within the `NetsniffRecorder` class object, with optional `startup_wait` parameter specifying the number of seconds that should be waited before the capture starts (initialization delay). + +To capture for predetermined number of seconds, call the `capture()` function with `capture_time` parameter. It starts the capture in a blocking manner. This function can optionally pass the `startup_wait` parameter to the `start()` function. + +Analogically, to stop the capture, call `stop()` function. This function stops the capture immediately. From 914a2db6f046597143e1b108df81fc29b2fe42c5 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Fri, 22 Aug 2025 14:01:42 +0000 Subject: [PATCH 21/35] Changing ramdisk fixture so it is universal --- tests/validation/conftest.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index 1515d8e35..442483c1b 100755 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -132,8 +132,9 @@ def delay_between_tests(test_config: dict): yield +# TODO: Legacy, remove in the future @pytest.fixture -def prepare_ramdisk(hosts, test_config): +def legacy_prepare_ramdisk(hosts, test_config): ramdisk_cfg = test_config.get("ramdisk", {}) capture_cfg = test_config.get("capture_cfg", {}) pcap_dir = ramdisk_cfg.get("pcap_dir", "/home/pcap_files") @@ -148,6 +149,24 @@ def prepare_ramdisk(hosts, test_config): ramdisk.mount() +@pytest.fixture(scope="session") +def prepare_ramdisk(hosts, test_config): + ramdisks = [] + ramdisks_configs = test_config.get("ramdisk", {}) + for ramdisk_name, ramdisk_config in ramdisks_configs.items(): + ramdisk_mountpoint = ramdisk_config.get("mountpoint", f"/mnt/ramdisk_{ramdisk_name}") + ramdisk_size_gib = ramdisk_config.get("size_gib", 32) + ramdisks += [ + Ramdisk(host=host, mount_point=ramdisk_mountpoint, size_gib=ramdisk_size_gib) + for host in hosts.values() + ] + for ramdisk in ramdisks: + ramdisk.mount() + yield + for ramdisk in ramdisks: + ramdisk.unmount() + + @pytest.fixture(scope="session") def media_ramdisk(hosts, test_config): ramdisk_config = test_config.get("ramdisk", {}).get("media", {}) From 59f464f01e836dd32eb330808b8ea9b8730e31e2 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Fri, 22 Aug 2025 14:03:30 +0000 Subject: [PATCH 22/35] Changing netsniff.capture() to netsniff.start() so the capture is non-blocking --- tests/validation/mtl_engine/GstreamerApp.py | 2 +- tests/validation/mtl_engine/RxTxApp.py | 4 ++-- tests/validation/mtl_engine/ffmpeg_app.py | 8 ++++---- tests/validation/mtl_engine/udp_app.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/validation/mtl_engine/GstreamerApp.py b/tests/validation/mtl_engine/GstreamerApp.py index f40704fc0..afad65b03 100755 --- a/tests/validation/mtl_engine/GstreamerApp.py +++ b/tests/validation/mtl_engine/GstreamerApp.py @@ -390,7 +390,7 @@ def execute_test( tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) if netsniff: logger.info("Starting netsniff-ng capture...") - netsniff.capture(capture_time=capture_cfg.get("capture_time", test_time)) + netsniff.start() # Let the test run for the specified duration logger.info(f"Running test for {test_time} seconds...") diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index b023913c6..4aeb6d0c3 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -653,7 +653,7 @@ def execute_test( log_to_file("Started tcpdump capture", host, build) # Start netsniff-ng capture (blocking, so it captures during traffic) if netsniff: - netsniff.capture(capture_time=capture_cfg.get("capture_time", 0.5)) + netsniff.start() logger.info(f"Started netsniff-ng capture on host {host.name}") log_to_file("Started netsniff-ng capture", host, build) finally: @@ -878,7 +878,7 @@ def execute_perf_test( tcpdump.capture(capture_time=capture_cfg.get("capture_time", 0.5)) log_to_file("Started performance test tcpdump capture", host, build) if netsniff: - netsniff.capture(capture_time=capture_cfg.get("capture_time", 0.5)) + netsniff.start() log_to_file("Started performance test netsniff-ng capture", host, build) finally: cp.wait() diff --git a/tests/validation/mtl_engine/ffmpeg_app.py b/tests/validation/mtl_engine/ffmpeg_app.py index e50f13ac4..e22c7fed1 100755 --- a/tests/validation/mtl_engine/ffmpeg_app.py +++ b/tests/validation/mtl_engine/ffmpeg_app.py @@ -210,7 +210,7 @@ def execute_test( tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) if netsniff: logger.info("Starting netsniff-ng capture...") - netsniff.capture(capture_time=capture_cfg.get("capture_time", test_time)) + netsniff.start() # Let the test run for the specified duration logger.info(f"Running test for {test_time} seconds...") @@ -387,7 +387,7 @@ def execute_test_rgb24( tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) if netsniff: logger.info("Starting netsniff-ng capture...") - netsniff.capture(capture_time=capture_cfg.get("capture_time", test_time)) + netsniff.start() logger.info( f"Waiting for RX process to complete (test_time: {test_time} seconds)..." @@ -580,7 +580,7 @@ def execute_test_rgb24_multiple( tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) if netsniff: logger.info("Starting netsniff-ng capture...") - netsniff.capture(capture_time=capture_cfg.get("capture_time", test_time)) + netsniff.start() logger.info(f"Waiting for RX process (test_time: {test_time} seconds)...") rx_proc.wait() @@ -1161,7 +1161,7 @@ def execute_dual_test( tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) if netsniff: logger.info("Starting netsniff capture...") - netsniff.capture(capture_time=capture_cfg.get("capture_time", test_time)) + netsniff.start() # Let the test run for the specified duration logger.info(f"Running test for {test_time} seconds...") diff --git a/tests/validation/mtl_engine/udp_app.py b/tests/validation/mtl_engine/udp_app.py index a759dd9c2..e8dd6a82f 100644 --- a/tests/validation/mtl_engine/udp_app.py +++ b/tests/validation/mtl_engine/udp_app.py @@ -73,7 +73,7 @@ def execute_test_sample( if tcpdump: tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) if netsniff: - netsniff.capture(capture_time=capture_cfg.get("capture_time", test_time)) + netsniff.start() # Wait for both processes to finish wait(client_proc) wait(server_proc) From ebacb8419d63e03b44b5469205266605cd38e6d9 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Fri, 29 Aug 2025 14:04:27 +0000 Subject: [PATCH 23/35] Compliance uploads and checks added (fixes required) --- tests/validation/compliance/__init__.py | 0 .../validation/compliance/check_compliance.py | 36 ++++++ tests/validation/compliance/csv_report.py | 31 +++++ .../validation/compliance/load_ebu_config.py | 13 ++ .../validation/compliance/pcap_compliance.py | 119 ++++++++++++++++++ tests/validation/compliance/upload_pcap.py | 45 +++++++ tests/validation/configs/ebu_list.yaml | 8 ++ tests/validation/mtl_engine/GstreamerApp.py | 18 +++ tests/validation/mtl_engine/ffmpeg_app.py | 89 ++++++++++++- tests/validation/mtl_engine/udp_app.py | 41 ++++++ .../ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py | 2 + 11 files changed, 399 insertions(+), 3 deletions(-) create mode 100644 tests/validation/compliance/__init__.py create mode 100644 tests/validation/compliance/check_compliance.py create mode 100644 tests/validation/compliance/csv_report.py create mode 100644 tests/validation/compliance/load_ebu_config.py create mode 100644 tests/validation/compliance/pcap_compliance.py create mode 100644 tests/validation/compliance/upload_pcap.py create mode 100644 tests/validation/configs/ebu_list.yaml diff --git a/tests/validation/compliance/__init__.py b/tests/validation/compliance/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/validation/compliance/check_compliance.py b/tests/validation/compliance/check_compliance.py new file mode 100644 index 000000000..020f0640a --- /dev/null +++ b/tests/validation/compliance/check_compliance.py @@ -0,0 +1,36 @@ +import sys +from pcap_compliance import PcapComplianceClient +from csv_report import TestCSVReport +import os + +# Usage: +# python3 check_compliance.py [csv_path] +# : UUID of the uploaded PCAP file (from upload step) +# : Name for the test (used for report directory) +# [csv_path] : (Optional) Path to CSV file for appending compliance results and path to logs + +if len(sys.argv) not in (3, 4): + print("Usage: python3 check_compliance.py [csv_path]") + sys.exit(1) + +uuid = sys.argv[1] +test_name = sys.argv[2] +csv_path = sys.argv[3] if len(sys.argv) == 4 else None + +# Step 1: Run compliance check and generate report/logs using PcapComplianceClient +checker = PcapComplianceClient() +checker.authenticate() +checker.pcap_id = uuid +report_path = checker.download_report(test_name) +is_compliant = checker.check_compliance(report_path) +print(f"Report and logs saved in: {os.path.abspath(checker.report_dir)}") + +# Step 2 (optional): If csv_path is provided, append compliance result to CSV +if csv_path: + report = TestCSVReport(csv_path) + compliance_result = "PASSED" if is_compliant else "FAILED" + report.add_result(test_name, compliance_result, checker.report_dir) + print(f"Compliance result added to CSV: {csv_path}") + +# Step 3 Delete the PCAP analysis from the EBU server +checker.delete_pcap(uuid) diff --git a/tests/validation/compliance/csv_report.py b/tests/validation/compliance/csv_report.py new file mode 100644 index 000000000..900f4d9ba --- /dev/null +++ b/tests/validation/compliance/csv_report.py @@ -0,0 +1,31 @@ +import csv +import os + +class TestCSVReport: + def __init__(self, csv_path): + """ + Initialize the CSV report. + If the file does not exist, create it and write the header row. + """ + self.csv_path = csv_path + # Write header if file does not exist + if not os.path.isfile(self.csv_path): + with open(self.csv_path, mode='w', newline='') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + "test_name", # Name of the test + "compliance_result", # Compliance check result (e.g., PASSED/FAILED) + "logs_path" # Final result (e.g., PASSED/FAILED) + ]) + + def add_result(self, test_name, compliance_result, logs_path): + """ + Append a new row with the test results to the CSV file. + """ + with open(self.csv_path, mode='a', newline='') as csvfile: + writer = csv.writer(csvfile) + writer.writerow([ + test_name, + compliance_result, + logs_path + ]) diff --git a/tests/validation/compliance/load_ebu_config.py b/tests/validation/compliance/load_ebu_config.py new file mode 100644 index 000000000..33b58d5cd --- /dev/null +++ b/tests/validation/compliance/load_ebu_config.py @@ -0,0 +1,13 @@ +import yaml + + +def load_ebu_config(config_path: str): + with open(config_path, "r") as f: + config = yaml.safe_load(f) + instance = config["instances"] + config_response = { + "ebu_ip": instance.get("name", ""), + "username": instance.get("username", ""), + "password": instance.get("password", "") + } + return config_response diff --git a/tests/validation/compliance/pcap_compliance.py b/tests/validation/compliance/pcap_compliance.py new file mode 100644 index 000000000..884acb0ff --- /dev/null +++ b/tests/validation/compliance/pcap_compliance.py @@ -0,0 +1,119 @@ +import requests +import os +import yaml +import json +import datetime + +class PcapComplianceClient: + def __init__(self, pcap_file=None, config_path="ebu_list.yaml"): + """ + Initialize the client with optional PCAP file and config path. + Loads EBU server IP and credentials from the YAML config. + """ + self.pcap_file = pcap_file + self.token = None + self.ebu_ip = None + self.user = None + self.password = None + self.pcap_id = None + self.report_dir = None + + # Load EBU IP and credentials from YAML config + if config_path: + with open(config_path, "r") as f: + config = yaml.safe_load(f) + instance = config["instances"] + self.ebu_ip = instance.get("name", "") + self.user = instance.get("username", "") + self.password = instance.get("password", "") + + def authenticate(self): + """ + Authenticate with the EBU server and store the access token. + """ + url = f"http://{self.ebu_ip}/auth/login" + headers = {'Content-Type': 'application/json'} + data = {"username": self.user, "password": self.password} + response = requests.post(url, headers=headers, json=data, verify=False) + response.raise_for_status() + self.token = response.json().get('content', {}).get('token') + if not self.token: + raise Exception("Authentication failed: No token received.") + + def upload_pcap(self): + """ + Upload the PCAP file to the EBU server and store the returned UUID. + Returns the UUID of the uploaded PCAP. + """ + url = f"http://{self.ebu_ip}/api/pcap" + headers = {'Authorization': f'Bearer {self.token}'} + with open(self.pcap_file, 'rb') as f: + files = {'pcap': (os.path.basename(self.pcap_file), f, 'application/vnd.tcpdump.pcap')} + response = requests.put(url, headers=headers, files=files, verify=False) + response.raise_for_status() + self.pcap_id = response.json().get('uuid') + if not self.pcap_id: + raise Exception("Upload failed: No UUID received.") + print(f"Extracted UUID: >>>{self.pcap_id}<<<") + return self.pcap_id + + def download_report(self, test_name): + """ + Download the compliance report for the uploaded PCAP file. + Saves the report as a JSON file in a timestamped directory. + Returns the path to the saved report. + """ + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + self.report_dir = os.path.join("reports", test_name, timestamp) + os.makedirs(self.report_dir, exist_ok=True) + url = f"http://{self.ebu_ip}/api/pcap/{self.pcap_id}/report?type=json" + headers = {'Authorization': f'Bearer {self.token}'} + response = requests.get(url, headers=headers, verify=False) + response.raise_for_status() + report_path = os.path.join(self.report_dir, f"{self.pcap_id}.json") + with open(report_path, "w") as f: + json.dump(response.json(), f, indent=2) + return report_path + + def check_compliance(self, report_path): + """ + Check the compliance result from the downloaded report. + Prints the result, writes PASSED/FAILED files, and lists error IDs if failed. + Returns True if compliant, False otherwise. + """ + with open(report_path, "r") as f: + report = json.load(f) + is_compliant = report.get('not_compliant_streams', 1) == 0 + result_file = "PASSED" if is_compliant else "FAILED" + print(f"Result: {result_file}") + with open(os.path.join(self.report_dir, result_file), "w") as f_result: + if is_compliant: + f_result.write("") + else: + error_ids = [] + for err in report.get('summary', {}).get('error_list', []): + error_id = err.get('value', {}).get('id') + print(error_id) + error_ids.append(str(error_id)) + for error_id in error_ids: + f_result.write(f"{error_id}\n") + print(f"Json file saved: file://{os.path.abspath(report_path)}") + return is_compliant + + def delete_pcap(self, pcap_id=None): + """ + Delete the PCAP file and its report from the EBU server. + If pcap_id is not provided, uses self.pcap_id. + """ + if pcap_id is None: + pcap_id = self.pcap_id + if not pcap_id: + raise ValueError("No PCAP ID provided for deletion.") + url = f"http://{self.ebu_ip}/api/pcap/{pcap_id}" + headers = {'Authorization': f'Bearer {self.token}'} + response = requests.delete(url, headers=headers, verify=False) + if response.status_code == 200: + print(f"PCAP {pcap_id} deleted successfully from EBU server.") + else: + print(f"Failed to delete PCAP {pcap_id}: {response.status_code} {response.text}") + response.raise_for_status() diff --git a/tests/validation/compliance/upload_pcap.py b/tests/validation/compliance/upload_pcap.py new file mode 100644 index 000000000..406be4497 --- /dev/null +++ b/tests/validation/compliance/upload_pcap.py @@ -0,0 +1,45 @@ +import argparse +import sys +import os + +if __name__ == "__main__": + from pcap_compliance import PcapComplianceClient +else: + from .pcap_compliance import PcapComplianceClient + + +def parse_args(): + parser = argparse.ArgumentParser(description="Upload a PCAP file to the EBU server.") + parser.add_argument("--pcap_file_path", type=str, required=True, help="Full path to the PCAP file to upload.") + parser.add_argument("--ip", type=str, required=True, help="IP address to the EBU LIST service.") + parser.add_argument("--login", type=str, required=True, help="Login to the EBU LIST service.") + parser.add_argument("--password", type=str, required=True, help="Password to the EBU LIST service.") + return parser.parse_args() + + +def upload_pcap(file_path, ip, login, password): + # Check for login and password + if not ip or not login or not password: + raise Exception(f"IP address, login and password are required.") + + # Check if the file exists before proceeding + if not os.path.isfile(file_path): + raise Exception(f"File not found: {file_path}") + + # Create the uploader object and upload the PCAP file + # Empty config_path to avoid loading from non-existing YAML file + uploader = PcapComplianceClient(pcap_file=file_path, config_path="") + uploader.ebu_ip = ip + uploader.user = login + uploader.password = password + uploader.authenticate() # Authenticate with the EBU server + uuid = uploader.upload_pcap() # Upload the PCAP file and get the UUID + + return uuid + + +if __name__ == "__main__": + args = parse_args() + uuid = upload_pcap(args.pcap_file_path, args.ip, args.login, args.password) + # Print extractable UUID + print(f">>>UUID>>>{uuid}<< tuple: def execute_dual_test( + mtl_path: str, test_time: int, build: str, tx_host, @@ -1074,6 +1125,7 @@ def execute_dual_test( ) if tx_is_ffmpeg: tx_cmd = ( + f"LD_PRELOAD=/usr/lib/gcc/x86_64-linux-gnu/11/libasan.so " f"ffmpeg -video_size {video_size} -f rawvideo -pix_fmt yuv422p10le " f"-i {video_url} -filter:v fps={fps} -p_port {tx_nic_port_list[0]} " f"-p_sip {ip_dict['tx_interfaces']} -p_tx_ip {ip_dict['tx_sessions']} " @@ -1229,10 +1281,37 @@ def execute_dual_test( rx_proc.wait(timeout=5) except Exception: pass + pcap_file = None + is_compliant = False if tcpdump: tcpdump.stop() + pcap_file = tcpdump.pcap_file if netsniff: netsniff.stop() + pcap_file = netsniff.pcap_file + logger.debug(f"PCAP file: {pcap_file}") + if pcap_file and mtl_path: + # FIXME: Get rid of hardcoded path + ebu_config_path = "configs/ebu_list.yaml" + from compliance.load_ebu_config import load_ebu_config + ebu_config = load_ebu_config(ebu_config_path) + # FIXME: This prints credentials into the logs + ebu_process = rx_host.connection.execute_command( + f"""http_proxy= python3 upload_pcap.py """ + f"""--pcap_file_path {pcap_file} """ + f"""--ip {ebu_config.get('ebu_ip')} """ + f"""--login {ebu_config.get('username')} """ + f"""--password {ebu_config.get('password')}""", + cwd=f"{mtl_path}/tests/validation/compliance/", + ) + ebu_process_output = ebu_process.stdout.strip() + ebu_uuid = ebu_process_output.split(">>>UUID>>>")[-1].split("<< None: diff --git a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py index 95d96cd4a..422a3590d 100755 --- a/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py +++ b/tests/validation/tests/dual/ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py @@ -19,6 +19,7 @@ ) @pytest.mark.parametrize("output_format", ["yuv", "h264"]) def test_rx_ffmpeg_tx_ffmpeg_dual( + mtl_path, hosts, test_time, build, @@ -46,6 +47,7 @@ def test_rx_ffmpeg_tx_ffmpeg_dual( video_file = yuv_files[video_format] ffmpeg_app.execute_dual_test( + mtl_path=mtl_path, test_time=test_time * test_time_multipler, build=build, tx_host=tx_host, From efcadb79729d20c841365679d070ff5df12c2bd7 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Mon, 1 Sep 2025 09:37:17 +0000 Subject: [PATCH 24/35] Re-adding netsniff packet capture after wrongly removing it during merge conflict resolution --- tests/validation/mtl_engine/ffmpeg_app.py | 300 +++++----------------- 1 file changed, 63 insertions(+), 237 deletions(-) diff --git a/tests/validation/mtl_engine/ffmpeg_app.py b/tests/validation/mtl_engine/ffmpeg_app.py index 0e92e6d4e..094cde5bd 100755 --- a/tests/validation/mtl_engine/ffmpeg_app.py +++ b/tests/validation/mtl_engine/ffmpeg_app.py @@ -240,29 +240,12 @@ def execute_test( try: rx_proc.kill() except Exception: - try: - # Force kill if terminate didn't work - rx_proc.kill() - rx_proc.wait(timeout=5) - except Exception: - pass - pcap_file = None - is_compliant = False + # SSH process might already be terminated or unreachable - ignore + pass if tcpdump: tcpdump.stop() - pcap_file = tcpdump.pcap_file if netsniff: netsniff.stop() - pcap_file = netsniff.pcap_file - if pcap_file: - from compliance.load_ebu_config import load_ebu_config - ebu_config = load_ebu_config("configs/ebu_list.yaml") - is_compliant = upload_pcap( - file_path=pcap_file, - ip=ebu_config.get("ip"), - login=ebu_config.get("login"), - password=ebu_config.get("password"), - ) passed = False match output_format: case "yuv": @@ -278,11 +261,7 @@ def execute_test( except Exception as e: logger.info(f"Could not remove output files: {e}") if not passed: - log_fail("Test failed") - if not is_compliant: - log_fail("PCAP compliance check failed") - else: - logger.info("PCAP compliance check passed") + log_fail("test failed") return passed @@ -400,34 +379,16 @@ def execute_test_rgb24( try: rx_proc.kill() except Exception: - try: - rx_proc.kill() - rx_proc.wait(timeout=3) - except Exception: - pass - pcap_file = None - is_compliant = False + # SSH process might already be terminated or unreachable - ignore + pass if tcpdump: tcpdump.stop() - pcap_file = tcpdump.pcap_file if netsniff: netsniff.stop() - pcap_file = netsniff.pcap_file - if pcap_file: - ebu_config = load_ebu_config("configs/ebu_list.yaml") - is_compliant = upload_pcap( - file_path=pcap_file, - ip=ebu_config.get("ip"), - login=ebu_config.get("login"), - password=ebu_config.get("password"), - ) if not check_output_rgb24(rx_output, 1): log_fail("rx video sessions failed") return False - if not is_compliant: - log_fail("PCAP compliance check failed") - else: - logger.info("PCAP compliance check passed") + time.sleep(5) return True @@ -480,8 +441,8 @@ def execute_test_rgb24_multiple( netsniff = prepare_netsniff( capture_cfg, host, - src_ip = f"({ip_dict_rgb24_multiple['p_sip_1']} or {ip_dict_rgb24_multiple['p_sip_2']})", - dst_ip = f"({ip_dict_rgb24_multiple['p_tx_ip_1']} or {ip_dict_rgb24_multiple['p_tx_ip_2']})" + src_ip = str(ip_dict['tx_interfaces']), + dst_ip = str(ip_dict['tx_sessions']) ) try: @@ -554,35 +515,16 @@ def execute_test_rgb24_multiple( try: proc.kill() except Exception: - try: - proc.kill() - proc.wait(timeout=3) - except Exception: - pass - pcap_file = None - is_compliant = False + # Process might already be terminated - ignore kill errors + pass if tcpdump: tcpdump.stop() - pcap_file = tcpdump.pcap_file if netsniff: netsniff.stop() - pcap_file = netsniff.pcap_file - if pcap_file: - from compliance.load_ebu_config import load_ebu_config - ebu_config = load_ebu_config("configs/ebu_list.yaml") - is_compliant = upload_pcap( - file_path=pcap_file, - ip=ebu_config.get("ip"), - login=ebu_config.get("login"), - password=ebu_config.get("password"), - ) if not check_output_rgb24(rx_output, 2): log_fail("rx video session failed") return False - if not is_compliant: - log_fail("PCAP compliance check failed") - else: - logger.info("PCAP compliance check passed") + time.sleep(5) return True @@ -915,7 +857,6 @@ def decode_video_format_to_st20p(video_format: str) -> tuple: def execute_dual_test( - mtl_path: str, test_time: int, build: str, tx_host, @@ -991,12 +932,6 @@ def execute_dual_test( logger.info(f"TX Host: {tx_host}") logger.info(f"RX Host: {rx_host}") - logger.info(f"RX Command: {rx_cmd}") - logger.info(f"TX Command: {tx_cmd}") - log_to_file(f"TX Host: {tx_host}", rx_host, build) - log_to_file(f"RX Host: {rx_host}", rx_host, build) - log_to_file(f"RX Command: {rx_cmd}", rx_host, build) - log_to_file(f"TX Command: {tx_cmd}", tx_host, build) rx_proc = None tx_proc = None @@ -1032,12 +967,12 @@ def execute_dual_test( host=tx_host, background=True, ) - # Start tcpdump after pipelines are running + # Start packet capture when pipelines are running if tcpdump: logger.info("Starting tcpdump capture...") tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) if netsniff: - logger.info("Starting netsniff capture...") + logger.info("Starting netsniff-ng capture...") netsniff.start() # Let the test run for the specified duration @@ -1047,8 +982,6 @@ def execute_dual_test( logger.info("Terminating processes...") if tx_proc: try: - tx_proc.terminate() - except Exception: tx_proc.kill() except Exception: # Process might already be terminated - ignore kill errors @@ -1062,16 +995,6 @@ def execute_dual_test( # Wait a bit for termination time.sleep(2) # Get output after processes have been terminated - try: - rx_output = f"RX Output:\n{rx_proc.stdout_text}" - log_to_file(rx_output, rx_host, build) - except Exception: - logger.info("Could not retrieve RX output") - try: - tx_output = f"TX Output:\n{tx_proc.stdout_text}" - log_to_file(tx_output, tx_host, build) - except Exception: - logger.info("Could not retrieve TX output") capture_stdout(rx_proc, "RX") capture_stdout(tx_proc, "TX") except Exception as e: @@ -1079,70 +1002,35 @@ def execute_dual_test( # Terminate processes immediately on error if tx_proc: try: - tx_proc.terminate() + tx_proc.kill() except Exception: + # Process might already be terminated - ignore kill errors pass if rx_proc: try: - rx_proc.terminate() + rx_proc.kill() except Exception: + # Process might already be terminated - ignore kill errors pass raise finally: - # Ensure processes are terminated with force kill if needed + # Ensure processes are terminated if tx_proc: try: - tx_proc.terminate() - tx_proc.wait(timeout=5) + tx_proc.kill() except Exception: - try: - # Force kill if terminate didn't work - tx_proc.kill() - tx_proc.wait(timeout=5) - except Exception: - pass + # Process might already be terminated - ignore kill errors + pass if rx_proc: try: - rx_proc.terminate() - rx_proc.wait(timeout=5) + rx_proc.kill() except Exception: - try: - # Force kill if terminate didn't work - rx_proc.kill() - rx_proc.wait(timeout=5) - except Exception: - pass - pcap_file = None - is_compliant = False + # Process might already be terminated - ignore kill errors + pass if tcpdump: tcpdump.stop() - pcap_file = tcpdump.pcap_file if netsniff: netsniff.stop() - pcap_file = netsniff.pcap_file - logger.debug(f"PCAP file: {pcap_file}") - if pcap_file and mtl_path: - # FIXME: Get rid of hardcoded path - ebu_config_path = "configs/ebu_list.yaml" - from compliance.load_ebu_config import load_ebu_config - ebu_config = load_ebu_config(ebu_config_path) - # FIXME: This prints credentials into the logs - ebu_process = rx_host.connection.execute_command( - f"""http_proxy= python3 upload_pcap.py """ - f"""--pcap_file_path {pcap_file} """ - f"""--ip {ebu_config.get('ebu_ip')} """ - f"""--login {ebu_config.get('username')} """ - f"""--password {ebu_config.get('password')}""", - cwd=f"{mtl_path}/tests/validation/compliance/", - ) - ebu_process_output = ebu_process.stdout.strip() - ebu_uuid = ebu_process_output.split(">>>UUID>>>")[-1].split("<< Date: Mon, 1 Sep 2025 13:59:59 +0000 Subject: [PATCH 25/35] Adding pcap_capture fixture stub --- tests/validation/conftest.py | 88 +++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 16 deletions(-) diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index 442483c1b..d0620a768 100755 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -10,27 +10,23 @@ from typing import Dict import pytest -from common.nicctl import Nicctl from mfd_common_libs.custom_logger import add_logging_level from mfd_common_libs.log_levels import TEST_FAIL, TEST_INFO, TEST_PASS from mfd_connect.exceptions import ConnectionCalledProcessError +from pytest_mfd_logging.amber_log_formatter import AmberLogFormatter + +from common.nicctl import Nicctl +from create_pcap_file.netsniff import NetsniffRecorder +from create_pcap_file.tcpdump import TcpDumpRecorder from mtl_engine.const import LOG_FOLDER, TESTCMD_LVL -from mtl_engine.csv_report import ( - csv_add_test, - csv_write_report, - update_compliance_result, -) +from mtl_engine.csv_report import (csv_add_test, csv_write_report, + update_compliance_result) +# FIXME: Perhaps, it could be set less statically +from mtl_engine.ffmpeg_app import ip_dict from mtl_engine.ramdisk import Ramdisk -from mtl_engine.stash import ( - clear_issue, - clear_result_log, - clear_result_media, - clear_result_note, - get_issue, - get_result_note, - remove_result_media, -) -from pytest_mfd_logging.amber_log_formatter import AmberLogFormatter +from mtl_engine.stash import (clear_issue, clear_result_log, + clear_result_media, clear_result_note, get_issue, + get_result_note, remove_result_media) logger = logging.getLogger(__name__) phase_report_key = pytest.StashKey[Dict[str, pytest.CollectReport]]() @@ -250,6 +246,66 @@ def log_session(): csv_write_report(f"{LOG_FOLDER}/latest/report.csv") +@pytest.fixture(scope="function", autouse=False) +def pcap_capture(test_config, hosts, nic_port_list, video_format, output_format): + capture_cfg = test_config.get("capture_cfg", {}) + + host = hosts['client'] + src_ip = ip_dict['rx_interfaces'] + dst_ip = ip_dict['rx_sessions'] + + # TODO: Remove the unnecessary logging + # logging.debug("pcap_capture fixture setup started with following parameters:") + # logging.debug(f"capture_cfg={capture_cfg}") + # logging.debug(f"host={host}") + # logging.debug(f"src_ip={src_ip}") + # logging.debug(f"dst_ip={dst_ip}") + dumper = None + if ( + capture_cfg + and capture_cfg.get("enable") + ): + if capture_cfg.get("tool") == "tcpdump": + dumper = TcpDumpRecorder( + host=hosts, + test_name=capture_cfg.get("test_name", "capture"), + pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), + interface=capture_cfg.get("interface"), + ) + elif capture_cfg.get("tool") in ["netsniff", "netsniff-ng"]: + # Filtering + capture_filter = "" + if src_ip: + capture_filter += f"src {src_ip}" + if dst_ip and not capture_filter == "": + capture_filter += f" and dst {dst_ip}" + elif dst_ip: + capture_filter += f"dst {dst_ip}" + # Class prep + dumper = NetsniffRecorder( + host=host, + test_name=capture_cfg.get("test_name", "capture"), + pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), + interface=capture_cfg.get("interface"), + capture_filter = capture_filter if capture_filter != "" else None, # Avoid forcing an empty filter + ) + else: + logging.error(f"Unknown capture tool {capture_cfg.get('tool')}") + if dumper: + if isinstance(dumper, TcpDumpRecorder): + dumper.capture(capture_time=capture_cfg.get("time", test_time)) + elif isinstance(dumper, NetsniffRecorder): + dumper.start() + else: + logging.error(f"Unknown dumper class {dumper.__class__.__name__}") + yield dumper + if dumper: + dumper.stop() + if capture_cfg and capture_cfg.get("compliance", False): + logging.debug("I am supposed to do something with compliance, but not yet implemented") # TODO: Implement compliance logic + pass + + @pytest.fixture(scope="session", autouse=True) def compliance_report(request, log_session, test_config): """ From da568325a5cc3df3b567932e859c7d22640bab0f Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Tue, 2 Sep 2025 08:43:21 +0000 Subject: [PATCH 26/35] Moving the capturing process into a fixture --- tests/validation/conftest.py | 46 ++++----- tests/validation/mtl_engine/ffmpeg_app.py | 117 ---------------------- 2 files changed, 23 insertions(+), 140 deletions(-) diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index d0620a768..7f11c2042 100755 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -22,7 +22,7 @@ from mtl_engine.csv_report import (csv_add_test, csv_write_report, update_compliance_result) # FIXME: Perhaps, it could be set less statically -from mtl_engine.ffmpeg_app import ip_dict +from mtl_engine.ffmpeg_app import ip_dict, ip_dict_rgb24_multiple from mtl_engine.ramdisk import Ramdisk from mtl_engine.stash import (clear_issue, clear_result_log, clear_result_media, clear_result_note, get_issue, @@ -249,24 +249,22 @@ def log_session(): @pytest.fixture(scope="function", autouse=False) def pcap_capture(test_config, hosts, nic_port_list, video_format, output_format): capture_cfg = test_config.get("capture_cfg", {}) + capture_cfg["test_name"] = ( + f"test_rx_ffmpeg_tx_ffmpeg_dual_{video_format}_{output_format}" + ) + + host = hosts['client'] if 'client' in hosts else list(hosts.values())[0] + # FIXME: If possible, change this not to be hardcoded + src_ip = ip_dict['tx_interfaces'] + dst_ip = ip_dict_rgb24_multiple['p_tx_ip_2'] - host = hosts['client'] - src_ip = ip_dict['rx_interfaces'] - dst_ip = ip_dict['rx_sessions'] - - # TODO: Remove the unnecessary logging - # logging.debug("pcap_capture fixture setup started with following parameters:") - # logging.debug(f"capture_cfg={capture_cfg}") - # logging.debug(f"host={host}") - # logging.debug(f"src_ip={src_ip}") - # logging.debug(f"dst_ip={dst_ip}") - dumper = None + capturer = None if ( capture_cfg and capture_cfg.get("enable") ): if capture_cfg.get("tool") == "tcpdump": - dumper = TcpDumpRecorder( + capturer = TcpDumpRecorder( host=hosts, test_name=capture_cfg.get("test_name", "capture"), pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), @@ -275,6 +273,7 @@ def pcap_capture(test_config, hosts, nic_port_list, video_format, output_format) elif capture_cfg.get("tool") in ["netsniff", "netsniff-ng"]: # Filtering capture_filter = "" + # TODO: Enable filtering back when checked if src_ip: capture_filter += f"src {src_ip}" if dst_ip and not capture_filter == "": @@ -282,7 +281,7 @@ def pcap_capture(test_config, hosts, nic_port_list, video_format, output_format) elif dst_ip: capture_filter += f"dst {dst_ip}" # Class prep - dumper = NetsniffRecorder( + capturer = NetsniffRecorder( host=host, test_name=capture_cfg.get("test_name", "capture"), pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), @@ -291,17 +290,18 @@ def pcap_capture(test_config, hosts, nic_port_list, video_format, output_format) ) else: logging.error(f"Unknown capture tool {capture_cfg.get('tool')}") - if dumper: - if isinstance(dumper, TcpDumpRecorder): - dumper.capture(capture_time=capture_cfg.get("time", test_time)) - elif isinstance(dumper, NetsniffRecorder): - dumper.start() + if capturer: + if isinstance(capturer, TcpDumpRecorder): + capturer.capture(capture_time=capture_cfg.get("time", test_time)) + elif isinstance(capturer, NetsniffRecorder): + capturer.start() else: - logging.error(f"Unknown dumper class {dumper.__class__.__name__}") - yield dumper - if dumper: - dumper.stop() + logging.error(f"Unknown capturer class {capturer.__class__.__name__}") + yield capturer + if capturer: + capturer.stop() if capture_cfg and capture_cfg.get("compliance", False): + # TODO: Implement compliance logic logging.debug("I am supposed to do something with compliance, but not yet implemented") # TODO: Implement compliance logic pass diff --git a/tests/validation/mtl_engine/ffmpeg_app.py b/tests/validation/mtl_engine/ffmpeg_app.py index 094cde5bd..71eabc70c 100755 --- a/tests/validation/mtl_engine/ffmpeg_app.py +++ b/tests/validation/mtl_engine/ffmpeg_app.py @@ -10,10 +10,6 @@ from mfd_connect import SSHConnection -from compliance.load_ebu_config import load_ebu_config -from compliance.upload_pcap import upload_pcap -from mtl_engine.RxTxApp import prepare_netsniff, prepare_tcpdump - from . import rxtxapp_config from .execute import log_fail, run @@ -152,13 +148,6 @@ def execute_test( rx_proc = None tx_proc = None - tcpdump = prepare_tcpdump(capture_cfg, host) - netsniff = prepare_netsniff( - capture_cfg, - host, - src_ip = str(ip_dict['tx_interfaces']), - dst_ip = str(ip_dict['tx_sessions']) - ) try: # Start RX pipeline first logger.info("Starting RX pipeline...") @@ -182,13 +171,6 @@ def execute_test( host=host, background=True, ) - # Start packet capture when pipelines are running - if tcpdump: - logger.info("Starting tcpdump capture...") - tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) - if netsniff: - logger.info("Starting netsniff-ng capture...") - netsniff.start() # Let the test run for the specified duration logger.info(f"Running test for {test_time} seconds...") @@ -242,10 +224,6 @@ def execute_test( except Exception: # SSH process might already be terminated or unreachable - ignore pass - if tcpdump: - tcpdump.stop() - if netsniff: - netsniff.stop() passed = False match output_format: case "yuv": @@ -297,13 +275,6 @@ def execute_test_rgb24( rx_proc = None tx_proc = None - tcpdump = prepare_tcpdump(capture_cfg, host) - netsniff = prepare_netsniff( - capture_cfg, - host, - src_ip = str(ip_dict['tx_interfaces']), - dst_ip = str(ip_dict['tx_sessions']) - ) try: # Start RX pipeline first @@ -327,13 +298,6 @@ def execute_test_rgb24( host=host, background=True, ) - # Start packet capture when pipelines are running - if tcpdump: - logger.info("Starting tcpdump capture...") - tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) - if netsniff: - logger.info("Starting netsniff-ng capture...") - netsniff.start() logger.info( f"Waiting for RX process to complete (test_time: {test_time} seconds)..." @@ -381,10 +345,6 @@ def execute_test_rgb24( except Exception: # SSH process might already be terminated or unreachable - ignore pass - if tcpdump: - tcpdump.stop() - if netsniff: - netsniff.stop() if not check_output_rgb24(rx_output, 1): log_fail("rx video sessions failed") return False @@ -437,13 +397,6 @@ def execute_test_rgb24_multiple( rx_proc = None tx_1_proc = None tx_2_proc = None - tcpdump = prepare_tcpdump(capture_cfg, host) - netsniff = prepare_netsniff( - capture_cfg, - host, - src_ip = str(ip_dict['tx_interfaces']), - dst_ip = str(ip_dict['tx_sessions']) - ) try: rx_proc = run( @@ -473,13 +426,6 @@ def execute_test_rgb24_multiple( host=host, background=True, ) - # Start packet capture when pipelines are running - if tcpdump: - logger.info("Starting tcpdump capture...") - tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) - if netsniff: - logger.info("Starting netsniff-ng capture...") - netsniff.start() logger.info(f"Waiting for RX process (test_time: {test_time} seconds)...") rx_proc.wait() @@ -517,10 +463,6 @@ def execute_test_rgb24_multiple( except Exception: # Process might already be terminated - ignore kill errors pass - if tcpdump: - tcpdump.stop() - if netsniff: - netsniff.stop() if not check_output_rgb24(rx_output, 2): log_fail("rx video session failed") return False @@ -935,14 +877,6 @@ def execute_dual_test( rx_proc = None tx_proc = None - # Use RX host for tcpdump capture - tcpdump = prepare_tcpdump(capture_cfg, rx_host) - netsniff = prepare_netsniff( - capture_cfg, - rx_host, - src_ip = str(ip_dict['tx_interfaces']), - dst_ip = str(ip_dict['tx_sessions']) - ) try: # Start RX pipeline first on RX host @@ -967,13 +901,6 @@ def execute_dual_test( host=tx_host, background=True, ) - # Start packet capture when pipelines are running - if tcpdump: - logger.info("Starting tcpdump capture...") - tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) - if netsniff: - logger.info("Starting netsniff-ng capture...") - netsniff.start() # Let the test run for the specified duration logger.info(f"Running test for {test_time} seconds...") @@ -1027,10 +954,6 @@ def execute_dual_test( except Exception: # Process might already be terminated - ignore kill errors pass - if tcpdump: - tcpdump.stop() - if netsniff: - netsniff.stop() passed = False match output_format: case "yuv": @@ -1094,14 +1017,6 @@ def execute_dual_test_rgb24( rx_proc = None tx_proc = None - # Use RX host for tcpdump capture - tcpdump = prepare_tcpdump(capture_cfg, rx_host) - netsniff = prepare_netsniff( - capture_cfg, - rx_host, - src_ip = str(ip_dict['tx_interfaces']), - dst_ip = str(ip_dict['tx_sessions']) - ) try: # Start RX pipeline first on RX host @@ -1127,14 +1042,6 @@ def execute_dual_test_rgb24( background=True, ) - # Start packet capture when pipelines are running - if tcpdump: - logger.info("Starting tcpdump capture...") - tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) - if netsniff: - logger.info("Starting netsniff-ng capture...") - netsniff.start() - logger.info( f"Waiting for RX process to complete (test_time: {test_time} seconds)..." ) @@ -1183,10 +1090,6 @@ def execute_dual_test_rgb24( except Exception: # Process might already be terminated - ignore kill errors pass - if tcpdump: - tcpdump.stop() - if netsniff: - netsniff.stop() if not check_output_rgb24(rx_output, 1): log_fail("rx video sessions failed") @@ -1249,14 +1152,6 @@ def execute_dual_test_rgb24_multiple( rx_proc = None tx_1_proc = None tx_2_proc = None - # Use RX host for tcpdump capture - tcpdump = prepare_tcpdump(capture_cfg, rx_host) - netsniff = prepare_netsniff( - capture_cfg, - rx_host, - src_ip = str(ip_dict['tx_interfaces']), - dst_ip = str(ip_dict['tx_sessions']) - ) try: # Start RX pipeline first on RX host @@ -1290,14 +1185,6 @@ def execute_dual_test_rgb24_multiple( background=True, ) - # Start packet capture when pipelines are running - if tcpdump: - logger.info("Starting tcpdump capture...") - tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) - if netsniff: - logger.info("Starting netsniff-ng capture...") - netsniff.start() - logger.info(f"Waiting for RX process (test_time: {test_time} seconds)...") rx_proc.wait() logger.info("RX process completed") @@ -1335,10 +1222,6 @@ def execute_dual_test_rgb24_multiple( except Exception: # Process might already be terminated - ignore kill errors pass - if tcpdump: - tcpdump.stop() - if netsniff: - netsniff.stop() if not check_output_rgb24(rx_output, 2): log_fail("rx video session failed") From c69405a92dbe065898d0651c12f04fb408238387 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Tue, 2 Sep 2025 13:34:50 +0000 Subject: [PATCH 27/35] Working EBU LIST compliance check upload --- .../validation/compliance/pcap_compliance.py | 22 ++++++---- tests/validation/compliance/upload_pcap.py | 15 +++++-- tests/validation/conftest.py | 42 +++++++++++++++---- .../ffmpeg/test_rx_ffmpeg_tx_ffmpeg_dual.py | 2 +- 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/tests/validation/compliance/pcap_compliance.py b/tests/validation/compliance/pcap_compliance.py index 884acb0ff..a9a22e5d0 100644 --- a/tests/validation/compliance/pcap_compliance.py +++ b/tests/validation/compliance/pcap_compliance.py @@ -5,7 +5,7 @@ import datetime class PcapComplianceClient: - def __init__(self, pcap_file=None, config_path="ebu_list.yaml"): + def __init__(self, pcap_file=None, config_path="ebu_list.yaml", proxies={'http': "", "https": "", "ftp": ""}): """ Initialize the client with optional PCAP file and config path. Loads EBU server IP and credentials from the YAML config. @@ -17,6 +17,9 @@ def __init__(self, pcap_file=None, config_path="ebu_list.yaml"): self.password = None self.pcap_id = None self.report_dir = None + self.proxies = proxies + self.session = requests.Session() + self.session.trust_env = False # Do not use system proxy settings # Load EBU IP and credentials from YAML config if config_path: @@ -34,7 +37,7 @@ def authenticate(self): url = f"http://{self.ebu_ip}/auth/login" headers = {'Content-Type': 'application/json'} data = {"username": self.user, "password": self.password} - response = requests.post(url, headers=headers, json=data, verify=False) + response = self.session.post(url, headers=headers, json=data, verify=False, proxies=self.proxies) response.raise_for_status() self.token = response.json().get('content', {}).get('token') if not self.token: @@ -47,11 +50,12 @@ def upload_pcap(self): """ url = f"http://{self.ebu_ip}/api/pcap" headers = {'Authorization': f'Bearer {self.token}'} - with open(self.pcap_file, 'rb') as f: - files = {'pcap': (os.path.basename(self.pcap_file), f, 'application/vnd.tcpdump.pcap')} - response = requests.put(url, headers=headers, files=files, verify=False) - response.raise_for_status() - self.pcap_id = response.json().get('uuid') + if self.pcap_file: + with open(self.pcap_file, 'rb') as f: + files = {'pcap': (os.path.basename(self.pcap_file), f, 'application/vnd.tcpdump.pcap')} + response = self.session.put(url, headers=headers, files=files, verify=False, proxies=self.proxies) + response.raise_for_status() + self.pcap_id = response.json().get('uuid') if not self.pcap_id: raise Exception("Upload failed: No UUID received.") print(f"Extracted UUID: >>>{self.pcap_id}<<<") @@ -68,7 +72,7 @@ def download_report(self, test_name): os.makedirs(self.report_dir, exist_ok=True) url = f"http://{self.ebu_ip}/api/pcap/{self.pcap_id}/report?type=json" headers = {'Authorization': f'Bearer {self.token}'} - response = requests.get(url, headers=headers, verify=False) + response = self.session.get(url, headers=headers, verify=False, proxies=self.proxies) response.raise_for_status() report_path = os.path.join(self.report_dir, f"{self.pcap_id}.json") with open(report_path, "w") as f: @@ -111,7 +115,7 @@ def delete_pcap(self, pcap_id=None): raise ValueError("No PCAP ID provided for deletion.") url = f"http://{self.ebu_ip}/api/pcap/{pcap_id}" headers = {'Authorization': f'Bearer {self.token}'} - response = requests.delete(url, headers=headers, verify=False) + response = self.session.delete(url, headers=headers, verify=False, proxies=self.proxies) if response.status_code == 200: print(f"PCAP {pcap_id} deleted successfully from EBU server.") else: diff --git a/tests/validation/compliance/upload_pcap.py b/tests/validation/compliance/upload_pcap.py index 406be4497..fbe9948f2 100644 --- a/tests/validation/compliance/upload_pcap.py +++ b/tests/validation/compliance/upload_pcap.py @@ -14,10 +14,11 @@ def parse_args(): parser.add_argument("--ip", type=str, required=True, help="IP address to the EBU LIST service.") parser.add_argument("--login", type=str, required=True, help="Login to the EBU LIST service.") parser.add_argument("--password", type=str, required=True, help="Password to the EBU LIST service.") + parser.add_argument("--proxy", type=str, required=False, help="Proxy used to upload to the EBU LIST service.") return parser.parse_args() -def upload_pcap(file_path, ip, login, password): +def upload_pcap(file_path, ip, login, password, proxies): # Check for login and password if not ip or not login or not password: raise Exception(f"IP address, login and password are required.") @@ -28,7 +29,7 @@ def upload_pcap(file_path, ip, login, password): # Create the uploader object and upload the PCAP file # Empty config_path to avoid loading from non-existing YAML file - uploader = PcapComplianceClient(pcap_file=file_path, config_path="") + uploader = PcapComplianceClient(pcap_file=file_path, config_path="", proxies=proxies) uploader.ebu_ip = ip uploader.user = login uploader.password = password @@ -40,6 +41,14 @@ def upload_pcap(file_path, ip, login, password): if __name__ == "__main__": args = parse_args() - uuid = upload_pcap(args.pcap_file_path, args.ip, args.login, args.password) + # TODO: Handle proxies better, so each type can have a proper value + proxies = None + if args.proxy is not None: + proxies = { + 'http': args.proxy, + 'https': args.proxy, + 'ftp': args.proxy + } + uuid = upload_pcap(args.pcap_file_path, args.ip, args.login, args.password, proxies) # Print extractable UUID print(f">>>UUID>>>{uuid}<<>>UUID>>>")[1].split("<< Date: Tue, 2 Sep 2025 14:21:11 +0000 Subject: [PATCH 28/35] Fixing linter issues --- .../validation/compliance/check_compliance.py | 5 ++-- tests/validation/compliance/csv_report.py | 1 + .../validation/compliance/pcap_compliance.py | 10 ++++---- tests/validation/compliance/upload_pcap.py | 3 +-- tests/validation/conftest.py | 15 ++++++------ tests/validation/create_pcap_file/netsniff.py | 9 ++++---- tests/validation/mtl_engine/GstreamerApp.py | 4 ++-- tests/validation/mtl_engine/RxTxApp.py | 23 ++++++++----------- tests/validation/mtl_engine/udp_app.py | 5 ++-- 9 files changed, 36 insertions(+), 39 deletions(-) diff --git a/tests/validation/compliance/check_compliance.py b/tests/validation/compliance/check_compliance.py index 020f0640a..ed14bb13f 100644 --- a/tests/validation/compliance/check_compliance.py +++ b/tests/validation/compliance/check_compliance.py @@ -1,7 +1,8 @@ +import os import sys -from pcap_compliance import PcapComplianceClient + from csv_report import TestCSVReport -import os +from pcap_compliance import PcapComplianceClient # Usage: # python3 check_compliance.py [csv_path] diff --git a/tests/validation/compliance/csv_report.py b/tests/validation/compliance/csv_report.py index 900f4d9ba..d4877ec50 100644 --- a/tests/validation/compliance/csv_report.py +++ b/tests/validation/compliance/csv_report.py @@ -1,6 +1,7 @@ import csv import os + class TestCSVReport: def __init__(self, csv_path): """ diff --git a/tests/validation/compliance/pcap_compliance.py b/tests/validation/compliance/pcap_compliance.py index a9a22e5d0..404fd751a 100644 --- a/tests/validation/compliance/pcap_compliance.py +++ b/tests/validation/compliance/pcap_compliance.py @@ -1,8 +1,10 @@ -import requests +import datetime +import json import os + +import requests import yaml -import json -import datetime + class PcapComplianceClient: def __init__(self, pcap_file=None, config_path="ebu_list.yaml", proxies={'http': "", "https": "", "ftp": ""}): @@ -103,7 +105,7 @@ def check_compliance(self, report_path): f_result.write(f"{error_id}\n") print(f"Json file saved: file://{os.path.abspath(report_path)}") return is_compliant - + def delete_pcap(self, pcap_id=None): """ Delete the PCAP file and its report from the EBU server. diff --git a/tests/validation/compliance/upload_pcap.py b/tests/validation/compliance/upload_pcap.py index fbe9948f2..cd38fc7c8 100644 --- a/tests/validation/compliance/upload_pcap.py +++ b/tests/validation/compliance/upload_pcap.py @@ -1,5 +1,4 @@ import argparse -import sys import os if __name__ == "__main__": @@ -21,7 +20,7 @@ def parse_args(): def upload_pcap(file_path, ip, login, password, proxies): # Check for login and password if not ip or not login or not password: - raise Exception(f"IP address, login and password are required.") + raise Exception("IP address, login and password are required.") # Check if the file exists before proceeding if not os.path.isfile(file_path): diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index cc395b948..b5c0704a1 100755 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -10,14 +10,12 @@ from typing import Dict import pytest -from mfd_common_libs.custom_logger import add_logging_level -from mfd_common_libs.log_levels import TEST_FAIL, TEST_INFO, TEST_PASS -from mfd_connect.exceptions import ConnectionCalledProcessError -from pytest_mfd_logging.amber_log_formatter import AmberLogFormatter - from common.nicctl import Nicctl from create_pcap_file.netsniff import NetsniffRecorder from create_pcap_file.tcpdump import TcpDumpRecorder +from mfd_common_libs.custom_logger import add_logging_level +from mfd_common_libs.log_levels import TEST_FAIL, TEST_INFO, TEST_PASS +from mfd_connect.exceptions import ConnectionCalledProcessError from mtl_engine.const import LOG_FOLDER, TESTCMD_LVL from mtl_engine.csv_report import (csv_add_test, csv_write_report, update_compliance_result) @@ -27,6 +25,7 @@ from mtl_engine.stash import (clear_issue, clear_result_log, clear_result_media, clear_result_note, get_issue, get_result_note, remove_result_media) +from pytest_mfd_logging.amber_log_formatter import AmberLogFormatter logger = logging.getLogger(__name__) phase_report_key = pytest.StashKey[Dict[str, pytest.CollectReport]]() @@ -301,7 +300,7 @@ def pcap_capture(test_config, hosts, nic_port_list, video_format, output_format, test_name=capture_cfg.get("test_name", "capture"), pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), interface=capture_cfg.get("interface", "eth0"), - capture_filter = capture_filter if capture_filter != "" else None, # Avoid forcing an empty filter + capture_filter=capture_filter if capture_filter != "" else None, # Avoid forcing an empty filter ) else: logging.error(f"Unknown capture tool {capture_cfg.get('tool')}") @@ -318,7 +317,9 @@ def pcap_capture(test_config, hosts, nic_port_list, video_format, output_format, capturer.stop() if capture_cfg and capture_cfg.get("compliance", False): # FIXME: This is generally a bad practice to call it like that, but for now it is the easiest way - ebu_ip, ebu_login, ebu_passwd, ebu_proxy = read_ebu_creds(config_path=capture_cfg.get("ebu_yaml_path", "configs/ebu_list.yaml")) # Reads from executor + ebu_ip, ebu_login, ebu_passwd, ebu_proxy = read_ebu_creds( + config_path=capture_cfg.get("ebu_yaml_path", "configs/ebu_list.yaml") + ) # Reads from executor proxy_cmd = (f' --proxy {ebu_proxy}' if ebu_proxy else '') compliance_upl = host.connection.execute_command( 'python3 ./tests/validation/compliance/upload_pcap.py' diff --git a/tests/validation/create_pcap_file/netsniff.py b/tests/validation/create_pcap_file/netsniff.py index a86486c0c..a11e3ae89 100644 --- a/tests/validation/create_pcap_file/netsniff.py +++ b/tests/validation/create_pcap_file/netsniff.py @@ -11,6 +11,7 @@ STARTUP_WAIT = 2 # Default wait time after starting the process + class NetsniffRecorder: """ Class to handle the recording of network traffic using netsniff-ng. @@ -34,7 +35,7 @@ def __init__( interface_index: int = 0, silent: bool = True, capture_filter: str | None = None, - ): + ): self.host = host self.test_name = test_name self.pcap_dir = pcap_dir @@ -81,9 +82,8 @@ def start(self, startup_wait=STARTUP_WAIT): logger.info(f"netsniff-ng started with PID {self.netsniff_process.pid}.") return True except ConnectionCalledProcessError as e: - logger.error(f"Failed to start netsniff-ng: {e}") - return False - + logger.error(f"Failed to start netsniff-ng: {e}") + return False def capture(self, capture_time=20, startup_wait=2): """ @@ -100,7 +100,6 @@ def capture(self, capture_time=20, startup_wait=2): else: logger.error("netsniff-ng did not start; skipping capture.") - def stop(self): """ Stops all netsniff-ng processes on the host using pkill. diff --git a/tests/validation/mtl_engine/GstreamerApp.py b/tests/validation/mtl_engine/GstreamerApp.py index 4ff1954a3..ebbf76634 100755 --- a/tests/validation/mtl_engine/GstreamerApp.py +++ b/tests/validation/mtl_engine/GstreamerApp.py @@ -6,8 +6,8 @@ import os import time -from mtl_engine.RxTxApp import prepare_netsniff, prepare_tcpdump from compliance.pcap_compliance import PcapComplianceClient +from mtl_engine.RxTxApp import prepare_netsniff, prepare_tcpdump from .execute import log_fail, run @@ -458,7 +458,7 @@ def execute_test( if pcap_file: # FIXME: Get rid of hardcoded path ebu_config_path = "configs/ebu_list.yaml" - compliance_client = PcapComplianceClient(config_path = ebu_config_path) + compliance_client = PcapComplianceClient(config_path=ebu_config_path) compliance_client.authenticate() compliance_client.upload_pcap() report_path = compliance_client.download_report(test_name=compliance_client.pcap_id) diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index fc8c519ea..022921ee0 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -9,10 +9,9 @@ import sys import time -from mfd_connect import SSHConnection - from create_pcap_file.netsniff import NetsniffRecorder from create_pcap_file.tcpdump import TcpDumpRecorder +from mfd_connect import SSHConnection from . import rxtxapp_config from .execute import log_fail, run @@ -148,9 +147,9 @@ def prepare_tcpdump(capture_cfg, host=None): def prepare_netsniff( capture_cfg, host=None, - src_ip: str|None = None, - dst_ip: str|None = None - ): + src_ip: str | None = None, + dst_ip: str | None = None +): """ Prepare NetsniffRecorder if capture_cfg is enabled. @@ -175,7 +174,7 @@ def prepare_netsniff( test_name=capture_cfg.get("test_name", "capture"), pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), interface=capture_cfg.get("interface"), - capture_filter = capture_filter if capture_filter != "" else None, # Avoid forcing an empty filter + capture_filter=capture_filter if capture_filter != "" else None, # Avoid forcing an empty filter ) return netsniff else: @@ -660,12 +659,10 @@ def execute_test( if tcpdump: tcpdump.capture(capture_time=capture_cfg.get("capture_time", 0.5)) logger.info(f"Started tcpdump capture on host {host.name}") - log_to_file("Started tcpdump capture", host, build) # Start netsniff-ng capture (blocking, so it captures during traffic) if netsniff: netsniff.start() logger.info(f"Started netsniff-ng capture on host {host.name}") - log_to_file("Started netsniff-ng capture", host, build) finally: cp.wait() @@ -866,10 +863,10 @@ def execute_perf_test( # Start tcpdump capture (blocking, so it captures during traffic) if tcpdump: tcpdump.capture(capture_time=capture_cfg.get("capture_time", 0.5)) - log_to_file("Started performance test tcpdump capture", host, build) + logger.info("Started performance test tcpdump capture") if netsniff: netsniff.start() - log_to_file("Started performance test netsniff-ng capture", host, build) + logger.info("Started performance test netsniff-ng capture") finally: cp.wait() @@ -1529,10 +1526,8 @@ def execute_dual_test( # Log test start logger.info(f"Starting dual RxTxApp test: {get_case_id()}") - log_to_file(f"Starting dual RxTxApp test: {get_case_id()}", tx_host, build) - log_to_file(f"Starting dual RxTxApp test: {get_case_id()}", rx_host, build) - log_to_file(f"TX config: {tx_config_json}", tx_host, build) - log_to_file(f"RX config: {rx_config_json}", rx_host, build) + logger.info(f"TX config: {tx_config_json}", tx_host, build) + logger.info(f"RX config: {rx_config_json}", rx_host, build) # Prepare TX config tx_config_file = f"{build}/tests/tx_config.json" diff --git a/tests/validation/mtl_engine/udp_app.py b/tests/validation/mtl_engine/udp_app.py index 55caea019..800df8e6b 100644 --- a/tests/validation/mtl_engine/udp_app.py +++ b/tests/validation/mtl_engine/udp_app.py @@ -9,7 +9,6 @@ from mtl_engine import udp_app_config from mtl_engine.RxTxApp import prepare_netsniff, prepare_tcpdump -from compliance.upload_pcap import upload_pcap from tests.validation.compliance.pcap_compliance import PcapComplianceClient from .const import LOG_FOLDER @@ -93,7 +92,7 @@ def execute_test_sample( if pcap_file: # FIXME: Get rid of hardcoded path ebu_config_path = "configs/ebu_list.yaml" - compliance_client = PcapComplianceClient(config_path = ebu_config_path) + compliance_client = PcapComplianceClient(config_path=ebu_config_path) compliance_client.authenticate() compliance_client.upload_pcap() report_path = compliance_client.download_report(test_name=compliance_client.pcap_id) @@ -183,7 +182,7 @@ def execute_test_librist( if pcap_file: # FIXME: Get rid of hardcoded path ebu_config_path = "configs/ebu_list.yaml" - compliance_client = PcapComplianceClient(config_path = ebu_config_path) + compliance_client = PcapComplianceClient(config_path=ebu_config_path) compliance_client.authenticate() compliance_client.upload_pcap() report_path = compliance_client.download_report(test_name=compliance_client.pcap_id) From 48d5ea919ffdfd60d0712c09b8c0a8fe70fa170e Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 3 Sep 2025 07:05:16 +0000 Subject: [PATCH 29/35] Further linter-raised fixes --- tests/validation/compliance/csv_report.py | 22 +++--- .../validation/compliance/load_ebu_config.py | 2 +- .../validation/compliance/pcap_compliance.py | 6 +- tests/validation/compliance/upload_pcap.py | 40 +++++++--- tests/validation/conftest.py | 73 ++++++++++++------- tests/validation/create_pcap_file/netsniff.py | 28 ++++--- .../create_pcap_file/netsniff_readme.md | 6 +- tests/validation/mtl_engine/GstreamerApp.py | 4 +- tests/validation/mtl_engine/RxTxApp.py | 9 +-- tests/validation/mtl_engine/udp_app.py | 8 +- 10 files changed, 123 insertions(+), 75 deletions(-) diff --git a/tests/validation/compliance/csv_report.py b/tests/validation/compliance/csv_report.py index d4877ec50..ad007adae 100644 --- a/tests/validation/compliance/csv_report.py +++ b/tests/validation/compliance/csv_report.py @@ -11,22 +11,20 @@ def __init__(self, csv_path): self.csv_path = csv_path # Write header if file does not exist if not os.path.isfile(self.csv_path): - with open(self.csv_path, mode='w', newline='') as csvfile: + with open(self.csv_path, mode="w", newline="") as csvfile: writer = csv.writer(csvfile) - writer.writerow([ - "test_name", # Name of the test - "compliance_result", # Compliance check result (e.g., PASSED/FAILED) - "logs_path" # Final result (e.g., PASSED/FAILED) - ]) + writer.writerow( + [ + "test_name", # Name of the test + "compliance_result", # Compliance check result (e.g., PASSED/FAILED) + "logs_path", # Final result (e.g., PASSED/FAILED) + ] + ) def add_result(self, test_name, compliance_result, logs_path): """ Append a new row with the test results to the CSV file. """ - with open(self.csv_path, mode='a', newline='') as csvfile: + with open(self.csv_path, mode="a", newline="") as csvfile: writer = csv.writer(csvfile) - writer.writerow([ - test_name, - compliance_result, - logs_path - ]) + writer.writerow([test_name, compliance_result, logs_path]) diff --git a/tests/validation/compliance/load_ebu_config.py b/tests/validation/compliance/load_ebu_config.py index 33b58d5cd..a241b7dd5 100644 --- a/tests/validation/compliance/load_ebu_config.py +++ b/tests/validation/compliance/load_ebu_config.py @@ -8,6 +8,6 @@ def load_ebu_config(config_path: str): config_response = { "ebu_ip": instance.get("name", ""), "username": instance.get("username", ""), - "password": instance.get("password", "") + "password": instance.get("password", ""), } return config_response diff --git a/tests/validation/compliance/pcap_compliance.py b/tests/validation/compliance/pcap_compliance.py index 404fd751a..7b3df0217 100644 --- a/tests/validation/compliance/pcap_compliance.py +++ b/tests/validation/compliance/pcap_compliance.py @@ -89,7 +89,7 @@ def check_compliance(self, report_path): """ with open(report_path, "r") as f: report = json.load(f) - is_compliant = report.get('not_compliant_streams', 1) == 0 + is_compliant = report.get("not_compliant_streams", 1) == 0 result_file = "PASSED" if is_compliant else "FAILED" print(f"Result: {result_file}") with open(os.path.join(self.report_dir, result_file), "w") as f_result: @@ -97,8 +97,8 @@ def check_compliance(self, report_path): f_result.write("") else: error_ids = [] - for err in report.get('summary', {}).get('error_list', []): - error_id = err.get('value', {}).get('id') + for err in report.get("summary", {}).get("error_list", []): + error_id = err.get("value", {}).get("id") print(error_id) error_ids.append(str(error_id)) for error_id in error_ids: diff --git a/tests/validation/compliance/upload_pcap.py b/tests/validation/compliance/upload_pcap.py index cd38fc7c8..ce505af4a 100644 --- a/tests/validation/compliance/upload_pcap.py +++ b/tests/validation/compliance/upload_pcap.py @@ -8,12 +8,30 @@ def parse_args(): - parser = argparse.ArgumentParser(description="Upload a PCAP file to the EBU server.") - parser.add_argument("--pcap_file_path", type=str, required=True, help="Full path to the PCAP file to upload.") - parser.add_argument("--ip", type=str, required=True, help="IP address to the EBU LIST service.") - parser.add_argument("--login", type=str, required=True, help="Login to the EBU LIST service.") - parser.add_argument("--password", type=str, required=True, help="Password to the EBU LIST service.") - parser.add_argument("--proxy", type=str, required=False, help="Proxy used to upload to the EBU LIST service.") + parser = argparse.ArgumentParser( + description="Upload a PCAP file to the EBU server." + ) + parser.add_argument( + "--pcap_file_path", + type=str, + required=True, + help="Full path to the PCAP file to upload.", + ) + parser.add_argument( + "--ip", type=str, required=True, help="IP address to the EBU LIST service." + ) + parser.add_argument( + "--login", type=str, required=True, help="Login to the EBU LIST service." + ) + parser.add_argument( + "--password", type=str, required=True, help="Password to the EBU LIST service." + ) + parser.add_argument( + "--proxy", + type=str, + required=False, + help="Proxy used to upload to the EBU LIST service.", + ) return parser.parse_args() @@ -28,7 +46,9 @@ def upload_pcap(file_path, ip, login, password, proxies): # Create the uploader object and upload the PCAP file # Empty config_path to avoid loading from non-existing YAML file - uploader = PcapComplianceClient(pcap_file=file_path, config_path="", proxies=proxies) + uploader = PcapComplianceClient( + pcap_file=file_path, config_path="", proxies=proxies + ) uploader.ebu_ip = ip uploader.user = login uploader.password = password @@ -43,11 +63,7 @@ def upload_pcap(file_path, ip, login, password, proxies): # TODO: Handle proxies better, so each type can have a proper value proxies = None if args.proxy is not None: - proxies = { - 'http': args.proxy, - 'https': args.proxy, - 'ftp': args.proxy - } + proxies = {"http": args.proxy, "https": args.proxy, "ftp": args.proxy} uuid = upload_pcap(args.pcap_file_path, args.ip, args.login, args.password, proxies) # Print extractable UUID print(f">>>UUID>>>{uuid}<<>>UUID>>>")[1].split("<<>>UUID>>>")[1] + .split("<< **Note:** At the moment, there is no automation present for netsniff-ng tool installation on the capturing machine. It must be installed on the system manually before running the test. If the capture fails, the test continues without the packet capture. -There is an official instruction on how to download the tool present on the [tool's website](http://netsniff-ng.org/). It can also be useful for finding mirrors of the repositories. Below, only the Github repository is considered as a source. +There is an official instruction on how to download the tool present on the [tool's site](http://netsniff-ng.org/). It can also be useful for finding mirrors of the repositories. Below, only the GitHub repository is considered as a source. ```shell NETSNIFF_REPO="https://github.com/netsniff-ng/netsniff-ng" @@ -44,7 +44,9 @@ tar xvz "${NETSNIFF_VERSION}" # 2. After the tool is installed on the capturing machine, it can be used within the test code. -In order to create and receive a `NetsniffRecorder` class instance, that can be used to start and stop the packet capture, use the `prepare_netsniff()` function. It takes the capture config (`capture_cfg`), host (`host`) and optional filters for source and destination IP addresses (`src_ip` and `dst_ip` respectively). The filtering IPs should be used to avoid capturing unrelated traffic during the test. +In order to create and receive a `NetsniffRecorder` class instance, that can be used to start and stop the packet capture, +use the `prepare_netsniff()` function. It takes the capture config (`capture_cfg`), host (`host`) and optional filters for source +and destination IP addresses (`src_ip` and `dst_ip` respectively). The filtering IPs should be used to avoid capturing unrelated traffic during the test. To start the capture, call `start()` function within the `NetsniffRecorder` class object, with optional `startup_wait` parameter specifying the number of seconds that should be waited before the capture starts (initialization delay). diff --git a/tests/validation/mtl_engine/GstreamerApp.py b/tests/validation/mtl_engine/GstreamerApp.py index ebbf76634..ac7252a36 100755 --- a/tests/validation/mtl_engine/GstreamerApp.py +++ b/tests/validation/mtl_engine/GstreamerApp.py @@ -461,7 +461,9 @@ def execute_test( compliance_client = PcapComplianceClient(config_path=ebu_config_path) compliance_client.authenticate() compliance_client.upload_pcap() - report_path = compliance_client.download_report(test_name=compliance_client.pcap_id) + report_path = compliance_client.download_report( + test_name=compliance_client.pcap_id + ) is_compliant = compliance_client.check_compliance(report_path=report_path) # Compare files for validation diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index 022921ee0..b8946e6e5 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -145,10 +145,7 @@ def prepare_tcpdump(capture_cfg, host=None): def prepare_netsniff( - capture_cfg, - host=None, - src_ip: str | None = None, - dst_ip: str | None = None + capture_cfg, host=None, src_ip: str | None = None, dst_ip: str | None = None ): """ Prepare NetsniffRecorder if capture_cfg is enabled. @@ -174,7 +171,9 @@ def prepare_netsniff( test_name=capture_cfg.get("test_name", "capture"), pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), interface=capture_cfg.get("interface"), - capture_filter=capture_filter if capture_filter != "" else None, # Avoid forcing an empty filter + capture_filter=( + capture_filter if capture_filter != "" else None + ), # Avoid forcing an empty filter ) return netsniff else: diff --git a/tests/validation/mtl_engine/udp_app.py b/tests/validation/mtl_engine/udp_app.py index 800df8e6b..1af150e80 100644 --- a/tests/validation/mtl_engine/udp_app.py +++ b/tests/validation/mtl_engine/udp_app.py @@ -95,7 +95,9 @@ def execute_test_sample( compliance_client = PcapComplianceClient(config_path=ebu_config_path) compliance_client.authenticate() compliance_client.upload_pcap() - report_path = compliance_client.download_report(test_name=compliance_client.pcap_id) + report_path = compliance_client.download_report( + test_name=compliance_client.pcap_id + ) is_compliant = compliance_client.check_compliance(report_path=report_path) if not check_received_packets(client_proc.output) or not check_received_packets( @@ -185,7 +187,9 @@ def execute_test_librist( compliance_client = PcapComplianceClient(config_path=ebu_config_path) compliance_client.authenticate() compliance_client.upload_pcap() - report_path = compliance_client.download_report(test_name=compliance_client.pcap_id) + report_path = compliance_client.download_report( + test_name=compliance_client.pcap_id + ) is_compliant = compliance_client.check_compliance(report_path=report_path) if not check_connected_receivers( From 9b083295f6f02dbc42c99d4cda1f1c6d373f1f07 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 3 Sep 2025 07:19:44 +0000 Subject: [PATCH 30/35] Further linter changes in pcap_compliance.py --- .../validation/compliance/pcap_compliance.py | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/tests/validation/compliance/pcap_compliance.py b/tests/validation/compliance/pcap_compliance.py index 7b3df0217..56ddc6780 100644 --- a/tests/validation/compliance/pcap_compliance.py +++ b/tests/validation/compliance/pcap_compliance.py @@ -7,7 +7,12 @@ class PcapComplianceClient: - def __init__(self, pcap_file=None, config_path="ebu_list.yaml", proxies={'http': "", "https": "", "ftp": ""}): + def __init__( + self, + pcap_file=None, + config_path="ebu_list.yaml", + proxies={"http": "", "https": "", "ftp": ""} + ): """ Initialize the client with optional PCAP file and config path. Loads EBU server IP and credentials from the YAML config. @@ -37,11 +42,13 @@ def authenticate(self): Authenticate with the EBU server and store the access token. """ url = f"http://{self.ebu_ip}/auth/login" - headers = {'Content-Type': 'application/json'} + headers = {"Content-Type": "application/json"} data = {"username": self.user, "password": self.password} - response = self.session.post(url, headers=headers, json=data, verify=False, proxies=self.proxies) + response = self.session.post( + url, headers=headers, json=data, verify=False, proxies=self.proxies + ) response.raise_for_status() - self.token = response.json().get('content', {}).get('token') + self.token = response.json().get("content", {}).get("token") if not self.token: raise Exception("Authentication failed: No token received.") @@ -51,13 +58,25 @@ def upload_pcap(self): Returns the UUID of the uploaded PCAP. """ url = f"http://{self.ebu_ip}/api/pcap" - headers = {'Authorization': f'Bearer {self.token}'} + headers = {"Authorization": f"Bearer {self.token}"} if self.pcap_file: - with open(self.pcap_file, 'rb') as f: - files = {'pcap': (os.path.basename(self.pcap_file), f, 'application/vnd.tcpdump.pcap')} - response = self.session.put(url, headers=headers, files=files, verify=False, proxies=self.proxies) + with open(self.pcap_file, "rb") as f: + files = { + "pcap": ( + os.path.basename(self.pcap_file), + f, + "application/vnd.tcpdump.pcap" + ) + } + response = self.session.put( + url, + headers=headers, + files=files, + verify=False, + proxies=self.proxies + ) response.raise_for_status() - self.pcap_id = response.json().get('uuid') + self.pcap_id = response.json().get("uuid") if not self.pcap_id: raise Exception("Upload failed: No UUID received.") print(f"Extracted UUID: >>>{self.pcap_id}<<<") @@ -73,7 +92,7 @@ def download_report(self, test_name): self.report_dir = os.path.join("reports", test_name, timestamp) os.makedirs(self.report_dir, exist_ok=True) url = f"http://{self.ebu_ip}/api/pcap/{self.pcap_id}/report?type=json" - headers = {'Authorization': f'Bearer {self.token}'} + headers = {"Authorization": f"Bearer {self.token}"} response = self.session.get(url, headers=headers, verify=False, proxies=self.proxies) response.raise_for_status() report_path = os.path.join(self.report_dir, f"{self.pcap_id}.json") @@ -116,10 +135,14 @@ def delete_pcap(self, pcap_id=None): if not pcap_id: raise ValueError("No PCAP ID provided for deletion.") url = f"http://{self.ebu_ip}/api/pcap/{pcap_id}" - headers = {'Authorization': f'Bearer {self.token}'} - response = self.session.delete(url, headers=headers, verify=False, proxies=self.proxies) + headers = {"Authorization": f"Bearer {self.token}"} + response = self.session.delete( + url, headers=headers, verify=False, proxies=self.proxies + ) if response.status_code == 200: print(f"PCAP {pcap_id} deleted successfully from EBU server.") else: - print(f"Failed to delete PCAP {pcap_id}: {response.status_code} {response.text}") + print( + f"Failed to delete PCAP {pcap_id}: {response.status_code} {response.text}" + ) response.raise_for_status() From 60ec24e54ae576bef45b72f483a8c40f1b60cc4d Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 3 Sep 2025 07:25:24 +0000 Subject: [PATCH 31/35] Further linter changes in pcap_compliance.py --- tests/validation/compliance/pcap_compliance.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/validation/compliance/pcap_compliance.py b/tests/validation/compliance/pcap_compliance.py index 56ddc6780..074d7b2d8 100644 --- a/tests/validation/compliance/pcap_compliance.py +++ b/tests/validation/compliance/pcap_compliance.py @@ -11,7 +11,7 @@ def __init__( self, pcap_file=None, config_path="ebu_list.yaml", - proxies={"http": "", "https": "", "ftp": ""} + proxies={"http": "", "https": "", "ftp": ""}, ): """ Initialize the client with optional PCAP file and config path. @@ -65,7 +65,7 @@ def upload_pcap(self): "pcap": ( os.path.basename(self.pcap_file), f, - "application/vnd.tcpdump.pcap" + "application/vnd.tcpdump.pcap", ) } response = self.session.put( @@ -73,7 +73,7 @@ def upload_pcap(self): headers=headers, files=files, verify=False, - proxies=self.proxies + proxies=self.proxies, ) response.raise_for_status() self.pcap_id = response.json().get("uuid") @@ -93,7 +93,9 @@ def download_report(self, test_name): os.makedirs(self.report_dir, exist_ok=True) url = f"http://{self.ebu_ip}/api/pcap/{self.pcap_id}/report?type=json" headers = {"Authorization": f"Bearer {self.token}"} - response = self.session.get(url, headers=headers, verify=False, proxies=self.proxies) + response = self.session.get( + url, headers=headers, verify=False, proxies=self.proxies + ) response.raise_for_status() report_path = os.path.join(self.report_dir, f"{self.pcap_id}.json") with open(report_path, "w") as f: From f7f8abdef6ab98866790010570519187378cbf13 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 3 Sep 2025 07:53:16 +0000 Subject: [PATCH 32/35] Removing netsniff and tcpdump references in GstreamerApp.py, as they are to be fully moved to a fixture --- tests/validation/mtl_engine/GstreamerApp.py | 42 +-------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/tests/validation/mtl_engine/GstreamerApp.py b/tests/validation/mtl_engine/GstreamerApp.py index 1307d9a4d..17fc9796e 100755 --- a/tests/validation/mtl_engine/GstreamerApp.py +++ b/tests/validation/mtl_engine/GstreamerApp.py @@ -7,7 +7,6 @@ import time from compliance.pcap_compliance import PcapComplianceClient -from mtl_engine.RxTxApp import prepare_netsniff, prepare_tcpdump from .execute import log_fail, run @@ -312,7 +311,7 @@ def setup_gstreamer_st40p_rx_pipeline( ): connection_params = create_connection_params( dev_port=nic_port_list, - payload_type=rx_payload_type, + payload_type=str(rx_payload_type), udp_port=40000, is_tx=False, ) @@ -350,7 +349,6 @@ def execute_test( rx_host=None, sleep_interval: int = 4, tx_first: bool = True, - capture_cfg=None, ): """ Execute GStreamer test with remote host support following RxTxApp pattern. @@ -367,7 +365,6 @@ def execute_test( :param rx_host: RX host object (for dual host tests) :param sleep_interval: Sleep interval between starting TX and RX :param tx_first: Whether to start TX first - :param capture_cfg: Capture configuration :return: True if test passed, False otherwise """ is_dual = tx_host is not None and rx_host is not None @@ -389,13 +386,6 @@ def execute_test( tx_process = None rx_process = None - if is_dual: - tx_tcpdump = prepare_tcpdump(capture_cfg, tx_host) if capture_cfg else None - # rx_tcpdump = prepare_tcpdump(capture_cfg, rx_host) if capture_cfg else None # Unused - tcpdump = tx_tcpdump - else: - tcpdump = prepare_tcpdump(capture_cfg, host) - try: if tx_first: # Start TX pipeline first @@ -443,13 +433,6 @@ def execute_test( host=tx_remote_host, background=True, ) - # --- Start packet capture after pipelines are running --- - if tcpdump: - logger.info("Starting tcpdump capture...") - tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) - if netsniff: - logger.info("Starting netsniff-ng capture...") - netsniff.start() logger.info( f"Waiting for RX process to complete (test_time: {test_time} seconds)..." @@ -494,24 +477,6 @@ def execute_test( rx_process.wait(timeout=10) except Exception: pass - pcap_file = None - is_compliant = False - if tcpdump: - tcpdump.stop() - pcap_file = tcpdump.pcap_file - if netsniff: - netsniff.stop() - pcap_file = netsniff.pcap_file - if pcap_file: - # FIXME: Get rid of hardcoded path - ebu_config_path = "configs/ebu_list.yaml" - compliance_client = PcapComplianceClient(config_path=ebu_config_path) - compliance_client.authenticate() - compliance_client.upload_pcap() - report_path = compliance_client.download_report( - test_name=compliance_client.pcap_id - ) - is_compliant = compliance_client.check_compliance(report_path=report_path) # Compare files for validation file_compare = compare_files( @@ -520,11 +485,6 @@ def execute_test( logger.info(f"File comparison: {file_compare}") - if not is_compliant: - logger.info("PCAP compliance check failed") - else: - logger.info("PCAP compliance check passed") - return file_compare From eeeee844a2acd9f4446ac3c1163b9a14ad06f782 Mon Sep 17 00:00:00 2001 From: MateuszGrabuszynski Date: Wed, 3 Sep 2025 07:58:43 +0000 Subject: [PATCH 33/35] Removing unused import in GstreamerApp --- tests/validation/mtl_engine/GstreamerApp.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/validation/mtl_engine/GstreamerApp.py b/tests/validation/mtl_engine/GstreamerApp.py index 17fc9796e..83a4388b6 100755 --- a/tests/validation/mtl_engine/GstreamerApp.py +++ b/tests/validation/mtl_engine/GstreamerApp.py @@ -6,8 +6,6 @@ import re import time -from compliance.pcap_compliance import PcapComplianceClient - from .execute import log_fail, run logger = logging.getLogger(__name__) From 45b7a4b44f9ce8a770797c88b5477e9cab516be4 Mon Sep 17 00:00:00 2001 From: andremiszcz Date: Mon, 15 Sep 2025 22:08:47 +0200 Subject: [PATCH 34/35] Refactor and prepare working scenario with full compliance check --- .../validation/compliance/pcap_compliance.py | 18 +- tests/validation/compliance/upload_pcap.py | 2 +- tests/validation/conftest.py | 198 ++++++++---------- tests/validation/create_pcap_file/netsniff.py | 83 ++++++-- tests/validation/mtl_engine/RxTxApp.py | 57 +---- tests/validation/mtl_engine/const.py | 2 + tests/validation/mtl_engine/execute.py | 28 +-- tests/validation/mtl_engine/udp_app.py | 18 +- .../st20p/resolutions/test_resolutions.py | 17 +- 9 files changed, 178 insertions(+), 245 deletions(-) diff --git a/tests/validation/compliance/pcap_compliance.py b/tests/validation/compliance/pcap_compliance.py index 074d7b2d8..cacb0823f 100644 --- a/tests/validation/compliance/pcap_compliance.py +++ b/tests/validation/compliance/pcap_compliance.py @@ -82,25 +82,27 @@ def upload_pcap(self): print(f"Extracted UUID: >>>{self.pcap_id}<<<") return self.pcap_id - def download_report(self, test_name): + def download_report(self, test_name=None): """ Download the compliance report for the uploaded PCAP file. Saves the report as a JSON file in a timestamped directory. Returns the path to the saved report. """ - timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") - self.report_dir = os.path.join("reports", test_name, timestamp) - os.makedirs(self.report_dir, exist_ok=True) url = f"http://{self.ebu_ip}/api/pcap/{self.pcap_id}/report?type=json" headers = {"Authorization": f"Bearer {self.token}"} response = self.session.get( url, headers=headers, verify=False, proxies=self.proxies ) response.raise_for_status() - report_path = os.path.join(self.report_dir, f"{self.pcap_id}.json") - with open(report_path, "w") as f: - json.dump(response.json(), f, indent=2) - return report_path + if test_name is not None: + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + self.report_dir = os.path.join("reports", test_name, timestamp) + os.makedirs(self.report_dir, exist_ok=True) + report_path = os.path.join(self.report_dir, f"{self.pcap_id}.json") + with open(report_path, "w") as f: + json.dump(response.json(), f, indent=2) + return report_path + return response.json() def check_compliance(self, report_path): """ diff --git a/tests/validation/compliance/upload_pcap.py b/tests/validation/compliance/upload_pcap.py index ce505af4a..1df7e674a 100644 --- a/tests/validation/compliance/upload_pcap.py +++ b/tests/validation/compliance/upload_pcap.py @@ -4,7 +4,7 @@ if __name__ == "__main__": from pcap_compliance import PcapComplianceClient else: - from .pcap_compliance import PcapComplianceClient + from compliance.pcap_compliance import PcapComplianceClient def parse_args(): diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index b403d7db3..fbb1b154a 100755 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -12,12 +12,12 @@ import pytest from common.mtl_manager.mtlManager import MtlManager from common.nicctl import Nicctl -from create_pcap_file.netsniff import NetsniffRecorder -from create_pcap_file.tcpdump import TcpDumpRecorder +from compliance.pcap_compliance import PcapComplianceClient +from create_pcap_file.netsniff import NetsniffRecorder, calculate_packets_per_frame from mfd_common_libs.custom_logger import add_logging_level from mfd_common_libs.log_levels import TEST_FAIL, TEST_INFO, TEST_PASS from mfd_connect.exceptions import ConnectionCalledProcessError -from mtl_engine.const import LOG_FOLDER, TESTCMD_LVL +from mtl_engine.const import DOWNLOAD_REPORT_TRIES, FRAMES_CAPTURE, LOG_FOLDER, TESTCMD_LVL from mtl_engine.csv_report import ( csv_add_test, csv_write_report, @@ -25,6 +25,7 @@ ) # FIXME: Perhaps, it could be set less statically +from mtl_engine.execute import log_fail from mtl_engine.ffmpeg_app import ip_dict, ip_dict_rgb24_multiple from mtl_engine.ramdisk import Ramdisk from mtl_engine.stash import ( @@ -38,6 +39,7 @@ ) from pytest_mfd_logging.amber_log_formatter import AmberLogFormatter + logger = logging.getLogger(__name__) phase_report_key = pytest.StashKey[Dict[str, pytest.CollectReport]]() @@ -273,126 +275,90 @@ def log_session(): if os.path.exists("pytest.log"): shutil.copy("pytest.log", f"{LOG_FOLDER}/latest/pytest.log") else: - logging.warning("pytest.log not found, skipping copy") + logger.warning("pytest.log not found, skipping copy") csv_write_report(f"{LOG_FOLDER}/latest/report.csv") -def read_ebu_creds(config_path): - # Load EBU IP and credentials from YAML config - import yaml - - if config_path: - with open(config_path, "r") as f: - config = yaml.safe_load(f) - instance = config["instances"] - ebu_ip = instance.get("name", None) - user = instance.get("username", None) - password = instance.get("password", None) - proxy = instance.get("proxy", None) - return ebu_ip, user, password, proxy - return None, None, None, None - - -@pytest.fixture(scope="function", autouse=False) -def pcap_capture( - test_config, hosts, nic_port_list, video_format, output_format, mtl_path +@pytest.fixture(scope="function") +def pcap_capture(request, media_file, test_config, hosts, mtl_path ): capture_cfg = test_config.get("capture_cfg", {}) - capture_cfg["test_name"] = ( - f"test_rx_ffmpeg_tx_ffmpeg_dual_{video_format}_{output_format}" - ) - - host = hosts["client"] if "client" in hosts else list(hosts.values())[0] - # FIXME: If possible, change this not to be hardcoded - src_ip = ip_dict["tx_interfaces"] - dst_ip = ip_dict_rgb24_multiple["p_tx_ip_2"] - capturer = None if capture_cfg and capture_cfg.get("enable"): - if capture_cfg.get("tool") == "tcpdump": - capturer = TcpDumpRecorder( - host=host, - test_name=capture_cfg.get("test_name", "capture"), - pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), - interface=capture_cfg.get("interface"), + host = hosts["client"] if "client" in hosts else list(hosts.values())[0] + media_file_info, _ = media_file + test_name = request.node.name + if "frames_number" not in capture_cfg and "capture_time" not in capture_cfg: + capture_cfg["packets_number"] = FRAMES_CAPTURE * calculate_packets_per_frame( + media_file_info["width"], + media_file_info["height"] ) - elif capture_cfg.get("tool") in ["netsniff", "netsniff-ng"]: - # Filtering - capture_filter = "" - # TODO: Enable filtering back when checked - if src_ip: - capture_filter += f"src {src_ip}" - if dst_ip and not capture_filter == "": - capture_filter += f" and dst {dst_ip}" - elif dst_ip: - capture_filter += f"dst {dst_ip}" - # Class prep - capturer = NetsniffRecorder( - host=host, - test_name=capture_cfg.get("test_name", "capture"), - pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), - interface=capture_cfg.get("interface", "eth0"), - capture_filter=( - capture_filter if capture_filter != "" else None - ), # Avoid forcing an empty filter + logger.info(f"Capture {capture_cfg['packets_number']} packets for {FRAMES_CAPTURE} frames") + elif "frames_number" in capture_cfg: + capture_cfg["packets_number"] = capture_cfg.pop("frames_number") * calculate_packets_per_frame( + media_file_info["width"], + media_file_info["height"] ) - else: - logging.error(f"Unknown capture tool {capture_cfg.get('tool')}") - if capturer: - if isinstance(capturer, TcpDumpRecorder): - # TODO: Perhaps it would need to be changed to use .start() instead of .capture() - capturer.capture(capture_time=capture_cfg.get("time", test_time)) - elif isinstance(capturer, NetsniffRecorder): - capturer.start() - else: - logging.error(f"Unknown capturer class {capturer.__class__.__name__}") + logger.info(f"Capture {capture_cfg['packets_number']} packets for {capture_cfg['frames_number']} frames") + capturer = NetsniffRecorder( + host=host, + test_name=test_name, + pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), + interface=host.network_interfaces[0].name, + silent=capture_cfg.get("silent", True), + packets_capture=capture_cfg.get("packets_number", None), + capture_time=capture_cfg.get("capture_time", None) + ) yield capturer if capturer: - capturer.stop() - if capture_cfg and capture_cfg.get("compliance", False): - # FIXME: This is generally a bad practice to call it like that, but for now it is the easiest way - ebu_ip, ebu_login, ebu_passwd, ebu_proxy = read_ebu_creds( - config_path=capture_cfg.get("ebu_yaml_path", "configs/ebu_list.yaml") - ) # Reads from executor - proxy_cmd = f" --proxy {ebu_proxy}" if ebu_proxy else "" - compliance_upl = host.connection.execute_command( - "python3 ./tests/validation/compliance/upload_pcap.py" - f" --ip {ebu_ip}" - f" --login {ebu_login}" - f" --password {ebu_passwd}" - f" --pcap_file_path {capturer.pcap_file}{proxy_cmd}", - cwd=f"{str(mtl_path)}", - ) - if compliance_upl.return_code != 0: - logging.error(f"PCAP upload failed: {compliance_upl.stderr}") - else: - uuid = ( - compliance_upl.stdout.split(">>>UUID>>>")[1] - .split("<<>>UUID>>>")[1] + .split("<< 0: + time.sleep(1) + tries -= 1 + report = uploader.download_report() + if not report.get("analyzed", False): + logger.error(f"Report is not ready after {DOWNLOAD_REPORT_TRIES} seconds, skipping compliance check") + return + if report.get("not_compliant_streams", 1) == 0: + logger.info("PCAP compliance check passed") + else: + log_fail("PCAP compliance check failed") @pytest.fixture(scope="function", autouse=True) @@ -415,19 +381,19 @@ def log_case(request, caplog: pytest.LogCaptureFixture): yield report = request.node.stash[phase_report_key] if report["setup"].failed: - logging.log(level=TEST_FAIL, msg=f"Setup failed for {case_id}") + logger.log(level=TEST_FAIL, msg=f"Setup failed for {case_id}") os.chmod(logfile, 0o4755) result = "Fail" elif ("call" not in report) or report["call"].failed: - logging.log(level=TEST_FAIL, msg=f"Test failed for {case_id}") + logger.log(level=TEST_FAIL, msg=f"Test failed for {case_id}") os.chmod(logfile, 0o4755) result = "Fail" elif report["call"].passed: - logging.log(level=TEST_PASS, msg=f"Test passed for {case_id}") + logger.log(level=TEST_PASS, msg=f"Test passed for {case_id}") os.chmod(logfile, 0o755) result = "Pass" else: - logging.log(level=TEST_INFO, msg=f"Test skipped for {case_id}") + logger.log(level=TEST_INFO, msg=f"Test skipped for {case_id}") result = "Skip" logger.removeHandler(fh) diff --git a/tests/validation/create_pcap_file/netsniff.py b/tests/validation/create_pcap_file/netsniff.py index a95c4b6f6..30a949981 100644 --- a/tests/validation/create_pcap_file/netsniff.py +++ b/tests/validation/create_pcap_file/netsniff.py @@ -4,14 +4,25 @@ import os from datetime import datetime from time import sleep +from wsgiref import headers -from mfd_connect.exceptions import ConnectionCalledProcessError +from mfd_connect.exceptions import ConnectionCalledProcessError, RemoteProcessInvalidState, RemoteProcessTimeoutExpired, SSHRemoteProcessEndException logger = logging.getLogger(__name__) STARTUP_WAIT = 2 # Default wait time after starting the process +def calculate_packets_per_frame(width: int, height: int, mtu: int = 1500) -> int: + # Simplified calculation for the number of packets per frame + # Supported only 4:2:2 format + pgroupsize = 5 + pgroupcoverage = 2 + headersize = 74 # Ethernet + IP + UDP + RTP headers + packets = 1 + int((width * height) / (int((mtu - headersize) / pgroupsize) * pgroupcoverage)) + return packets + + class NetsniffRecorder: """ Class to handle the recording of network traffic using netsniff-ng. @@ -35,13 +46,13 @@ def __init__( interface_index: int = 0, silent: bool = True, capture_filter: str | None = None, + packets_capture: int | None = None, + capture_time: int = 5, ): self.host = host self.test_name = test_name self.pcap_dir = pcap_dir - self.pcap_file = os.path.join( - pcap_dir, f"{test_name}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.pcap" - ) + self.pcap_file = os.path.join(pcap_dir, f"{test_name}.pcap") if interface is not None: self.interface = interface else: @@ -49,8 +60,10 @@ def __init__( self.netsniff_process = None self.silent = silent self.capture_filter = capture_filter + self.packets_capture = packets_capture + self.capture_time = capture_time - def start(self, startup_wait=STARTUP_WAIT): + def start(self): """ Starts the netsniff-ng """ @@ -64,6 +77,7 @@ def start(self, startup_wait=STARTUP_WAIT): str(self.interface), "--out", self.pcap_file, + f"--num {self.packets_capture}" if self.packets_capture is not None else "", f'-f "{self.capture_filter}"' if self.capture_filter else "", ] logger.info(f"Running command: {' '.join(cmd)}") @@ -74,11 +88,8 @@ def start(self, startup_wait=STARTUP_WAIT): f"PCAP file will be saved at: {os.path.abspath(self.pcap_file)}" ) - # Give netsniff-ng a moment to start and possibly error out - sleep(startup_wait) - if not self.netsniff_process.running: - err = self.netsniff_process.stderr_text + err = self.netsniff_process.stdout_text logger.error(f"netsniff-ng failed to start. Error output:\n{err}") return False logger.info( @@ -89,18 +100,27 @@ def start(self, startup_wait=STARTUP_WAIT): logger.error(f"Failed to start netsniff-ng: {e}") return False - def capture(self, capture_time=20, startup_wait=2): + def capture(self, startup_wait=2): """ Starts netsniff-ng, captures for capture_time seconds, then stops. - :param capture_time: Duration in seconds to capture packets. :param startup_wait: Time in seconds to wait after starting netsniff-ng (default: 2) """ - started = self.start(startup_wait=startup_wait) + started = self.start() if started: - logger.info(f"Capturing traffic for {capture_time} seconds...") - sleep(capture_time) - self.stop() - logger.info("Capture complete.") + if self.packets_capture is None: + logger.info(f"Capturing traffic for {self.capture_time} seconds...") + sleep(self.capture_time + startup_wait) + self.stop() + logger.info("Capture complete.") + else: + try: + logger.info(f"Capturing traffic for {self.packets_capture} packets...") + self.netsniff_process.wait(timeout=startup_wait+10) + logger.info("Capture complete.") + logger.debug(self.netsniff_process.stdout_text) + except RemoteProcessTimeoutExpired: + logger.error("Capture timed out.") + self.stop() else: logger.error("netsniff-ng did not start; skipping capture.") @@ -108,10 +128,31 @@ def stop(self): """ Stops all netsniff-ng processes on the host using pkill. """ - connection = self.host.connection + try: logger.info("Stopping netsniff-ng using pkill netsniff-ng...") - connection.execute_command("pkill netsniff-ng") - logger.info("netsniff-ng stopped (via pkill).") - except ConnectionCalledProcessError as e: - logger.error(f"Failed to stop netsniff-ng: {e}") + self.netsniff_process.stop(wait=5) + except SSHRemoteProcessEndException: + try: + self.netsniff_process.kill() + except RemoteProcessInvalidState: + logger.debug("Process killed.") + logger.error("netsniff-ng process did not stopped by itself.") + logger.error(self.netsniff_process.stdout_text) + + def update_filter(self, src_ip=None, dst_ip=None): + """ + Updates the capture filter with new source and/or destination IP addresses. + :param src_ip: New source IP address to filter (optional). + :param dst_ip: New destination IP address to filter (optional). + """ + filters = [] + if src_ip: + filters.append(f"src {src_ip}") + if dst_ip: + filters.append(f"dst {dst_ip}") + if len(filters) > 1: + self.capture_filter = " and ".join(filters) + elif len(filters) == 1: + self.capture_filter = filters[0] + logger.info(f"Updated capture filter to: {self.capture_filter}") \ No newline at end of file diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index 83daa229c..f1563fc85 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -118,32 +118,6 @@ def add_interfaces(config: dict, nic_port_list: list, test_mode: str) -> dict: return config -def prepare_tcpdump(capture_cfg, host=None): - """ - Prepare TcpDumpRecorder if capture_cfg is enabled and tool is tcpdump. - - returns: TcpDumpRecorder instance or None. - """ - if ( - capture_cfg - and capture_cfg.get("enable") - and capture_cfg.get("tool") == "tcpdump" - ): - tcpdump = TcpDumpRecorder( - host=host, - test_name=capture_cfg.get("test_name", "capture"), - pcap_dir=capture_cfg.get("pcap_dir", "/tmp"), - interface=capture_cfg.get("interface"), - ) - return tcpdump - else: - logger.info( - "Not preparing tcpdump for capture as capturing not enabled " - "or tool is not tcpdump." - ) - return None - - def prepare_netsniff( capture_cfg, host=None, src_ip: str | None = None, dst_ip: str | None = None ): @@ -571,12 +545,8 @@ def execute_test( rx_timing_parser: bool = False, ptp: bool = False, host=None, - capture_cfg=None, + netsniff=None, ) -> bool: - # Only initialize logging if it hasn't been initialized already - global _log_timestamp - if _log_timestamp is None: - init_test_logging() case_id = os.environ["PYTEST_CURRENT_TEST"] case_id = case_id[: case_id.rfind("(") - 1] @@ -631,10 +601,6 @@ def execute_test( logger.info(f"RxTxApp Command: {command}") - # Prepare capturing programs if capture is enabled in the test configuration. - tcpdump = prepare_tcpdump(capture_cfg, host) - netsniff = prepare_netsniff(capture_cfg, host) - # For 4TX and 8k streams more timeout is needed timeout = test_time + 90 if len(config["tx_sessions"]) >= 4 or any( @@ -651,20 +617,15 @@ def execute_test( timeout=timeout, testcmd=True, host=remote_host, + background=True ) - - try: - # Start tcpdump capture (blocking, so it captures during traffic) - if tcpdump: - tcpdump.capture(capture_time=capture_cfg.get("capture_time", 0.5)) - logger.info(f"Started tcpdump capture on host {host.name}") - # Start netsniff-ng capture (blocking, so it captures during traffic) - if netsniff: - netsniff.start() - logger.info(f"Started netsniff-ng capture on host {host.name}") - finally: - cp.wait() - + + if netsniff: + netsniff.update_filter(dst_ip=config["tx_sessions"][0]["dip"][0]) + netsniff.capture() + logger.info(f"Finished netsniff-ng capture on host {host.name}") + + cp.wait(timeout=timeout) # Capture stdout output for logging capture_stdout(cp, "RxTxApp") diff --git a/tests/validation/mtl_engine/const.py b/tests/validation/mtl_engine/const.py index 9ebc50e0a..40e2628d8 100644 --- a/tests/validation/mtl_engine/const.py +++ b/tests/validation/mtl_engine/const.py @@ -3,3 +3,5 @@ LOG_FOLDER = "logs" TESTCMD_LVL = 24 # Custom logging level for test commands +FRAMES_CAPTURE = 4 # Number of frames to capture for compliance tests +DOWNLOAD_REPORT_TRIES = 5 # Number of tries to download EBU report \ No newline at end of file diff --git a/tests/validation/mtl_engine/execute.py b/tests/validation/mtl_engine/execute.py index df0787d95..15cdaf2d7 100755 --- a/tests/validation/mtl_engine/execute.py +++ b/tests/validation/mtl_engine/execute.py @@ -199,39 +199,13 @@ def run( host=None, env: dict = None, background: bool = False, - enable_sudo: bool = False, ) -> any: + if testcmd: logger.log(level=TESTCMD_LVL, msg=f"Test command: {command}") else: logger.debug(f"Run command: {command}") - # if host is None: - # # Local execution - # try: - # cp = subprocess.run( - # "exec " + command, - # stdout=subprocess.PIPE, - # stderr=subprocess.STDOUT, - # timeout=timeout, - # shell=True, - # text=True, - # cwd=cwd, - # env=env, - # ) - # except subprocess.TimeoutExpired: - # logger.debug("Timeout expired") - # raise - - # for line in cp.stdout.splitlines(): - # logger.debug(line.rstrip()) - # logger.debug(f"RC: {cp.returncode}") - # return cp - # else: - # Remote execution - # if enable_sudo: - # host.connection.enable_sudo() - process = host.connection.start_process( command, shell=True, diff --git a/tests/validation/mtl_engine/udp_app.py b/tests/validation/mtl_engine/udp_app.py index 1af150e80..9e95ac39e 100644 --- a/tests/validation/mtl_engine/udp_app.py +++ b/tests/validation/mtl_engine/udp_app.py @@ -8,8 +8,8 @@ import re from mtl_engine import udp_app_config -from mtl_engine.RxTxApp import prepare_netsniff, prepare_tcpdump -from tests.validation.compliance.pcap_compliance import PcapComplianceClient +from mtl_engine.RxTxApp import prepare_netsniff +from compliance.pcap_compliance import PcapComplianceClient from .const import LOG_FOLDER from .execute import call, log_fail, wait @@ -65,16 +65,12 @@ def execute_test_sample( client_command = f"./build/app/UfdClientSample --p_tx_ip {sample_ip_dict['server']} --sessions_cnt {sessions_cnt}" server_command = f"./build/app/UfdServerSample --sessions_cnt {sessions_cnt}" - tcpdump = prepare_tcpdump(capture_cfg, host) netsniff = prepare_netsniff(capture_cfg, host) client_proc = call(client_command, build, test_time, sigint=True, env=client_env) server_proc = call(server_command, build, test_time, sigint=True, env=server_env) try: - # Start packet capture when traffic is flowing - if tcpdump: - tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) if netsniff: netsniff.start() # Wait for both processes to finish @@ -83,9 +79,6 @@ def execute_test_sample( finally: pcap_file = None is_compliant = False - if tcpdump: - tcpdump.stop() - pcap_file = tcpdump.pcap_file if netsniff: netsniff.stop() pcap_file = netsniff.pcap_file @@ -157,16 +150,12 @@ def execute_test_librist( + f" --bind_ip={librist_ip_dict['receive']} --sessions_cnt={sessions_cnt}" ) - tcpdump = prepare_tcpdump(capture_cfg, host) netsniff = prepare_netsniff(capture_cfg, host) send_proc = call(send_command, build, test_time, sigint=True, env=send_env) receive_proc = call(receive_command, build, test_time, sigint=True, env=receive_env) try: - # Start packet capture when traffic is flowing - if tcpdump: - tcpdump.capture(capture_time=capture_cfg.get("capture_time", test_time)) if netsniff: netsniff.start() # Wait for both processes to finish @@ -175,9 +164,6 @@ def execute_test_librist( finally: pcap_file = None is_compliant = False - if tcpdump: - tcpdump.stop() - pcap_file = tcpdump.pcap_file if netsniff: netsniff.stop() pcap_file = netsniff.pcap_file diff --git a/tests/validation/tests/single/st20p/resolutions/test_resolutions.py b/tests/validation/tests/single/st20p/resolutions/test_resolutions.py index b428c81cd..431ae3135 100755 --- a/tests/validation/tests/single/st20p/resolutions/test_resolutions.py +++ b/tests/validation/tests/single/st20p/resolutions/test_resolutions.py @@ -1,8 +1,12 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2024-2025 Intel Corporation +from venv import logger +from conftest import pcap_capture +from create_pcap_file.netsniff import calculate_packets_per_frame import mtl_engine.RxTxApp as rxtxapp import pytest +from mtl_engine.const import FRAMES_CAPTURE from mtl_engine.media_files import yuv_files_422rfc10 @@ -13,6 +17,7 @@ ids=list(yuv_files_422rfc10.keys()), ) def test_resolutions( + request, hosts, build, media, @@ -21,18 +26,12 @@ def test_resolutions( test_config, prepare_ramdisk, media_file, + pcap_capture, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_resolutions_{media_file_info['filename']}" # Set a unique pcap file name - ) - config = rxtxapp.create_empty_config() config = rxtxapp.add_st20p_sessions( config=config, @@ -52,5 +51,7 @@ def test_resolutions( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=pcap_capture, + ptp=False, ) + From 7643980ca2e0d6218f3b25994029c87e5e9fb1c7 Mon Sep 17 00:00:00 2001 From: andremiszcz Date: Mon, 22 Sep 2025 19:44:25 +0200 Subject: [PATCH 35/35] Adjust ST20 and ST30 tests with netsniff --- tests/validation/conftest.py | 76 +++++++++++-------- tests/validation/create_pcap_file/netsniff.py | 48 ++++++++---- tests/validation/mtl_engine/RxTxApp.py | 47 +++++------- tests/validation/mtl_engine/const.py | 2 +- tests/validation/mtl_engine/csv_report.py | 14 +++- tests/validation/mtl_engine/udp_app.py | 2 +- .../gstreamer/anc_format/test_anc_format.py | 7 +- .../kernel_socket/kernel_lo/test_kernel_lo.py | 28 +++---- .../tests/single/st20p/format/test_format.py | 51 +++++++++---- .../tests/single/st20p/fps/test_fps.py | 16 ++-- .../single/st20p/integrity/test_integrity.py | 16 +--- .../single/st20p/interlace/test_interlace.py | 17 ++--- .../tests/single/st20p/pacing/test_pacing.py | 16 ++-- .../single/st20p/packing/test_packing.py | 16 ++-- .../st20p/resolutions/test_resolutions.py | 12 +-- .../single/st20p/test_mode/test_multicast.py | 12 +-- .../tests/single/st22p/codec/test_codec.py | 10 +-- .../tests/single/st22p/format/test_format.py | 10 +-- .../tests/single/st22p/fps/test_fps.py | 11 +-- .../single/st22p/interlace/test_interlace.py | 10 +-- .../single/st22p/quality/test_quality.py | 10 +-- .../single/st22p/test_mode/test_unicast.py | 10 +-- .../single/st30p/integrity/test_integrity.py | 17 +---- .../st30p/st30p_channel/test_st30p_channel.py | 13 +--- .../st30p/st30p_format/test_st30p_format.py | 10 +-- .../st30p/st30p_ptime/test_st30p_ptime.py | 10 +-- .../st30p_sampling/test_st30p_sampling.py | 10 +-- .../single/st30p/test_mode/test_multicast.py | 20 ++--- 28 files changed, 216 insertions(+), 305 deletions(-) diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index fbb1b154a..8b444ffba 100755 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -17,10 +17,16 @@ from mfd_common_libs.custom_logger import add_logging_level from mfd_common_libs.log_levels import TEST_FAIL, TEST_INFO, TEST_PASS from mfd_connect.exceptions import ConnectionCalledProcessError -from mtl_engine.const import DOWNLOAD_REPORT_TRIES, FRAMES_CAPTURE, LOG_FOLDER, TESTCMD_LVL +from mtl_engine.const import ( + DOWNLOAD_REPORT_TRIES, + FRAMES_CAPTURE, + LOG_FOLDER, + TESTCMD_LVL, +) from mtl_engine.csv_report import ( csv_add_test, csv_write_report, + get_compliance_result, update_compliance_result, ) @@ -39,7 +45,6 @@ ) from pytest_mfd_logging.amber_log_formatter import AmberLogFormatter - logger = logging.getLogger(__name__) phase_report_key = pytest.StashKey[Dict[str, pytest.CollectReport]]() @@ -280,8 +285,7 @@ def log_session(): @pytest.fixture(scope="function") -def pcap_capture(request, media_file, test_config, hosts, mtl_path -): +def pcap_capture(request, media_file, test_config, hosts, mtl_path): capture_cfg = test_config.get("capture_cfg", {}) capturer = None if capture_cfg and capture_cfg.get("enable"): @@ -289,17 +293,19 @@ def pcap_capture(request, media_file, test_config, hosts, mtl_path media_file_info, _ = media_file test_name = request.node.name if "frames_number" not in capture_cfg and "capture_time" not in capture_cfg: - capture_cfg["packets_number"] = FRAMES_CAPTURE * calculate_packets_per_frame( - media_file_info["width"], - media_file_info["height"] + capture_cfg["packets_number"] = ( + FRAMES_CAPTURE * calculate_packets_per_frame(media_file_info) + ) + logger.info( + f"Capture {capture_cfg['packets_number']} packets for {FRAMES_CAPTURE} frames" ) - logger.info(f"Capture {capture_cfg['packets_number']} packets for {FRAMES_CAPTURE} frames") elif "frames_number" in capture_cfg: - capture_cfg["packets_number"] = capture_cfg.pop("frames_number") * calculate_packets_per_frame( - media_file_info["width"], - media_file_info["height"] + capture_cfg["packets_number"] = capture_cfg[ + "frames_number" + ] * calculate_packets_per_frame(media_file_info) + logger.info( + f"Capture {capture_cfg['packets_number']} packets for {capture_cfg['frames_number']} frames" ) - logger.info(f"Capture {capture_cfg['packets_number']} packets for {capture_cfg['frames_number']} frames") capturer = NetsniffRecorder( host=host, test_name=test_name, @@ -307,10 +313,10 @@ def pcap_capture(request, media_file, test_config, hosts, mtl_path interface=host.network_interfaces[0].name, silent=capture_cfg.get("silent", True), packets_capture=capture_cfg.get("packets_number", None), - capture_time=capture_cfg.get("capture_time", None) + capture_time=capture_cfg.get("capture_time", None), ) yield capturer - if capturer: + if capturer and capturer.netsniff_process: ebu_server = test_config.get("ebu_server", {}) if not ebu_server: logger.error("EBU server configuration not found in test_config.yaml") @@ -320,7 +326,7 @@ def pcap_capture(request, media_file, test_config, hosts, mtl_path ebu_passwd = ebu_server.get("password", None) ebu_proxy = ebu_server.get("proxy", None) proxy_cmd = f" --proxy {ebu_proxy}" if ebu_proxy else "" - compliance_upl = host.connection.execute_command( + compliance_upl = capturer.host.connection.execute_command( "python3 ./tests/validation/compliance/upload_pcap.py" f" --ip {ebu_ip}" f" --login {ebu_login}" @@ -332,13 +338,9 @@ def pcap_capture(request, media_file, test_config, hosts, mtl_path logger.error(f"PCAP upload failed: {compliance_upl.stderr}") return uuid = ( - compliance_upl.stdout.split(">>>UUID>>>")[1] - .split("<<>>UUID>>>")[1].split("<< int: +def calculate_packets_per_frame(media_file_info, mtu: int = 1500) -> int: # Simplified calculation for the number of packets per frame - # Supported only 4:2:2 format - pgroupsize = 5 - pgroupcoverage = 2 - headersize = 74 # Ethernet + IP + UDP + RTP headers - packets = 1 + int((width * height) / (int((mtu - headersize) / pgroupsize) * pgroupcoverage)) + # Supported only 4:2:2 format or audio formats assuming 1 packet per 1ms + packets = 1000 + if "width" in media_file_info and "height" in media_file_info: + pgroupsize = 5 + pgroupcoverage = 2 + headersize = 74 # Ethernet + IP + UDP + RTP headers + packets = 1 + int( + (media_file_info["width"] * media_file_info["height"]) + / (int((mtu - headersize) / pgroupsize) * pgroupcoverage) + ) return packets @@ -77,7 +84,11 @@ def start(self): str(self.interface), "--out", self.pcap_file, - f"--num {self.packets_capture}" if self.packets_capture is not None else "", + ( + f"--num {self.packets_capture}" + if self.packets_capture is not None + else "" + ), f'-f "{self.capture_filter}"' if self.capture_filter else "", ] logger.info(f"Running command: {' '.join(cmd)}") @@ -114,12 +125,17 @@ def capture(self, startup_wait=2): logger.info("Capture complete.") else: try: - logger.info(f"Capturing traffic for {self.packets_capture} packets...") - self.netsniff_process.wait(timeout=startup_wait+10) + logger.info( + f"Capturing traffic for {self.packets_capture} packets..." + ) + self.netsniff_process.wait(timeout=startup_wait + 10) logger.info("Capture complete.") logger.debug(self.netsniff_process.stdout_text) except RemoteProcessTimeoutExpired: - logger.error("Capture timed out.") + logger.warning( + "Capture timed out. Probably not enough packets were sent. " + "Please adjust packets_capture or capture_time to the test case." + ) self.stop() else: logger.error("netsniff-ng did not start; skipping capture.") @@ -128,7 +144,7 @@ def stop(self): """ Stops all netsniff-ng processes on the host using pkill. """ - + try: logger.info("Stopping netsniff-ng using pkill netsniff-ng...") self.netsniff_process.stop(wait=5) @@ -155,4 +171,4 @@ def update_filter(self, src_ip=None, dst_ip=None): self.capture_filter = " and ".join(filters) elif len(filters) == 1: self.capture_filter = filters[0] - logger.info(f"Updated capture filter to: {self.capture_filter}") \ No newline at end of file + logger.info(f"Updated capture filter to: {self.capture_filter}") diff --git a/tests/validation/mtl_engine/RxTxApp.py b/tests/validation/mtl_engine/RxTxApp.py index f1563fc85..004d2dd3b 100755 --- a/tests/validation/mtl_engine/RxTxApp.py +++ b/tests/validation/mtl_engine/RxTxApp.py @@ -126,6 +126,7 @@ def prepare_netsniff( returns: NetsniffRecorder instance or None. """ + # TODO: remove me as this is now in udp_app.py but it's not needed anymore if ( capture_cfg and capture_cfg.get("enable") @@ -617,14 +618,14 @@ def execute_test( timeout=timeout, testcmd=True, host=remote_host, - background=True + background=True, ) - + if netsniff: netsniff.update_filter(dst_ip=config["tx_sessions"][0]["dip"][0]) netsniff.capture() logger.info(f"Finished netsniff-ng capture on host {host.name}") - + cp.wait(timeout=timeout) # Capture stdout output for logging capture_stdout(cp, "RxTxApp") @@ -746,7 +747,7 @@ def execute_perf_test( build: str, test_time: int, fail_on_error: bool = True, - capture_cfg=None, + netsniff=None, host=None, ) -> bool: # Only initialize logging if it hasn't been initialized already @@ -774,11 +775,6 @@ def execute_perf_test( logger.info(f"Performance RxTxApp Command: {command}") - # Prepare tcpdump recorder if capture is enabled in the test configuration. - tcpdump = prepare_tcpdump(capture_cfg, host) - netsniff = prepare_netsniff(capture_cfg, host) - background = (tcpdump is not None) or (netsniff is not None) - # For 4TX and 8k streams more timeout is needed # Also scale timeout with replica count for performance tests total_replicas = 0 @@ -816,19 +812,15 @@ def execute_perf_test( timeout=timeout, testcmd=True, host=host, - background=background, + background=True, ) - try: - # Start tcpdump capture (blocking, so it captures during traffic) - if tcpdump: - tcpdump.capture(capture_time=capture_cfg.get("capture_time", 0.5)) - logger.info("Started performance test tcpdump capture") - if netsniff: - netsniff.start() - logger.info("Started performance test netsniff-ng capture") - finally: - cp.wait() + if netsniff: + netsniff.update_filter(dst_ip=config["tx_sessions"][0]["dip"][0]) + netsniff.capture() + logger.info(f"Finished netsniff-ng capture on host {host.name}") + + cp.wait() # Capture stdout output for logging capture_stdout(cp, "RxTxApp Performance") @@ -1435,7 +1427,7 @@ def execute_dual_test( virtio_user: bool = False, rx_timing_parser: bool = False, ptp: bool = False, - capture_cfg=None, + netsniff=None, ) -> bool: case_id = os.environ["PYTEST_CURRENT_TEST"] case_id = case_id[: case_id.rfind("(") - 1] @@ -1510,11 +1502,10 @@ def execute_dual_test( host=tx_host, ) - # Start tcpdump capture if enabled - tcpdump = prepare_tcpdump(capture_cfg, rx_host) - if tcpdump: - tcpdump.capture(capture_time=capture_cfg.get("capture_time", 0.5)) - logger.info("Started dual test tcpdump capture") + if netsniff: + netsniff.update_filter(dst_ip=tx_config["tx_sessions"][0]["dip"][0]) + netsniff.capture() + logger.info(f"Finished netsniff-ng capture on host {netsniff.host.name}") # Wait for both processes tx_cp.wait() @@ -1524,10 +1515,6 @@ def execute_dual_test( capture_stdout(tx_cp, "TX RxTxApp") capture_stdout(rx_cp, "RX RxTxApp") - # Stop tcpdump if it was started - if tcpdump: - tcpdump.stop() - # Get output from both hosts tx_output = tx_cp.stdout_text.splitlines() rx_output = rx_cp.stdout_text.splitlines() diff --git a/tests/validation/mtl_engine/const.py b/tests/validation/mtl_engine/const.py index 40e2628d8..4e369e9b8 100644 --- a/tests/validation/mtl_engine/const.py +++ b/tests/validation/mtl_engine/const.py @@ -4,4 +4,4 @@ LOG_FOLDER = "logs" TESTCMD_LVL = 24 # Custom logging level for test commands FRAMES_CAPTURE = 4 # Number of frames to capture for compliance tests -DOWNLOAD_REPORT_TRIES = 5 # Number of tries to download EBU report \ No newline at end of file +DOWNLOAD_REPORT_TRIES = 5 # Number of tries to download EBU report diff --git a/tests/validation/mtl_engine/csv_report.py b/tests/validation/mtl_engine/csv_report.py index a24b5b95a..d9efa0421 100644 --- a/tests/validation/mtl_engine/csv_report.py +++ b/tests/validation/mtl_engine/csv_report.py @@ -12,13 +12,17 @@ def csv_add_test( issue: str, result_note: str | None = None, ): - report[test_case] = { + res_dict = { "Test case": test_case, "Commands": commands, "Result": result, "Issue": issue, "Result note": result_note, } + if test_case in report: + report[test_case].update(res_dict) + else: + report[test_case] = res_dict def csv_write_report(filename: str): @@ -29,6 +33,7 @@ def csv_write_report(filename: str): "Commands", "Status", "Result", + "Compliance", "Issue", "Result note", ] @@ -44,3 +49,10 @@ def csv_write_report(filename: str): def update_compliance_result(test_case: str, result: str): if test_case in report: report[test_case]["Compliance"] = result + else: + report[test_case] = {"Compliance": result} + + +def get_compliance_result(test_case: str) -> str: + if test_case in report and "Compliance" in report[test_case]: + return report[test_case]["Compliance"] diff --git a/tests/validation/mtl_engine/udp_app.py b/tests/validation/mtl_engine/udp_app.py index 9e95ac39e..7d2309da2 100644 --- a/tests/validation/mtl_engine/udp_app.py +++ b/tests/validation/mtl_engine/udp_app.py @@ -7,9 +7,9 @@ import os import re +from compliance.pcap_compliance import PcapComplianceClient from mtl_engine import udp_app_config from mtl_engine.RxTxApp import prepare_netsniff -from compliance.pcap_compliance import PcapComplianceClient from .const import LOG_FOLDER from .execute import call, log_fail, wait diff --git a/tests/validation/tests/single/gstreamer/anc_format/test_anc_format.py b/tests/validation/tests/single/gstreamer/anc_format/test_anc_format.py index a7ad7d6ac..eafd5a983 100755 --- a/tests/validation/tests/single/gstreamer/anc_format/test_anc_format.py +++ b/tests/validation/tests/single/gstreamer/anc_format/test_anc_format.py @@ -21,7 +21,7 @@ def test_st40p_fps_size( framebuff, test_time, test_config, - prepare_ramdisk, + pcap_capture, ): # Get the first host for remote execution host = list(hosts.values())[0] @@ -53,9 +53,6 @@ def test_st40p_fps_size( timeout=15, ) - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = f"test_st40p_fps_size_{fps}_{file_size_kb}_{framebuff}" - try: GstreamerApp.execute_test( build=build, @@ -67,7 +64,7 @@ def test_st40p_fps_size( host=host, tx_first=False, sleep_interval=1, - capture_cfg=capture_cfg, + netsniff=pcap_capture, ) finally: # Remove the files after the test diff --git a/tests/validation/tests/single/kernel_socket/kernel_lo/test_kernel_lo.py b/tests/validation/tests/single/kernel_socket/kernel_lo/test_kernel_lo.py index 8ecea9637..aab93fef2 100755 --- a/tests/validation/tests/single/kernel_socket/kernel_lo/test_kernel_lo.py +++ b/tests/validation/tests/single/kernel_socket/kernel_lo/test_kernel_lo.py @@ -18,33 +18,25 @@ def test_kernello_mixed_format( test_mode, video_format, replicas, - test_config, - prepare_ramdisk, + media_file, ): video_file = yuv_files[video_format] audio_file = audio_files["PCM24"] ancillary_file = anc_files["text_p50"] host = list(hosts.values())[0] - - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_kernel_lo_{test_mode}_{video_format}_replicas{replicas}" - ) - + media_file_info, media_file_path = media_file config = rxtxapp.create_empty_config() config = rxtxapp.add_st20p_sessions( config=config, nic_port_list=["kernel:lo", "kernel:lo"], test_mode=test_mode, - width=video_file["width"], - height=video_file["height"], - fps=f"p{video_file['fps']}", - input_format=video_file["file_format"], - transport_format=video_file["format"], - output_format=video_file["file_format"], - st20p_url=os.path.join(media, video_file["filename"]), + width=media_file_info["width"], + height=media_file_info["height"], + fps=f"p{media_file_info['fps']}", + input_format=media_file_info["file_format"], + transport_format=media_file_info["format"], + output_format=media_file_info["file_format"], + st20p_url=os.path.join(media, media_file_info["filename"]), ) config = rxtxapp.change_replicas( config=config, session_type="st20p", replicas=replicas @@ -80,5 +72,5 @@ def test_kernello_mixed_format( build=build, test_time=test_time * replicas * 3, host=host, - capture_cfg=capture_cfg, + netsniff=None, ) diff --git a/tests/validation/tests/single/st20p/format/test_format.py b/tests/validation/tests/single/st20p/format/test_format.py index 86da1b12c..ca82c662b 100644 --- a/tests/validation/tests/single/st20p/format/test_format.py +++ b/tests/validation/tests/single/st20p/format/test_format.py @@ -20,8 +20,8 @@ def test_422p10le( nic_port_list, test_time, test_config, - prepare_ramdisk, media_file, + pcap_capture, ): """ Send files in YUV422PLANAR10LE format converting to transport format YUV_422_10bit @@ -29,13 +29,6 @@ def test_422p10le( media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_format_{media_file_info['filename']}" # Set a unique pcap file name - ) - config = rxtxapp.create_empty_config() config = rxtxapp.add_st20p_sessions( config=config, @@ -55,7 +48,8 @@ def test_422p10le( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=pcap_capture, + ptp=test_config.get("ptp", False), ) @@ -91,7 +85,15 @@ def test_422p10le( ) @pytest.mark.parametrize("format", convert1_formats.keys()) def test_convert_on_rx( - hosts, build, media, nic_port_list, test_time, format, media_file + hosts, + build, + media, + nic_port_list, + test_time, + format, + media_file, + pcap_capture, + test_config, ): """ Send file in YUV_422_10bit pixel formats with supported convertion on RX side @@ -114,7 +116,14 @@ def test_convert_on_rx( st20p_url=media_file_path, ) - rxtxapp.execute_test(config=config, build=build, test_time=test_time, host=host) + rxtxapp.execute_test( + config=config, + build=build, + test_time=test_time, + host=host, + netsniff=pcap_capture, + ptp=test_config.get("ptp", False), + ) # List of supported two-way convertions based on st_frame_get_converter() @@ -156,12 +165,14 @@ def test_tx_rx_conversion( test_time, format, media_file, + pcap_capture, + test_config, ): """ Send random file in different pixel formats with supported two-way convertion on TX and RX """ media_file_info, media_file_path = media_file - text_format, transport_format, _ = convert2_formats[format] + _, transport_format, _ = convert2_formats[format] host = list(hosts.values())[0] config = rxtxapp.create_empty_config() config = rxtxapp.add_st20p_sessions( @@ -178,7 +189,14 @@ def test_tx_rx_conversion( st20p_url=media_file_path, ) - rxtxapp.execute_test(config=config, build=build, test_time=test_time, host=host) + rxtxapp.execute_test( + config=config, + build=build, + test_time=test_time, + host=host, + netsniff=pcap_capture, + ptp=test_config.get("ptp", False), + ) @pytest.mark.parametrize( @@ -196,14 +214,14 @@ def test_formats( test_time, format, test_config, - prepare_ramdisk, media_file, + pcap_capture, ): """ Send random file in different supported pixel formats without convertion during transport """ media_file_info, media_file_path = media_file - text_format, file_format = pixel_formats[format] + _, file_format = pixel_formats[format] host = list(hosts.values())[0] # Get capture configuration from test_config.yaml @@ -233,5 +251,6 @@ def test_formats( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=pcap_capture, + ptp=test_config.get("ptp", False), ) diff --git a/tests/validation/tests/single/st20p/fps/test_fps.py b/tests/validation/tests/single/st20p/fps/test_fps.py index 80ba12f0e..83033cc09 100644 --- a/tests/validation/tests/single/st20p/fps/test_fps.py +++ b/tests/validation/tests/single/st20p/fps/test_fps.py @@ -1,10 +1,13 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2024-2025 Intel Corporation +import logging import mtl_engine.RxTxApp as rxtxapp import pytest from mtl_engine.media_files import yuv_files_422rfc10 +logger = logging.getLogger(__name__) + @pytest.mark.nightly @pytest.mark.parametrize( @@ -37,19 +40,11 @@ def test_fps( test_time, fps, test_config, - prepare_ramdisk, media_file, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_fps_{media_file_info['filename']}_{fps}" # Set a unique pcap file name - ) - config = rxtxapp.create_empty_config() config = rxtxapp.add_st20p_sessions( config=config, @@ -63,11 +58,12 @@ def test_fps( output_format=media_file_info["file_format"], st20p_url=media_file_path, ) - + logger.info(f"Compliance check disabled as test_mode is unicast!") rxtxapp.execute_test( config=config, build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=None, + ptp=test_config.get("ptp", False), ) diff --git a/tests/validation/tests/single/st20p/integrity/test_integrity.py b/tests/validation/tests/single/st20p/integrity/test_integrity.py index 9c696ebfa..66a863387 100644 --- a/tests/validation/tests/single/st20p/integrity/test_integrity.py +++ b/tests/validation/tests/single/st20p/integrity/test_integrity.py @@ -37,23 +37,12 @@ def test_integrity( media, nic_port_list, test_time, - test_config, - prepare_ramdisk, media_file, ): - media_file_info, media_file_path = media_file - # Ensure the output directory exists for the integrity test output file. - log_dir = os.path.join(os.getcwd(), LOG_FOLDER, "latest") - os.makedirs(log_dir, exist_ok=True) - out_file_url = os.path.join(log_dir, "out.yuv") + media_file_info, media_file_path = media_file host = list(hosts.values())[0] - - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - # Set a unique pcap file name - capture_cfg["test_name"] = f"test_integrity_{media_file_info['filename']}" + out_file_url = host.connection.path(media_file_path).parent / "out.yuv" config = rxtxapp.create_empty_config() config = rxtxapp.add_st20p_sessions( @@ -75,7 +64,6 @@ def test_integrity( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, ) frame_size = calculate_yuv_frame_size( diff --git a/tests/validation/tests/single/st20p/interlace/test_interlace.py b/tests/validation/tests/single/st20p/interlace/test_interlace.py index 8f94f77e4..a2cf88805 100755 --- a/tests/validation/tests/single/st20p/interlace/test_interlace.py +++ b/tests/validation/tests/single/st20p/interlace/test_interlace.py @@ -1,10 +1,13 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2024-2025 Intel Corporation +import logging import mtl_engine.RxTxApp as rxtxapp import pytest from mtl_engine.media_files import yuv_files_interlace +logger = logging.getLogger(__name__) + @pytest.mark.parametrize( "media_file", @@ -19,20 +22,11 @@ def test_interlace( nic_port_list, test_time, test_config, - prepare_ramdisk, media_file, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - # capture_time: 15 - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_interlace_{media_file_info['filename']}" # Set a unique pcap file name - ) - config = rxtxapp.create_empty_config() config = rxtxapp.add_st20p_sessions( config=config, @@ -47,11 +41,12 @@ def test_interlace( st20p_url=media_file_path, interlaced=True, ) - + logger.info(f"Compliance check disabled as test_mode is unicast!") rxtxapp.execute_test( config=config, build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=None, + ptp=test_config.get("ptp", False), ) diff --git a/tests/validation/tests/single/st20p/pacing/test_pacing.py b/tests/validation/tests/single/st20p/pacing/test_pacing.py index 00c0138fc..82c09cc7d 100755 --- a/tests/validation/tests/single/st20p/pacing/test_pacing.py +++ b/tests/validation/tests/single/st20p/pacing/test_pacing.py @@ -1,10 +1,13 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2024-2025 Intel Corporation +import logging import mtl_engine.RxTxApp as rxtxapp import pytest from mtl_engine.media_files import yuv_files_422rfc10 +logger = logging.getLogger(__name__) + @pytest.mark.nightly @pytest.mark.parametrize("pacing", ["narrow", "wide", "linear"]) @@ -26,19 +29,11 @@ def test_pacing( test_time, pacing, test_config, - prepare_ramdisk, media_file, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_pacing_{media_file_info['filename']}_{pacing}" # Set a unique pcap file name - ) - config = rxtxapp.create_empty_config() config = rxtxapp.add_st20p_sessions( config=config, @@ -53,11 +48,12 @@ def test_pacing( st20p_url=media_file_path, pacing=pacing, ) - + logger.info(f"Compliance check disabled as test_mode is unicast!") rxtxapp.execute_test( config=config, build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=None, + ptp=test_config.get("ptp", False), ) diff --git a/tests/validation/tests/single/st20p/packing/test_packing.py b/tests/validation/tests/single/st20p/packing/test_packing.py index 166538d52..b7c0fd74e 100755 --- a/tests/validation/tests/single/st20p/packing/test_packing.py +++ b/tests/validation/tests/single/st20p/packing/test_packing.py @@ -1,10 +1,13 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2024-2025 Intel Corporation +import logging import mtl_engine.RxTxApp as rxtxapp import pytest from mtl_engine.media_files import yuv_files_422rfc10 +logger = logging.getLogger(__name__) + @pytest.mark.nightly @pytest.mark.parametrize("packing", ["GPM_SL", "GPM"]) @@ -26,19 +29,11 @@ def test_packing( test_time, packing, test_config, - prepare_ramdisk, media_file, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_packing_{media_file_info['filename']}_{packing}" # Set a unique pcap file name - ) - config = rxtxapp.create_empty_config() config = rxtxapp.add_st20p_sessions( config=config, @@ -53,11 +48,12 @@ def test_packing( st20p_url=media_file_path, packing=packing, ) - + logger.info(f"Compliance check disabled as test_mode is unicast!") rxtxapp.execute_test( config=config, build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=None, + ptp=test_config.get("ptp", False), ) diff --git a/tests/validation/tests/single/st20p/resolutions/test_resolutions.py b/tests/validation/tests/single/st20p/resolutions/test_resolutions.py index 431ae3135..4daed4bdc 100755 --- a/tests/validation/tests/single/st20p/resolutions/test_resolutions.py +++ b/tests/validation/tests/single/st20p/resolutions/test_resolutions.py @@ -1,12 +1,8 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2024-2025 Intel Corporation -from venv import logger -from conftest import pcap_capture -from create_pcap_file.netsniff import calculate_packets_per_frame import mtl_engine.RxTxApp as rxtxapp import pytest -from mtl_engine.const import FRAMES_CAPTURE from mtl_engine.media_files import yuv_files_422rfc10 @@ -17,19 +13,16 @@ ids=list(yuv_files_422rfc10.keys()), ) def test_resolutions( - request, hosts, build, media, nic_port_list, - test_time, test_config, - prepare_ramdisk, + test_time, media_file, pcap_capture, ): media_file_info, media_file_path = media_file - host = list(hosts.values())[0] config = rxtxapp.create_empty_config() @@ -52,6 +45,5 @@ def test_resolutions( test_time=test_time, host=host, netsniff=pcap_capture, - ptp=False, + ptp=test_config.get("ptp", False), ) - diff --git a/tests/validation/tests/single/st20p/test_mode/test_multicast.py b/tests/validation/tests/single/st20p/test_mode/test_multicast.py index 684c90350..46b8d4c85 100755 --- a/tests/validation/tests/single/st20p/test_mode/test_multicast.py +++ b/tests/validation/tests/single/st20p/test_mode/test_multicast.py @@ -23,19 +23,12 @@ def test_multicast( nic_port_list, test_time, test_config, - prepare_ramdisk, media_file, + pcap_capture, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_multicast_{media_file_info['filename']}" # Set a unique pcap file name - ) - config = rxtxapp.create_empty_config() config = rxtxapp.add_st20p_sessions( config=config, @@ -55,5 +48,6 @@ def test_multicast( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=pcap_capture, + ptp=test_config.get("ptp", False), ) diff --git a/tests/validation/tests/single/st22p/codec/test_codec.py b/tests/validation/tests/single/st22p/codec/test_codec.py index 203714e62..00e30f85a 100755 --- a/tests/validation/tests/single/st22p/codec/test_codec.py +++ b/tests/validation/tests/single/st22p/codec/test_codec.py @@ -23,17 +23,12 @@ def test_codec( test_time, codec, test_config, - prepare_ramdisk, media_file, + pcap_capture, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = f"test_codec_{codec}_Penguin_1080p" - config = rxtxapp.create_empty_config() config = rxtxapp.add_st22p_sessions( config=config, @@ -55,5 +50,6 @@ def test_codec( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=pcap_capture, + ptp=test_config.get("ptp", False), ) diff --git a/tests/validation/tests/single/st22p/format/test_format.py b/tests/validation/tests/single/st22p/format/test_format.py index 73c8d2f2a..e085f4c24 100755 --- a/tests/validation/tests/single/st22p/format/test_format.py +++ b/tests/validation/tests/single/st22p/format/test_format.py @@ -20,17 +20,12 @@ def test_format( nic_port_list, test_time, test_config, - prepare_ramdisk, media_file, + pcap_capture, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = f"test_format_{media_file_info['filename']}" - config = rxtxapp.create_empty_config() config = rxtxapp.add_st22p_sessions( config=config, @@ -52,5 +47,6 @@ def test_format( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=pcap_capture, + ptp=test_config.get("ptp", False), ) diff --git a/tests/validation/tests/single/st22p/fps/test_fps.py b/tests/validation/tests/single/st22p/fps/test_fps.py index 4186f7679..e824289c2 100644 --- a/tests/validation/tests/single/st22p/fps/test_fps.py +++ b/tests/validation/tests/single/st22p/fps/test_fps.py @@ -1,7 +1,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2024-2025 Intel Corporation - import mtl_engine.RxTxApp as rxtxapp import pytest from mtl_engine.media_files import yuv_files_422p10le @@ -39,17 +38,12 @@ def test_fps( fps, codec, test_config, - prepare_ramdisk, media_file, + pcap_capture, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = f"test_fps_{codec}_{media_file_info['filename']}_{fps}" - config = rxtxapp.create_empty_config() config = rxtxapp.add_st22p_sessions( config=config, @@ -71,5 +65,6 @@ def test_fps( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=pcap_capture, + ptp=test_config.get("ptp", False), ) diff --git a/tests/validation/tests/single/st22p/interlace/test_interlace.py b/tests/validation/tests/single/st22p/interlace/test_interlace.py index 5639dfc0a..33dbf0f58 100755 --- a/tests/validation/tests/single/st22p/interlace/test_interlace.py +++ b/tests/validation/tests/single/st22p/interlace/test_interlace.py @@ -20,17 +20,12 @@ def test_interlace( nic_port_list, test_time, test_config, - prepare_ramdisk, media_file, + pcap_capture, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = f"test_interlace_{media_file_info['filename']}" - config = rxtxapp.create_empty_config() config = rxtxapp.add_st22p_sessions( config=config, @@ -53,5 +48,6 @@ def test_interlace( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=pcap_capture, + ptp=test_config.get("ptp", False), ) diff --git a/tests/validation/tests/single/st22p/quality/test_quality.py b/tests/validation/tests/single/st22p/quality/test_quality.py index 241829725..30847fa56 100755 --- a/tests/validation/tests/single/st22p/quality/test_quality.py +++ b/tests/validation/tests/single/st22p/quality/test_quality.py @@ -23,17 +23,12 @@ def test_quality( test_time, quality, test_config, - prepare_ramdisk, media_file, + pcap_capture, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = f"test_quality_{quality}_{media_file_info['filename']}" - config = rxtxapp.create_empty_config() config = rxtxapp.add_st22p_sessions( config=config, @@ -55,5 +50,6 @@ def test_quality( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=pcap_capture, + ptp=test_config.get("ptp", False), ) diff --git a/tests/validation/tests/single/st22p/test_mode/test_unicast.py b/tests/validation/tests/single/st22p/test_mode/test_unicast.py index 974b4acb0..a1df64559 100755 --- a/tests/validation/tests/single/st22p/test_mode/test_unicast.py +++ b/tests/validation/tests/single/st22p/test_mode/test_unicast.py @@ -17,19 +17,13 @@ def test_unicast( build, media, nic_port_list, - test_time, test_config, - prepare_ramdisk, + test_time, media_file, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = f"test_unicast_{media_file_info['filename']}" - config = rxtxapp.create_empty_config() config = rxtxapp.add_st22p_sessions( config=config, @@ -51,5 +45,5 @@ def test_unicast( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + ptp=test_config.get("ptp", False), ) diff --git a/tests/validation/tests/single/st30p/integrity/test_integrity.py b/tests/validation/tests/single/st30p/integrity/test_integrity.py index ab260e360..16e39ca2a 100644 --- a/tests/validation/tests/single/st30p/integrity/test_integrity.py +++ b/tests/validation/tests/single/st30p/integrity/test_integrity.py @@ -2,12 +2,10 @@ # Copyright(c) 2024-2025 Intel Corporation import logging -import os import mtl_engine.RxTxApp as rxtxapp import pytest from mfd_common_libs.log_levels import TEST_PASS -from mtl_engine.const import LOG_FOLDER from mtl_engine.execute import log_fail from mtl_engine.integrity import calculate_st30p_framebuff_size, check_st30p_integrity from mtl_engine.media_files import audio_files @@ -34,24 +32,13 @@ def test_integrity( media, nic_port_list, test_time, - test_config, - prepare_ramdisk, media_file, ): media_file_info, media_file_path = media_file # Ensure the output directory exists. - log_dir = os.path.join(os.getcwd(), LOG_FOLDER, "latest") - os.makedirs(log_dir, exist_ok=True) - out_file_url = os.path.join(log_dir, "out.wav") host = list(hosts.values())[0] - - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_integrity_{media_file_info['format']}" # Set a unique pcap file name - ) + out_file_url = host.connection.path(media_file_path).parent / "out.pcm" config = rxtxapp.create_empty_config() config = rxtxapp.add_st30p_sessions( @@ -71,7 +58,7 @@ def test_integrity( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=None, ) size = calculate_st30p_framebuff_size( diff --git a/tests/validation/tests/single/st30p/st30p_channel/test_st30p_channel.py b/tests/validation/tests/single/st30p/st30p_channel/test_st30p_channel.py index 8ce24042e..e565ede79 100755 --- a/tests/validation/tests/single/st30p/st30p_channel/test_st30p_channel.py +++ b/tests/validation/tests/single/st30p/st30p_channel/test_st30p_channel.py @@ -40,7 +40,7 @@ def test_st30p_channel( audio_channel, request, test_config, - prepare_ramdisk, + pcap_capture, media_file, ): media_file_info, media_file_path = media_file @@ -48,14 +48,6 @@ def test_st30p_channel( SDBQ1001_audio_channel_check(audio_channel, media_file_info["format"], request) host = list(hosts.values())[0] - - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_st30p_channel_{media_file_info['format']}_{audio_channel}" # e.g., test_st30p_channel_PCM8_M - ) - out_file_url = host.connection.path(media_file_path).parent / "out.pcm" config = rxtxapp.create_empty_config() @@ -76,7 +68,8 @@ def test_st30p_channel( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=pcap_capture, + ptp=test_config.get("ptp", False), ) if test_config.get("integrity_check", True): diff --git a/tests/validation/tests/single/st30p/st30p_format/test_st30p_format.py b/tests/validation/tests/single/st30p/st30p_format/test_st30p_format.py index 7061c0c67..3c5bcec54 100755 --- a/tests/validation/tests/single/st30p/st30p_format/test_st30p_format.py +++ b/tests/validation/tests/single/st30p/st30p_format/test_st30p_format.py @@ -35,19 +35,11 @@ def test_st30p_format( nic_port_list, test_time, test_config, - prepare_ramdisk, media_file, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_st30p_format_{media_file_info['format']}" # Set a unique pcap file name - ) - out_file_url = host.connection.path(media_file_path).parent / "out.pcm" config = rxtxapp.create_empty_config() @@ -68,7 +60,7 @@ def test_st30p_format( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=None, ) if test_config.get("integrity_check", True): logger.info("Running audio integrity check...") diff --git a/tests/validation/tests/single/st30p/st30p_ptime/test_st30p_ptime.py b/tests/validation/tests/single/st30p/st30p_ptime/test_st30p_ptime.py index 3c0a604d7..f4334cfa5 100755 --- a/tests/validation/tests/single/st30p/st30p_ptime/test_st30p_ptime.py +++ b/tests/validation/tests/single/st30p/st30p_ptime/test_st30p_ptime.py @@ -36,19 +36,11 @@ def test_st30p_ptime( test_time, audio_ptime, test_config, - prepare_ramdisk, media_file, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_st30p_ptime_{media_file_info['format']}" # Set a unique pcap file name - ) - out_file_url = host.connection.path(media_file_path).parent / "out.pcm" config = rxtxapp.create_empty_config() @@ -69,7 +61,7 @@ def test_st30p_ptime( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=None, ) if test_config.get("integrity_check", True): diff --git a/tests/validation/tests/single/st30p/st30p_sampling/test_st30p_sampling.py b/tests/validation/tests/single/st30p/st30p_sampling/test_st30p_sampling.py index bb7aac932..255e206cc 100755 --- a/tests/validation/tests/single/st30p/st30p_sampling/test_st30p_sampling.py +++ b/tests/validation/tests/single/st30p/st30p_sampling/test_st30p_sampling.py @@ -36,19 +36,11 @@ def test_st30p_sampling( test_time, audio_sampling, test_config, - prepare_ramdisk, media_file, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_st30p_sampling_{media_file_info['format']}" # Set a unique pcap file name - ) - out_file_url = host.connection.path(media_file_path).parent / "out.pcm" config = rxtxapp.create_empty_config() @@ -69,7 +61,7 @@ def test_st30p_sampling( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=None, ) if test_config.get("integrity_check", True): diff --git a/tests/validation/tests/single/st30p/test_mode/test_multicast.py b/tests/validation/tests/single/st30p/test_mode/test_multicast.py index 64227f581..384a29e6a 100755 --- a/tests/validation/tests/single/st30p/test_mode/test_multicast.py +++ b/tests/validation/tests/single/st30p/test_mode/test_multicast.py @@ -28,25 +28,14 @@ def test_multicast( build, media, nic_port_list, - test_time, test_config, - prepare_ramdisk, + test_time, + pcap_capture, media_file, ): media_file_info, media_file_path = media_file host = list(hosts.values())[0] - - # Get capture configuration from test_config.yaml - # Collect packet capture configuration and assign test_name - capture_cfg = dict(test_config.get("capture_cfg", {})) - capture_cfg["test_name"] = ( - f"test_multicast_{media_file_info['format']}" # Set a unique pcap file name - ) - - # Ensure the output directory exists. - log_dir = os.path.join(os.getcwd(), LOG_FOLDER, "latest") - os.makedirs(log_dir, exist_ok=True) - out_file_url = os.path.join(log_dir, "out.wav") + out_file_url = host.connection.path(media_file_path).parent / "out.pcm" config = rxtxapp.create_empty_config() config = rxtxapp.add_st30p_sessions( @@ -66,5 +55,6 @@ def test_multicast( build=build, test_time=test_time, host=host, - capture_cfg=capture_cfg, + netsniff=pcap_capture, + ptp=test_config.get("ptp", False), )