From d58c55a959c69c1a3fb48bb2abcf9f5f520834fb Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 28 Jul 2025 10:30:19 +0000 Subject: [PATCH 001/123] Ci: Add basic smoke test workflow for self-hosted runners - Add hello-world-test job with basic system checks - Test environment variables, file system access, and basic commands - Provide foundation for validating runner setup before complex builds - Set 15-minute timeout for quick validation cycles --- .github/workflows/smoke_tests.yml | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/smoke_tests.yml diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml new file mode 100644 index 000000000..9833d8c39 --- /dev/null +++ b/.github/workflows/smoke_tests.yml @@ -0,0 +1,71 @@ +name: smoke-tests + +on: + push: + branches: + - smoke-tests + +env: + BUILD_TYPE: 'Release' + DPDK_VERSION: '23.11' + DPDK_REBUILD: 'false' + +permissions: + contents: read + +jobs: + hello-world-test: + runs-on: [Linux, self-hosted] + timeout-minutes: 15 + steps: + - name: 'preparation: Harden Runner' + uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2 + with: + egress-policy: audit + + - name: 'preparation: Restore valid repository owner and print env' + if: always() + run: | + sudo chown -R "${USER}" "$(pwd)" || true + env | grep BUILD_ || true + env | grep DPDK_ || true + + - name: 'basic: Check system information' + run: | + echo "=== Hello World Smoke Test ===" + echo "System information:" + uname -a + echo "Current user: $(whoami)" + echo "Current directory: $(pwd)" + echo "Date: $(date)" + + - name: 'basic: Test basic commands' + run: | + echo "Testing basic shell commands..." + ls -la + echo "PATH: $PATH" + which gcc || echo "gcc not found" + which python3 || echo "python3 not found" + which git || echo "git not found" + + - name: 'basic: Test environment variables' + run: | + echo "Testing environment variables:" + echo "BUILD_TYPE: $BUILD_TYPE" + echo "DPDK_VERSION: $DPDK_VERSION" + echo "DPDK_REBUILD: $DPDK_REBUILD" + + - name: 'basic: Test file system access' + run: | + echo "Testing file system operations..." + touch /tmp/smoke_test_file + echo "Hello from smoke test" > /tmp/smoke_test_file + cat /tmp/smoke_test_file + rm /tmp/smoke_test_file + echo "File system test completed successfully" + + - name: 'basic: Final hello world' + run: | + echo "🎉 Hello World! Smoke test completed successfully! 🎉" + echo "Machine is ready for more complex operations." + \ No newline at end of file From bf591e9cfe5d372bf467ac117ac21589618f9c0d Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 28 Jul 2025 11:26:38 +0000 Subject: [PATCH 002/123] DPDK added --- .github/workflows/smoke_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 9833d8c39..3269f269f 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -15,7 +15,7 @@ permissions: jobs: hello-world-test: - runs-on: [Linux, self-hosted] + runs-on: [Linux, self-hosted, DPDK] timeout-minutes: 15 steps: - name: 'preparation: Harden Runner' From 5f88810114e45595626825bed2bda879a65b36d8 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 29 Jul 2025 13:05:27 +0000 Subject: [PATCH 003/123] add smoke tests --- .../functional/local/audio/test_audio.py | 10 +++++++++- .../validation/functional/local/blob/test_blob.py | 7 ++++++- .../functional/local/video/test_ffmpeg_video.py | 12 +++++++++++- .../functional/local/video/test_video.py | 14 +++++++++++++- tests/validation/pytest.ini | 4 +++- 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/tests/validation/functional/local/audio/test_audio.py b/tests/validation/functional/local/audio/test_audio.py index 6d8226fb6..7dc5c7fe9 100644 --- a/tests/validation/functional/local/audio/test_audio.py +++ b/tests/validation/functional/local/audio/test_audio.py @@ -15,7 +15,15 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize("file", audio_files.keys()) +@pytest.mark.parametrize( + "file", + [ + pytest.param("PCM16_48000_Mono", marks=pytest.mark.smoke), + "PCM16_48000_Stereo", + "PCM24_48000_Mono", + "PCM24_48000_Stereo", + ] +) def test_audio(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: # Get TX and RX hosts diff --git a/tests/validation/functional/local/blob/test_blob.py b/tests/validation/functional/local/blob/test_blob.py index aacaa392b..fdde81887 100644 --- a/tests/validation/functional/local/blob/test_blob.py +++ b/tests/validation/functional/local/blob/test_blob.py @@ -16,7 +16,12 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize("file", [file for file in blob_files.keys()]) +@pytest.mark.parametrize( + "file", + [ + pytest.param("random_bin_100M", marks=pytest.mark.smoke), + ] +) def test_blob(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: # Get TX and RX hosts diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index fdb6c2c5a..363412d93 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -21,7 +21,17 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize("video_type", [k for k in yuv_files.keys()]) +@pytest.mark.parametrize( + "video_type", + [ + "i720p25", + "i720p30", + pytest.param("i1080p25", marks=pytest.mark.smoke), + "i1080p50", + "i1080p60", + "i2160p30", + ] +) def test_local_ffmpeg_video(media_proxy, hosts, test_config, video_type: str) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts diff --git a/tests/validation/functional/local/video/test_video.py b/tests/validation/functional/local/video/test_video.py index 0ea45f99a..e9dd7fbd0 100644 --- a/tests/validation/functional/local/video/test_video.py +++ b/tests/validation/functional/local/video/test_video.py @@ -16,7 +16,19 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize("file", [file for file in yuv_files.keys()]) +@pytest.mark.parametrize( + "file", + [ + "i720p25", + "i720p30", + pytest.param("i1080p25", marks=pytest.mark.smoke), + pytest.param("i1080p30", marks=pytest.mark.smoke), + "i1080p50", + "i1080p60", + "i2160p25", + "i2160p30", + ] +) def test_video(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: # Get TX and RX hosts diff --git a/tests/validation/pytest.ini b/tests/validation/pytest.ini index 7d6a37fa3..bfd933dac 100644 --- a/tests/validation/pytest.ini +++ b/tests/validation/pytest.ini @@ -5,4 +5,6 @@ log_cli_level = info log_file = pytest.log log_file_level = debug log_file_format = %(asctime)s,%(msecs)03d %(levelname)-8s %(filename)s:%(lineno)d %(message)s -log_file_date_format = %Y-%m-%d %H:%M:%S \ No newline at end of file +log_file_date_format = %Y-%m-%d %H:%M:%S +markers = + smoke: marks tests as smoke tests (deselect with '-m "not smoke"') \ No newline at end of file From 3ae63917c2062fd0b12a13b2c426f1f3d0d183e9 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 29 Jul 2025 13:30:23 +0000 Subject: [PATCH 004/123] smoke_test --- .github/workflows/smoke_tests.yml | 69 +++++++++++++------------------ 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 3269f269f..d75a79301 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -1,9 +1,9 @@ -name: smoke-tests +name: smoke-tests-bare-metal on: push: branches: - - smoke-tests + - 'smoke-tests' env: BUILD_TYPE: 'Release' @@ -14,9 +14,11 @@ permissions: contents: read jobs: - hello-world-test: + validation-build-mcm: runs-on: [Linux, self-hosted, DPDK] - timeout-minutes: 15 + timeout-minutes: 60 + outputs: + pipenv-activate: ${{ steps.pipenv-install.outputs.VIRTUAL_ENV }} steps: - name: 'preparation: Harden Runner' uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2 @@ -29,43 +31,28 @@ jobs: sudo chown -R "${USER}" "$(pwd)" || true env | grep BUILD_ || true env | grep DPDK_ || true - - - name: 'basic: Check system information' - run: | - echo "=== Hello World Smoke Test ===" - echo "System information:" - uname -a - echo "Current user: $(whoami)" - echo "Current directory: $(pwd)" - echo "Date: $(date)" - - - name: 'basic: Test basic commands' - run: | - echo "Testing basic shell commands..." - ls -la - echo "PATH: $PATH" - which gcc || echo "gcc not found" - which python3 || echo "python3 not found" - which git || echo "git not found" - - - name: 'basic: Test environment variables' + - name: 'installation: Install pipenv environment' + working-directory: tests/validation + id: pipenv-install run: | - echo "Testing environment variables:" - echo "BUILD_TYPE: $BUILD_TYPE" - echo "DPDK_VERSION: $DPDK_VERSION" - echo "DPDK_REBUILD: $DPDK_REBUILD" - - - name: 'basic: Test file system access' + python3 -m venv venv + source venv/bin/activate + pip install -r requirements.txt + echo "VIRTUAL_ENV=$PWD/venv/bin/activate" >> "$GITHUB_ENV" + validation-run-tests: + needs: validation-build-mcm + runs-on: [Linux, self-hosted, DPDK] + timeout-minutes: 60 + env: + PYTEST_RETRIES: '3' + steps: + - name: 'preparation: Kill pytest routines' run: | - echo "Testing file system operations..." - touch /tmp/smoke_test_file - echo "Hello from smoke test" > /tmp/smoke_test_file - cat /tmp/smoke_test_file - rm /tmp/smoke_test_file - echo "File system test completed successfully" - - - name: 'basic: Final hello world' + sudo killall -SIGINT pipenv || true + sudo killall -SIGINT pytest || true + - name: 'execution: Run validation-bare-metal tests in virtual environment' run: | - echo "🎉 Hello World! Smoke test completed successfully! 🎉" - echo "Machine is ready for more complex operations." - \ No newline at end of file + sudo tests/validation/venv/bin/python3 -m pytest \ + --topology_config=tests/validation/configs/topology_config.yaml \ + --test_config=tests/validation/configs/test_config.yaml -m smoke \ + --template=html/index.html --report=report.html \ No newline at end of file From 25bd1b741a3a54347cd5e658cf2734fee0f56804 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 29 Jul 2025 14:09:18 +0000 Subject: [PATCH 005/123] preparation: Checkout MCM --- .github/workflows/smoke_tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index d75a79301..59270948d 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -31,6 +31,10 @@ jobs: sudo chown -R "${USER}" "$(pwd)" || true env | grep BUILD_ || true env | grep DPDK_ || true + - name: 'preparation: Checkout MCM' + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: 'smoke-tests' - name: 'installation: Install pipenv environment' working-directory: tests/validation id: pipenv-install From aa5ac1e718a949b3881de4bb18102c58b04ffb8d Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 29 Jul 2025 14:29:33 +0000 Subject: [PATCH 006/123] add list smoke tests --- .github/workflows/smoke_tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 59270948d..13db59f20 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -54,9 +54,14 @@ jobs: run: | sudo killall -SIGINT pipenv || true sudo killall -SIGINT pytest || true + - name: 'list all smoke tests' + run: | + sudo tests/validation/venv/bin/python3 -m pytest \ + --collect-only --quiet ./tests/validation/functional/ - name: 'execution: Run validation-bare-metal tests in virtual environment' run: | sudo tests/validation/venv/bin/python3 -m pytest \ --topology_config=tests/validation/configs/topology_config.yaml \ --test_config=tests/validation/configs/test_config.yaml -m smoke \ + ./tests/validation/functional/ \ --template=html/index.html --report=report.html \ No newline at end of file From 1118512caf04957eeaa487161d6d2a5fd7000964 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 29 Jul 2025 14:41:37 +0000 Subject: [PATCH 007/123] add configs for workflow --- .github/workflows/smoke_tests.yml | 6 +++--- .../configs/test_config_workflow.yaml | 2 ++ .../configs/topology_config_workflow.yaml | 19 +++++++++++++++++++ tests/validation/functional/test_demo.py | 3 +++ 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 tests/validation/configs/test_config_workflow.yaml create mode 100644 tests/validation/configs/topology_config_workflow.yaml diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 13db59f20..becf5ca19 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -57,11 +57,11 @@ jobs: - name: 'list all smoke tests' run: | sudo tests/validation/venv/bin/python3 -m pytest \ - --collect-only --quiet ./tests/validation/functional/ + --collect-only --quiet ./tests/validation/functional/ -m smoke - name: 'execution: Run validation-bare-metal tests in virtual environment' run: | sudo tests/validation/venv/bin/python3 -m pytest \ - --topology_config=tests/validation/configs/topology_config.yaml \ - --test_config=tests/validation/configs/test_config.yaml -m smoke \ + --topology_config=tests/validation/configs/topology_config_workflow.yaml \ + --test_config=tests/validation/configs/test_config_workflow.yaml -m smoke \ ./tests/validation/functional/ \ --template=html/index.html --report=report.html \ No newline at end of file diff --git a/tests/validation/configs/test_config_workflow.yaml b/tests/validation/configs/test_config_workflow.yaml new file mode 100644 index 000000000..19839d6c6 --- /dev/null +++ b/tests/validation/configs/test_config_workflow.yaml @@ -0,0 +1,2 @@ +payload_type: 100 +test_time_sec: 90 \ No newline at end of file diff --git a/tests/validation/configs/topology_config_workflow.yaml b/tests/validation/configs/topology_config_workflow.yaml new file mode 100644 index 000000000..29d060e58 --- /dev/null +++ b/tests/validation/configs/topology_config_workflow.yaml @@ -0,0 +1,19 @@ +--- +metadata: + version: '2.4' +hosts: + - name: mesh-agent + instantiate: true + role: sut + network_interfaces: + - pci_device: 8086:1592 + interface_index: 0 # all + connections: + - ip_address: 127.0.0.1 + connection_type: LocalConnection + extra_info: + mesh_agent: + control_port: 8100 + proxy_port: 50051 + media_proxy: + sdk_port: 8002 diff --git a/tests/validation/functional/test_demo.py b/tests/validation/functional/test_demo.py index 627a420e9..4850b1fc5 100644 --- a/tests/validation/functional/test_demo.py +++ b/tests/validation/functional/test_demo.py @@ -10,6 +10,7 @@ StreamVideoIntegrityRunner, ) from Engine.mcm_apps import MEDIA_PROXY_PORT +import pytest logger = logging.getLogger(__name__) @@ -64,6 +65,7 @@ def test_list_command_on_sut(hosts): process.stop() +@pytest.mark.smoke def test_mesh_agent_lifecycle(mesh_agent): """Test starting and stopping the mesh agent.""" logger.info("Testing mesh_agent lifecycle") @@ -74,6 +76,7 @@ def test_mesh_agent_lifecycle(mesh_agent): logger.info("Mesh agent lifecycle test completed successfully.") +@pytest.mark.smoke def test_media_proxy(media_proxy): """Test starting and stopping the media proxy without sudo.""" logger.info("Testing media_proxy lifecycle") From 3b8b4ba624183f94bd4367544d2bdf3fe12bfbbc Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 29 Jul 2025 15:01:54 +0000 Subject: [PATCH 008/123] suppress errors 1 for cleanup_processes --- tests/validation/conftest.py | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index f16f84c36..a393dc462 100644 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -261,32 +261,24 @@ def media_config(hosts: dict) -> None: @pytest.fixture(scope="session", autouse=True) def cleanup_processes(hosts: dict) -> None: """ - Kills mesh-agent, media_proxy, ffmpeg, and all Rx*App and Tx*App processes on all hosts before running the tests. + Kill mesh-agent, media_proxy, ffmpeg, and all Rx*App/Tx*App processes on all hosts before running tests. """ for host in hosts.values(): + connection = host.connection for proc in ["mesh-agent", "media_proxy", "ffmpeg"]: try: - connection = host.connection - # connection.enable_sudo() connection.execute_command(f"pgrep {proc}", stderr_to_stdout=True) connection.execute_command(f"pkill -9 {proc}", stderr_to_stdout=True) except Exception as e: - logger.warning(f"Failed to check/kill {proc} on {host.name}: {e}") - # Kill all Rx*App and Tx*App processes (e.g., RxVideoApp, RxAudioApp, RxBlobApp, TxVideoApp, etc.) + if not (hasattr(e, "returncode") and e.returncode == 1): + logger.warning(f"Failed to check/kill {proc} on {host.name}: {e}") for pattern in ["^Rx[A-Za-z]+App$", "^Tx[A-Za-z]+App$"]: try: - connection = host.connection - # connection.enable_sudo() - connection.execute_command( - f"pgrep -f '{pattern}'", stderr_to_stdout=True - ) - connection.execute_command( - f"pkill -9 -f '{pattern}'", stderr_to_stdout=True - ) + connection.execute_command(f"pgrep -f '{pattern}'", stderr_to_stdout=True) + connection.execute_command(f"pkill -9 -f '{pattern}'", stderr_to_stdout=True) except Exception as e: - logger.warning( - f"Failed to check/kill processes matching {pattern} on {host.name}: {e}" - ) + if not (hasattr(e, "returncode") and e.returncode == 1): + logger.warning(f"Failed to check/kill processes matching {pattern} on {host.name}: {e}") logger.info("Cleanup of processes completed.") From e80af023e0d27223c62b9b8832a2f4cb29c0426a Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 29 Jul 2025 15:04:47 +0000 Subject: [PATCH 009/123] add data to extra info in media proxy --- tests/validation/configs/topology_config_workflow.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/validation/configs/topology_config_workflow.yaml b/tests/validation/configs/topology_config_workflow.yaml index 29d060e58..c14bd5e02 100644 --- a/tests/validation/configs/topology_config_workflow.yaml +++ b/tests/validation/configs/topology_config_workflow.yaml @@ -17,3 +17,6 @@ hosts: proxy_port: 50051 media_proxy: sdk_port: 8002 + st2110: true + rdma: true + rdma_ports: '9100-9999' From dd36a1452da8ca4eb54b56db61e26c6ea8dc06a6 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 29 Jul 2025 16:55:05 +0000 Subject: [PATCH 010/123] test SSHConnection configuration --- .github/workflows/smoke_tests.yml | 18 ++++++++++++++++++ .../configs/topology_config_workflow.yaml | 9 +++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index becf5ca19..9acb5b7fa 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -31,6 +31,19 @@ jobs: sudo chown -R "${USER}" "$(pwd)" || true env | grep BUILD_ || true env | grep DPDK_ || true + - name: 'preparation: Add SSH key for user' + run: | + sudo mkdir -p /home/"${USER}"/.ssh + # Copy keys with new names + sudo cp ~/.ssh/id_rsa /home/"${USER}"/.ssh/mcm_key + sudo cp ~/.ssh/id_rsa.pub /home/"${USER}"/.ssh/mcm_key.pub + sudo cp ~/.ssh/id_rsa.pub /home/"${USER}"/.ssh/authorized_keys + # Set proper permissions + sudo chmod 600 /home/"${USER}"/.ssh/mcm_key + sudo chmod 644 /home/"${USER}"/.ssh/mcm_key.pub + sudo chmod 600 /home/"${USER}"/.ssh/authorized_keys + sudo chmod 700 /home/"${USER}"/.ssh + sudo chown -R "${USER}":"${USER}" /home/"${USER}"/.ssh - name: 'preparation: Checkout MCM' uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: @@ -43,6 +56,11 @@ jobs: source venv/bin/activate pip install -r requirements.txt echo "VIRTUAL_ENV=$PWD/venv/bin/activate" >> "$GITHUB_ENV" + - name: 'add user name to environment and config' + run: | + echo "USER=${USER}" >> "$GITHUB_ENV" + sed -i "s/{{ USER }}/${USER}/g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml validation-run-tests: needs: validation-build-mcm runs-on: [Linux, self-hosted, DPDK] diff --git a/tests/validation/configs/topology_config_workflow.yaml b/tests/validation/configs/topology_config_workflow.yaml index c14bd5e02..3f90424a8 100644 --- a/tests/validation/configs/topology_config_workflow.yaml +++ b/tests/validation/configs/topology_config_workflow.yaml @@ -7,10 +7,15 @@ hosts: role: sut network_interfaces: - pci_device: 8086:1592 - interface_index: 0 # all + all_interfaces: true connections: - ip_address: 127.0.0.1 - connection_type: LocalConnection + connection_type: SSHConnection + connection_options: + port: 22 + username: {{ USER }} + password: None + key_path: {{ KEY_PATH }} extra_info: mesh_agent: control_port: 8100 From 2b50febb6e4d51c969ee730db969212c7801e63c Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 29 Jul 2025 16:57:09 +0000 Subject: [PATCH 011/123] SSHConnection --- .github/workflows/smoke_tests.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 9acb5b7fa..ee60c2525 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -31,19 +31,26 @@ jobs: sudo chown -R "${USER}" "$(pwd)" || true env | grep BUILD_ || true env | grep DPDK_ || true - - name: 'preparation: Add SSH key for user' + - name: 'preparation: Generate and configure SSH keys' run: | + # Create .ssh directory sudo mkdir -p /home/"${USER}"/.ssh - # Copy keys with new names - sudo cp ~/.ssh/id_rsa /home/"${USER}"/.ssh/mcm_key - sudo cp ~/.ssh/id_rsa.pub /home/"${USER}"/.ssh/mcm_key.pub - sudo cp ~/.ssh/id_rsa.pub /home/"${USER}"/.ssh/authorized_keys + + # Generate new SSH key pair directly in the target location + sudo ssh-keygen -t rsa -N "" -f /home/"${USER}"/.ssh/mcm_key + + # Copy public key to authorized_keys + sudo cp /home/"${USER}"/.ssh/mcm_key.pub /home/"${USER}"/.ssh/authorized_keys + # Set proper permissions sudo chmod 600 /home/"${USER}"/.ssh/mcm_key sudo chmod 644 /home/"${USER}"/.ssh/mcm_key.pub sudo chmod 600 /home/"${USER}"/.ssh/authorized_keys sudo chmod 700 /home/"${USER}"/.ssh sudo chown -R "${USER}":"${USER}" /home/"${USER}"/.ssh + + # Add localhost to known_hosts to avoid prompts + sudo -u "${USER}" ssh-keyscan -H localhost >> /home/"${USER}"/.ssh/known_hosts - name: 'preparation: Checkout MCM' uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: From 31f253c20ee7948edcebad64d71c643c3acaae97 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 30 Jul 2025 11:05:40 +0000 Subject: [PATCH 012/123] assure removal of file after the test --- tests/validation/functional/local/audio/test_audio.py | 7 +++++-- tests/validation/functional/local/blob/test_blob.py | 5 ++++- .../functional/local/video/test_ffmpeg_video.py | 8 ++++++++ tests/validation/functional/local/video/test_video.py | 5 ++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/validation/functional/local/audio/test_audio.py b/tests/validation/functional/local/audio/test_audio.py index 7dc5c7fe9..84892e242 100644 --- a/tests/validation/functional/local/audio/test_audio.py +++ b/tests/validation/functional/local/audio/test_audio.py @@ -9,7 +9,7 @@ import Engine.rx_tx_app_connection import Engine.rx_tx_app_engine_mcm as utils import Engine.rx_tx_app_payload -from Engine.const import DEFAULT_LOOP_COUNT, MCM_ESTABLISH_TIMEOUT +from Engine.const import DEFAULT_LOOP_COUNT, MCM_ESTABLISH_TIMEOUT, MCM_RXTXAPP_RUN_TIMEOUT from Engine.media_files import audio_files logger = logging.getLogger(__name__) @@ -65,7 +65,10 @@ def test_audio(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> tx_executor.stop() rx_executor.stop() + # TODO add validate() function to check if the output file is correct + + rx_executor.cleanup() + assert tx_executor.is_pass is True, "TX process did not pass" assert rx_executor.is_pass is True, "RX process did not pass" - # TODO add validate() function to check if the output file is correct diff --git a/tests/validation/functional/local/blob/test_blob.py b/tests/validation/functional/local/blob/test_blob.py index fdde81887..868c160c4 100644 --- a/tests/validation/functional/local/blob/test_blob.py +++ b/tests/validation/functional/local/blob/test_blob.py @@ -63,7 +63,10 @@ def test_blob(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> tx_executor.stop() rx_executor.stop() + # TODO add validate() function to check if the output file is correct + + rx_executor.cleanup() + assert tx_executor.is_pass is True, "TX process did not pass" assert rx_executor.is_pass is True, "RX process did not pass" - # TODO add validate() function to check if the output file is correct diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index 363412d93..ca672cb9b 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -7,6 +7,8 @@ import pytest import logging +from tests.validation.Engine.rx_tx_app_file_validation_utils import cleanup_file + from ....Engine.media_files import yuv_files from common.ffmpeg_handler.ffmpeg import FFmpeg, FFmpegExecutor @@ -113,3 +115,9 @@ def test_local_ffmpeg_video(media_proxy, hosts, test_config, video_type: str) -> mcm_tx_executor.start() mcm_rx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) mcm_tx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) + + success = cleanup_file(rx_host.connection, str(mcm_rx_outp.output_path)) + if success: + logger.debug(f"Cleaned up Rx output file: {mcm_rx_outp.output_path}") + else: + logger.warning(f"Failed to clean up Rx output file: {mcm_rx_outp.output_path}") diff --git a/tests/validation/functional/local/video/test_video.py b/tests/validation/functional/local/video/test_video.py index e9dd7fbd0..9cbc71f14 100644 --- a/tests/validation/functional/local/video/test_video.py +++ b/tests/validation/functional/local/video/test_video.py @@ -70,7 +70,10 @@ def test_video(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> tx_executor.stop() rx_executor.stop() + # TODO add validate() function to check if the output file is correct + + rx_executor.cleanup() + assert tx_executor.is_pass is True, "TX process did not pass" assert rx_executor.is_pass is True, "RX process did not pass" - # TODO add validate() function to check if the output file is correct From 5e92d6c631d7b10b10fc3369436cbec3366a4d25 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 30 Jul 2025 12:56:45 +0000 Subject: [PATCH 013/123] add preparation --- .github/workflows/smoke_tests.yml | 61 +++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index ee60c2525..9d2544470 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -4,16 +4,48 @@ on: push: branches: - 'smoke-tests' - + workflow_dispatch: + inputs: + branch-to-checkout: + type: string + default: 'main' + required: false + description: 'Branch name to use' + list_tests: + type: choice + required: false + description: 'List all tests before running' + options: + - "true" + - "false" + markers: + type: string + default: 'smoke' + required: false + description: 'Markers to use for pytest' env: BUILD_TYPE: 'Release' DPDK_VERSION: '23.11' DPDK_REBUILD: 'false' - + MCM_BINARIES_DIR: './mcm-binaries' + MEDIA_PROXY: './mcm-binaries/media_proxy' + MESH_AGENT: './mcm-binaries/mesh-agent' + MCM_FFMPEG_6: './mcm-binaries/ffmpeg-6-1/ffmpeg' + MCM_FFMPEG_7: './mcm-binaries/ffmpeg-7-0/ffmpeg' + MTL__FFMPEG_6: './mtl-binaries/ffmpeg-6-1/ffmpeg' + MTL__FFMPEG_7: './mtl-binaries/ffmpeg-7-0/ffmpeg' permissions: contents: read - jobs: + mtl-ffmpeg-build: + runs-on: [Linux, self-hosted, DPDK] + timeout-minutes: 60 + steps: + - name: 'preparation: Harden Runner' + uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2 + with: + egress-policy: audit + # download mtl ffmpeg binaries or build locally validation-build-mcm: runs-on: [Linux, self-hosted, DPDK] timeout-minutes: 60 @@ -55,6 +87,16 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: 'smoke-tests' + - name: 'Download build artifacts' + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 + with: + name: mcm-build + path: ./mcm-binaries + - name: 'Make binaries executable' + run: | + chmod +x ./mcm-binaries/media_proxy + chmod +x ./mcm-binaries/mesh-agent + chmod +x ./mcm-binaries/ffmpeg* - name: 'installation: Install pipenv environment' working-directory: tests/validation id: pipenv-install @@ -74,19 +116,24 @@ jobs: timeout-minutes: 60 env: PYTEST_RETRIES: '3' + MARKERS: ${{ github.event.inputs.markers || 'smoke' }} + LIST_TESTS: ${{ github.event.inputs.list_tests || 'true' }} steps: - name: 'preparation: Kill pytest routines' run: | sudo killall -SIGINT pipenv || true sudo killall -SIGINT pytest || true - - name: 'list all smoke tests' + - name: 'list all tests marked with ${{ env.MARKERS }}' + if: ${{ env.LIST_TESTS == 'true' }} run: | sudo tests/validation/venv/bin/python3 -m pytest \ - --collect-only --quiet ./tests/validation/functional/ -m smoke + --collect-only --quiet ./tests/validation/functional/ \ + -m "${{ env.MARKERS }}" - name: 'execution: Run validation-bare-metal tests in virtual environment' run: | sudo tests/validation/venv/bin/python3 -m pytest \ --topology_config=tests/validation/configs/topology_config_workflow.yaml \ - --test_config=tests/validation/configs/test_config_workflow.yaml -m smoke \ + --test_config=tests/validation/configs/test_config_workflow.yaml \ ./tests/validation/functional/ \ - --template=html/index.html --report=report.html \ No newline at end of file + --template=html/index.html --report=report.html" \ + -m "${{ env.MARKERS }}" \ No newline at end of file From 6a1374e15cbc697162fc8e8b93b823b6b9928930 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 30 Jul 2025 13:18:54 +0000 Subject: [PATCH 014/123] remove ssh key creation --- .github/workflows/smoke_tests.yml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 9d2544470..638bd47fe 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -63,26 +63,6 @@ jobs: sudo chown -R "${USER}" "$(pwd)" || true env | grep BUILD_ || true env | grep DPDK_ || true - - name: 'preparation: Generate and configure SSH keys' - run: | - # Create .ssh directory - sudo mkdir -p /home/"${USER}"/.ssh - - # Generate new SSH key pair directly in the target location - sudo ssh-keygen -t rsa -N "" -f /home/"${USER}"/.ssh/mcm_key - - # Copy public key to authorized_keys - sudo cp /home/"${USER}"/.ssh/mcm_key.pub /home/"${USER}"/.ssh/authorized_keys - - # Set proper permissions - sudo chmod 600 /home/"${USER}"/.ssh/mcm_key - sudo chmod 644 /home/"${USER}"/.ssh/mcm_key.pub - sudo chmod 600 /home/"${USER}"/.ssh/authorized_keys - sudo chmod 700 /home/"${USER}"/.ssh - sudo chown -R "${USER}":"${USER}" /home/"${USER}"/.ssh - - # Add localhost to known_hosts to avoid prompts - sudo -u "${USER}" ssh-keyscan -H localhost >> /home/"${USER}"/.ssh/known_hosts - name: 'preparation: Checkout MCM' uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: From ac469a6470d2266b70a3c0a7e77c7057a51b47cd Mon Sep 17 00:00:00 2001 From: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:41:18 +0200 Subject: [PATCH 015/123] Update smoke_tests.yml Signed-off-by: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> --- .github/workflows/smoke_tests.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 638bd47fe..4ca2b3a80 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -67,16 +67,16 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: 'smoke-tests' - - name: 'Download build artifacts' - uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 - with: - name: mcm-build - path: ./mcm-binaries - - name: 'Make binaries executable' - run: | - chmod +x ./mcm-binaries/media_proxy - chmod +x ./mcm-binaries/mesh-agent - chmod +x ./mcm-binaries/ffmpeg* + # - name: 'Download build artifacts' + # uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 + # with: + # name: mcm-build + # path: ./mcm-binaries + # - name: 'Make binaries executable' + # run: | + # chmod +x ./mcm-binaries/media_proxy + # chmod +x ./mcm-binaries/mesh-agent + # chmod +x ./mcm-binaries/ffmpeg* - name: 'installation: Install pipenv environment' working-directory: tests/validation id: pipenv-install @@ -116,4 +116,4 @@ jobs: --test_config=tests/validation/configs/test_config_workflow.yaml \ ./tests/validation/functional/ \ --template=html/index.html --report=report.html" \ - -m "${{ env.MARKERS }}" \ No newline at end of file + -m "${{ env.MARKERS }}" From 0cbf8ddcaf59f86ee6b45150159ad12dfe56e6bb Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 30 Jul 2025 13:25:36 +0000 Subject: [PATCH 016/123] correct command --- .github/workflows/smoke_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 4ca2b3a80..83262c4b5 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -115,5 +115,5 @@ jobs: --topology_config=tests/validation/configs/topology_config_workflow.yaml \ --test_config=tests/validation/configs/test_config_workflow.yaml \ ./tests/validation/functional/ \ - --template=html/index.html --report=report.html" \ + --template=html/index.html --report=report.html \ -m "${{ env.MARKERS }}" From 7b7cca69815be1593d6004fabe6b264bd030ed0c Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 30 Jul 2025 15:05:53 +0000 Subject: [PATCH 017/123] build update --- .github/workflows/smoke_tests.yml | 81 +++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 83262c4b5..9e9f887bd 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -27,26 +27,19 @@ env: BUILD_TYPE: 'Release' DPDK_VERSION: '23.11' DPDK_REBUILD: 'false' + MEDIA_PATH: '/mnt/media' + BUILD_DIR: "${{ github.workspace }}/_build" MCM_BINARIES_DIR: './mcm-binaries' MEDIA_PROXY: './mcm-binaries/media_proxy' MESH_AGENT: './mcm-binaries/mesh-agent' - MCM_FFMPEG_6: './mcm-binaries/ffmpeg-6-1/ffmpeg' - MCM_FFMPEG_7: './mcm-binaries/ffmpeg-7-0/ffmpeg' - MTL__FFMPEG_6: './mtl-binaries/ffmpeg-6-1/ffmpeg' - MTL__FFMPEG_7: './mtl-binaries/ffmpeg-7-0/ffmpeg' + MCM_FFMPEG_7_0: ./mcm-binaries/ffmpeg-7-0/ffmpeg + MTL_FFMPEG_7_0: ./mtl-binaries/ffmpeg-7-0/ffmpeg + MCM_FFMPEG_6_1: ./mcm-binaries/ffmpeg-6-1/ffmpeg + MTL_FFMPEG_6_1: ./mtl-binaries/ffmpeg-6-1/ffmpeg permissions: contents: read jobs: - mtl-ffmpeg-build: - runs-on: [Linux, self-hosted, DPDK] - timeout-minutes: 60 - steps: - - name: 'preparation: Harden Runner' - uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2 - with: - egress-policy: audit - # download mtl ffmpeg binaries or build locally - validation-build-mcm: + validation-prepare-setup-mcm: runs-on: [Linux, self-hosted, DPDK] timeout-minutes: 60 outputs: @@ -56,7 +49,6 @@ jobs: uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2 with: egress-policy: audit - - name: 'preparation: Restore valid repository owner and print env' if: always() run: | @@ -67,16 +59,53 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: 'smoke-tests' - # - name: 'Download build artifacts' - # uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe # v4.1.2 - # with: - # name: mcm-build - # path: ./mcm-binaries - # - name: 'Make binaries executable' - # run: | - # chmod +x ./mcm-binaries/media_proxy - # chmod +x ./mcm-binaries/mesh-agent - # chmod +x ./mcm-binaries/ffmpeg* + - name: 'install dependencies' + run: | + eval 'source scripts/setup_build_env.sh' + - name: 'Check local dependencies build cache' + id: load-local-dependencies-cache + uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ env.BUILD_DIR }} + key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} + - name: 'Download, unpack and patch build dependencies' + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' + + - name: 'Clone and patch ffmpeg 6.1 and 7.0' + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: | + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" + - name: 'Build MCM SDK and Media Proxy' + run: | + ./build.sh + - name: 'Build FFmpeg 6.1 with MCM plugin' + working-directory: ${{ github.workspace }}/ffmpeg-plugin + run: | + ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ + ./build-ffmpeg.sh "6.1" + + - name: 'Build FFmpeg 7.0 with MCM plugin' + working-directory: ${{ github.workspace }}/ffmpeg-plugin + run: | + ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ + ./build-ffmpeg.sh "7.0" + - name: 'build RxTxApp' + working-directory: ${{ github.workspace }}/tests/tools/TestApp + run: | + mkdir build && cd build && \ + cmake .. && \ + make + - name: 'clone FFMPEG repository' + run: | + echo "Cloning FFMPEG repository" + - name: 'clone MTL repository' + run: | + echo "Cloning MTL repository" + - name: 'build MTL FFMPEG' + run: | + echo "Building MTL FFMPEG" - name: 'installation: Install pipenv environment' working-directory: tests/validation id: pipenv-install @@ -91,7 +120,7 @@ jobs: sed -i "s/{{ USER }}/${USER}/g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml validation-run-tests: - needs: validation-build-mcm + needs: validation-prepare-setup-mcm runs-on: [Linux, self-hosted, DPDK] timeout-minutes: 60 env: From 8824e6f4c5d52ef0e34c0bcc8cbb4db15017e86a Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 30 Jul 2025 15:11:47 +0000 Subject: [PATCH 018/123] correct build --- .github/workflows/smoke_tests.yml | 33 ++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 9e9f887bd..90d2b446f 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -59,15 +59,16 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: 'smoke-tests' - - name: 'install dependencies' - run: | - eval 'source scripts/setup_build_env.sh' + - name: 'Install OS level dependencies' + run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' + - name: 'Check local dependencies build cache' id: load-local-dependencies-cache uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ${{ env.BUILD_DIR }} key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} + - name: 'Download, unpack and patch build dependencies' if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' @@ -77,9 +78,31 @@ jobs: run: | ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" + + - name: 'Build and Install xdp and libbpf' + run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' + + - name: 'Build and Install libfabric' + run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' + + - name: 'Build and Install the DPDK' + run: eval 'source scripts/setup_build_env.sh && lib_install_dpdk' + + - name: 'Build and Install the MTL' + run: eval 'source scripts/setup_build_env.sh && lib_install_mtl' + + - name: 'Build and Install JPEG XS' + run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' + + - name: 'Build and Install JPEG XS ffmpeg plugin' + run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' + + - name: 'Build gRPC and dependencies' + run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' + - name: 'Build MCM SDK and Media Proxy' - run: | - ./build.sh + run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' + - name: 'Build FFmpeg 6.1 with MCM plugin' working-directory: ${{ github.workspace }}/ffmpeg-plugin run: | From ba96639bc6cdb75e22e1a812a511d7b5114e87c4 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 30 Jul 2025 15:19:40 +0000 Subject: [PATCH 019/123] use base_build.yml --- .github/workflows/smoke_tests.yml | 61 +++---------------------------- 1 file changed, 6 insertions(+), 55 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 90d2b446f..fc29db0c6 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -39,8 +39,13 @@ env: permissions: contents: read jobs: + call-base-build: + uses: ./.github/workflows/base_build.yml + with: + branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} validation-prepare-setup-mcm: runs-on: [Linux, self-hosted, DPDK] + needs: call-base-build timeout-minutes: 60 outputs: pipenv-activate: ${{ steps.pipenv-install.outputs.VIRTUAL_ENV }} @@ -59,61 +64,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: 'smoke-tests' - - name: 'Install OS level dependencies' - run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' - - - name: 'Check local dependencies build cache' - id: load-local-dependencies-cache - uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - with: - path: ${{ env.BUILD_DIR }} - key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} - - - name: 'Download, unpack and patch build dependencies' - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' - - - name: 'Clone and patch ffmpeg 6.1 and 7.0' - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: | - ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" - ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" - - - name: 'Build and Install xdp and libbpf' - run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' - - - name: 'Build and Install libfabric' - run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' - - - name: 'Build and Install the DPDK' - run: eval 'source scripts/setup_build_env.sh && lib_install_dpdk' - - - name: 'Build and Install the MTL' - run: eval 'source scripts/setup_build_env.sh && lib_install_mtl' - - - name: 'Build and Install JPEG XS' - run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' - - - name: 'Build and Install JPEG XS ffmpeg plugin' - run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' - - - name: 'Build gRPC and dependencies' - run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' - - - name: 'Build MCM SDK and Media Proxy' - run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' - - - name: 'Build FFmpeg 6.1 with MCM plugin' - working-directory: ${{ github.workspace }}/ffmpeg-plugin - run: | - ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ - ./build-ffmpeg.sh "6.1" - - - name: 'Build FFmpeg 7.0 with MCM plugin' - working-directory: ${{ github.workspace }}/ffmpeg-plugin - run: | - ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ - ./build-ffmpeg.sh "7.0" + # Build steps are now handled by base_build.yml - name: 'build RxTxApp' working-directory: ${{ github.workspace }}/tests/tools/TestApp run: | From e0525bcbfe1fd304577985f29fb09bdcb1d2d4a3 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 31 Jul 2025 08:06:18 +0000 Subject: [PATCH 020/123] workflow_call test --- .github/workflows/base_build.yml | 19 ++++++++++++++----- .github/workflows/smoke_tests.yml | 3 +-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index 6abb7ee03..ce5e7eb9c 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -1,11 +1,18 @@ name: Base Build on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - workflow_dispatch: + # push: + # branches: [ "main" ] + # pull_request: + # branches: [ "main" ] + # workflow_dispatch: + workflow_call: + inputs: + branch: + required: false + type: string + default: 'main' + description: 'Branch to checkout' env: BUILD_TYPE: Release @@ -37,6 +44,8 @@ jobs: - name: 'Checkout repository' uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ inputs.branch }} - name: 'Install OS level dependencies' run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index fc29db0c6..d1d892585 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -63,8 +63,7 @@ jobs: - name: 'preparation: Checkout MCM' uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: 'smoke-tests' - # Build steps are now handled by base_build.yml + ref: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} - name: 'build RxTxApp' working-directory: ${{ github.workspace }}/tests/tools/TestApp run: | From 9a0298584cc532cf1c0bb6be249dec4af43bd57e Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 31 Jul 2025 08:57:05 +0000 Subject: [PATCH 021/123] add RxTxApp dependencies --- .github/workflows/base_build.yml | 10 +++++----- .github/workflows/smoke_tests.yml | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index ce5e7eb9c..f7554203c 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -1,11 +1,11 @@ name: Base Build on: - # push: - # branches: [ "main" ] - # pull_request: - # branches: [ "main" ] - # workflow_dispatch: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + workflow_dispatch: workflow_call: inputs: branch: diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index d1d892585..74d6e0af7 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -64,6 +64,8 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} + - name: Install RxTxApp dependencies + run: sudo apt-get update && sudo apt-get install -y libjansson-dev - name: 'build RxTxApp' working-directory: ${{ github.workspace }}/tests/tools/TestApp run: | From 2261418c417874f9d1cc3e4cc4c4c59131f3194a Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 31 Jul 2025 10:03:32 +0000 Subject: [PATCH 022/123] add rxtxapp build dir --- .github/workflows/smoke_tests.yml | 1 + tests/validation/configs/topology_config_workflow.yaml | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 74d6e0af7..4b6a5bcd9 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -94,6 +94,7 @@ jobs: echo "USER=${USER}" >> "$GITHUB_ENV" sed -i "s/{{ USER }}/${USER}/g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ MCM_PATH }}|${{ github.workspace }}|g" tests/validation/configs/topology_config_workflow.yaml validation-run-tests: needs: validation-prepare-setup-mcm runs-on: [Linux, self-hosted, DPDK] diff --git a/tests/validation/configs/topology_config_workflow.yaml b/tests/validation/configs/topology_config_workflow.yaml index 3f90424a8..6ef04be0c 100644 --- a/tests/validation/configs/topology_config_workflow.yaml +++ b/tests/validation/configs/topology_config_workflow.yaml @@ -17,6 +17,13 @@ hosts: password: None key_path: {{ KEY_PATH }} extra_info: + mcm_path: {{ MCM_PATH }} + # input_path: /opt/intel/integrity/ + ffmpeg_path: ffmpeg + # output_path: /home/gta/received/ + # integrity_path: /opt/intel/val_tests/validation/common/integrity + # mtl_path: /opt/intel/mtl + # nicctl_path: /opt/intel/mtl/script mesh_agent: control_port: 8100 proxy_port: 50051 From 191abe9c81700050cc8c9cc3807dcd6fbc8f08d9 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 4 Aug 2025 13:25:03 +0000 Subject: [PATCH 023/123] linter fix --- .actionlint.yaml | 2 ++ tests/validation/conftest.py | 12 +++++++++--- .../validation/functional/local/audio/test_audio.py | 11 +++++++---- tests/validation/functional/local/blob/test_blob.py | 7 +++---- .../functional/local/video/test_ffmpeg_video.py | 4 ++-- .../validation/functional/local/video/test_video.py | 7 +++---- 6 files changed, 26 insertions(+), 17 deletions(-) create mode 100644 .actionlint.yaml diff --git a/.actionlint.yaml b/.actionlint.yaml new file mode 100644 index 000000000..a26f4ca40 --- /dev/null +++ b/.actionlint.yaml @@ -0,0 +1,2 @@ +runner-label: + - DPDK # Custom label for DPDK-enabled runners diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index a393dc462..26ca6e9eb 100644 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -274,11 +274,17 @@ def cleanup_processes(hosts: dict) -> None: logger.warning(f"Failed to check/kill {proc} on {host.name}: {e}") for pattern in ["^Rx[A-Za-z]+App$", "^Tx[A-Za-z]+App$"]: try: - connection.execute_command(f"pgrep -f '{pattern}'", stderr_to_stdout=True) - connection.execute_command(f"pkill -9 -f '{pattern}'", stderr_to_stdout=True) + connection.execute_command( + f"pgrep -f '{pattern}'", stderr_to_stdout=True + ) + connection.execute_command( + f"pkill -9 -f '{pattern}'", stderr_to_stdout=True + ) except Exception as e: if not (hasattr(e, "returncode") and e.returncode == 1): - logger.warning(f"Failed to check/kill processes matching {pattern} on {host.name}: {e}") + logger.warning( + f"Failed to check/kill processes matching {pattern} on {host.name}: {e}" + ) logger.info("Cleanup of processes completed.") diff --git a/tests/validation/functional/local/audio/test_audio.py b/tests/validation/functional/local/audio/test_audio.py index 84892e242..de4512564 100644 --- a/tests/validation/functional/local/audio/test_audio.py +++ b/tests/validation/functional/local/audio/test_audio.py @@ -9,20 +9,24 @@ import Engine.rx_tx_app_connection import Engine.rx_tx_app_engine_mcm as utils import Engine.rx_tx_app_payload -from Engine.const import DEFAULT_LOOP_COUNT, MCM_ESTABLISH_TIMEOUT, MCM_RXTXAPP_RUN_TIMEOUT +from Engine.const import ( + DEFAULT_LOOP_COUNT, + MCM_ESTABLISH_TIMEOUT, + MCM_RXTXAPP_RUN_TIMEOUT, +) from Engine.media_files import audio_files logger = logging.getLogger(__name__) @pytest.mark.parametrize( - "file", + "file", [ pytest.param("PCM16_48000_Mono", marks=pytest.mark.smoke), "PCM16_48000_Stereo", "PCM24_48000_Mono", "PCM24_48000_Stereo", - ] + ], ) def test_audio(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: @@ -71,4 +75,3 @@ def test_audio(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> assert tx_executor.is_pass is True, "TX process did not pass" assert rx_executor.is_pass is True, "RX process did not pass" - diff --git a/tests/validation/functional/local/blob/test_blob.py b/tests/validation/functional/local/blob/test_blob.py index 868c160c4..e45783ab6 100644 --- a/tests/validation/functional/local/blob/test_blob.py +++ b/tests/validation/functional/local/blob/test_blob.py @@ -17,10 +17,10 @@ @pytest.mark.parametrize( - "file", + "file", [ pytest.param("random_bin_100M", marks=pytest.mark.smoke), - ] + ], ) def test_blob(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: @@ -64,9 +64,8 @@ def test_blob(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> rx_executor.stop() # TODO add validate() function to check if the output file is correct - + rx_executor.cleanup() assert tx_executor.is_pass is True, "TX process did not pass" assert rx_executor.is_pass is True, "RX process did not pass" - diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index ca672cb9b..3a2de6b3a 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -24,7 +24,7 @@ @pytest.mark.parametrize( - "video_type", + "video_type", [ "i720p25", "i720p30", @@ -32,7 +32,7 @@ "i1080p50", "i1080p60", "i2160p30", - ] + ], ) def test_local_ffmpeg_video(media_proxy, hosts, test_config, video_type: str) -> None: # media_proxy fixture used only to ensure that the media proxy is running diff --git a/tests/validation/functional/local/video/test_video.py b/tests/validation/functional/local/video/test_video.py index 9cbc71f14..4a4208373 100644 --- a/tests/validation/functional/local/video/test_video.py +++ b/tests/validation/functional/local/video/test_video.py @@ -20,14 +20,14 @@ "file", [ "i720p25", - "i720p30", + "i720p30", pytest.param("i1080p25", marks=pytest.mark.smoke), pytest.param("i1080p30", marks=pytest.mark.smoke), "i1080p50", - "i1080p60", + "i1080p60", "i2160p25", "i2160p30", - ] + ], ) def test_video(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: @@ -76,4 +76,3 @@ def test_video(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> assert tx_executor.is_pass is True, "TX process did not pass" assert rx_executor.is_pass is True, "RX process did not pass" - From 8f02168af486c8c7792d85652dc0d72372b5bb45 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 4 Aug 2025 13:51:06 +0000 Subject: [PATCH 024/123] DPDK fix --- .actionlint.yaml | 2 -- .github/workflows/smoke_tests.yml | 15 +++++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) delete mode 100644 .actionlint.yaml diff --git a/.actionlint.yaml b/.actionlint.yaml deleted file mode 100644 index a26f4ca40..000000000 --- a/.actionlint.yaml +++ /dev/null @@ -1,2 +0,0 @@ -runner-label: - - DPDK # Custom label for DPDK-enabled runners diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 4b6a5bcd9..6211f6e20 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -2,8 +2,9 @@ name: smoke-tests-bare-metal on: push: - branches: - - 'smoke-tests' + branches: ["main", "smoke-tests"] + pull_request: + branches: [ "main" ] workflow_dispatch: inputs: branch-to-checkout: @@ -36,15 +37,21 @@ env: MTL_FFMPEG_7_0: ./mtl-binaries/ffmpeg-7-0/ffmpeg MCM_FFMPEG_6_1: ./mcm-binaries/ffmpeg-6-1/ffmpeg MTL_FFMPEG_6_1: ./mtl-binaries/ffmpeg-6-1/ffmpeg + permissions: contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + jobs: call-base-build: uses: ./.github/workflows/base_build.yml with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} validation-prepare-setup-mcm: - runs-on: [Linux, self-hosted, DPDK] + runs-on: [Linux, self-hosted] needs: call-base-build timeout-minutes: 60 outputs: @@ -97,7 +104,7 @@ jobs: sed -i "s|{{ MCM_PATH }}|${{ github.workspace }}|g" tests/validation/configs/topology_config_workflow.yaml validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: [Linux, self-hosted, DPDK] + runs-on: [Linux, self-hosted] timeout-minutes: 60 env: PYTEST_RETRIES: '3' From b06df5550bba6a3c0ce4590d2d4aea43558afcf6 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 4 Aug 2025 14:13:31 +0000 Subject: [PATCH 025/123] prettier fix --- .github/workflows/base_build.yml | 162 +++++++++++++++--------------- .github/workflows/smoke_tests.yml | 63 ++++++------ 2 files changed, 114 insertions(+), 111 deletions(-) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index f7554203c..ad81c2cff 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -2,17 +2,17 @@ name: Base Build on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] workflow_dispatch: workflow_call: inputs: branch: required: false type: string - default: 'main' - description: 'Branch to checkout' + default: "main" + description: "Branch to checkout" env: BUILD_TYPE: Release @@ -34,82 +34,82 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: 'ubuntu-22.04' + runs-on: "ubuntu-22.04" timeout-minutes: 120 steps: - - name: 'Harden Runner' - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 - with: - egress-policy: audit - - - name: 'Checkout repository' - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - ref: ${{ inputs.branch }} - - - name: 'Install OS level dependencies' - run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' - - - name: 'Check local dependencies build cache' - id: load-local-dependencies-cache - uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - with: - path: ${{ env.BUILD_DIR }} - key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} - - - name: 'Download, unpack and patch build dependencies' - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' - - - name: 'Clone and patch ffmpeg 6.1 and 7.0' - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: | - ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" - ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" - - - name: 'Build and Install xdp and libbpf' - run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' - - - name: 'Build and Install libfabric' - run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' - - - name: 'Build and Install the DPDK' - run: eval 'source scripts/setup_build_env.sh && lib_install_dpdk' - - - name: 'Build and Install the MTL' - run: eval 'source scripts/setup_build_env.sh && lib_install_mtl' - - - name: 'Build and Install JPEG XS' - run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' - - - name: 'Build and Install JPEG XS ffmpeg plugin' - run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' - - - name: 'Build gRPC and dependencies' - run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' - - - name: 'Build MCM SDK and Media Proxy' - run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' - - - name: 'Build FFmpeg 6.1 with MCM plugin' - working-directory: ${{ github.workspace }}/ffmpeg-plugin - run: | - ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ - ./build-ffmpeg.sh "6.1" - - - name: 'Build FFmpeg 7.0 with MCM plugin' - working-directory: ${{ github.workspace }}/ffmpeg-plugin - run: | - ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ - ./build-ffmpeg.sh "7.0" - - - name: 'upload media-proxy and mcm binaries' - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: mcm-build - path: | - ${{ env.BUILD_DIR }}/mcm/bin/media_proxy - ${{ env.BUILD_DIR }}/mcm/bin/mesh-agent - ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* - ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg - ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg + - name: "Harden Runner" + uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + with: + egress-policy: audit + + - name: "Checkout repository" + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ inputs.branch }} + + - name: "Install OS level dependencies" + run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' + + - name: "Check local dependencies build cache" + id: load-local-dependencies-cache + uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ env.BUILD_DIR }} + key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} + + - name: "Download, unpack and patch build dependencies" + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' + + - name: "Clone and patch ffmpeg 6.1 and 7.0" + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: | + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" + + - name: "Build and Install xdp and libbpf" + run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' + + - name: "Build and Install libfabric" + run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' + + - name: "Build and Install the DPDK" + run: eval 'source scripts/setup_build_env.sh && lib_install_dpdk' + + - name: "Build and Install the MTL" + run: eval 'source scripts/setup_build_env.sh && lib_install_mtl' + + - name: "Build and Install JPEG XS" + run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' + + - name: "Build and Install JPEG XS ffmpeg plugin" + run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' + + - name: "Build gRPC and dependencies" + run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' + + - name: "Build MCM SDK and Media Proxy" + run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' + + - name: "Build FFmpeg 6.1 with MCM plugin" + working-directory: ${{ github.workspace }}/ffmpeg-plugin + run: | + ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ + ./build-ffmpeg.sh "6.1" + + - name: "Build FFmpeg 7.0 with MCM plugin" + working-directory: ${{ github.workspace }}/ffmpeg-plugin + run: | + ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ + ./build-ffmpeg.sh "7.0" + + - name: "upload media-proxy and mcm binaries" + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: mcm-build + path: | + ${{ env.BUILD_DIR }}/mcm/bin/media_proxy + ${{ env.BUILD_DIR }}/mcm/bin/mesh-agent + ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* + ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg + ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 6211f6e20..f09d04413 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -4,35 +4,35 @@ on: push: branches: ["main", "smoke-tests"] pull_request: - branches: [ "main" ] + branches: ["main"] workflow_dispatch: inputs: branch-to-checkout: type: string - default: 'main' + default: "main" required: false - description: 'Branch name to use' + description: "Branch name to use" list_tests: type: choice required: false - description: 'List all tests before running' + description: "List all tests before running" options: - "true" - "false" markers: type: string - default: 'smoke' + default: "smoke" required: false - description: 'Markers to use for pytest' + description: "Markers to use for pytest" env: - BUILD_TYPE: 'Release' - DPDK_VERSION: '23.11' - DPDK_REBUILD: 'false' - MEDIA_PATH: '/mnt/media' + BUILD_TYPE: "Release" + DPDK_VERSION: "23.11" + DPDK_REBUILD: "false" + MEDIA_PATH: "/mnt/media" BUILD_DIR: "${{ github.workspace }}/_build" - MCM_BINARIES_DIR: './mcm-binaries' - MEDIA_PROXY: './mcm-binaries/media_proxy' - MESH_AGENT: './mcm-binaries/mesh-agent' + MCM_BINARIES_DIR: "./mcm-binaries" + MEDIA_PROXY: "./mcm-binaries/media_proxy" + MESH_AGENT: "./mcm-binaries/mesh-agent" MCM_FFMPEG_7_0: ./mcm-binaries/ffmpeg-7-0/ffmpeg MTL_FFMPEG_7_0: ./mtl-binaries/ffmpeg-7-0/ffmpeg MCM_FFMPEG_6_1: ./mcm-binaries/ffmpeg-6-1/ffmpeg @@ -41,54 +41,57 @@ env: permissions: contents: read -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - jobs: call-base-build: uses: ./.github/workflows/base_build.yml with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} + concurrency: + group: build-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + validation-prepare-setup-mcm: runs-on: [Linux, self-hosted] needs: call-base-build timeout-minutes: 60 outputs: pipenv-activate: ${{ steps.pipenv-install.outputs.VIRTUAL_ENV }} + concurrency: + group: setup-${{ github.head_ref || github.run_id }} + cancel-in-progress: true steps: - - name: 'preparation: Harden Runner' + - name: "preparation: Harden Runner" uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2 with: egress-policy: audit - - name: 'preparation: Restore valid repository owner and print env' + - name: "preparation: Restore valid repository owner and print env" if: always() run: | sudo chown -R "${USER}" "$(pwd)" || true env | grep BUILD_ || true env | grep DPDK_ || true - - name: 'preparation: Checkout MCM' + - name: "preparation: Checkout MCM" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} - name: Install RxTxApp dependencies run: sudo apt-get update && sudo apt-get install -y libjansson-dev - - name: 'build RxTxApp' + - name: "build RxTxApp" working-directory: ${{ github.workspace }}/tests/tools/TestApp run: | mkdir build && cd build && \ cmake .. && \ make - - name: 'clone FFMPEG repository' + - name: "clone FFMPEG repository" run: | echo "Cloning FFMPEG repository" - - name: 'clone MTL repository' + - name: "clone MTL repository" run: | echo "Cloning MTL repository" - - name: 'build MTL FFMPEG' + - name: "build MTL FFMPEG" run: | echo "Building MTL FFMPEG" - - name: 'installation: Install pipenv environment' + - name: "installation: Install pipenv environment" working-directory: tests/validation id: pipenv-install run: | @@ -96,7 +99,7 @@ jobs: source venv/bin/activate pip install -r requirements.txt echo "VIRTUAL_ENV=$PWD/venv/bin/activate" >> "$GITHUB_ENV" - - name: 'add user name to environment and config' + - name: "add user name to environment and config" run: | echo "USER=${USER}" >> "$GITHUB_ENV" sed -i "s/{{ USER }}/${USER}/g" tests/validation/configs/topology_config_workflow.yaml @@ -107,21 +110,21 @@ jobs: runs-on: [Linux, self-hosted] timeout-minutes: 60 env: - PYTEST_RETRIES: '3' + PYTEST_RETRIES: "3" MARKERS: ${{ github.event.inputs.markers || 'smoke' }} LIST_TESTS: ${{ github.event.inputs.list_tests || 'true' }} steps: - - name: 'preparation: Kill pytest routines' + - name: "preparation: Kill pytest routines" run: | sudo killall -SIGINT pipenv || true sudo killall -SIGINT pytest || true - - name: 'list all tests marked with ${{ env.MARKERS }}' + - name: "list all tests marked with ${{ env.MARKERS }}" if: ${{ env.LIST_TESTS == 'true' }} run: | sudo tests/validation/venv/bin/python3 -m pytest \ --collect-only --quiet ./tests/validation/functional/ \ -m "${{ env.MARKERS }}" - - name: 'execution: Run validation-bare-metal tests in virtual environment' + - name: "execution: Run validation-bare-metal tests in virtual environment" run: | sudo tests/validation/venv/bin/python3 -m pytest \ --topology_config=tests/validation/configs/topology_config_workflow.yaml \ From cfa2f81bc1f6906a0c80361e0dbcd329ed97201a Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 5 Aug 2025 10:46:41 +0000 Subject: [PATCH 026/123] update smoke test list --- .github/workflows/smoke_tests.yml | 38 ++++++++++++++ .../configs/test_config_workflow.yaml | 20 +++++++- tests/validation/conftest.py | 11 ++++ .../functional/local/audio/test_audio.py | 10 +--- .../local/audio/test_audio_25_03.py | 14 ++++-- .../local/audio/test_ffmpeg_audio.py | 50 ++++++++++++------- .../functional/local/blob/test_blob_25_03.py | 5 +- .../local/video/test_ffmpeg_video.py | 30 ++++++----- .../functional/local/video/test_video.py | 16 +----- .../local/video/test_video_25_03.py | 14 ++++-- 10 files changed, 144 insertions(+), 64 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index f09d04413..d56eb61f7 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -37,6 +37,7 @@ env: MTL_FFMPEG_7_0: ./mtl-binaries/ffmpeg-7-0/ffmpeg MCM_FFMPEG_6_1: ./mcm-binaries/ffmpeg-6-1/ffmpeg MTL_FFMPEG_6_1: ./mtl-binaries/ffmpeg-6-1/ffmpeg + LOG_DIR: "${{ github.workspace }}/logs" permissions: contents: read @@ -105,6 +106,7 @@ jobs: sed -i "s/{{ USER }}/${USER}/g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MCM_PATH }}|${{ github.workspace }}|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ LOG_PATH }}|${{ env.LOG_DIR }}|g" tests/validation/configs/test_config_workflow.yaml validation-run-tests: needs: validation-prepare-setup-mcm runs-on: [Linux, self-hosted] @@ -132,3 +134,39 @@ jobs: ./tests/validation/functional/ \ --template=html/index.html --report=report.html \ -m "${{ env.MARKERS }}" + - name: "upload logs and reports" + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: smoke-tests-logs + path: | + report.html + ${{ env.LOG_DIR }}/* + - name: "Add report to summary" + if: always() + run: | + echo "## Smoke Tests Report" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Check if report exists + if [ -f "report.html" ]; then + # Extract relevant parts from the HTML report + echo "### Test Results Summary" >> $GITHUB_STEP_SUMMARY + + # Extract pass/fail counts (adjust the grep patterns based on your HTML structure) + PASSED=$(grep -o "passed=[0-9]*" report.html | cut -d= -f2) + FAILED=$(grep -o "failed=[0-9]*" report.html | cut -d= -f2) + SKIPPED=$(grep -o "skipped=[0-9]*" report.html | cut -d= -f2) + + # Add summary stats + echo "| Status | Count |" >> $GITHUB_STEP_SUMMARY + echo "| ------ | ----- |" >> $GITHUB_STEP_SUMMARY + echo "| ✅ Passed | ${PASSED:-0} |" >> $GITHUB_STEP_SUMMARY + echo "| ❌ Failed | ${FAILED:-0} |" >> $GITHUB_STEP_SUMMARY + echo "| ⏭️ Skipped | ${SKIPPED:-0} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Add link to full report artifact + echo "📄 [View Full HTML Report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY + else + echo "❌ No report.html file was generated" >> $GITHUB_STEP_SUMMARY + fi diff --git a/tests/validation/configs/test_config_workflow.yaml b/tests/validation/configs/test_config_workflow.yaml index 19839d6c6..979991fb2 100644 --- a/tests/validation/configs/test_config_workflow.yaml +++ b/tests/validation/configs/test_config_workflow.yaml @@ -1,2 +1,20 @@ +tx: + filepath: /mnt/media/ + p_sip: "192.168.39.39" + ffmpeg_path: ffmpeg + prefix_variables: + NO_PROXY: 127.0.0.1,localhost + no_proxy: 127.0.0.1,localhost +rx: + filepath: /dev/null/ + ffmpeg_path: ffmpeg + prefix_variables: + NO_PROXY: 127.0.0.1,localhost + no_proxy: 127.0.0.1,localhost + +# other +broadcast_ip: '239.2.39.239' +port: 20000 payload_type: 100 -test_time_sec: 90 \ No newline at end of file +test_time_sec: 90 +log_path: {{ LOG_PATH }} \ No newline at end of file diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index 26ca6e9eb..3b8ef23a2 100644 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -153,6 +153,17 @@ def media_path(test_config: dict) -> str: @pytest.fixture(scope="session") def log_path_dir(test_config: dict) -> str: + """ + Creates and returns the main log directory path for the test session. + + The directory is created under either the path specified in test_config['log_path'] + or under the default LOG_FOLDER in the validation directory. If keep_logs is False, + the existing log directory is removed before creating a new one. + + :param test_config: Dictionary containing test configuration. + :return: Path to the main log directory for the session. + :rtype: str + """ keep_logs = test_config.get("keep_logs", True) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") log_dir_name = f"log_{timestamp}" diff --git a/tests/validation/functional/local/audio/test_audio.py b/tests/validation/functional/local/audio/test_audio.py index de4512564..f6edbe4ec 100644 --- a/tests/validation/functional/local/audio/test_audio.py +++ b/tests/validation/functional/local/audio/test_audio.py @@ -19,15 +19,7 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize( - "file", - [ - pytest.param("PCM16_48000_Mono", marks=pytest.mark.smoke), - "PCM16_48000_Stereo", - "PCM24_48000_Mono", - "PCM24_48000_Stereo", - ], -) +@pytest.mark.parametrize("file", audio_files.keys()) def test_audio(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: # Get TX and RX hosts diff --git a/tests/validation/functional/local/audio/test_audio_25_03.py b/tests/validation/functional/local/audio/test_audio_25_03.py index 60034d568..fe3ca9991 100644 --- a/tests/validation/functional/local/audio/test_audio_25_03.py +++ b/tests/validation/functional/local/audio/test_audio_25_03.py @@ -16,8 +16,13 @@ ) from Engine.media_files import audio_files_25_03 - -@pytest.mark.parametrize("file", audio_files_25_03.keys()) +@pytest.mark.parametrize( + "file", + [ + pytest.param("PCM16_48000_Mono", marks=pytest.mark.smoke), + *[f for f in audio_files_25_03.keys() if f != "PCM16_48000_Mono"] + ] +) def test_audio_25_03( build_TestApp, hosts, media_proxy, media_path, file, log_path ) -> None: @@ -61,7 +66,10 @@ def test_audio_25_03( tx_executor.stop() rx_executor.stop() + # TODO add validate() function to check if the output file is correct + + rx_executor.cleanup() + assert tx_executor.is_pass is True, "TX process did not pass" assert rx_executor.is_pass is True, "RX process did not pass" - # TODO add validate() function to check if the output file is correct diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index f458e003e..f4e00bfa7 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -7,7 +7,7 @@ import pytest import logging -from ....Engine.media_files import audio_files +from Engine.media_files import audio_files_25_03 from common.ffmpeg_handler.ffmpeg import FFmpeg, FFmpegExecutor from common.ffmpeg_handler.ffmpeg_enums import ( @@ -17,12 +17,20 @@ from common.ffmpeg_handler.ffmpeg_io import FFmpegAudioIO from common.ffmpeg_handler.mcm_ffmpeg import FFmpegMcmMemifAudioIO +from Engine.rx_tx_app_file_validation_utils import cleanup_file logger = logging.getLogger(__name__) -@pytest.mark.parametrize("audio_type", [k for k in audio_files.keys()]) +# @pytest.mark.parametrize("audio_type", [k for k in audio_files_25_03.keys()]) +@pytest.mark.parametrize( + "audio_type", + [ + pytest.param("PCM16_48000_Stereo", marks=pytest.mark.smoke), + *[f for f in audio_files_25_03.keys() if f != "PCM16_48000_Stereo"] + ] +) def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts @@ -40,29 +48,29 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str) -> ) audio_format = audio_file_format_to_format_dict( - str(audio_files[audio_type]["format"]) + str(audio_files_25_03[audio_type]["format"]) ) # audio format - audio_channel_layout = audio_files[audio_type].get( + audio_channel_layout = audio_files_25_03[audio_type].get( "channel_layout", - audio_channel_number_to_layout(int(audio_files[audio_type]["channels"])), + audio_channel_number_to_layout(int(audio_files_25_03[audio_type]["channels"])), ) - if audio_files[audio_type]["sample_rate"] not in [48000, 44100, 96000]: + if audio_files_25_03[audio_type]["sample_rate"] not in [48000, 44100, 96000]: raise Exception( - f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" + f"Not expected audio sample rate of {audio_files_25_03[audio_type]['sample_rate']}!" ) # >>>>> MCM Tx mcm_tx_inp = FFmpegAudioIO( f=audio_format["ffmpeg_f"], - ac=int(audio_files[audio_type]["channels"]), - ar=int(audio_files[audio_type]["sample_rate"]), + ac=int(audio_files_25_03[audio_type]["channels"]), + ar=int(audio_files_25_03[audio_type]["sample_rate"]), stream_loop=False, - input_path=f'{test_config["tx"]["filepath"]}{audio_files[audio_type]["filename"]}', + input_path=f'{test_config["tx"]["filepath"]}{audio_files_25_03[audio_type]["filename"]}', ) mcm_tx_outp = FFmpegMcmMemifAudioIO( - channels=int(audio_files[audio_type]["channels"]), - sample_rate=int(audio_files[audio_type]["sample_rate"]), + channels=int(audio_files_25_03[audio_type]["channels"]), + sample_rate=int(audio_files_25_03[audio_type]["sample_rate"]), f=audio_format["mcm_f"], output_path="-", ) @@ -79,17 +87,17 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str) -> # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMemifAudioIO( - channels=int(audio_files[audio_type]["channels"]), - sample_rate=int(audio_files[audio_type]["sample_rate"]), + channels=int(audio_files_25_03[audio_type]["channels"]), + sample_rate=int(audio_files_25_03[audio_type]["sample_rate"]), f=audio_format["mcm_f"], input_path="-", ) mcm_rx_outp = FFmpegAudioIO( f=audio_format["ffmpeg_f"], - ac=int(audio_files[audio_type]["channels"]), - ar=int(audio_files[audio_type]["sample_rate"]), + ac=int(audio_files_25_03[audio_type]["channels"]), + ar=int(audio_files_25_03[audio_type]["sample_rate"]), channel_layout=audio_channel_layout, - output_path=f'{test_config["rx"]["filepath"]}test_{audio_files[audio_type]["filename"]}', + output_path=f'{test_config["rx"]["filepath"]}test_{audio_files_25_03[audio_type]["filename"]}', ) mcm_rx_ff = FFmpeg( prefix_variables=rx_prefix_variables, @@ -106,3 +114,11 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str) -> mcm_tx_executor.start() mcm_rx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) mcm_tx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) + + # TODO add validate() function to check if the output file is correct + + success = cleanup_file(rx_host.connection, str(mcm_rx_outp.output_path)) + if success: + logger.debug(f"Cleaned up Rx output file: {mcm_rx_outp.output_path}") + else: + logger.warning(f"Failed to clean up Rx output file: {mcm_rx_outp.output_path}") diff --git a/tests/validation/functional/local/blob/test_blob_25_03.py b/tests/validation/functional/local/blob/test_blob_25_03.py index c64d0dd50..f321bc096 100644 --- a/tests/validation/functional/local/blob/test_blob_25_03.py +++ b/tests/validation/functional/local/blob/test_blob_25_03.py @@ -62,7 +62,10 @@ def test_blob_25_03( tx_executor.stop() rx_executor.stop() + # TODO add validate() function to check if the output file is correct + + rx_executor.cleanup() + assert tx_executor.is_pass is True, "TX process did not pass" assert rx_executor.is_pass is True, "RX process did not pass" - # TODO add validate() function to check if the output file is correct diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index 3a2de6b3a..f866009b8 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -7,9 +7,9 @@ import pytest import logging -from tests.validation.Engine.rx_tx_app_file_validation_utils import cleanup_file +from Engine.rx_tx_app_file_validation_utils import cleanup_file -from ....Engine.media_files import yuv_files +from Engine.media_files import video_files_25_03 from common.ffmpeg_handler.ffmpeg import FFmpeg, FFmpegExecutor from common.ffmpeg_handler.ffmpeg_enums import ( @@ -24,17 +24,13 @@ @pytest.mark.parametrize( - "video_type", + "file", [ - "i720p25", - "i720p30", - pytest.param("i1080p25", marks=pytest.mark.smoke), - "i1080p50", - "i1080p60", - "i2160p30", - ], + pytest.param("FullHD_59.94", marks=pytest.mark.smoke), + *[f for f in video_files_25_03.keys() if f != "FullHD_59.94"] + ] ) -def test_local_ffmpeg_video(media_proxy, hosts, test_config, video_type: str) -> None: +def test_local_ffmpeg_video(media_proxy, hosts, test_config, file: str) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts host_list = list(hosts.values()) @@ -50,10 +46,10 @@ def test_local_ffmpeg_video(media_proxy, hosts, test_config, video_type: str) -> rx_host.topology.extra_info.media_proxy["sdk_port"] ) - frame_rate = str(yuv_files[video_type]["fps"]) - video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' + frame_rate = str(video_files_25_03[file]["fps"]) + video_size = f'{video_files_25_03[file]["width"]}x{video_files_25_03[file]["height"]}' pixel_format = video_file_format_to_payload_format( - str(yuv_files[video_type]["file_format"]) + str(video_files_25_03[file]["file_format"]) ) conn_type = McmConnectionType.mpg.value @@ -63,7 +59,7 @@ def test_local_ffmpeg_video(media_proxy, hosts, test_config, video_type: str) -> video_size=video_size, pixel_format=pixel_format, stream_loop=False, - input_path=f'{test_config["tx"]["filepath"]}{yuv_files[video_type]["filename"]}', + input_path=f'{test_config["tx"]["filepath"]}{video_files_25_03[file]["filename"]}', ) mcm_tx_outp = FFmpegMcmMemifVideoIO( f="mcm", @@ -98,7 +94,7 @@ def test_local_ffmpeg_video(media_proxy, hosts, test_config, video_type: str) -> framerate=frame_rate, video_size=video_size, pixel_format=pixel_format, - output_path=f'{test_config["rx"]["filepath"]}test_{yuv_files[video_type]["filename"]}', + output_path=f'{test_config["rx"]["filepath"]}test_{video_files_25_03[file]["filename"]}', ) mcm_rx_ff = FFmpeg( prefix_variables=rx_prefix_variables, @@ -116,6 +112,8 @@ def test_local_ffmpeg_video(media_proxy, hosts, test_config, video_type: str) -> mcm_rx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) mcm_tx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) + # TODO add validate() function to check if the output file is correct + success = cleanup_file(rx_host.connection, str(mcm_rx_outp.output_path)) if success: logger.debug(f"Cleaned up Rx output file: {mcm_rx_outp.output_path}") diff --git a/tests/validation/functional/local/video/test_video.py b/tests/validation/functional/local/video/test_video.py index 4a4208373..5cfa06414 100644 --- a/tests/validation/functional/local/video/test_video.py +++ b/tests/validation/functional/local/video/test_video.py @@ -10,25 +10,13 @@ import Engine.rx_tx_app_connection import Engine.rx_tx_app_engine_mcm as utils import Engine.rx_tx_app_payload -from Engine.const import DEFAULT_LOOP_COUNT, MCM_ESTABLISH_TIMEOUT +from Engine.const import DEFAULT_LOOP_COUNT, MCM_ESTABLISH_TIMEOUT, MCM_RXTXAPP_RUN_TIMEOUT from Engine.media_files import yuv_files logger = logging.getLogger(__name__) -@pytest.mark.parametrize( - "file", - [ - "i720p25", - "i720p30", - pytest.param("i1080p25", marks=pytest.mark.smoke), - pytest.param("i1080p30", marks=pytest.mark.smoke), - "i1080p50", - "i1080p60", - "i2160p25", - "i2160p30", - ], -) +@pytest.mark.parametrize("file", [file for file in yuv_files.keys()]) def test_video(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: # Get TX and RX hosts diff --git a/tests/validation/functional/local/video/test_video_25_03.py b/tests/validation/functional/local/video/test_video_25_03.py index 600efcc4f..6535e0f47 100644 --- a/tests/validation/functional/local/video/test_video_25_03.py +++ b/tests/validation/functional/local/video/test_video_25_03.py @@ -17,8 +17,13 @@ ) from Engine.media_files import video_files_25_03 - -@pytest.mark.parametrize("file", [file for file in video_files_25_03.keys()]) +@pytest.mark.parametrize( + "file", + [ + pytest.param("FullHD_60", marks=pytest.mark.smoke), + *[f for f in video_files_25_03.keys() if f != "FullHD_60"] + ] +) def test_video_25_03( build_TestApp, hosts, media_proxy, media_path, file, log_path ) -> None: @@ -62,7 +67,10 @@ def test_video_25_03( tx_executor.stop() rx_executor.stop() + # TODO add validate() function to check if the output file is correct + + rx_executor.cleanup() + assert tx_executor.is_pass is True, "TX process did not pass" assert rx_executor.is_pass is True, "RX process did not pass" - # TODO add validate() function to check if the output file is correct From 6028d8abd2c792ac6eb2f45f60c889533d03c607 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 5 Aug 2025 11:20:59 +0000 Subject: [PATCH 027/123] linter fix --- .github/workflows/smoke_tests.yml | 54 ++++++++++--------- .../local/audio/test_audio_25_03.py | 8 +-- .../local/audio/test_ffmpeg_audio.py | 6 +-- .../functional/local/blob/test_blob_25_03.py | 3 +- .../local/video/test_ffmpeg_video.py | 10 ++-- .../functional/local/video/test_video.py | 6 ++- .../local/video/test_video_25_03.py | 10 ++-- 7 files changed, 52 insertions(+), 45 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index d56eb61f7..004379cad 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -144,29 +144,31 @@ jobs: - name: "Add report to summary" if: always() run: | - echo "## Smoke Tests Report" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Check if report exists - if [ -f "report.html" ]; then - # Extract relevant parts from the HTML report - echo "### Test Results Summary" >> $GITHUB_STEP_SUMMARY - - # Extract pass/fail counts (adjust the grep patterns based on your HTML structure) - PASSED=$(grep -o "passed=[0-9]*" report.html | cut -d= -f2) - FAILED=$(grep -o "failed=[0-9]*" report.html | cut -d= -f2) - SKIPPED=$(grep -o "skipped=[0-9]*" report.html | cut -d= -f2) - - # Add summary stats - echo "| Status | Count |" >> $GITHUB_STEP_SUMMARY - echo "| ------ | ----- |" >> $GITHUB_STEP_SUMMARY - echo "| ✅ Passed | ${PASSED:-0} |" >> $GITHUB_STEP_SUMMARY - echo "| ❌ Failed | ${FAILED:-0} |" >> $GITHUB_STEP_SUMMARY - echo "| ⏭️ Skipped | ${SKIPPED:-0} |" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Add link to full report artifact - echo "📄 [View Full HTML Report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY - else - echo "❌ No report.html file was generated" >> $GITHUB_STEP_SUMMARY - fi + { + echo "## Smoke Tests Report" + echo "" + + # Check if report exists + if [ -f "report.html" ]; then + # Extract relevant parts from the HTML report + echo "### Test Results Summary" + + # Extract pass/fail counts with proper quoting + PASSED="$(grep -o "passed=[0-9]*" report.html | cut -d= -f2)" + FAILED="$(grep -o "failed=[0-9]*" report.html | cut -d= -f2)" + SKIPPED="$(grep -o "skipped=[0-9]*" report.html | cut -d= -f2)" + + # Add summary stats + echo "| Status | Count |" + echo "| ------ | ----- |" + echo "| ✅ Passed | ${PASSED:-0} |" + echo "| ❌ Failed | ${FAILED:-0} |" + echo "| ⏭️ Skipped | ${SKIPPED:-0} |" + echo "" + + # Add link to full report artifact + echo "📄 [View Full HTML Report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})" + else + echo "❌ No report.html file was generated" + fi + } >> "$GITHUB_STEP_SUMMARY" \ No newline at end of file diff --git a/tests/validation/functional/local/audio/test_audio_25_03.py b/tests/validation/functional/local/audio/test_audio_25_03.py index fe3ca9991..8e81e44eb 100644 --- a/tests/validation/functional/local/audio/test_audio_25_03.py +++ b/tests/validation/functional/local/audio/test_audio_25_03.py @@ -16,12 +16,13 @@ ) from Engine.media_files import audio_files_25_03 + @pytest.mark.parametrize( - "file", + "file", [ pytest.param("PCM16_48000_Mono", marks=pytest.mark.smoke), - *[f for f in audio_files_25_03.keys() if f != "PCM16_48000_Mono"] - ] + *[f for f in audio_files_25_03.keys() if f != "PCM16_48000_Mono"], + ], ) def test_audio_25_03( build_TestApp, hosts, media_proxy, media_path, file, log_path @@ -72,4 +73,3 @@ def test_audio_25_03( assert tx_executor.is_pass is True, "TX process did not pass" assert rx_executor.is_pass is True, "RX process did not pass" - diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index f4e00bfa7..52c75db9a 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -25,11 +25,11 @@ # @pytest.mark.parametrize("audio_type", [k for k in audio_files_25_03.keys()]) @pytest.mark.parametrize( - "audio_type", + "audio_type", [ pytest.param("PCM16_48000_Stereo", marks=pytest.mark.smoke), - *[f for f in audio_files_25_03.keys() if f != "PCM16_48000_Stereo"] - ] + *[f for f in audio_files_25_03.keys() if f != "PCM16_48000_Stereo"], + ], ) def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str) -> None: # media_proxy fixture used only to ensure that the media proxy is running diff --git a/tests/validation/functional/local/blob/test_blob_25_03.py b/tests/validation/functional/local/blob/test_blob_25_03.py index f321bc096..0f535b84f 100644 --- a/tests/validation/functional/local/blob/test_blob_25_03.py +++ b/tests/validation/functional/local/blob/test_blob_25_03.py @@ -63,9 +63,8 @@ def test_blob_25_03( rx_executor.stop() # TODO add validate() function to check if the output file is correct - + rx_executor.cleanup() assert tx_executor.is_pass is True, "TX process did not pass" assert rx_executor.is_pass is True, "RX process did not pass" - diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index f866009b8..1afc71a52 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -24,11 +24,11 @@ @pytest.mark.parametrize( - "file", + "file", [ pytest.param("FullHD_59.94", marks=pytest.mark.smoke), - *[f for f in video_files_25_03.keys() if f != "FullHD_59.94"] - ] + *[f for f in video_files_25_03.keys() if f != "FullHD_59.94"], + ], ) def test_local_ffmpeg_video(media_proxy, hosts, test_config, file: str) -> None: # media_proxy fixture used only to ensure that the media proxy is running @@ -47,7 +47,9 @@ def test_local_ffmpeg_video(media_proxy, hosts, test_config, file: str) -> None: ) frame_rate = str(video_files_25_03[file]["fps"]) - video_size = f'{video_files_25_03[file]["width"]}x{video_files_25_03[file]["height"]}' + video_size = ( + f'{video_files_25_03[file]["width"]}x{video_files_25_03[file]["height"]}' + ) pixel_format = video_file_format_to_payload_format( str(video_files_25_03[file]["file_format"]) ) diff --git a/tests/validation/functional/local/video/test_video.py b/tests/validation/functional/local/video/test_video.py index 5cfa06414..de274d12d 100644 --- a/tests/validation/functional/local/video/test_video.py +++ b/tests/validation/functional/local/video/test_video.py @@ -10,7 +10,11 @@ import Engine.rx_tx_app_connection import Engine.rx_tx_app_engine_mcm as utils import Engine.rx_tx_app_payload -from Engine.const import DEFAULT_LOOP_COUNT, MCM_ESTABLISH_TIMEOUT, MCM_RXTXAPP_RUN_TIMEOUT +from Engine.const import ( + DEFAULT_LOOP_COUNT, + MCM_ESTABLISH_TIMEOUT, + MCM_RXTXAPP_RUN_TIMEOUT, +) from Engine.media_files import yuv_files logger = logging.getLogger(__name__) diff --git a/tests/validation/functional/local/video/test_video_25_03.py b/tests/validation/functional/local/video/test_video_25_03.py index 6535e0f47..d5d868b79 100644 --- a/tests/validation/functional/local/video/test_video_25_03.py +++ b/tests/validation/functional/local/video/test_video_25_03.py @@ -17,12 +17,13 @@ ) from Engine.media_files import video_files_25_03 + @pytest.mark.parametrize( - "file", + "file", [ pytest.param("FullHD_60", marks=pytest.mark.smoke), - *[f for f in video_files_25_03.keys() if f != "FullHD_60"] - ] + *[f for f in video_files_25_03.keys() if f != "FullHD_60"], + ], ) def test_video_25_03( build_TestApp, hosts, media_proxy, media_path, file, log_path @@ -68,9 +69,8 @@ def test_video_25_03( rx_executor.stop() # TODO add validate() function to check if the output file is correct - + rx_executor.cleanup() assert tx_executor.is_pass is True, "TX process did not pass" assert rx_executor.is_pass is True, "RX process did not pass" - From 84275099d550bf1d71735234ecb27f0912053eca Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 5 Aug 2025 12:22:45 +0000 Subject: [PATCH 028/123] concurrency and logging correction --- .github/workflows/smoke_tests.yml | 19 +++++++++++++++++-- tests/validation/conftest.py | 19 ++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 004379cad..40539b615 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -3,8 +3,14 @@ name: smoke-tests-bare-metal on: push: branches: ["main", "smoke-tests"] + paths-ignore: + - '**.md' + - 'docs/**' pull_request: branches: ["main"] + paths-ignore: + - '**.md' + - 'docs/**' workflow_dispatch: inputs: branch-to-checkout: @@ -111,6 +117,10 @@ jobs: needs: validation-prepare-setup-mcm runs-on: [Linux, self-hosted] timeout-minutes: 60 + concurrency: + group: run-tests-${{ github.head_ref || github.ref_name }} + cancel-in-progress: true + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: PYTEST_RETRIES: "3" MARKERS: ${{ github.event.inputs.markers || 'smoke' }} @@ -134,13 +144,18 @@ jobs: ./tests/validation/functional/ \ --template=html/index.html --report=report.html \ -m "${{ env.MARKERS }}" - - name: "upload logs and reports" + - name: "upload logs" uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: smoke-tests-logs path: | - report.html ${{ env.LOG_DIR }}/* + - name: "upload report" + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: smoke-tests-report + path: | + report.html - name: "Add report to summary" if: always() run: | diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index 3b8ef23a2..bb091b1b8 100644 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -19,6 +19,7 @@ from Engine.const import * from Engine.mcm_apps import MediaProxy, MeshAgent, get_mcm_path, get_mtl_path from datetime import datetime +import re logger = logging.getLogger(__name__) @@ -178,19 +179,31 @@ def log_path_dir(test_config: dict) -> str: return str(log_dir) +def sanitize_name(test_name): + """ + Sanitizes a test name by replacing invalid characters with underscores, + collapsing multiple underscores into one, and removing leading/trailing underscores. + """ + sanitized_name = re.sub(r'[|<>:"*?\r\n\[\]]| = |_{2,}', '_', test_name) + sanitized_name = re.sub(r'_{2,}', '_', sanitized_name).strip('_') + return sanitized_name + + @pytest.fixture(scope="function") def log_path(log_path_dir: str, request) -> str: """ Create a test-specific subdirectory within the main log directory. + Sanitizes test names to ensure valid directory names by replacing invalid characters. - :param log_path: The main log directory path from the log_path fixture. - :type log_path: str + :param log_path_dir: The main log directory path from the log_path_dir fixture. + :type log_path_dir: str :param request: Pytest request object to get test name. :return: Path to test-specific log subdirectory. :rtype: str """ test_name = request.node.name - test_log_path = Path(log_path_dir, test_name) + sanitized_name = sanitize_name(test_name) + test_log_path = Path(log_path_dir, sanitized_name) test_log_path.mkdir(parents=True, exist_ok=True) return str(test_log_path) From 451db232b7b21d19c74b477e08ff187766bb3ac8 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 5 Aug 2025 12:30:41 +0000 Subject: [PATCH 029/123] linter fix --- .github/workflows/smoke_tests.yml | 12 ++++++------ tests/validation/conftest.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 40539b615..6524cef50 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -4,13 +4,13 @@ on: push: branches: ["main", "smoke-tests"] paths-ignore: - - '**.md' - - 'docs/**' + - "**.md" + - "docs/**" pull_request: branches: ["main"] paths-ignore: - - '**.md' - - 'docs/**' + - "**.md" + - "docs/**" workflow_dispatch: inputs: branch-to-checkout: @@ -162,7 +162,7 @@ jobs: { echo "## Smoke Tests Report" echo "" - + # Check if report exists if [ -f "report.html" ]; then # Extract relevant parts from the HTML report @@ -186,4 +186,4 @@ jobs: else echo "❌ No report.html file was generated" fi - } >> "$GITHUB_STEP_SUMMARY" \ No newline at end of file + } >> "$GITHUB_STEP_SUMMARY" diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index bb091b1b8..66770e14b 100644 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -184,8 +184,8 @@ def sanitize_name(test_name): Sanitizes a test name by replacing invalid characters with underscores, collapsing multiple underscores into one, and removing leading/trailing underscores. """ - sanitized_name = re.sub(r'[|<>:"*?\r\n\[\]]| = |_{2,}', '_', test_name) - sanitized_name = re.sub(r'_{2,}', '_', sanitized_name).strip('_') + sanitized_name = re.sub(r'[|<>:"*?\r\n\[\]]| = |_{2,}', "_", test_name) + sanitized_name = re.sub(r"_{2,}", "_", sanitized_name).strip("_") return sanitized_name From fe6d9b0bbc89cc44ad44e1745f09fe8e7fa5d397 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 5 Aug 2025 14:43:16 +0000 Subject: [PATCH 030/123] use root and change concurrency --- .github/workflows/smoke_tests.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 6524cef50..4475b853b 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -30,6 +30,12 @@ on: default: "smoke" required: false description: "Markers to use for pytest" + +# Add workflow-level concurrency to prevent duplicated runs +concurrency: + group: smoke-tests-${{ github.workflow }}-${{ github.ref_name || github.head_ref }} + cancel-in-progress: true + env: BUILD_TYPE: "Release" DPDK_VERSION: "23.11" @@ -53,19 +59,15 @@ jobs: uses: ./.github/workflows/base_build.yml with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} - concurrency: - group: build-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - + # Remove job-level concurrency as it's now handled at the workflow level + validation-prepare-setup-mcm: runs-on: [Linux, self-hosted] needs: call-base-build timeout-minutes: 60 outputs: pipenv-activate: ${{ steps.pipenv-install.outputs.VIRTUAL_ENV }} - concurrency: - group: setup-${{ github.head_ref || github.run_id }} - cancel-in-progress: true + # Remove job-level concurrency as it's now handled at the workflow level steps: - name: "preparation: Harden Runner" uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2 @@ -109,7 +111,7 @@ jobs: - name: "add user name to environment and config" run: | echo "USER=${USER}" >> "$GITHUB_ENV" - sed -i "s/{{ USER }}/${USER}/g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s/{{ USER }}/root/g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MCM_PATH }}|${{ github.workspace }}|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ LOG_PATH }}|${{ env.LOG_DIR }}|g" tests/validation/configs/test_config_workflow.yaml From 2fdefcb227f43826e116c2cd001e765bc6ebda3a Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 5 Aug 2025 15:14:42 +0000 Subject: [PATCH 031/123] linter fix --- .github/workflows/smoke_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 4475b853b..e5eac776b 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -60,7 +60,7 @@ jobs: with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} # Remove job-level concurrency as it's now handled at the workflow level - + validation-prepare-setup-mcm: runs-on: [Linux, self-hosted] needs: call-base-build From 7c3135e82f361b8c1c0dbeda8457da7a598071fc Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 5 Aug 2025 16:07:05 +0000 Subject: [PATCH 032/123] concurrency fix --- .github/workflows/smoke_tests.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index e5eac776b..9f61e2017 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -31,11 +31,6 @@ on: required: false description: "Markers to use for pytest" -# Add workflow-level concurrency to prevent duplicated runs -concurrency: - group: smoke-tests-${{ github.workflow }}-${{ github.ref_name || github.head_ref }} - cancel-in-progress: true - env: BUILD_TYPE: "Release" DPDK_VERSION: "23.11" @@ -59,15 +54,14 @@ jobs: uses: ./.github/workflows/base_build.yml with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} - # Remove job-level concurrency as it's now handled at the workflow level validation-prepare-setup-mcm: runs-on: [Linux, self-hosted] needs: call-base-build timeout-minutes: 60 + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository outputs: pipenv-activate: ${{ steps.pipenv-install.outputs.VIRTUAL_ENV }} - # Remove job-level concurrency as it's now handled at the workflow level steps: - name: "preparation: Harden Runner" uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2 @@ -111,17 +105,15 @@ jobs: - name: "add user name to environment and config" run: | echo "USER=${USER}" >> "$GITHUB_ENV" - sed -i "s/{{ USER }}/root/g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s/{{ USER }}/${USER}/g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MCM_PATH }}|${{ github.workspace }}|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ LOG_PATH }}|${{ env.LOG_DIR }}|g" tests/validation/configs/test_config_workflow.yaml + validation-run-tests: needs: validation-prepare-setup-mcm runs-on: [Linux, self-hosted] timeout-minutes: 60 - concurrency: - group: run-tests-${{ github.head_ref || github.ref_name }} - cancel-in-progress: true if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: PYTEST_RETRIES: "3" From cbca596befdf610adddedba81bfc7050d274232c Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 6 Aug 2025 09:29:36 +0000 Subject: [PATCH 033/123] improve summary report --- .github/workflows/smoke_tests.yml | 38 ++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 9f61e2017..cc968b046 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -51,6 +51,7 @@ permissions: jobs: call-base-build: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository uses: ./.github/workflows/base_build.yml with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} @@ -137,6 +138,7 @@ jobs: --test_config=tests/validation/configs/test_config_workflow.yaml \ ./tests/validation/functional/ \ --template=html/index.html --report=report.html \ + --json-report --json-report-file=report.json \ -m "${{ env.MARKERS }}" - name: "upload logs" uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 @@ -157,27 +159,37 @@ jobs: echo "## Smoke Tests Report" echo "" - # Check if report exists - if [ -f "report.html" ]; then - # Extract relevant parts from the HTML report - echo "### Test Results Summary" - - # Extract pass/fail counts with proper quoting - PASSED="$(grep -o "passed=[0-9]*" report.html | cut -d= -f2)" - FAILED="$(grep -o "failed=[0-9]*" report.html | cut -d= -f2)" - SKIPPED="$(grep -o "skipped=[0-9]*" report.html | cut -d= -f2)" - + # Check if JSON report exists + if [ -f "report.json" ]; then + # Parse JSON report + PASSED=$(jq '.summary.passed' report.json) + FAILED=$(jq '.summary.failed' report.json) + SKIPPED=$(jq '.summary.skipped' report.json) + ERROR=$(jq '.summary.errors' report.json) + # Add summary stats echo "| Status | Count |" echo "| ------ | ----- |" echo "| ✅ Passed | ${PASSED:-0} |" echo "| ❌ Failed | ${FAILED:-0} |" + echo "| ⚠️ Error | ${ERROR:-0} |" echo "| ⏭️ Skipped | ${SKIPPED:-0} |" echo "" - + + # Add test result details if available + TOTAL=$((${PASSED:-0} + ${FAILED:-0} + ${ERROR:-0} + ${SKIPPED:-0})) + echo "**Total Tests:** $TOTAL" + echo "" + if [ "${FAILED:-0}" -gt 0 ] || [ "${ERROR:-0}" -gt 0 ]; then + echo "❌ **Some tests failed!** Please check the detailed report." + else + echo "✅ **All tests passed!**" + fi + echo "" + # Add link to full report artifact - echo "📄 [View Full HTML Report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})" + echo "📄 [Download Full HTML Report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts)" else - echo "❌ No report.html file was generated" + echo "❌ No report.json file was generated" fi } >> "$GITHUB_STEP_SUMMARY" From 9f8a9367386f09312f5a39880392d50a19e5035b Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 6 Aug 2025 10:44:16 +0000 Subject: [PATCH 034/123] correct summary --- .github/workflows/smoke_tests.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index cc968b046..9e8961fe1 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -147,6 +147,7 @@ jobs: path: | ${{ env.LOG_DIR }}/* - name: "upload report" + id: upload-report uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: smoke-tests-report @@ -162,10 +163,10 @@ jobs: # Check if JSON report exists if [ -f "report.json" ]; then # Parse JSON report - PASSED=$(jq '.summary.passed' report.json) - FAILED=$(jq '.summary.failed' report.json) - SKIPPED=$(jq '.summary.skipped' report.json) - ERROR=$(jq '.summary.errors' report.json) + PASSED=$(jq '.summary.passed // 0' report.json) + FAILED=$(jq '.summary.failed // 0' report.json) + SKIPPED=$(jq '.summary.skipped // 0' report.json) + ERROR=$(jq '.summary.errors // 0' report.json) # Add summary stats echo "| Status | Count |" @@ -188,7 +189,7 @@ jobs: echo "" # Add link to full report artifact - echo "📄 [Download Full HTML Report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts)" + echo "📄 [Download Full HTML Report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts/${{ steps.upload-report.outputs.artifact-id }})" else echo "❌ No report.json file was generated" fi From ccda66e9799d17aa5142afa784a0071d0fcff589 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 6 Aug 2025 15:25:25 +0000 Subject: [PATCH 035/123] change user to root for tests --- .github/workflows/smoke_tests.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 9e8961fe1..f68bcf2d9 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -103,10 +103,31 @@ jobs: source venv/bin/activate pip install -r requirements.txt echo "VIRTUAL_ENV=$PWD/venv/bin/activate" >> "$GITHUB_ENV" + - name: "ssh key root setup" + run: | + # Ensure root .ssh directory exists + sudo mkdir -p /root/.ssh + sudo chmod 700 /root/.ssh + + # Copy the public key to root's authorized_keys + if [ -f "/home/${USER}/.ssh/mcm_key.pub" ]; then + sudo cp /home/${USER}/.ssh/mcm_key.pub /root/.ssh/authorized_keys + elif [ -f "/home/${USER}/.ssh/mcm_key" ]; then + # Generate public key from private key if public key doesn't exist + ssh-keygen -y -f /home/${USER}/.ssh/mcm_key | sudo tee /root/.ssh/authorized_keys + fi + + # Set proper permissions + sudo chmod 600 /root/.ssh/authorized_keys + sudo chown root:root /root/.ssh/authorized_keys + + # Ensure the private key has correct permissions for gta user + chmod 600 /home/${USER}/.ssh/mcm_key + - name: "add user name to environment and config" run: | echo "USER=${USER}" >> "$GITHUB_ENV" - sed -i "s/{{ USER }}/${USER}/g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s/{{ USER }}/root/g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MCM_PATH }}|${{ github.workspace }}|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ LOG_PATH }}|${{ env.LOG_DIR }}|g" tests/validation/configs/test_config_workflow.yaml From 2cb20f35a5f8212bd619464b8cc4e5a1a89ee9ff Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 6 Aug 2025 15:25:58 +0000 Subject: [PATCH 036/123] linter fix --- .github/workflows/smoke_tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index f68bcf2d9..85a568134 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -108,7 +108,7 @@ jobs: # Ensure root .ssh directory exists sudo mkdir -p /root/.ssh sudo chmod 700 /root/.ssh - + # Copy the public key to root's authorized_keys if [ -f "/home/${USER}/.ssh/mcm_key.pub" ]; then sudo cp /home/${USER}/.ssh/mcm_key.pub /root/.ssh/authorized_keys @@ -116,14 +116,14 @@ jobs: # Generate public key from private key if public key doesn't exist ssh-keygen -y -f /home/${USER}/.ssh/mcm_key | sudo tee /root/.ssh/authorized_keys fi - + # Set proper permissions sudo chmod 600 /root/.ssh/authorized_keys sudo chown root:root /root/.ssh/authorized_keys - + # Ensure the private key has correct permissions for gta user chmod 600 /home/${USER}/.ssh/mcm_key - + - name: "add user name to environment and config" run: | echo "USER=${USER}" >> "$GITHUB_ENV" From eebdee9a31bbcbf184103a2dc086589415bf38da Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 6 Aug 2025 16:16:28 +0000 Subject: [PATCH 037/123] linter fix --- .github/workflows/smoke_tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 85a568134..e90705902 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -111,10 +111,10 @@ jobs: # Copy the public key to root's authorized_keys if [ -f "/home/${USER}/.ssh/mcm_key.pub" ]; then - sudo cp /home/${USER}/.ssh/mcm_key.pub /root/.ssh/authorized_keys + sudo cp "/home/${USER}/.ssh/mcm_key.pub" /root/.ssh/authorized_keys elif [ -f "/home/${USER}/.ssh/mcm_key" ]; then # Generate public key from private key if public key doesn't exist - ssh-keygen -y -f /home/${USER}/.ssh/mcm_key | sudo tee /root/.ssh/authorized_keys + ssh-keygen -y -f "/home/${USER}/.ssh/mcm_key" | sudo tee /root/.ssh/authorized_keys fi # Set proper permissions @@ -122,7 +122,7 @@ jobs: sudo chown root:root /root/.ssh/authorized_keys # Ensure the private key has correct permissions for gta user - chmod 600 /home/${USER}/.ssh/mcm_key + chmod 600 "/home/${USER}/.ssh/mcm_key" - name: "add user name to environment and config" run: | From 79f33e3ca4c745ef888283abd726ec312883bb7d Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 7 Aug 2025 11:04:13 +0000 Subject: [PATCH 038/123] use 25.03 tag for testing --- .github/workflows/base_build.yml | 6 +++++- .github/workflows/smoke_tests.yml | 15 +++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index ad81c2cff..8dca58e4c 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -13,6 +13,10 @@ on: type: string default: "main" description: "Branch to checkout" + tag: # Add tag input + required: false + type: string + description: "Tag to checkout" env: BUILD_TYPE: Release @@ -45,7 +49,7 @@ jobs: - name: "Checkout repository" uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: - ref: ${{ inputs.branch }} + ref: ${{ inputs.tag || inputs.branch }} # Use tag if provided, otherwise use branch - name: "Install OS level dependencies" run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index e90705902..57fa5d642 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -18,6 +18,10 @@ on: default: "main" required: false description: "Branch name to use" + tag-to-checkout: # added tag input + type: string + required: false + description: "Tag name to use" list_tests: type: choice required: false @@ -45,6 +49,7 @@ env: MCM_FFMPEG_6_1: ./mcm-binaries/ffmpeg-6-1/ffmpeg MTL_FFMPEG_6_1: ./mtl-binaries/ffmpeg-6-1/ffmpeg LOG_DIR: "${{ github.workspace }}/logs" + TAG_TO_CHECKOUT: "25.03" # Hardcoded tag for testing permissions: contents: read @@ -55,6 +60,7 @@ jobs: uses: ./.github/workflows/base_build.yml with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} + tag: ${{ github.event.inputs.tag-to-checkout || '25.03' }} # added tag input validation-prepare-setup-mcm: runs-on: [Linux, self-hosted] @@ -74,10 +80,11 @@ jobs: sudo chown -R "${USER}" "$(pwd)" || true env | grep BUILD_ || true env | grep DPDK_ || true - - name: "preparation: Checkout MCM" + - name: "preparation: Checkout MCM at tag" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - ref: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} + repository: ${{ github.repository }} + ref: ${{ github.event.inputs.tag-to-checkout || env.TAG_TO_CHECKOUT }} - name: Install RxTxApp dependencies run: sudo apt-get update && sudo apt-get install -y libjansson-dev - name: "build RxTxApp" @@ -131,6 +138,10 @@ jobs: sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MCM_PATH }}|${{ github.workspace }}|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ LOG_PATH }}|${{ env.LOG_DIR }}|g" tests/validation/configs/test_config_workflow.yaml + - name: "Checkout back to original branch" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ github.ref_name }} validation-run-tests: needs: validation-prepare-setup-mcm From 1a8cfe27f4abf6bb8e94ee9656145bea0add3f66 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 7 Aug 2025 13:42:46 +0000 Subject: [PATCH 039/123] validation-prepare-setup-mcm remove checkout to tag --- .github/workflows/smoke_tests.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 57fa5d642..2388c826c 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -80,11 +80,10 @@ jobs: sudo chown -R "${USER}" "$(pwd)" || true env | grep BUILD_ || true env | grep DPDK_ || true - - name: "preparation: Checkout MCM at tag" + - name: "Checkout back to ${{ github.ref_name }} branch" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - repository: ${{ github.repository }} - ref: ${{ github.event.inputs.tag-to-checkout || env.TAG_TO_CHECKOUT }} + ref: ${{ github.ref_name }} - name: Install RxTxApp dependencies run: sudo apt-get update && sudo apt-get install -y libjansson-dev - name: "build RxTxApp" @@ -138,10 +137,6 @@ jobs: sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MCM_PATH }}|${{ github.workspace }}|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ LOG_PATH }}|${{ env.LOG_DIR }}|g" tests/validation/configs/test_config_workflow.yaml - - name: "Checkout back to original branch" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ github.ref_name }} validation-run-tests: needs: validation-prepare-setup-mcm From f4c64c5e0ba4d8c777357ea584e2538bb2b5f55e Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 7 Aug 2025 15:36:43 +0000 Subject: [PATCH 040/123] improve log check --- .github/workflows/smoke_tests.yml | 4 +- tests/validation/Engine/const.py | 42 ++++++- .../validation/Engine/rx_tx_app_engine_mcm.py | 110 ++++++++++++------ 3 files changed, 120 insertions(+), 36 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 2388c826c..4f6fdbc1b 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -49,7 +49,6 @@ env: MCM_FFMPEG_6_1: ./mcm-binaries/ffmpeg-6-1/ffmpeg MTL_FFMPEG_6_1: ./mtl-binaries/ffmpeg-6-1/ffmpeg LOG_DIR: "${{ github.workspace }}/logs" - TAG_TO_CHECKOUT: "25.03" # Hardcoded tag for testing permissions: contents: read @@ -60,7 +59,7 @@ jobs: uses: ./.github/workflows/base_build.yml with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} - tag: ${{ github.event.inputs.tag-to-checkout || '25.03' }} # added tag input + tag: ${{ github.event.inputs.tag-to-checkout }} # added tag input validation-prepare-setup-mcm: runs-on: [Linux, self-hosted] @@ -166,6 +165,7 @@ jobs: ./tests/validation/functional/ \ --template=html/index.html --report=report.html \ --json-report --json-report-file=report.json \ + --show-capture=no \ -m "${{ env.MARKERS }}" - name: "upload logs" uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 diff --git a/tests/validation/Engine/const.py b/tests/validation/Engine/const.py index cbcf3c3e7..c8caa5b51 100644 --- a/tests/validation/Engine/const.py +++ b/tests/validation/Engine/const.py @@ -2,6 +2,46 @@ # Copyright 2024-2025 Intel Corporation # Media Communications Mesh +# Required ordered log phrases for Rx validation +RX_REQUIRED_LOG_PHRASES = [ + '[RX] Launching RX App', + '[RX] Reading client configuration', + '[RX] Reading connection configuration', + '[DEBU] JSON client config:', + '[INFO] Media Communications Mesh SDK version', + '[DEBU] JSON conn config:', + '[RX] Waiting for packets', + '[RX] Fetched mesh data buffer', + '[RX] Saving buffer data to a file', + '[RX] Frame: 1', + '[RX] Done reading the data', + '[RX] dropping connection to media-proxy', + '[RX] Shuting down connection', + 'INFO - memif disconnected!', + '[INFO] gRPC: connection deleted', + '[RX] Shuting down client', +] +TX_REQUIRED_LOG_PHRASES = [ + '[TX] Launching TX app', + '[TX] Reading client configuration', + '[TX] Reading connection configuration', + '[DEBU] JSON client config:', + '[INFO] Media Communications Mesh SDK version', + '[DEBU] JSON conn config:', + '[INFO] gRPC: connection created', + 'INFO - Create memif socket.', + 'INFO - Create memif interface.', + 'INFO - memif connected!', + '[INFO] gRPC: connection active', + '[TX] sending video frames', + '[TX] sending blob packets', + '[TX] Sending packet: 1', + '[TX] Graceful shutdown requested', + '[TX] Shuting down connection', + '[INFO] gRPC: connection deleted', + '[TX] Shuting down client', +] + LOG_FOLDER = "logs" DEFAULT_OUTPUT_PATH = "/dev/null" @@ -16,7 +56,7 @@ MESH_AGENT_ERROR_KEYWORDS = ["[ERRO]"] RX_TX_APP_ERROR_KEYWORDS = ["[ERRO]"] -DEFAULT_MPG_URN = "ipv4:224.0.0.1:9003" +DEFAULT_MPG_URN = "ipv4:224.1.1.1:9003" DEFAULT_REMOTE_IP_ADDR = "239.2.39.238" DEFAULT_REMOTE_PORT = 20000 DEFAULT_PACING = "narrow" diff --git a/tests/validation/Engine/rx_tx_app_engine_mcm.py b/tests/validation/Engine/rx_tx_app_engine_mcm.py index c15294702..093c5addd 100644 --- a/tests/validation/Engine/rx_tx_app_engine_mcm.py +++ b/tests/validation/Engine/rx_tx_app_engine_mcm.py @@ -11,7 +11,7 @@ import Engine.rx_tx_app_client_json import Engine.rx_tx_app_connection_json -from Engine.const import LOG_FOLDER, RX_TX_APP_ERROR_KEYWORDS, DEFAULT_OUTPUT_PATH +from Engine.const import LOG_FOLDER, RX_TX_APP_ERROR_KEYWORDS, DEFAULT_OUTPUT_PATH, RX_REQUIRED_LOG_PHRASES, TX_REQUIRED_LOG_PHRASES from Engine.mcm_apps import ( get_media_proxy_port, output_validator, @@ -171,6 +171,8 @@ def start(self): def stop(self): validation_info = [] file_validation_passed = True + app_log_validation_status = False + app_log_error_count = 0 if self.process: try: @@ -183,37 +185,78 @@ def stop(self): logger.info("Process has already finished (nothing to stop).") logger.info(f"{self.direction} app stopped.") - log_dir = self.log_path if self.log_path else LOG_FOLDER + log_dir = self.log_path if self.log_path is not None else LOG_FOLDER subdir = f"RxTx/{self.host.name}" filename = f"{self.direction.lower()}.log" log_file_path = os.path.join(log_dir, subdir, filename) - result = output_validator( - log_file_path=log_file_path, - error_keywords=RX_TX_APP_ERROR_KEYWORDS, - ) - if result["errors"]: - logger.warning(f"Errors found: {result['errors']}") - - self.is_pass = result["is_pass"] - - # Collect log validation info - validation_info.append(f"=== {self.direction} App Log Validation ===") - validation_info.append(f"Log file: {log_file_path}") - validation_info.append( - f"Validation result: {'PASS' if result['is_pass'] else 'FAIL'}" - ) - validation_info.append(f"Errors found: {len(result['errors'])}") - if result["errors"]: - validation_info.append("Error details:") - for error in result["errors"]: - validation_info.append(f" - {error}") - if result["phrase_mismatches"]: - validation_info.append("Phrase mismatches:") - for phrase, found, expected in result["phrase_mismatches"]: - validation_info.append( - f" - {phrase}: found '{found}', expected '{expected}'" - ) + app_log_validation_status = False + app_log_error_count = 0 + + # Custom Rx log validation: check for required phrases in order, ignoring timestamps + def check_phrases_in_order(log_path, phrases): + found_indices = [] + missing_phrases = [] + with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: + lines = [line.strip() for line in f] + idx = 0 + for phrase in phrases: + found = False + phrase_stripped = phrase.strip() + while idx < len(lines): + line_stripped = lines[idx].strip() + if phrase_stripped in line_stripped: + found_indices.append(idx) + found = True + idx += 1 + break + idx += 1 + if not found: + missing_phrases.append(phrase) + return len(missing_phrases) == 0, missing_phrases + + if self.direction in ("Rx", "Tx"): + required_phrases = RX_REQUIRED_LOG_PHRASES if self.direction == "Rx" else TX_REQUIRED_LOG_PHRASES + log_pass, missing = check_phrases_in_order(log_file_path, required_phrases) + self.is_pass = log_pass + app_log_validation_status = log_pass + app_log_error_count = len(missing) + validation_info.append(f"=== {self.direction} App Log Validation ===") + validation_info.append(f"Log file: {log_file_path}") + validation_info.append(f"Validation result: {'PASS' if log_pass else 'FAIL'}") + validation_info.append(f"Errors found: {app_log_error_count}") + if not log_pass: + validation_info.append(f"Missing or out-of-order phrases:") + for phrase in missing: + validation_info.append(f" - {phrase}") + if missing: + print(f"{self.direction} process did not pass. First missing phrase: {missing[0]}") + else: + result = output_validator( + log_file_path=log_file_path, + error_keywords=RX_TX_APP_ERROR_KEYWORDS, + ) + if result["errors"]: + logger.warning(f"Errors found: {result['errors']}") + self.is_pass = result["is_pass"] + app_log_validation_status = result["is_pass"] + app_log_error_count = len(result["errors"]) + validation_info.append(f"=== {self.direction} App Log Validation ===") + validation_info.append(f"Log file: {log_file_path}") + validation_info.append( + f"Validation result: {'PASS' if result['is_pass'] else 'FAIL'}" + ) + validation_info.append(f"Errors found: {len(result['errors'])}") + if result["errors"]: + validation_info.append("Error details:") + for error in result["errors"]: + validation_info.append(f" - {error}") + if result["phrase_mismatches"]: + validation_info.append("Phrase mismatches:") + for phrase, found, expected in result["phrase_mismatches"]: + validation_info.append( + f" - {phrase}: found '{found}', expected '{expected}'" + ) # File validation for Rx only run if output path isn't "/dev/null" if self.direction == "Rx" and self.output and self.output_path != "/dev/null": @@ -234,7 +277,7 @@ def stop(self): ) validation_info.append(f"Overall validation: {overall_status}") validation_info.append( - f"App log validation: {'PASS' if result['is_pass'] else 'FAIL'}" + f"App log validation: {'PASS' if app_log_validation_status else 'FAIL'}" ) if self.direction == "Rx": file_status = "PASS" if file_validation_passed else "FAIL" @@ -245,8 +288,7 @@ def stop(self): "Note: Overall validation fails if either app log or file validation fails" ) - # Save to validation report file - log_dir = self.log_path if self.log_path else LOG_FOLDER + log_dir = self.log_path if self.log_path is not None else LOG_FOLDER subdir = f"RxTx/{self.host.name}" validation_filename = f"{self.direction.lower()}_validation.log" @@ -288,13 +330,14 @@ def start(self): filename = "tx.log" def log_output(): + log_dir = self.log_path if self.log_path is not None else LOG_FOLDER for line in self.process.get_stdout_iter(): save_process_log( subdir=subdir, filename=filename, text=line.rstrip(), cmd=cmd, - log_dir=self.log_path, + log_dir=log_dir, ) threading.Thread(target=log_output, daemon=True).start() @@ -343,13 +386,14 @@ def start(self): filename = "rx.log" def log_output(): + log_dir = self.log_path if self.log_path is not None else LOG_FOLDER for line in self.process.get_stdout_iter(): save_process_log( subdir=subdir, filename=filename, text=line.rstrip(), cmd=cmd, - log_dir=self.log_path, + log_dir=log_dir, ) threading.Thread(target=log_output, daemon=True).start() From 52f60305e92e3164a52d3a451b55dea3aec7a91e Mon Sep 17 00:00:00 2001 From: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> Date: Fri, 8 Aug 2025 09:10:48 +0200 Subject: [PATCH 041/123] Update smoke_tests.yml Signed-off-by: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> --- .github/workflows/smoke_tests.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 4f6fdbc1b..e4d1e5bd7 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -168,14 +168,17 @@ jobs: --show-capture=no \ -m "${{ env.MARKERS }}" - name: "upload logs" - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + if: always() + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 with: name: smoke-tests-logs path: | ${{ env.LOG_DIR }}/* + - name: "upload report" + if: always() id: upload-report - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 with: name: smoke-tests-report path: | From 7873dad1856eb85b1ae1471fcac83b5adb4b28eb Mon Sep 17 00:00:00 2001 From: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:58:44 +0200 Subject: [PATCH 042/123] Update tests/validation/functional/local/audio/test_ffmpeg_audio.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> --- tests/validation/functional/local/audio/test_ffmpeg_audio.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index 52c75db9a..e540f01f6 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -23,7 +23,6 @@ logger = logging.getLogger(__name__) -# @pytest.mark.parametrize("audio_type", [k for k in audio_files_25_03.keys()]) @pytest.mark.parametrize( "audio_type", [ From a37d50a4be82f70fbd2e1cb62caf31b844867476 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 11 Aug 2025 10:29:43 +0000 Subject: [PATCH 043/123] save configs --- tests/validation/Engine/mcm_apps.py | 2 +- tests/validation/Engine/rx_tx_app_client_json.py | 14 ++++++++++++++ .../Engine/rx_tx_app_connection_json.py | 13 +++++++++++++ tests/validation/Engine/rx_tx_app_engine_mcm.py | 16 ++++++++++++---- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/tests/validation/Engine/mcm_apps.py b/tests/validation/Engine/mcm_apps.py index fc55c805f..7f1f7b5b7 100644 --- a/tests/validation/Engine/mcm_apps.py +++ b/tests/validation/Engine/mcm_apps.py @@ -72,7 +72,7 @@ def save_process_log( write_cmd = True with open(log_file, "a") as f: if write_cmd: - f.write(cmd + "\n\n") + f.write((cmd if cmd is not None else "") + "\n\n") f.write(cleaned_text + "\n") diff --git a/tests/validation/Engine/rx_tx_app_client_json.py b/tests/validation/Engine/rx_tx_app_client_json.py index 46421435d..4d0f13792 100644 --- a/tests/validation/Engine/rx_tx_app_client_json.py +++ b/tests/validation/Engine/rx_tx_app_client_json.py @@ -3,6 +3,7 @@ # Media Communications Mesh import json +from pathlib import Path class ClientJson: @@ -47,3 +48,16 @@ def prepare_and_save_json(self, output_path: str = "client.json") -> None: f = self.host.connection.path(output_path) json_content = self.to_json().replace('"', '\\"') f.write_text(json_content) + + + def copy_json_to_logs(self, log_path: str) -> None: + """Copy the client.json file to the log path on runner.""" + source_path = self.host.connection.path("client.json") + dest_path = Path(log_path) / "client.json" + + # Create log directory if it doesn't exist + Path(log_path).mkdir(parents=True, exist_ok=True) + + # Copy the client.json file to the log path + with open(dest_path, "w") as dest_file: + dest_file.write(self.to_json()) diff --git a/tests/validation/Engine/rx_tx_app_connection_json.py b/tests/validation/Engine/rx_tx_app_connection_json.py index 797564709..ac7599673 100644 --- a/tests/validation/Engine/rx_tx_app_connection_json.py +++ b/tests/validation/Engine/rx_tx_app_connection_json.py @@ -3,6 +3,7 @@ # Media Communications Mesh import json +from pathlib import Path from .rx_tx_app_connection import RxTxAppConnection from .rx_tx_app_payload import Payload @@ -45,3 +46,15 @@ def prepare_and_save_json(self, output_path: str = "connection.json") -> None: f = self.host.connection.path(output_path) json_content = self.to_json().replace('"', '\\"') f.write_text(json_content) + + def copy_json_to_logs(self, log_path: str) -> None: + """Copy the connection.json file to the log path on runner.""" + source_path = self.host.connection.path("connection.json") + dest_path = Path(log_path) / "connection.json" + + # Create log directory if it doesn't exist + Path(log_path).mkdir(parents=True, exist_ok=True) + + # Copy the connection.json file to the log path + with open(dest_path, "w") as dest_file: + dest_file.write(self.to_json()) diff --git a/tests/validation/Engine/rx_tx_app_engine_mcm.py b/tests/validation/Engine/rx_tx_app_engine_mcm.py index 093c5addd..48bfc052a 100644 --- a/tests/validation/Engine/rx_tx_app_engine_mcm.py +++ b/tests/validation/Engine/rx_tx_app_engine_mcm.py @@ -24,7 +24,7 @@ def create_client_json( - build: str, client: Engine.rx_tx_app_client_json.ClientJson + build: str, client: Engine.rx_tx_app_client_json.ClientJson, log_path: str = "" ) -> None: logger.debug("Client JSON:") for line in client.to_json().splitlines(): @@ -32,10 +32,13 @@ def create_client_json( output_path = str(Path(build, "tests", "tools", "TestApp", "build", "client.json")) logger.debug(f"Client JSON path: {output_path}") client.prepare_and_save_json(output_path=output_path) + # Use provided log_path or default to LOG_FOLDER + log_dir = log_path if log_path else LOG_FOLDER + client.copy_json_to_logs(log_path=log_dir) def create_connection_json( - build: str, rx_tx_app_connection: Engine.rx_tx_app_connection_json.ConnectionJson + build: str, rx_tx_app_connection: Engine.rx_tx_app_connection_json.ConnectionJson, log_path: str = "" ) -> None: logger.debug("Connection JSON:") for line in rx_tx_app_connection.to_json().splitlines(): @@ -45,6 +48,9 @@ def create_connection_json( ) logger.debug(f"Connection JSON path: {output_path}") rx_tx_app_connection.prepare_and_save_json(output_path=output_path) + # Use provided log_path or default to LOG_FOLDER + log_dir = log_path if log_path else LOG_FOLDER + rx_tx_app_connection.copy_json_to_logs(log_path=log_dir) class AppRunnerBase: @@ -164,8 +170,10 @@ def _ensure_output_directory_exists(self): logger.warning(f"Error creating directory {output_dir}: {str(e)}") def start(self): - create_client_json(self.mcm_path, self.rx_tx_app_client_json) - create_connection_json(self.mcm_path, self.rx_tx_app_connection_json) + # Use self.log_path for consistent logging across the application + log_dir = self.log_path if self.log_path else LOG_FOLDER + create_client_json(self.mcm_path, self.rx_tx_app_client_json, log_path=log_dir) + create_connection_json(self.mcm_path, self.rx_tx_app_connection_json, log_path=log_dir) self._ensure_output_directory_exists() def stop(self): From 5bd096e96d24fdae57577dcefa6aadb0a3e662d4 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 11 Aug 2025 11:14:30 +0000 Subject: [PATCH 044/123] update log validation --- .github/workflows/smoke_tests.yml | 2 +- tests/validation/Engine/const.py | 7 ++----- tests/validation/functional/local/blob/test_blob.py | 7 +------ tests/validation/functional/local/blob/test_blob_25_03.py | 2 +- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index e4d1e5bd7..3d624c86d 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -174,7 +174,7 @@ jobs: name: smoke-tests-logs path: | ${{ env.LOG_DIR }}/* - + - name: "upload report" if: always() id: upload-report diff --git a/tests/validation/Engine/const.py b/tests/validation/Engine/const.py index c8caa5b51..35b8d1c6c 100644 --- a/tests/validation/Engine/const.py +++ b/tests/validation/Engine/const.py @@ -33,10 +33,7 @@ 'INFO - Create memif interface.', 'INFO - memif connected!', '[INFO] gRPC: connection active', - '[TX] sending video frames', - '[TX] sending blob packets', - '[TX] Sending packet: 1', - '[TX] Graceful shutdown requested', + '[TX] sending', '[TX] Shuting down connection', '[INFO] gRPC: connection deleted', '[TX] Shuting down client', @@ -56,7 +53,7 @@ MESH_AGENT_ERROR_KEYWORDS = ["[ERRO]"] RX_TX_APP_ERROR_KEYWORDS = ["[ERRO]"] -DEFAULT_MPG_URN = "ipv4:224.1.1.1:9003" +DEFAULT_MPG_URN = "ipv4:224.0.0.1" DEFAULT_REMOTE_IP_ADDR = "239.2.39.238" DEFAULT_REMOTE_PORT = 20000 DEFAULT_PACING = "narrow" diff --git a/tests/validation/functional/local/blob/test_blob.py b/tests/validation/functional/local/blob/test_blob.py index e45783ab6..e98e69e46 100644 --- a/tests/validation/functional/local/blob/test_blob.py +++ b/tests/validation/functional/local/blob/test_blob.py @@ -16,12 +16,7 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize( - "file", - [ - pytest.param("random_bin_100M", marks=pytest.mark.smoke), - ], -) +@pytest.mark.parametrize("file", [file for file in blob_files.keys()]) def test_blob(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: # Get TX and RX hosts diff --git a/tests/validation/functional/local/blob/test_blob_25_03.py b/tests/validation/functional/local/blob/test_blob_25_03.py index 0f535b84f..a767351c1 100644 --- a/tests/validation/functional/local/blob/test_blob_25_03.py +++ b/tests/validation/functional/local/blob/test_blob_25_03.py @@ -17,7 +17,7 @@ ) from Engine.media_files import blob_files_25_03 - +@pytest.mark.smoke @pytest.mark.parametrize("file", [file for file in blob_files_25_03.keys()]) def test_blob_25_03( build_TestApp, hosts, media_proxy, media_path, file, log_path From a83675d5a46ae936fa02c1dc0ae5f0556673c7d0 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 11 Aug 2025 15:56:18 +0000 Subject: [PATCH 045/123] make build run on self-hosted --- .github/workflows/base_build.yml | 7 ++++++- .github/workflows/smoke_tests.yml | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index 8dca58e4c..dc47ffa48 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -17,6 +17,11 @@ on: required: false type: string description: "Tag to checkout" + use_self_hosted: + required: false + type: boolean + default: false + description: "Whether to use self-hosted runners" env: BUILD_TYPE: Release @@ -38,7 +43,7 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: "ubuntu-22.04" + runs-on: ${{ inputs.use_self_hosted && fromJSON('["Linux", "self-hosted"]') || 'ubuntu-22.04' }} timeout-minutes: 120 steps: - name: "Harden Runner" diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 3d624c86d..0f31d8b1f 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -60,6 +60,7 @@ jobs: with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} tag: ${{ github.event.inputs.tag-to-checkout }} # added tag input + use_self_hosted: true validation-prepare-setup-mcm: runs-on: [Linux, self-hosted] @@ -79,6 +80,16 @@ jobs: sudo chown -R "${USER}" "$(pwd)" || true env | grep BUILD_ || true env | grep DPDK_ || true + - name: "Verify build artifacts" + run: | + # Just verify that binaries exist and are executable + echo "Verifying binaries in build directory..." + ls -la ${{ env.BUILD_DIR }}/mcm/bin/ || true + ls -la ${{ env.BUILD_DIR }}/mcm/bin/media_proxy || true + ls -la ${{ env.BUILD_DIR }}/mcm/bin/mesh-agent || true + ls -la ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* || true + ls -la ${{ env.BUILD_DIR }}/ffmpeg-6-1/ || true + ls -la ${{ env.BUILD_DIR }}/ffmpeg-7-0/ || true - name: "Checkout back to ${{ github.ref_name }} branch" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: From 5c19c619d6be92856fa36fc4e13f435a6029f4bc Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 11 Aug 2025 16:02:25 +0000 Subject: [PATCH 046/123] Install CMake from Kitware --- .github/workflows/base_build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index dc47ffa48..c220dbf9f 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -56,6 +56,12 @@ jobs: with: ref: ${{ inputs.tag || inputs.branch }} # Use tag if provided, otherwise use branch + - name: "Install CMake from Kitware" + run: | + wget https://github.com/Kitware/CMake/releases/download/v3.22.6/cmake-3.22.6-linux-x86_64.sh + sudo sh cmake-3.22.6-linux-x86_64.sh --skip-license --prefix=/usr/local + cmake --version + - name: "Install OS level dependencies" run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' From e22bc5c2aaee3b061b8db4b66f182b801c3783d1 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 11 Aug 2025 16:06:07 +0000 Subject: [PATCH 047/123] Install CMake via pip --- .github/workflows/base_build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index c220dbf9f..8797bd24e 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -56,10 +56,10 @@ jobs: with: ref: ${{ inputs.tag || inputs.branch }} # Use tag if provided, otherwise use branch - - name: "Install CMake from Kitware" + - name: "Install CMake via pip" run: | - wget https://github.com/Kitware/CMake/releases/download/v3.22.6/cmake-3.22.6-linux-x86_64.sh - sudo sh cmake-3.22.6-linux-x86_64.sh --skip-license --prefix=/usr/local + pip install --upgrade pip + pip install cmake cmake --version - name: "Install OS level dependencies" From 3e53995b40d4da56dc4d6d40e24bbf541977d94d Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 11 Aug 2025 16:09:21 +0000 Subject: [PATCH 048/123] Install CMake via pip --- .github/workflows/base_build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index 8797bd24e..d15309ea8 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -58,8 +58,8 @@ jobs: - name: "Install CMake via pip" run: | - pip install --upgrade pip - pip install cmake + pip install --break-system-packages --upgrade pip + pip install --break-system-packages cmake cmake --version - name: "Install OS level dependencies" From 22c91aef8fa60b907cdef3ef82e487cec557ad62 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 11 Aug 2025 16:19:41 +0000 Subject: [PATCH 049/123] Install CMake from Kitware --- .github/workflows/base_build.yml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index d15309ea8..edeff3ee0 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -56,14 +56,28 @@ jobs: with: ref: ${{ inputs.tag || inputs.branch }} # Use tag if provided, otherwise use branch - - name: "Install CMake via pip" + - name: "Install CMake from Kitware" run: | - pip install --break-system-packages --upgrade pip - pip install --break-system-packages cmake + # Remove any existing CMake installations to avoid conflicts + sudo apt-get remove --purge -y cmake cmake-data + sudo rm -rf /usr/local/share/cmake-* + + # Download and install CMake 3.22.6 + wget https://github.com/Kitware/CMake/releases/download/v3.22.6/cmake-3.22.6-linux-x86_64.sh + chmod +x cmake-3.22.6-linux-x86_64.sh + sudo ./cmake-3.22.6-linux-x86_64.sh --skip-license --prefix=/usr/local + + # Ensure the new CMake is in the PATH and update the GitHub ENV + export PATH=/usr/local/bin:$PATH + echo "PATH=/usr/local/bin:$PATH" >> $GITHUB_ENV + hash -r cmake --version - name: "Install OS level dependencies" - run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' + run: | + # Create a symbolic link to make apt think cmake is already installed + sudo ln -sf /usr/local/bin/cmake /usr/bin/cmake + eval 'source scripts/setup_build_env.sh && install_package_dependencies' - name: "Check local dependencies build cache" id: load-local-dependencies-cache From cce3b63c6c9d54b4f32f79cf02c6ecdb923e10e5 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 19 Aug 2025 10:38:46 +0000 Subject: [PATCH 050/123] add get_log_folder_path --- tests/validation/Engine/mcm_apps.py | 15 +++++++++++++++ tests/validation/README.md | 9 ++++++--- tests/validation/conftest.py | 12 ++++-------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/tests/validation/Engine/mcm_apps.py b/tests/validation/Engine/mcm_apps.py index 680737111..351118eed 100644 --- a/tests/validation/Engine/mcm_apps.py +++ b/tests/validation/Engine/mcm_apps.py @@ -17,6 +17,21 @@ logger = logging.getLogger(__name__) +def get_log_folder_path(test_config: dict) -> Path: + """ + Returns the path to the log folder on the given host. + If the host has a custom log folder path set in its extra_info, it will return that. + Otherwise, it returns the default log folder path. + """ + validation_dir = Path(__file__).parent.parent + log_path = test_config.get("log_path") + default_log_path = Path(validation_dir, LOG_FOLDER) + + if log_path: + return Path(log_path) + return default_log_path + + def get_mtl_path(host) -> str: """ Returns the path to the Media Transport Library (MTL) on the given host. diff --git a/tests/validation/README.md b/tests/validation/README.md index 09f7df1ab..6e59a6a33 100644 --- a/tests/validation/README.md +++ b/tests/validation/README.md @@ -65,9 +65,12 @@ To manually run the `test_blob_25_03` test from `test_blob_25_03.py` with the pa ```bash sudo .venv/bin/python3 -m pytest \ - --topology_config=./configs/topology_config_workflow.yaml \ - --test_config=./configs/test_config_workflow.yaml \ - ./functional/local/blob/test_blob_25_03.py::test_blob_25_03[|file = random_bin_100M|] + --topology_config=./configs/topology_config.yaml \ + --test_config=./configs/test_config.yaml \ + ./functional/local/blob/test_blob_25_03.py::test_blob_25_03[|file = random_bin_100M|] \ + --template=html/index.html --report=report.html \ + --json-report --json-report-file=report.json \ + --show-capture=no ``` To collect all smoke tests use: diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index 001fa7d95..430bf749c 100644 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -28,12 +28,11 @@ DEFAULT_OPENH264_PATH, DEFAULT_OUTPUT_PATH, INTEL_BASE_PATH, - LOG_FOLDER, MCM_BUILD_PATH, MTL_BUILD_PATH, OPENH264_VERSION_TAG, ) -from Engine.mcm_apps import MediaProxy, MeshAgent, get_mcm_path, get_mtl_path +from Engine.mcm_apps import MediaProxy, MeshAgent, get_mcm_path, get_mtl_path, get_log_folder_path from datetime import datetime import re @@ -179,9 +178,8 @@ def log_path_dir(test_config: dict) -> str: """ Creates and returns the main log directory path for the test session. - The directory is created under either the path specified in test_config['log_path'] - or under the default LOG_FOLDER in the validation directory. If keep_logs is False, - the existing log directory is removed before creating a new one. + The directory is created under the path provided by get_log_folder_path. + If keep_logs is False, the existing log directory is removed before creating a new one. :param test_config: Dictionary containing test configuration. :return: Path to the main log directory for the session. @@ -190,9 +188,7 @@ def log_path_dir(test_config: dict) -> str: keep_logs = test_config.get("keep_logs", True) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") log_dir_name = f"log_{timestamp}" - validation_dir = Path(__file__).parent - log_path = test_config.get("log_path") - log_dir = Path(log_path) if log_path else Path(validation_dir, LOG_FOLDER) + log_dir = get_log_folder_path(test_config) if log_dir.exists() and not keep_logs: shutil.rmtree(log_dir) From ff666b2953180f3336504fb65fe7114a2ef81acf Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 19 Aug 2025 15:32:23 +0000 Subject: [PATCH 051/123] parametrize added to correct logging in demo --- tests/validation/.gitignore | 2 +- tests/validation/Engine/mcm_apps.py | 3 +++ tests/validation/Engine/rx_tx_app_engine_mcm.py | 16 ++++------------ tests/validation/conftest.py | 4 ++-- tests/validation/functional/test_demo.py | 12 ++++++++++-- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/tests/validation/.gitignore b/tests/validation/.gitignore index 192e7bc5e..ee45cec74 100644 --- a/tests/validation/.gitignore +++ b/tests/validation/.gitignore @@ -1,5 +1,5 @@ .venv .vscode -logs +logs* pytest.log __pycache__ diff --git a/tests/validation/Engine/mcm_apps.py b/tests/validation/Engine/mcm_apps.py index 351118eed..a7ec19d4c 100644 --- a/tests/validation/Engine/mcm_apps.py +++ b/tests/validation/Engine/mcm_apps.py @@ -123,6 +123,9 @@ def output_validator( with open(log_file_path, "r") as f: output = f.read() + if output is None: + output = "" + errors = [] phrase_mismatches = [] diff --git a/tests/validation/Engine/rx_tx_app_engine_mcm.py b/tests/validation/Engine/rx_tx_app_engine_mcm.py index 48bfc052a..7261c1ba0 100644 --- a/tests/validation/Engine/rx_tx_app_engine_mcm.py +++ b/tests/validation/Engine/rx_tx_app_engine_mcm.py @@ -333,16 +333,12 @@ def start(self): cmd, shell=True, stderr_to_stdout=True, cwd=self.app_path ) - # Start background logging thread - subdir = f"RxTx/{self.host.name}" - filename = "tx.log" - def log_output(): log_dir = self.log_path if self.log_path is not None else LOG_FOLDER for line in self.process.get_stdout_iter(): save_process_log( - subdir=subdir, - filename=filename, + subdir=f"RxTx/{self.host.name}", + filename="tx.log", text=line.rstrip(), cmd=cmd, log_dir=log_dir, @@ -389,16 +385,12 @@ def start(self): cmd, shell=True, stderr_to_stdout=True, cwd=self.app_path ) - # Start background logging thread - subdir = f"RxTx/{self.host.name}" - filename = "rx.log" - def log_output(): log_dir = self.log_path if self.log_path is not None else LOG_FOLDER for line in self.process.get_stdout_iter(): save_process_log( - subdir=subdir, - filename=filename, + subdir=f"RxTx/{self.host.name}", + filename="rx.log", text=line.rstrip(), cmd=cmd, log_dir=log_dir, diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index 430bf749c..fac402989 100644 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -208,7 +208,7 @@ def sanitize_name(test_name): @pytest.fixture(scope="function") -def log_path(log_path_dir: str, request) -> str: +def log_path(log_path_dir: str, request) -> Path: """ Create a test-specific subdirectory within the main log directory. Sanitizes test names to ensure valid directory names by replacing invalid characters. @@ -223,7 +223,7 @@ def log_path(log_path_dir: str, request) -> str: sanitized_name = sanitize_name(test_name) test_log_path = Path(log_path_dir, sanitized_name) test_log_path.mkdir(parents=True, exist_ok=True) - return str(test_log_path) + return Path(test_log_path) @pytest.fixture(scope="session") diff --git a/tests/validation/functional/test_demo.py b/tests/validation/functional/test_demo.py index 0ca2c64c7..2e9f670d7 100644 --- a/tests/validation/functional/test_demo.py +++ b/tests/validation/functional/test_demo.py @@ -77,7 +77,8 @@ def test_list_command_on_sut(hosts): @pytest.mark.smoke -def test_mesh_agent_lifecycle(mesh_agent): +@pytest.mark.parametrize("logging", ["logging_on"]) +def test_mesh_agent_lifecycle(mesh_agent, logging): """Test starting and stopping the mesh agent.""" logger.info("Testing mesh_agent lifecycle") assert ( @@ -88,7 +89,8 @@ def test_mesh_agent_lifecycle(mesh_agent): @pytest.mark.smoke -def test_media_proxy(media_proxy): +@pytest.mark.parametrize("logging", ["logging_on"]) +def test_media_proxy(media_proxy, logging): """Test starting and stopping the media proxy without sudo.""" logger.info("Testing media_proxy lifecycle") for proxy in media_proxy.values(): @@ -502,3 +504,9 @@ def test_build_mtl_ffmpeg(build_mtl_ffmpeg, hosts, test_config): """ logger.info("Testing MTL FFmpeg build process") assert build_mtl_ffmpeg, "MTL FFmpeg build failed" + +def test_simple(log_path_dir, log_path, request): + # For this test, log_path will be based on "test_simple" + logging.info(f"Log path dir for test_simple: {log_path_dir}") + logging.info(f"Log path for test_simple: {log_path}") + logging.info(f"Request: {request.node.name}") \ No newline at end of file From dc916c4b2be0ee3a1b5c23ef23c77171ccb406ae Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 19 Aug 2025 15:48:53 +0000 Subject: [PATCH 052/123] ffmpeg logging fix --- tests/validation/common/ffmpeg_handler/ffmpeg.py | 1 + .../functional/cluster/audio/test_ffmpeg_audio.py | 6 +++--- .../functional/cluster/video/test_ffmpeg_video.py | 6 +++--- .../functional/local/audio/test_ffmpeg_audio.py | 6 +++--- .../functional/local/video/test_ffmpeg_video.py | 6 +++--- .../st2110/st20/test_3_2_st2110_standalone_video.py | 2 +- .../st2110/st20/test_6_1_st2110_ffmpeg_video.py | 5 ++++- .../st2110/st20/test_ffmpeg_mcm_to_mtl_video.py | 6 +++--- .../st2110/st20/test_ffmpeg_mtl_to_mcm_video.py | 6 +++--- .../st2110/st30/test_3_2_st2110_standalone_audio.py | 2 +- .../st2110/st30/test_6_1_st2110_ffmpeg_audio.py | 5 ++++- .../st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py | 6 +++--- .../st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py | 6 +++--- tests/validation/functional/test_demo.py | 12 ++++++------ 14 files changed, 41 insertions(+), 34 deletions(-) diff --git a/tests/validation/common/ffmpeg_handler/ffmpeg.py b/tests/validation/common/ffmpeg_handler/ffmpeg.py index 33bc8b38f..f1a5734c1 100644 --- a/tests/validation/common/ffmpeg_handler/ffmpeg.py +++ b/tests/validation/common/ffmpeg_handler/ffmpeg.py @@ -118,6 +118,7 @@ def start(self): filename = prefix + filename def log_output(): + log_dir = self.log_path if self.log_path is not None else LOG_FOLDER for line in ffmpeg_process.get_stdout_iter(): save_process_log( subdir=subdir, diff --git a/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py b/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py index 9b29cef98..3a5b25d00 100644 --- a/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py @@ -23,7 +23,7 @@ @pytest.mark.parametrize("audio_type", [file for file in audio_files.keys()]) -def test_cluster_ffmpeg_audio(hosts, media_proxy, test_config, audio_type: str) -> None: +def test_cluster_ffmpeg_audio(hosts, media_proxy, test_config, audio_type: str, log_path) -> None: # Get TX and RX hosts host_list = list(hosts.values()) if len(host_list) < 2: @@ -75,7 +75,7 @@ def test_cluster_ffmpeg_audio(hosts, media_proxy, test_config, audio_type: str) ) logger.debug(f"Tx command on {tx_host.name}: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMultipointGroupAudioIO( @@ -100,7 +100,7 @@ def test_cluster_ffmpeg_audio(hosts, media_proxy, test_config, audio_type: str) ) logger.debug(f"Rx command on {rx_host.name}: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) mcm_rx_executor.start() mcm_tx_executor.start() diff --git a/tests/validation/functional/cluster/video/test_ffmpeg_video.py b/tests/validation/functional/cluster/video/test_ffmpeg_video.py index ec26a0d74..7d47298ce 100644 --- a/tests/validation/functional/cluster/video/test_ffmpeg_video.py +++ b/tests/validation/functional/cluster/video/test_ffmpeg_video.py @@ -20,7 +20,7 @@ @pytest.mark.parametrize("video_type", [k for k in yuv_files.keys()]) -def test_cluster_ffmpeg_video(hosts, media_proxy, test_config, video_type: str) -> None: +def test_cluster_ffmpeg_video(hosts, media_proxy, test_config, video_type: str, log_path) -> None: # Get TX and RX hosts host_list = list(hosts.values()) if len(host_list) < 2: @@ -68,7 +68,7 @@ def test_cluster_ffmpeg_video(hosts, media_proxy, test_config, video_type: str) ) logger.debug(f"Tx command on {tx_host.name}: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMultipointGroupVideoIO( @@ -95,7 +95,7 @@ def test_cluster_ffmpeg_video(hosts, media_proxy, test_config, video_type: str) ) logger.debug(f"Rx command on {rx_host.name}: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) mcm_rx_executor.start() mcm_tx_executor.start() diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index e540f01f6..1659160ce 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -30,7 +30,7 @@ *[f for f in audio_files_25_03.keys() if f != "PCM16_48000_Stereo"], ], ) -def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str) -> None: +def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, log_path) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts host_list = list(hosts.values()) @@ -82,7 +82,7 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str) -> ) logger.debug(f"Tx command: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMemifAudioIO( @@ -107,7 +107,7 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str) -> ) logger.debug(f"Rx command: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) mcm_rx_executor.start() mcm_tx_executor.start() diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index 1afc71a52..255f58901 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -30,7 +30,7 @@ *[f for f in video_files_25_03.keys() if f != "FullHD_59.94"], ], ) -def test_local_ffmpeg_video(media_proxy, hosts, test_config, file: str) -> None: +def test_local_ffmpeg_video(media_proxy, hosts, test_config, file: str, log_path) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts host_list = list(hosts.values()) @@ -80,7 +80,7 @@ def test_local_ffmpeg_video(media_proxy, hosts, test_config, file: str) -> None: ) logger.debug(f"Tx command: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMemifVideoIO( @@ -107,7 +107,7 @@ def test_local_ffmpeg_video(media_proxy, hosts, test_config, file: str) -> None: ) logger.debug(f"Rx command: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) mcm_rx_executor.start() mcm_tx_executor.start() diff --git a/tests/validation/functional/st2110/st20/test_3_2_st2110_standalone_video.py b/tests/validation/functional/st2110/st20/test_3_2_st2110_standalone_video.py index fe8db755c..b98b960d0 100644 --- a/tests/validation/functional/st2110/st20/test_3_2_st2110_standalone_video.py +++ b/tests/validation/functional/st2110/st20/test_3_2_st2110_standalone_video.py @@ -94,7 +94,7 @@ def test_3_2_st2110_standalone_video(hosts, test_config, video_type, log_path): ffmpeg_output=rx_ffmpeg_output, yes_overwrite=True, ) - rx_executor = FFmpegExecutor(rx_host, ffmpeg_instance=rx_ffmpeg) + rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=rx_ffmpeg) tx_executor.start() sleep(MCM_ESTABLISH_TIMEOUT) diff --git a/tests/validation/functional/st2110/st20/test_6_1_st2110_ffmpeg_video.py b/tests/validation/functional/st2110/st20/test_6_1_st2110_ffmpeg_video.py index 6b825e229..c8040e81d 100644 --- a/tests/validation/functional/st2110/st20/test_6_1_st2110_ffmpeg_video.py +++ b/tests/validation/functional/st2110/st20/test_6_1_st2110_ffmpeg_video.py @@ -19,7 +19,7 @@ @pytest.mark.usefixtures("media_proxy") @pytest.mark.parametrize("video_file", video_files) -def test_6_1_st2110_ffmpeg_video(hosts, test_config, video_file): +def test_6_1_st2110_ffmpeg_video(hosts, test_config, video_file, log_path): video_file = video_files[video_file] tx_host = hosts["mesh-agent"] @@ -76,6 +76,7 @@ def test_6_1_st2110_ffmpeg_video(hosts, test_config, video_file): mtl_tx_ffmpeg_executor = FFmpegExecutor( host=tx_host, ffmpeg_instance=mtl_tx_ffmpeg, + log_path=log_path, ) # Host A --- MCM FFmpeg Rx @@ -115,6 +116,7 @@ def test_6_1_st2110_ffmpeg_video(hosts, test_config, video_file): mcm_rx_a_ffmpeg_executor = FFmpegExecutor( host=rx_a_host, ffmpeg_instance=mcm_rx_a_ffmpeg, + log_path=log_path, ) # Host B --- MCM FFmpeg Rx @@ -154,6 +156,7 @@ def test_6_1_st2110_ffmpeg_video(hosts, test_config, video_file): mcm_rx_b_ffmpeg_executor = FFmpegExecutor( host=rx_b_host, ffmpeg_instance=mcm_rx_b_ffmpeg, + log_path=log_path, ) mtl_tx_ffmpeg_executor.start() diff --git a/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py b/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py index 1dd5e7287..c0d008398 100644 --- a/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py +++ b/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py @@ -29,7 +29,7 @@ @pytest.mark.parametrize("video_type", [k for k in yuv_files.keys()]) def test_st2110_ffmpeg_mcm_to_mtl_video( - media_proxy, hosts, test_config, video_type: str + media_proxy, hosts, test_config, video_type: str, log_path ) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts @@ -94,7 +94,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) # MTL Rx mtl_rx_inp = FFmpegMtlSt20pRx( @@ -136,7 +136,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( yes_overwrite=True, ) logger.debug(f"Rx command executed on {rx_host.name}: {mtl_rx_ff.get_command()}") - mtl_rx_executor = FFmpegExecutor(rx_host, ffmpeg_instance=mtl_rx_ff) + mtl_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mtl_rx_ff) time.sleep(2) # wait for media_proxy to start mtl_rx_executor.start() diff --git a/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py b/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py index 3db28eb1e..b3f85111c 100644 --- a/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py +++ b/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py @@ -32,7 +32,7 @@ @pytest.mark.parametrize("video_type", [k for k in yuv_files.keys()]) -def test_st2110_ffmpeg_video(media_proxy, hosts, test_config, video_type: str) -> None: +def test_st2110_ffmpeg_video(media_proxy, hosts, test_config, video_type: str, log_path) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts host_list = list(hosts.values()) @@ -97,7 +97,7 @@ def test_st2110_ffmpeg_video(media_proxy, hosts, test_config, video_type: str) - yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mtl_tx_ff.get_command()}") - mtl_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mtl_tx_ff) + mtl_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mtl_tx_ff) # MCM Rx mcm_rx_inp = FFmpegMcmST2110VideoRx( @@ -129,7 +129,7 @@ def test_st2110_ffmpeg_video(media_proxy, hosts, test_config, video_type: str) - yes_overwrite=True, ) logger.debug(f"Rx command executed on {rx_host.name}: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) time.sleep(2) # wait for media_proxy to start mcm_rx_executor.start() diff --git a/tests/validation/functional/st2110/st30/test_3_2_st2110_standalone_audio.py b/tests/validation/functional/st2110/st30/test_3_2_st2110_standalone_audio.py index e35714b7f..5b938afa9 100644 --- a/tests/validation/functional/st2110/st30/test_3_2_st2110_standalone_audio.py +++ b/tests/validation/functional/st2110/st30/test_3_2_st2110_standalone_audio.py @@ -107,7 +107,7 @@ def test_3_2_st2110_standalone_audio(hosts, test_config, audio_type, log_path): ffmpeg_output=rx_ffmpeg_output, yes_overwrite=True, ) - rx_executor = FFmpegExecutor(rx_host, ffmpeg_instance=rx_ffmpeg) + rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=rx_ffmpeg) tx_executor.start() sleep(MCM_ESTABLISH_TIMEOUT) diff --git a/tests/validation/functional/st2110/st30/test_6_1_st2110_ffmpeg_audio.py b/tests/validation/functional/st2110/st30/test_6_1_st2110_ffmpeg_audio.py index 402e0b57c..45ba18339 100644 --- a/tests/validation/functional/st2110/st30/test_6_1_st2110_ffmpeg_audio.py +++ b/tests/validation/functional/st2110/st30/test_6_1_st2110_ffmpeg_audio.py @@ -19,7 +19,7 @@ @pytest.mark.usefixtures("media_proxy") @pytest.mark.parametrize("audio_file", audio_files) -def test_6_1_st2110_ffmpeg_audio(hosts, test_config, audio_file): +def test_6_1_st2110_ffmpeg_audio(hosts, test_config, audio_file, log_path): audio_file = audio_files[audio_file] tx_host = hosts["mesh-agent"] @@ -85,6 +85,7 @@ def test_6_1_st2110_ffmpeg_audio(hosts, test_config, audio_file): mtl_tx_ffmpeg_executor = FFmpegExecutor( host=tx_host, ffmpeg_instance=mtl_tx_ffmpeg, + log_path=log_path, ) # Host A --- MCM FFmpeg Rx @@ -122,6 +123,7 @@ def test_6_1_st2110_ffmpeg_audio(hosts, test_config, audio_file): mcm_rx_a_ffmpeg_executor = FFmpegExecutor( host=rx_a_host, ffmpeg_instance=mcm_rx_a_ffmpeg, + log_path=log_path, ) # Host B --- MCM FFmpeg Rx @@ -159,6 +161,7 @@ def test_6_1_st2110_ffmpeg_audio(hosts, test_config, audio_file): mcm_rx_b_ffmpeg_executor = FFmpegExecutor( host=rx_b_host, ffmpeg_instance=mcm_rx_b_ffmpeg, + log_path=log_path, ) mtl_tx_ffmpeg_executor.start() diff --git a/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py b/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py index d50185f36..232f109b7 100644 --- a/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py +++ b/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py @@ -30,7 +30,7 @@ @pytest.mark.parametrize("audio_type", [k for k in audio_files.keys()]) def test_st2110_ffmpeg_mcm_to_mtl_audio( - media_proxy, hosts, test_config, audio_type: str + media_proxy, hosts, test_config, audio_type: str, log_path ) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts @@ -109,7 +109,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) # MTL Rx mtl_rx_inp = FFmpegMtlSt30pRx( # TODO: Verify the variables @@ -148,7 +148,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( yes_overwrite=True, ) logger.debug(f"Rx command executed on {rx_host.name}: {mtl_rx_ff.get_command()}") - mtl_rx_executor = FFmpegExecutor(rx_host, ffmpeg_instance=mtl_rx_ff) + mtl_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mtl_rx_ff) time.sleep(2) # wait for media_proxy to start mtl_rx_executor.start() diff --git a/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py b/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py index 6ddd69092..f793906f4 100644 --- a/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py +++ b/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py @@ -36,7 +36,7 @@ @pytest.mark.parametrize("audio_type", [k for k in audio_files.keys()]) def test_st2110_ffmpeg_mtl_to_mcm_audio( - media_proxy, hosts, test_config, audio_type: str + media_proxy, hosts, test_config, audio_type: str, log_path ) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts @@ -115,7 +115,7 @@ def test_st2110_ffmpeg_mtl_to_mcm_audio( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mtl_tx_ff.get_command()}") - mtl_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mtl_tx_ff) + mtl_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mtl_tx_ff) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmST2110AudioRx( @@ -145,7 +145,7 @@ def test_st2110_ffmpeg_mtl_to_mcm_audio( yes_overwrite=True, ) logger.debug(f"Rx command executed on {rx_host.name}: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) time.sleep(2) # wait for media_proxy to start mcm_rx_executor.start() diff --git a/tests/validation/functional/test_demo.py b/tests/validation/functional/test_demo.py index 2e9f670d7..3c5f05f2e 100644 --- a/tests/validation/functional/test_demo.py +++ b/tests/validation/functional/test_demo.py @@ -115,7 +115,7 @@ def test_sudo_command(hosts): logger.info("Sudo command execution test completed") -def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config) -> None: +def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config, log_path) -> None: # media_proxy fixture used only to ensure that the media proxy is running tx_host = rx_host = list(hosts.values())[0] prefix_variables = test_config.get("prefix_variables", {}) @@ -167,7 +167,7 @@ def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config) -> N ) logger.debug(f"Tx command: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMemifVideoIO( @@ -196,7 +196,7 @@ def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config) -> N ) logger.debug(f"Rx command: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) integrator = FileVideoIntegrityRunner( host=rx_host, @@ -219,7 +219,7 @@ def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config) -> N assert result, "Integrity check failed" -def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config) -> None: +def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config, log_path) -> None: # media_proxy fixture used only to ensure that the media proxy is running tx_host = rx_host = list(hosts.values())[0] prefix_variables = test_config.get("prefix_variables", {}) @@ -272,7 +272,7 @@ def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config) -> None ) logger.debug(f"Tx command: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMemifVideoIO( @@ -302,7 +302,7 @@ def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config) -> None ) logger.debug(f"Rx command: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) integrator = StreamVideoIntegrityRunner( host=rx_host, From fb8cc01e93348230c3941526e239a42d24801405 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 19 Aug 2025 16:12:06 +0000 Subject: [PATCH 053/123] shutting to shuting and app to App --- tests/tools/TestApp/rx_blob_app.c | 4 ++-- tests/tools/TestApp/rx_video_app.c | 4 ++-- tests/tools/TestApp/tx_audio_app.c | 6 +++--- tests/tools/TestApp/tx_blob_app.c | 6 +++--- tests/tools/TestApp/tx_video_app.c | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/tools/TestApp/rx_blob_app.c b/tests/tools/TestApp/rx_blob_app.c index 0a8ad0dc1..c4c94f43f 100644 --- a/tests/tools/TestApp/rx_blob_app.c +++ b/tests/tools/TestApp/rx_blob_app.c @@ -60,11 +60,11 @@ int main(int argc, char *argv[]) { safe_exit: LOG("[RX] dropping connection to media-proxy..."); if (connection) { - LOG("[RX] Shuting down connection"); + LOG("[RX] Shutting down connection"); mesh_delete_connection(&connection); } if (client) { - LOG("[RX] Shuting down client"); + LOG("[RX] Shutting down client"); mesh_delete_client(&client); } free(client_cfg); diff --git a/tests/tools/TestApp/rx_video_app.c b/tests/tools/TestApp/rx_video_app.c index 36fde8d9f..495c78975 100644 --- a/tests/tools/TestApp/rx_video_app.c +++ b/tests/tools/TestApp/rx_video_app.c @@ -60,11 +60,11 @@ int main(int argc, char *argv[]) { safe_exit: LOG("[RX] dropping connection to media-proxy..."); if (connection) { - LOG("[RX] Shuting down connection"); + LOG("[RX] Shutting down connection"); mesh_delete_connection(&connection); } if (client) { - LOG("[RX] Shuting down client"); + LOG("[RX] Shutting down client"); mesh_delete_client(&client); } free(client_cfg); diff --git a/tests/tools/TestApp/tx_audio_app.c b/tests/tools/TestApp/tx_audio_app.c index f3ea5a070..c4a4f663b 100644 --- a/tests/tools/TestApp/tx_audio_app.c +++ b/tests/tools/TestApp/tx_audio_app.c @@ -36,7 +36,7 @@ int main(int argc, char **argv) { char *conn_cfg_file = argv[2]; char *video_file = argv[3]; - LOG("[TX] Launching TX app"); + LOG("[TX] Launching Tx App"); LOG("[TX] Reading client configuration..."); client_cfg = input_parse_file_to_string(client_cfg_file); @@ -91,11 +91,11 @@ int main(int argc, char **argv) { } safe_exit: - LOG("[TX] Shuting down connection"); + LOG("[TX] Shutting down connection"); if (connection) { mesh_delete_connection(&connection); } - LOG("[TX] Shuting down client"); + LOG("[TX] Shutting down client"); if (client) { mesh_delete_client(&client); } diff --git a/tests/tools/TestApp/tx_blob_app.c b/tests/tools/TestApp/tx_blob_app.c index 7092a60fa..df30805ee 100644 --- a/tests/tools/TestApp/tx_blob_app.c +++ b/tests/tools/TestApp/tx_blob_app.c @@ -36,7 +36,7 @@ int main(int argc, char **argv) { char *conn_cfg_file = argv[2]; char *video_file = argv[3]; - LOG("[TX] Launching TX app"); + LOG("[TX] Launching Tx App"); LOG("[TX] Reading client configuration..."); client_cfg = input_parse_file_to_string(client_cfg_file); @@ -83,11 +83,11 @@ int main(int argc, char **argv) { } } safe_exit: - LOG("[TX] Shuting down connection"); + LOG("[TX] Shutting down connection"); if (connection) { mesh_delete_connection(&connection); } - LOG("[TX] Shuting down client"); + LOG("[TX] Shutting down client"); if (client) { mesh_delete_client(&client); } diff --git a/tests/tools/TestApp/tx_video_app.c b/tests/tools/TestApp/tx_video_app.c index 343164748..fc44466be 100644 --- a/tests/tools/TestApp/tx_video_app.c +++ b/tests/tools/TestApp/tx_video_app.c @@ -36,7 +36,7 @@ int main(int argc, char **argv) { char *conn_cfg_file = argv[2]; char *video_file = argv[3]; - LOG("[TX] Launching TX app"); + LOG("[TX] Launching Tx App"); LOG("[TX] Reading client configuration..."); client_cfg = input_parse_file_to_string(client_cfg_file); @@ -91,11 +91,11 @@ int main(int argc, char **argv) { } safe_exit: - LOG("[TX] Shuting down connection"); + LOG("[TX] Shutting down connection"); if (connection) { mesh_delete_connection(&connection); } - LOG("[TX] Shuting down client"); + LOG("[TX] Shutting down client"); if (client) { mesh_delete_client(&client); } From 61fc2fc294f676b846039b476cdeec6aef334c15 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 19 Aug 2025 16:15:04 +0000 Subject: [PATCH 054/123] RX to Rx --- tests/tools/TestApp/rx_audio_app.c | 2 +- tests/tools/TestApp/rx_blob_app.c | 2 +- tests/tools/TestApp/rx_video_app.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/tools/TestApp/rx_audio_app.c b/tests/tools/TestApp/rx_audio_app.c index c19b62a20..268947bbc 100644 --- a/tests/tools/TestApp/rx_audio_app.c +++ b/tests/tools/TestApp/rx_audio_app.c @@ -35,7 +35,7 @@ int main(int argc, char *argv[]) { char *conn_cfg_file = argv[2]; char *out_filename = argv[3]; - LOG("[RX] Launching RX App"); + LOG("[RX] Launching Rx App"); LOG("[RX] Reading client configuration..."); client_cfg = input_parse_file_to_string(client_cfg_file); LOG("[RX] Reading connection configuration..."); diff --git a/tests/tools/TestApp/rx_blob_app.c b/tests/tools/TestApp/rx_blob_app.c index c4c94f43f..ad4c9cab0 100644 --- a/tests/tools/TestApp/rx_blob_app.c +++ b/tests/tools/TestApp/rx_blob_app.c @@ -35,7 +35,7 @@ int main(int argc, char *argv[]) { char *conn_cfg_file = argv[2]; char *out_filename = argv[3]; - LOG("[RX] Launching RX App"); + LOG("[RX] Launching Rx App"); LOG("[RX] Reading client configuration..."); client_cfg = input_parse_file_to_string(client_cfg_file); LOG("[RX] Reading connection configuration..."); diff --git a/tests/tools/TestApp/rx_video_app.c b/tests/tools/TestApp/rx_video_app.c index 495c78975..a8adc06f5 100644 --- a/tests/tools/TestApp/rx_video_app.c +++ b/tests/tools/TestApp/rx_video_app.c @@ -35,7 +35,7 @@ int main(int argc, char *argv[]) { char *conn_cfg_file = argv[2]; char *out_filename = argv[3]; - LOG("[RX] Launching RX App"); + LOG("[RX] Launching Rx App"); LOG("[RX] Reading client configuration..."); client_cfg = input_parse_file_to_string(client_cfg_file); LOG("[RX] Reading connection configuration..."); From 47842141d55e7d6efed848aead3db8943c3cc1b1 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 19 Aug 2025 16:17:06 +0000 Subject: [PATCH 055/123] correct logging validation --- tests/validation/Engine/const.py | 12 ++++----- .../validation/Engine/rx_tx_app_engine_mcm.py | 26 +++++++++++++++---- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/tests/validation/Engine/const.py b/tests/validation/Engine/const.py index cd1c94326..da17ede45 100644 --- a/tests/validation/Engine/const.py +++ b/tests/validation/Engine/const.py @@ -4,7 +4,7 @@ # Required ordered log phrases for Rx validation RX_REQUIRED_LOG_PHRASES = [ - '[RX] Launching RX App', + '[RX] Launching Rx App', '[RX] Reading client configuration', '[RX] Reading connection configuration', '[DEBU] JSON client config:', @@ -16,13 +16,13 @@ '[RX] Frame: 1', '[RX] Done reading the data', '[RX] dropping connection to media-proxy', - '[RX] Shuting down connection', + '[RX] Shutting down connection', 'INFO - memif disconnected!', '[INFO] gRPC: connection deleted', - '[RX] Shuting down client', + '[RX] Shutting down client', ] TX_REQUIRED_LOG_PHRASES = [ - '[TX] Launching TX app', + '[TX] Launching Tx App', '[TX] Reading client configuration', '[TX] Reading connection configuration', '[DEBU] JSON client config:', @@ -34,9 +34,9 @@ 'INFO - memif connected!', '[INFO] gRPC: connection active', '[TX] sending', - '[TX] Shuting down connection', + '[TX] Shutting down connection', '[INFO] gRPC: connection deleted', - '[TX] Shuting down client', + '[TX] Shutting down client', ] LOG_FOLDER = "logs" diff --git a/tests/validation/Engine/rx_tx_app_engine_mcm.py b/tests/validation/Engine/rx_tx_app_engine_mcm.py index 7261c1ba0..3a33d7e85 100644 --- a/tests/validation/Engine/rx_tx_app_engine_mcm.py +++ b/tests/validation/Engine/rx_tx_app_engine_mcm.py @@ -205,12 +205,16 @@ def stop(self): def check_phrases_in_order(log_path, phrases): found_indices = [] missing_phrases = [] + lines_around_missing = {} with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: lines = [line.strip() for line in f] + idx = 0 - for phrase in phrases: + for phrase_idx, phrase in enumerate(phrases): found = False phrase_stripped = phrase.strip() + start_idx = idx # Remember where we started searching + while idx < len(lines): line_stripped = lines[idx].strip() if phrase_stripped in line_stripped: @@ -219,13 +223,19 @@ def check_phrases_in_order(log_path, phrases): idx += 1 break idx += 1 + if not found: missing_phrases.append(phrase) - return len(missing_phrases) == 0, missing_phrases + # Store context - lines around where we were searching + context_start = max(0, start_idx - 3) + context_end = min(len(lines), start_idx + 7) + lines_around_missing[phrase] = lines[context_start:context_end] + + return len(missing_phrases) == 0, missing_phrases, lines_around_missing if self.direction in ("Rx", "Tx"): required_phrases = RX_REQUIRED_LOG_PHRASES if self.direction == "Rx" else TX_REQUIRED_LOG_PHRASES - log_pass, missing = check_phrases_in_order(log_file_path, required_phrases) + log_pass, missing, context_lines = check_phrases_in_order(log_file_path, required_phrases) self.is_pass = log_pass app_log_validation_status = log_pass app_log_error_count = len(missing) @@ -234,9 +244,15 @@ def check_phrases_in_order(log_path, phrases): validation_info.append(f"Validation result: {'PASS' if log_pass else 'FAIL'}") validation_info.append(f"Errors found: {app_log_error_count}") if not log_pass: - validation_info.append(f"Missing or out-of-order phrases:") + validation_info.append(f"Missing or out-of-order phrases analysis:") for phrase in missing: - validation_info.append(f" - {phrase}") + validation_info.append(f"\n Expected phrase: \"{phrase}\"") + validation_info.append(f" Context in log file:") + if phrase in context_lines: + for line in context_lines[phrase]: + validation_info.append(f" {line}") + else: + validation_info.append(" ") if missing: print(f"{self.direction} process did not pass. First missing phrase: {missing[0]}") else: From 84223866e205a6486a628fe5d3a76e091cd08064 Mon Sep 17 00:00:00 2001 From: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:15:09 +0200 Subject: [PATCH 056/123] Update .github/workflows/smoke_tests.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> --- .github/workflows/smoke_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 0f31d8b1f..b674902e5 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -165,12 +165,12 @@ jobs: - name: "list all tests marked with ${{ env.MARKERS }}" if: ${{ env.LIST_TESTS == 'true' }} run: | - sudo tests/validation/venv/bin/python3 -m pytest \ + tests/validation/venv/bin/python3 -m pytest \ --collect-only --quiet ./tests/validation/functional/ \ -m "${{ env.MARKERS }}" - name: "execution: Run validation-bare-metal tests in virtual environment" run: | - sudo tests/validation/venv/bin/python3 -m pytest \ + tests/validation/venv/bin/python3 -m pytest \ --topology_config=tests/validation/configs/topology_config_workflow.yaml \ --test_config=tests/validation/configs/test_config_workflow.yaml \ ./tests/validation/functional/ \ From b875ac662bfe8a07b74d60fdc106268ba2979739 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 20 Aug 2025 11:04:05 +0000 Subject: [PATCH 057/123] Fix permissions before checkout --- .github/workflows/base_build.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index edeff3ee0..ce0869c53 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -51,6 +51,13 @@ jobs: with: egress-policy: audit + - name: "Fix permissions before checkout" + run: | + if [ -d "${{ github.workspace }}" ]; then + sudo chown -R "${USER}" "${{ github.workspace }}" || true + sudo chmod -R u+w "${{ github.workspace }}" || true + fi + - name: "Checkout repository" uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: From d0250868a5bf327915d1d564c9b349b2f189e22a Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 20 Aug 2025 12:54:02 +0000 Subject: [PATCH 058/123] add mtl script path --- .github/workflows/smoke_tests.yml | 3 +++ tests/validation/configs/topology_config_workflow.yaml | 9 +++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index b674902e5..dd0fc5ca5 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -146,6 +146,9 @@ jobs: sed -i "s/{{ USER }}/root/g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MCM_PATH }}|${{ github.workspace }}|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ MCM_FFMPEG_7_0 }}|${{ env.BUILD_DIR }}/ffmpeg-7-0/|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ OUTPUT_PATH }}|${{ github.workspace }}/received/|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ NICCTL_PATH }}|${{ env.BUILD_DIR }}/mtl/script/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ LOG_PATH }}|${{ env.LOG_DIR }}|g" tests/validation/configs/test_config_workflow.yaml validation-run-tests: diff --git a/tests/validation/configs/topology_config_workflow.yaml b/tests/validation/configs/topology_config_workflow.yaml index 6ef04be0c..a2292b3b0 100644 --- a/tests/validation/configs/topology_config_workflow.yaml +++ b/tests/validation/configs/topology_config_workflow.yaml @@ -18,12 +18,9 @@ hosts: key_path: {{ KEY_PATH }} extra_info: mcm_path: {{ MCM_PATH }} - # input_path: /opt/intel/integrity/ - ffmpeg_path: ffmpeg - # output_path: /home/gta/received/ - # integrity_path: /opt/intel/val_tests/validation/common/integrity - # mtl_path: /opt/intel/mtl - # nicctl_path: /opt/intel/mtl/script + ffmpeg_path: {{ MCM_FFMPEG_7_0 }} + output_path: {{ OUTPUT_PATH }} + nicctl_path: {{ NICCTL_PATH }} mesh_agent: control_port: 8100 proxy_port: 50051 From ce0f4b86437e1fd280fa58a8c5b5f194d362fac0 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 20 Aug 2025 13:59:56 +0000 Subject: [PATCH 059/123] remove checkout --- .github/workflows/smoke_tests.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index dd0fc5ca5..65ae8eba0 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -84,16 +84,14 @@ jobs: run: | # Just verify that binaries exist and are executable echo "Verifying binaries in build directory..." + ls -la ${{ env.BUILD_DIR }}/ || true + ls -la ${{ env.BUILD_DIR }}/mtl/ || true ls -la ${{ env.BUILD_DIR }}/mcm/bin/ || true ls -la ${{ env.BUILD_DIR }}/mcm/bin/media_proxy || true ls -la ${{ env.BUILD_DIR }}/mcm/bin/mesh-agent || true ls -la ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* || true ls -la ${{ env.BUILD_DIR }}/ffmpeg-6-1/ || true ls -la ${{ env.BUILD_DIR }}/ffmpeg-7-0/ || true - - name: "Checkout back to ${{ github.ref_name }} branch" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ github.ref_name }} - name: Install RxTxApp dependencies run: sudo apt-get update && sudo apt-get install -y libjansson-dev - name: "build RxTxApp" From 476db7651fcb35cd00e2a44aa9b2896cd22febb2 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Fri, 22 Aug 2025 11:51:40 +0000 Subject: [PATCH 060/123] remove mtl and dpdk build and install --- .github/workflows/bare-metal-build.yml | 142 +++++++++++++++ .github/workflows/base_build.yml | 199 +++++++++------------- .github/workflows/smoke_tests.yml | 11 +- tests/validation/configs/config_readme.md | 15 +- 4 files changed, 235 insertions(+), 132 deletions(-) create mode 100644 .github/workflows/bare-metal-build.yml diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml new file mode 100644 index 000000000..516dd19a1 --- /dev/null +++ b/.github/workflows/bare-metal-build.yml @@ -0,0 +1,142 @@ +name: Base Build + +on: + workflow_call: + inputs: + branch: + required: false + type: string + default: "main" + description: "Branch to checkout" + tag: + required: false + type: string + description: "Tag to checkout" + +env: + BUILD_TYPE: Release + BUILD_DIR: "${{ github.workspace }}/_build" + DEBIAN_FRONTEND: noninteractive + MTL_BUILD_DISABLE_PCAPNG: true + PREFIX_DIR: "${{ github.workspace }}/_install" + +defaults: + run: + shell: bash + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + build-baremetal-ubuntu: + runs-on: [Linux, self-hosted] + timeout-minutes: 120 + steps: + - name: "Harden Runner" + uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + with: + egress-policy: audit + - name: "Fix permissions before checkout" + run: | + if [ -d "${{ github.workspace }}" ]; then + sudo chown -R "${USER}" "${{ github.workspace }}" || true + sudo chmod -R u+w "${{ github.workspace }}" || true + fi + + - name: "Checkout repository" + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ inputs.tag || inputs.branch }} + + - name: 'Install OS level dependencies' + run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' + + - name: "Check local dependencies build cache" + id: load-local-dependencies-cache + uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ env.BUILD_DIR }} + key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} + + - name: "Download, unpack and patch build dependencies" + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' + + - name: "Clone and patch ffmpeg 6.1 and 7.0" + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: | + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" + + - name: "Build and Install xdp and libbpf" + run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' + + - name: "Build and Install libfabric" + run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' + + - name: "Build and Install the DPDK (skipped: already installed on runner)" + if: always() && false # Always skip this step for now + run: | + echo "Skipping DPDK build and install as it is already installed on the machine." + # eval 'source scripts/setup_build_env.sh && lib_install_dpdk' + + # - name: "Switch DPDK version to currently used on the machine" + # run: | + # sed -i 's|DPDK_VER=23.11|DPDK_VER=25.03|g' \ + # "${{ github.workspace }}/versions.env" \ + # "${{ github.workspace }}/ffmpeg-plugin/versions.env" \ + # "${{ github.workspace }}/media-proxy/versions.env" \ + # "${{ github.workspace }}/sdk/versions.env" + + # - name: "Switch MTL version to currently used on the machine" + # run: | + # sed -i 's|MTL_VER=main|MTL_VER=main|g' \ + # "${{ github.workspace }}/versions.env" \ + # "${{ github.workspace }}/ffmpeg-plugin/versions.env" \ + # "${{ github.workspace }}/media-proxy/versions.env" \ + # "${{ github.workspace }}/sdk/versions.env" + + - name: "Build and Install the MTL(skipped: already installed on runner)" + if: always() && false # Always skip this step for now + run: | + echo "Skipping MTL build and install as it is already installed on the machine." + # eval 'source scripts/setup_build_env.sh && lib_install_mtl' + + - name: "Build and Install JPEG XS" + run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' + + - name: "Build and Install JPEG XS ffmpeg plugin" + run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' + + - name: "Build gRPC and dependencies" + run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' + + - name: "Build MCM SDK and Media Proxy" + run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' + + - name: "Build FFmpeg 6.1 with MCM plugin" + working-directory: ${{ github.workspace }}/ffmpeg-plugin + run: | + ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ + ./build-ffmpeg.sh "6.1" + + - name: "Build FFmpeg 7.0 with MCM plugin" + working-directory: ${{ github.workspace }}/ffmpeg-plugin + run: | + ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ + ./build-ffmpeg.sh "7.0" + + - name: "upload media-proxy and mcm binaries" + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: mcm-build + path: | + ${{ env.BUILD_DIR }}/mcm/bin/media_proxy + ${{ env.BUILD_DIR }}/mcm/bin/mesh-agent + ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* + ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg + ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index ce0869c53..6abb7ee03 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -2,26 +2,10 @@ name: Base Build on: push: - branches: ["main"] + branches: [ "main" ] pull_request: - branches: ["main"] + branches: [ "main" ] workflow_dispatch: - workflow_call: - inputs: - branch: - required: false - type: string - default: "main" - description: "Branch to checkout" - tag: # Add tag input - required: false - type: string - description: "Tag to checkout" - use_self_hosted: - required: false - type: boolean - default: false - description: "Whether to use self-hosted runners" env: BUILD_TYPE: Release @@ -43,109 +27,80 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: ${{ inputs.use_self_hosted && fromJSON('["Linux", "self-hosted"]') || 'ubuntu-22.04' }} + runs-on: 'ubuntu-22.04' timeout-minutes: 120 steps: - - name: "Harden Runner" - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 - with: - egress-policy: audit - - - name: "Fix permissions before checkout" - run: | - if [ -d "${{ github.workspace }}" ]; then - sudo chown -R "${USER}" "${{ github.workspace }}" || true - sudo chmod -R u+w "${{ github.workspace }}" || true - fi - - - name: "Checkout repository" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - ref: ${{ inputs.tag || inputs.branch }} # Use tag if provided, otherwise use branch - - - name: "Install CMake from Kitware" - run: | - # Remove any existing CMake installations to avoid conflicts - sudo apt-get remove --purge -y cmake cmake-data - sudo rm -rf /usr/local/share/cmake-* - - # Download and install CMake 3.22.6 - wget https://github.com/Kitware/CMake/releases/download/v3.22.6/cmake-3.22.6-linux-x86_64.sh - chmod +x cmake-3.22.6-linux-x86_64.sh - sudo ./cmake-3.22.6-linux-x86_64.sh --skip-license --prefix=/usr/local - - # Ensure the new CMake is in the PATH and update the GitHub ENV - export PATH=/usr/local/bin:$PATH - echo "PATH=/usr/local/bin:$PATH" >> $GITHUB_ENV - hash -r - cmake --version - - - name: "Install OS level dependencies" - run: | - # Create a symbolic link to make apt think cmake is already installed - sudo ln -sf /usr/local/bin/cmake /usr/bin/cmake - eval 'source scripts/setup_build_env.sh && install_package_dependencies' - - - name: "Check local dependencies build cache" - id: load-local-dependencies-cache - uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - with: - path: ${{ env.BUILD_DIR }} - key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} - - - name: "Download, unpack and patch build dependencies" - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' - - - name: "Clone and patch ffmpeg 6.1 and 7.0" - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: | - ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" - ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" - - - name: "Build and Install xdp and libbpf" - run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' - - - name: "Build and Install libfabric" - run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' - - - name: "Build and Install the DPDK" - run: eval 'source scripts/setup_build_env.sh && lib_install_dpdk' - - - name: "Build and Install the MTL" - run: eval 'source scripts/setup_build_env.sh && lib_install_mtl' - - - name: "Build and Install JPEG XS" - run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' - - - name: "Build and Install JPEG XS ffmpeg plugin" - run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' - - - name: "Build gRPC and dependencies" - run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' - - - name: "Build MCM SDK and Media Proxy" - run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' - - - name: "Build FFmpeg 6.1 with MCM plugin" - working-directory: ${{ github.workspace }}/ffmpeg-plugin - run: | - ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ - ./build-ffmpeg.sh "6.1" - - - name: "Build FFmpeg 7.0 with MCM plugin" - working-directory: ${{ github.workspace }}/ffmpeg-plugin - run: | - ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ - ./build-ffmpeg.sh "7.0" - - - name: "upload media-proxy and mcm binaries" - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: mcm-build - path: | - ${{ env.BUILD_DIR }}/mcm/bin/media_proxy - ${{ env.BUILD_DIR }}/mcm/bin/mesh-agent - ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* - ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg - ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg + - name: 'Harden Runner' + uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + with: + egress-policy: audit + + - name: 'Checkout repository' + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: 'Install OS level dependencies' + run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' + + - name: 'Check local dependencies build cache' + id: load-local-dependencies-cache + uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ env.BUILD_DIR }} + key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} + + - name: 'Download, unpack and patch build dependencies' + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' + + - name: 'Clone and patch ffmpeg 6.1 and 7.0' + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: | + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" + + - name: 'Build and Install xdp and libbpf' + run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' + + - name: 'Build and Install libfabric' + run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' + + - name: 'Build and Install the DPDK' + run: eval 'source scripts/setup_build_env.sh && lib_install_dpdk' + + - name: 'Build and Install the MTL' + run: eval 'source scripts/setup_build_env.sh && lib_install_mtl' + + - name: 'Build and Install JPEG XS' + run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' + + - name: 'Build and Install JPEG XS ffmpeg plugin' + run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' + + - name: 'Build gRPC and dependencies' + run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' + + - name: 'Build MCM SDK and Media Proxy' + run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' + + - name: 'Build FFmpeg 6.1 with MCM plugin' + working-directory: ${{ github.workspace }}/ffmpeg-plugin + run: | + ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ + ./build-ffmpeg.sh "6.1" + + - name: 'Build FFmpeg 7.0 with MCM plugin' + working-directory: ${{ github.workspace }}/ffmpeg-plugin + run: | + ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ + ./build-ffmpeg.sh "7.0" + + - name: 'upload media-proxy and mcm binaries' + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: mcm-build + path: | + ${{ env.BUILD_DIR }}/mcm/bin/media_proxy + ${{ env.BUILD_DIR }}/mcm/bin/mesh-agent + ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* + ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg + ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 65ae8eba0..1e13f3188 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -18,7 +18,7 @@ on: default: "main" required: false description: "Branch name to use" - tag-to-checkout: # added tag input + tag-to-checkout: type: string required: false description: "Tag name to use" @@ -54,17 +54,16 @@ permissions: contents: read jobs: - call-base-build: + call-bare-metal-build: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository - uses: ./.github/workflows/base_build.yml + uses: ./.github/workflows/bare-metal-build.yml with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} - tag: ${{ github.event.inputs.tag-to-checkout }} # added tag input - use_self_hosted: true + tag: ${{ github.event.inputs.tag-to-checkout }} validation-prepare-setup-mcm: runs-on: [Linux, self-hosted] - needs: call-base-build + needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository outputs: diff --git a/tests/validation/configs/config_readme.md b/tests/validation/configs/config_readme.md index e84685ab2..da21fc72b 100644 --- a/tests/validation/configs/config_readme.md +++ b/tests/validation/configs/config_readme.md @@ -171,13 +171,20 @@ Key path constants defined in `Engine/const.py`: - `INTEL_BASE_PATH = "/opt/intel"` - Base path for all Intel software - `MCM_PATH = "/opt/intel/mcm"` - Path to the MCM repository - `MTL_PATH = "/opt/intel/mtl"` - Path to the MTL repository +- `MCM_BUILD_PATH = "/opt/intel/_build/mcm"` - Path for MCM built binaries +- `MTL_BUILD_PATH = "/opt/intel/_build/mtl"` - Path for MTL built binaries - `DEFAULT_FFMPEG_PATH = "/opt/intel/ffmpeg"` - Path to the FFmpeg repository - `DEFAULT_OPENH264_PATH = "/opt/intel/openh264"` - Path to the OpenH264 installation -- `DEFAULT_MCM_FFMPEG_PATH` - Path to the MCM FFmpeg build -- `DEFAULT_MTL_FFMPEG_PATH` - Path to the MTL FFmpeg build +- `ALLOWED_FFMPEG_VERSIONS = ["6.1", "7.0"]` - Supported FFmpeg versions +- `DEFAULT_MCM_FFMPEG_VERSION = "7.0"` - Default FFmpeg version for MCM +- `DEFAULT_MCM_FFMPEG_PATH = "/opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build"` - Path to the MCM FFmpeg build +- `DEFAULT_MCM_FFMPEG_LD_LIBRARY_PATH = "/opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build/lib"` - Library path for MCM FFmpeg +- `DEFAULT_MTL_FFMPEG_VERSION = "7.0"` - Default FFmpeg version for MTL +- `DEFAULT_MTL_FFMPEG_PATH = "/opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mtl_build"` - Path to the MTL FFmpeg build +- `DEFAULT_MTL_FFMPEG_LD_LIBRARY_PATH = "/opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mtl_build/lib"` - Library path for MTL FFmpeg - `DEFAULT_MEDIA_PATH = "/mnt/media/"` - Path to the media files for testing -- `DEFAULT_INPUT_PATH = "/opt/intel/input_path/"` - Path for input files -- `DEFAULT_OUTPUT_PATH = "/opt/intel/output_path/"` - Path for output files +- `DEFAULT_INPUT_PATH = "/opt/intel/input_path"` - Path for input files +- `DEFAULT_OUTPUT_PATH = "/opt/intel/output_path"` - Path for output files ## Using the Test Configuration From 15f1f39abc7ea1e255200f678d8babe581a7b6d8 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 25 Aug 2025 09:24:12 +0000 Subject: [PATCH 061/123] improved validation utilities and FFmpeg handling log checks and cleanup --- .github/workflows/bare-metal-build.yml | 202 +++++++++++++++-- tests/validation/Engine/const.py | 17 +- .../validation/Engine/log_validation_utils.py | 46 ++++ .../validation/Engine/rx_tx_app_engine_mcm.py | 81 ++----- tests/validation/common/__init__.py | 19 ++ .../common/ffmpeg_handler/__init__.py | 19 ++ .../common/ffmpeg_handler/ffmpeg.py | 137 ++++++++++++ .../common/ffmpeg_handler/log_constants.py | 39 ++++ tests/validation/common/log_constants.py | 53 +++++ .../validation/common/log_validation_utils.py | 207 ++++++++++++++++++ .../local/audio/test_ffmpeg_audio.py | 20 +- .../local/video/test_ffmpeg_video.py | 19 +- 12 files changed, 763 insertions(+), 96 deletions(-) create mode 100644 tests/validation/Engine/log_validation_utils.py create mode 100644 tests/validation/common/ffmpeg_handler/log_constants.py create mode 100644 tests/validation/common/log_constants.py create mode 100644 tests/validation/common/log_validation_utils.py diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 516dd19a1..ad0bada70 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -52,7 +52,24 @@ jobs: with: ref: ${{ inputs.tag || inputs.branch }} + - name: 'Check OS level dependencies' + id: check_deps + run: | + # Check for key Ubuntu packages that indicate the system is set up + if dpkg -l | grep -q "\sbuild-essential\s" && \ + dpkg -l | grep -q "\slibnuma-dev\s" && \ + dpkg -l | grep -q "\slibssl-dev\s" && \ + dpkg -l | grep -q "\srdma-core\s" && \ + dpkg -l | grep -q "\slibpcap-dev\s"; then + echo "All key OS packages are already installed." + echo "packages_installed=true" >> $GITHUB_OUTPUT + else + echo "Missing some required OS packages." + echo "packages_installed=false" >> $GITHUB_OUTPUT + fi + - name: 'Install OS level dependencies' + if: steps.check_deps.outputs.packages_installed == 'false' run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' - name: "Check local dependencies build cache" @@ -61,21 +78,70 @@ jobs: with: path: ${{ env.BUILD_DIR }} key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} + + - name: "Check if build dependencies are already downloaded" + id: check_deps_download + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: | + # Check for key directories that would indicate dependencies are downloaded + if [ -d "${BUILD_DIR}/grpc" ] && [ -d "${BUILD_DIR}/xdp" ] && [ -d "${BUILD_DIR}/dpdk" ] && [ -d "${BUILD_DIR}/jpegxs" ]; then + echo "Build dependencies seem to be already downloaded." + echo "downloaded=true" >> $GITHUB_OUTPUT + else + echo "Build dependencies need to be downloaded." + echo "downloaded=false" >> $GITHUB_OUTPUT + fi - name: "Download, unpack and patch build dependencies" - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' && steps.check_deps_download.outputs.downloaded != 'true' run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' - - name: "Clone and patch ffmpeg 6.1 and 7.0" + - name: "Check if FFmpeg source is already cloned and patched" + id: check_ffmpeg_source if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: | + if [ -d "ffmpeg-plugin/6.1" ] && [ -d "ffmpeg-plugin/7.0" ]; then + echo "FFmpeg sources already cloned and patched." + echo "cloned=true" >> $GITHUB_OUTPUT + else + echo "FFmpeg sources need to be cloned and patched." + echo "cloned=false" >> $GITHUB_OUTPUT + fi + + - name: "Clone and patch ffmpeg 6.1 and 7.0" + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' && steps.check_ffmpeg_source.outputs.cloned != 'true' run: | ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" + - name: "Check if xdp and libbpf are installed" + id: check_xdp + run: | + if [ -f "/usr/local/lib/libxdp.so" ] && [ -f "/usr/local/lib/libbpf.so" ]; then + echo "XDP and BPF libraries already installed." + echo "installed=true" >> $GITHUB_OUTPUT + else + echo "XDP and BPF libraries need to be installed." + echo "installed=false" >> $GITHUB_OUTPUT + fi + - name: "Build and Install xdp and libbpf" + if: steps.check_xdp.outputs.installed != 'true' run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' + - name: "Check if libfabric is installed" + id: check_libfabric + run: | + if [ -f "/usr/local/lib/libfabric.so" ]; then + echo "Libfabric already installed." + echo "installed=true" >> $GITHUB_OUTPUT + else + echo "Libfabric needs to be installed." + echo "installed=false" >> $GITHUB_OUTPUT + fi + - name: "Build and Install libfabric" + if: steps.check_libfabric.outputs.installed != 'true' run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' - name: "Build and Install the DPDK (skipped: already installed on runner)" @@ -84,21 +150,51 @@ jobs: echo "Skipping DPDK build and install as it is already installed on the machine." # eval 'source scripts/setup_build_env.sh && lib_install_dpdk' - # - name: "Switch DPDK version to currently used on the machine" - # run: | - # sed -i 's|DPDK_VER=23.11|DPDK_VER=25.03|g' \ - # "${{ github.workspace }}/versions.env" \ - # "${{ github.workspace }}/ffmpeg-plugin/versions.env" \ - # "${{ github.workspace }}/media-proxy/versions.env" \ - # "${{ github.workspace }}/sdk/versions.env" - - # - name: "Switch MTL version to currently used on the machine" - # run: | - # sed -i 's|MTL_VER=main|MTL_VER=main|g' \ - # "${{ github.workspace }}/versions.env" \ - # "${{ github.workspace }}/ffmpeg-plugin/versions.env" \ - # "${{ github.workspace }}/media-proxy/versions.env" \ - # "${{ github.workspace }}/sdk/versions.env" + - name: "Check if DPDK version needs to be updated" + id: check_dpdk_version + run: | + if grep -q "DPDK_VER=25.03" "${{ github.workspace }}/versions.env" && \ + grep -q "DPDK_VER=25.03" "${{ github.workspace }}/ffmpeg-plugin/versions.env" && \ + grep -q "DPDK_VER=25.03" "${{ github.workspace }}/media-proxy/versions.env" && \ + grep -q "DPDK_VER=25.03" "${{ github.workspace }}/sdk/versions.env"; then + echo "DPDK version is already set correctly." + echo "need_update=false" >> $GITHUB_OUTPUT + else + echo "DPDK version needs to be updated." + echo "need_update=true" >> $GITHUB_OUTPUT + fi + + - name: "Switch DPDK version to currently used on the machine" + if: steps.check_dpdk_version.outputs.need_update == 'true' + run: | + sed -i 's|DPDK_VER=23.11|DPDK_VER=25.03|g' \ + "${{ github.workspace }}/versions.env" \ + "${{ github.workspace }}/ffmpeg-plugin/versions.env" \ + "${{ github.workspace }}/media-proxy/versions.env" \ + "${{ github.workspace }}/sdk/versions.env" + + - name: "Check if MTL version needs to be updated" + id: check_mtl_version + run: | + if grep -q "MTL_VER=main" "${{ github.workspace }}/versions.env" && \ + grep -q "MTL_VER=main" "${{ github.workspace }}/ffmpeg-plugin/versions.env" && \ + grep -q "MTL_VER=main" "${{ github.workspace }}/media-proxy/versions.env" && \ + grep -q "MTL_VER=main" "${{ github.workspace }}/sdk/versions.env"; then + echo "MTL version is already set correctly." + echo "need_update=false" >> $GITHUB_OUTPUT + else + echo "MTL version needs to be updated." + echo "need_update=true" >> $GITHUB_OUTPUT + fi + + - name: "Switch MTL version to currently used on the machine" + if: steps.check_mtl_version.outputs.need_update == 'true' + run: | + sed -i 's|MTL_VER=v25.02|MTL_VER=main|g' \ + "${{ github.workspace }}/versions.env" \ + "${{ github.workspace }}/ffmpeg-plugin/versions.env" \ + "${{ github.workspace }}/media-proxy/versions.env" \ + "${{ github.workspace }}/sdk/versions.env" - name: "Build and Install the MTL(skipped: already installed on runner)" if: always() && false # Always skip this step for now @@ -106,25 +202,97 @@ jobs: echo "Skipping MTL build and install as it is already installed on the machine." # eval 'source scripts/setup_build_env.sh && lib_install_mtl' + - name: "Check if JPEG XS is installed" + id: check_jpegxs + run: | + if [ -d "/usr/local/include/svt-jpegxs" ] && [ -f "/usr/local/lib/libSvtJpegXS.so" ]; then + echo "JPEG XS already installed." + echo "installed=true" >> $GITHUB_OUTPUT + else + echo "JPEG XS needs to be installed." + echo "installed=false" >> $GITHUB_OUTPUT + fi + - name: "Build and Install JPEG XS" + if: steps.check_jpegxs.outputs.installed != 'true' run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' + - name: "Check if JPEG XS ffmpeg plugin is installed" + id: check_jpegxs_plugin + run: | + if [ -f "/usr/local/etc/imtl.json" ]; then + echo "JPEG XS ffmpeg plugin already installed." + echo "installed=true" >> $GITHUB_OUTPUT + else + echo "JPEG XS ffmpeg plugin needs to be installed." + echo "installed=false" >> $GITHUB_OUTPUT + fi + - name: "Build and Install JPEG XS ffmpeg plugin" + if: steps.check_jpegxs_plugin.outputs.installed != 'true' run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' + - name: "Check if gRPC is installed" + id: check_grpc + run: | + if [ -d "/usr/local/include/grpc" ] && [ -d "/usr/local/include/grpcpp" ]; then + echo "gRPC already installed." + echo "installed=true" >> $GITHUB_OUTPUT + else + echo "gRPC needs to be installed." + echo "installed=false" >> $GITHUB_OUTPUT + fi + - name: "Build gRPC and dependencies" + if: steps.check_grpc.outputs.installed != 'true' run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' + - name: "Check if MCM SDK and Media Proxy are built" + id: check_mcm + run: | + if [ -f "${BUILD_DIR}/mcm/bin/media_proxy" ] && [ -f "${BUILD_DIR}/mcm/bin/mesh-agent" ] && [ -f "${BUILD_DIR}/mcm/lib/libmcm_dp.so" ]; then + echo "MCM SDK and Media Proxy are already built." + echo "built=true" >> $GITHUB_OUTPUT + else + echo "MCM SDK and Media Proxy need to be built." + echo "built=false" >> $GITHUB_OUTPUT + fi + - name: "Build MCM SDK and Media Proxy" + if: steps.check_mcm.outputs.built != 'true' run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' + - name: "Check if FFmpeg 6.1 with MCM plugin is built" + id: check_ffmpeg_61 + run: | + if [ -f "${BUILD_DIR}/ffmpeg-6-1/ffmpeg" ]; then + echo "FFmpeg 6.1 with MCM plugin is already built." + echo "built=true" >> $GITHUB_OUTPUT + else + echo "FFmpeg 6.1 with MCM plugin needs to be built." + echo "built=false" >> $GITHUB_OUTPUT + fi + - name: "Build FFmpeg 6.1 with MCM plugin" + if: steps.check_ffmpeg_61.outputs.built != 'true' working-directory: ${{ github.workspace }}/ffmpeg-plugin run: | ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ ./build-ffmpeg.sh "6.1" + - name: "Check if FFmpeg 7.0 with MCM plugin is built" + id: check_ffmpeg_70 + run: | + if [ -f "${BUILD_DIR}/ffmpeg-7-0/ffmpeg" ]; then + echo "FFmpeg 7.0 with MCM plugin is already built." + echo "built=true" >> $GITHUB_OUTPUT + else + echo "FFmpeg 7.0 with MCM plugin needs to be built." + echo "built=false" >> $GITHUB_OUTPUT + fi + - name: "Build FFmpeg 7.0 with MCM plugin" + if: steps.check_ffmpeg_70.outputs.built != 'true' working-directory: ${{ github.workspace }}/ffmpeg-plugin run: | ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ diff --git a/tests/validation/Engine/const.py b/tests/validation/Engine/const.py index da17ede45..7efb732f9 100644 --- a/tests/validation/Engine/const.py +++ b/tests/validation/Engine/const.py @@ -4,25 +4,23 @@ # Required ordered log phrases for Rx validation RX_REQUIRED_LOG_PHRASES = [ - '[RX] Launching Rx App', + '[RX] Launching RX App', '[RX] Reading client configuration', '[RX] Reading connection configuration', '[DEBU] JSON client config:', '[INFO] Media Communications Mesh SDK version', '[DEBU] JSON conn config:', - '[RX] Waiting for packets', '[RX] Fetched mesh data buffer', '[RX] Saving buffer data to a file', - '[RX] Frame: 1', '[RX] Done reading the data', '[RX] dropping connection to media-proxy', - '[RX] Shutting down connection', + '[RX] Shuting down connection', 'INFO - memif disconnected!', '[INFO] gRPC: connection deleted', '[RX] Shutting down client', ] TX_REQUIRED_LOG_PHRASES = [ - '[TX] Launching Tx App', + '[TX] Launching TX app', '[TX] Reading client configuration', '[TX] Reading connection configuration', '[DEBU] JSON client config:', @@ -31,10 +29,8 @@ '[INFO] gRPC: connection created', 'INFO - Create memif socket.', 'INFO - Create memif interface.', - 'INFO - memif connected!', - '[INFO] gRPC: connection active', - '[TX] sending', - '[TX] Shutting down connection', + '[TX] Sending packet:', + '[TX] Shuting down connection', '[INFO] gRPC: connection deleted', '[TX] Shutting down client', ] @@ -44,6 +40,9 @@ DEFAULT_INPUT_PATH = "/opt/intel/input_path/" DEFAULT_OUTPUT_PATH = "/opt/intel/output_path/" +FFMPEG_ESTABLISH_TIMEOUT = 5 # or use the same value as MCM_ESTABLISH_TIMEOUT +FFMPEG_RUN_TIMEOUT = 60 # or use the same value as MCM_RXTXAPP_RUN_TIMEOUT + # time for establishing connection for example between TX and RX in st2110 MTL_ESTABLISH_TIMEOUT = 2 MCM_ESTABLISH_TIMEOUT = 5 diff --git a/tests/validation/Engine/log_validation_utils.py b/tests/validation/Engine/log_validation_utils.py new file mode 100644 index 000000000..43549f4ee --- /dev/null +++ b/tests/validation/Engine/log_validation_utils.py @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2024-2025 Intel Corporation +# Media Communications Mesh + +""" +Common log validation utilities for use by both rxtxapp and ffmpeg validation. +""" +import logging +from typing import List, Tuple, Dict + +logger = logging.getLogger(__name__) + +def check_phrases_in_order(log_path: str, phrases: List[str]) -> Tuple[bool, List[str], Dict[str, List[str]]]: + """ + Check that all required phrases appear in order in the log file. + Returns (all_found, missing_phrases, context_lines) + """ + found_indices = [] + missing_phrases = [] + lines_around_missing = {} + with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: + lines = [line.strip() for line in f] + + idx = 0 + for phrase_idx, phrase in enumerate(phrases): + found = False + phrase_stripped = phrase.strip() + start_idx = idx # Remember where we started searching + + while idx < len(lines): + line_stripped = lines[idx].strip() + if phrase_stripped in line_stripped: + found_indices.append(idx) + found = True + idx += 1 + break + idx += 1 + + if not found: + missing_phrases.append(phrase) + # Store context - lines around where we were searching + context_start = max(0, start_idx - 3) + context_end = min(len(lines), start_idx + 7) + lines_around_missing[phrase] = lines[context_start:context_end] + + return len(missing_phrases) == 0, missing_phrases, lines_around_missing diff --git a/tests/validation/Engine/rx_tx_app_engine_mcm.py b/tests/validation/Engine/rx_tx_app_engine_mcm.py index 3a33d7e85..53e31430f 100644 --- a/tests/validation/Engine/rx_tx_app_engine_mcm.py +++ b/tests/validation/Engine/rx_tx_app_engine_mcm.py @@ -11,14 +11,14 @@ import Engine.rx_tx_app_client_json import Engine.rx_tx_app_connection_json -from Engine.const import LOG_FOLDER, RX_TX_APP_ERROR_KEYWORDS, DEFAULT_OUTPUT_PATH, RX_REQUIRED_LOG_PHRASES, TX_REQUIRED_LOG_PHRASES +from Engine.const import LOG_FOLDER, DEFAULT_OUTPUT_PATH +from common.log_constants import RX_REQUIRED_LOG_PHRASES, TX_REQUIRED_LOG_PHRASES, RX_TX_APP_ERROR_KEYWORDS from Engine.mcm_apps import ( get_media_proxy_port, - output_validator, save_process_log, get_mcm_path, ) -from Engine.rx_tx_app_file_validation_utils import validate_file +from Engine.rx_tx_app_file_validation_utils import validate_file, cleanup_file logger = logging.getLogger(__name__) @@ -200,62 +200,27 @@ def stop(self): app_log_validation_status = False app_log_error_count = 0 - - # Custom Rx log validation: check for required phrases in order, ignoring timestamps - def check_phrases_in_order(log_path, phrases): - found_indices = [] - missing_phrases = [] - lines_around_missing = {} - with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: - lines = [line.strip() for line in f] - - idx = 0 - for phrase_idx, phrase in enumerate(phrases): - found = False - phrase_stripped = phrase.strip() - start_idx = idx # Remember where we started searching - - while idx < len(lines): - line_stripped = lines[idx].strip() - if phrase_stripped in line_stripped: - found_indices.append(idx) - found = True - idx += 1 - break - idx += 1 - - if not found: - missing_phrases.append(phrase) - # Store context - lines around where we were searching - context_start = max(0, start_idx - 3) - context_end = min(len(lines), start_idx + 7) - lines_around_missing[phrase] = lines[context_start:context_end] - - return len(missing_phrases) == 0, missing_phrases, lines_around_missing + + # Using common log validation utility + from common.log_validation_utils import check_phrases_in_order if self.direction in ("Rx", "Tx"): + from common.log_validation_utils import validate_log_file required_phrases = RX_REQUIRED_LOG_PHRASES if self.direction == "Rx" else TX_REQUIRED_LOG_PHRASES - log_pass, missing, context_lines = check_phrases_in_order(log_file_path, required_phrases) - self.is_pass = log_pass - app_log_validation_status = log_pass - app_log_error_count = len(missing) - validation_info.append(f"=== {self.direction} App Log Validation ===") - validation_info.append(f"Log file: {log_file_path}") - validation_info.append(f"Validation result: {'PASS' if log_pass else 'FAIL'}") - validation_info.append(f"Errors found: {app_log_error_count}") - if not log_pass: - validation_info.append(f"Missing or out-of-order phrases analysis:") - for phrase in missing: - validation_info.append(f"\n Expected phrase: \"{phrase}\"") - validation_info.append(f" Context in log file:") - if phrase in context_lines: - for line in context_lines[phrase]: - validation_info.append(f" {line}") - else: - validation_info.append(" ") - if missing: - print(f"{self.direction} process did not pass. First missing phrase: {missing[0]}") + + # Use the common validation function + validation_result = validate_log_file(log_file_path, required_phrases, self.direction) + + self.is_pass = validation_result['is_pass'] + app_log_validation_status = validation_result['is_pass'] + app_log_error_count = validation_result['error_count'] + validation_info.extend(validation_result['validation_info']) + + # Additional logging if validation failed + if not validation_result['is_pass'] and validation_result['missing_phrases']: + print(f"{self.direction} process did not pass. First missing phrase: {validation_result['missing_phrases'][0]}") else: + from common.log_validation_utils import output_validator result = output_validator( log_file_path=log_file_path, error_keywords=RX_TX_APP_ERROR_KEYWORDS, @@ -282,8 +247,8 @@ def check_phrases_in_order(log_path, phrases): f" - {phrase}: found '{found}', expected '{expected}'" ) - # File validation for Rx only run if output path isn't "/dev/null" - if self.direction == "Rx" and self.output and self.output_path != "/dev/null": + # File validation for Rx only run if output path isn't "/dev/null" or doesn't start with "/dev/null/" + if self.direction == "Rx" and self.output and self.output_path and not str(self.output_path).startswith("/dev/null"): validation_info.append(f"\n=== {self.direction} Output File Validation ===") validation_info.append(f"Expected output file: {self.output}") @@ -417,8 +382,6 @@ def log_output(): def cleanup(self): """Clean up the output file created by the Rx app.""" - from Engine.rx_tx_app_file_validation_utils import cleanup_file - if self.output: success = cleanup_file(self.host.connection, str(self.output)) if success: diff --git a/tests/validation/common/__init__.py b/tests/validation/common/__init__.py index e69de29bb..931d8daa9 100644 --- a/tests/validation/common/__init__.py +++ b/tests/validation/common/__init__.py @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2024-2025 Intel Corporation +# Media Communications Mesh + +""" +Common utilities package for validation code. +""" + +from common.log_validation_utils import check_phrases_in_order, validate_log_file, output_validator +from common.log_constants import RX_REQUIRED_LOG_PHRASES, TX_REQUIRED_LOG_PHRASES, RX_TX_APP_ERROR_KEYWORDS + +__all__ = [ + 'check_phrases_in_order', + 'validate_log_file', + 'output_validator', + 'RX_REQUIRED_LOG_PHRASES', + 'TX_REQUIRED_LOG_PHRASES', + 'RX_TX_APP_ERROR_KEYWORDS' +] diff --git a/tests/validation/common/ffmpeg_handler/__init__.py b/tests/validation/common/ffmpeg_handler/__init__.py index e69de29bb..08bda312e 100644 --- a/tests/validation/common/ffmpeg_handler/__init__.py +++ b/tests/validation/common/ffmpeg_handler/__init__.py @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2024-2025 Intel Corporation +# Media Communications Mesh + +""" +FFmpeg handler package for Media Communications Mesh validation. +""" + +from common.ffmpeg_handler.log_constants import ( + FFMPEG_RX_REQUIRED_LOG_PHRASES, + FFMPEG_TX_REQUIRED_LOG_PHRASES, + FFMPEG_ERROR_KEYWORDS +) + +__all__ = [ + 'FFMPEG_RX_REQUIRED_LOG_PHRASES', + 'FFMPEG_TX_REQUIRED_LOG_PHRASES', + 'FFMPEG_ERROR_KEYWORDS' +] diff --git a/tests/validation/common/ffmpeg_handler/ffmpeg.py b/tests/validation/common/ffmpeg_handler/ffmpeg.py index f1a5734c1..bea8ea524 100644 --- a/tests/validation/common/ffmpeg_handler/ffmpeg.py +++ b/tests/validation/common/ffmpeg_handler/ffmpeg.py @@ -2,6 +2,7 @@ # Copyright 2025 Intel Corporation # Media Communications Mesh import logging +import os import threading import time @@ -11,6 +12,7 @@ ) from Engine.const import LOG_FOLDER from Engine.mcm_apps import save_process_log +from Engine.rx_tx_app_file_validation_utils import cleanup_file from .ffmpeg_io import FFmpegIO @@ -73,6 +75,114 @@ def __init__(self, host, ffmpeg_instance: FFmpeg, log_path=None): self.ff = ffmpeg_instance self.log_path = log_path self._processes = [] + self.is_pass = False + + def validate(self): + """ + Validates the FFmpeg process execution and output. + + Performs two types of validation: + 1. Log validation - checks for required phrases and error keywords + 2. File validation - checks if the output file exists and has expected characteristics + + Generates validation report files. + + Returns: + bool: True if validation passed, False otherwise + """ + process_passed = True + validation_info = [] + + for process in self._processes: + if process.return_code != 0: + logger.warning(f"FFmpeg process on {self.host.name} failed with return code {process.return_code}") + process_passed = False + + # Determine if this is a receiver or transmitter + is_receiver = False + if self.ff.ffmpeg_input and self.ff.ffmpeg_output: + input_path = getattr(self.ff.ffmpeg_input, "input_path", None) + output_path = getattr(self.ff.ffmpeg_output, "output_path", None) + + if input_path == "-" or ( + output_path and output_path != "-" and "." in output_path + ): + is_receiver = True + + direction = "Rx" if is_receiver else "Tx" + + # Find the log file + log_dir = self.log_path if self.log_path else LOG_FOLDER + subdir = f"RxTx/{self.host.name}" + input_class_name = None + if self.ff.ffmpeg_input: + input_class_name = self.ff.ffmpeg_input.__class__.__name__ + prefix = "mtl_" if input_class_name and "Mtl" in input_class_name else "mcm_" + log_filename = prefix + ("ffmpeg_rx.log" if is_receiver else "ffmpeg_tx.log") + + log_file_path = os.path.join(log_dir, subdir, log_filename) + + # Perform log validation + from common.log_validation_utils import validate_log_file + from common.ffmpeg_handler.log_constants import ( + FFMPEG_RX_REQUIRED_LOG_PHRASES, + FFMPEG_TX_REQUIRED_LOG_PHRASES, + FFMPEG_ERROR_KEYWORDS + ) + + required_phrases = FFMPEG_RX_REQUIRED_LOG_PHRASES if is_receiver else FFMPEG_TX_REQUIRED_LOG_PHRASES + + if os.path.exists(log_file_path): + validation_result = validate_log_file( + log_file_path, + required_phrases, + direction, + FFMPEG_ERROR_KEYWORDS + ) + + log_validation_passed = validation_result['is_pass'] + validation_info.extend(validation_result['validation_info']) + else: + logger.warning(f"Log file not found at {log_file_path}") + validation_info.append(f"=== {direction} Log Validation ===") + validation_info.append(f"Log file: {log_file_path}") + validation_info.append(f"Validation result: FAIL") + validation_info.append(f"Errors found: 1") + validation_info.append(f"Missing log file") + log_validation_passed = False + + # File validation for Rx only run if output path isn't "/dev/null" or doesn't start with "/dev/null/" + file_validation_passed = True + if is_receiver and self.ff.ffmpeg_output and hasattr(self.ff.ffmpeg_output, "output_path"): + output_path = self.ff.ffmpeg_output.output_path + if output_path and not str(output_path).startswith("/dev/null"): + validation_info.append(f"\n=== {direction} Output File Validation ===") + validation_info.append(f"Expected output file: {output_path}") + + from Engine.rx_tx_app_file_validation_utils import validate_file + file_info, file_validation_passed = validate_file( + self.host.connection, output_path, cleanup=False + ) + validation_info.extend(file_info) + + # Overall validation status + self.is_pass = process_passed and log_validation_passed and file_validation_passed + + # Save validation report + validation_info.append(f"\n=== Overall Validation Summary ===") + validation_info.append(f"Overall validation: {'PASS' if self.is_pass else 'FAIL'}") + validation_info.append(f"Process validation: {'PASS' if process_passed else 'FAIL'}") + validation_info.append(f"Log validation: {'PASS' if log_validation_passed else 'FAIL'}") + if is_receiver: + validation_info.append(f"File validation: {'PASS' if file_validation_passed else 'FAIL'}") + validation_info.append(f"Note: Overall validation fails if any validation step fails") + + # Save validation report to a file + from common.log_validation_utils import save_validation_report + validation_path = os.path.join(log_dir, subdir, f"{direction.lower()}_validation.log") + save_validation_report(validation_path, validation_info, self.is_pass) + + return self.is_pass def start(self): """Starts the FFmpeg process on the host, waits for the process to start.""" @@ -172,6 +282,33 @@ def stop(self, wait: float = 0.0) -> float: logger.info("No FFmpeg process to stop!") return elapsed + def wait_with_timeout(self, timeout=None): + """Wait for the process to complete with a timeout.""" + if not timeout: + timeout = 60 # Default timeout or use a constant like MCM_RXTXAPP_RUN_TIMEOUT + + try: + for process in self._processes: + if process.running: + process.wait(timeout=timeout) + except Exception as e: + logger.warning(f"FFmpeg process did not finish in time or error occurred: {e}") + return False + return True + + def cleanup(self): + """Clean up any resources or output files.""" + if (self.ff.ffmpeg_output and + hasattr(self.ff.ffmpeg_output, "output_path") and + self.ff.ffmpeg_output.output_path and + self.ff.ffmpeg_output.output_path != "-" and + not str(self.ff.ffmpeg_output.output_path).startswith("/dev/null")): + + success = cleanup_file(self.host.connection, str(self.ff.ffmpeg_output.output_path)) + if success: + logger.debug(f"Cleaned up output file: {self.ff.ffmpeg_output.output_path}") + else: + logger.warning(f"Failed to clean up output file: {self.ff.ffmpeg_output.output_path}") def no_proxy_to_prefix_variables(host, prefix_variables: dict | None = None): """ diff --git a/tests/validation/common/ffmpeg_handler/log_constants.py b/tests/validation/common/ffmpeg_handler/log_constants.py new file mode 100644 index 000000000..b589e16f2 --- /dev/null +++ b/tests/validation/common/ffmpeg_handler/log_constants.py @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2024-2025 Intel Corporation +# Media Communications Mesh + +""" +Log validation constants for FFmpeg validation. +""" + +# Required ordered log phrases for FFmpeg Rx validation +FFMPEG_RX_REQUIRED_LOG_PHRASES = [ + '[DEBU] JSON client config:', + '[INFO] Media Communications Mesh SDK version', + '[DEBU] JSON conn config:', + '[INFO] gRPC: connection created', + 'INFO - Create memif socket.', + 'INFO - Create memif interface.', + 'INFO - memif connected!', + '[INFO] gRPC: connection active' +] + +# Required ordered log phrases for FFmpeg Tx validation +FFMPEG_TX_REQUIRED_LOG_PHRASES = [ + '[DEBU] JSON client config:', + '[INFO] Media Communications Mesh SDK version', + '[DEBU] JSON conn config:', + '[DEBU] BUF PARTS' +] + +# Common error keywords to look for in logs +FFMPEG_ERROR_KEYWORDS = [ + "ERROR", + "FATAL", + "exception", + "segfault", + "core dumped", + "failed", + "FAIL", + "[error]" +] diff --git a/tests/validation/common/log_constants.py b/tests/validation/common/log_constants.py new file mode 100644 index 000000000..3c9782c7f --- /dev/null +++ b/tests/validation/common/log_constants.py @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2024-2025 Intel Corporation +# Media Communications Mesh + +""" +Common constants for log validation used by both rxtxapp and ffmpeg validation. +""" + +# Required ordered log phrases for Rx validation +RX_REQUIRED_LOG_PHRASES = [ + '[RX] Launching RX App', + '[RX] Reading client configuration', + '[RX] Reading connection configuration', + '[DEBU] JSON client config:', + '[INFO] Media Communications Mesh SDK version', + '[DEBU] JSON conn config:', + '[RX] Fetched mesh data buffer', + '[RX] Saving buffer data to a file', + '[RX] Done reading the data', + '[RX] dropping connection to media-proxy', + '[RX] Shuting down connection', + 'INFO - memif disconnected!', + '[INFO] gRPC: connection deleted', + '[RX] Shutting down client', +] + +# Required ordered log phrases for Tx validation +TX_REQUIRED_LOG_PHRASES = [ + '[TX] Launching TX app', + '[TX] Reading client configuration', + '[TX] Reading connection configuration', + '[DEBU] JSON client config:', + '[INFO] Media Communications Mesh SDK version', + '[DEBU] JSON conn config:', + '[INFO] gRPC: connection created', + 'INFO - Create memif socket.', + 'INFO - Create memif interface.', + '[TX] Sending packet:', + '[TX] Shuting down connection', + '[INFO] gRPC: connection deleted', + '[TX] Shutting down client', +] + +# Common error keywords to look for in logs +RX_TX_APP_ERROR_KEYWORDS = [ + "ERROR", + "FATAL", + "exception", + "segfault", + "core dumped", + "failed", + "FAIL" +] diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py new file mode 100644 index 000000000..84a81965a --- /dev/null +++ b/tests/validation/common/log_validation_utils.py @@ -0,0 +1,207 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright 2024-2025 Intel Corporation +# Media Communications Mesh + +""" +Common log validation utilities for use by both rxtxapp and ffmpeg validation. +""" +import logging +import os +from typing import List, Tuple, Dict, Any, Optional + +from common.log_constants import RX_TX_APP_ERROR_KEYWORDS + +logger = logging.getLogger(__name__) + +def check_phrases_in_order(log_path: str, phrases: List[str]) -> Tuple[bool, List[str], Dict[str, List[str]]]: + """ + Check that all required phrases appear in order in the log file. + Returns (all_found, missing_phrases, context_lines) + """ + found_indices = [] + missing_phrases = [] + lines_around_missing = {} + with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: + lines = [line.strip() for line in f] + + idx = 0 + for phrase_idx, phrase in enumerate(phrases): + found = False + phrase_stripped = phrase.strip() + start_idx = idx # Remember where we started searching + + while idx < len(lines): + line_stripped = lines[idx].strip() + if phrase_stripped in line_stripped: + found_indices.append(idx) + found = True + idx += 1 + break + idx += 1 + + if not found: + missing_phrases.append(phrase) + # Store context - lines around where we were searching + context_start = max(0, start_idx - 3) + context_end = min(len(lines), start_idx + 7) + lines_around_missing[phrase] = lines[context_start:context_end] + + return len(missing_phrases) == 0, missing_phrases, lines_around_missing + +def check_for_errors(log_path: str, error_keywords: Optional[List[str]] = None) -> Tuple[bool, List[Dict[str, Any]]]: + """ + Check the log file for error keywords. + + Args: + log_path: Path to the log file + error_keywords: List of keywords indicating errors (default: RX_TX_APP_ERROR_KEYWORDS) + + Returns: + Tuple of (is_pass, errors_found) + errors_found is a list of dicts with 'line', 'line_number', and 'keyword' keys + """ + if error_keywords is None: + error_keywords = RX_TX_APP_ERROR_KEYWORDS + + errors = [] + if not os.path.exists(log_path): + logger.error(f"Log file not found: {log_path}") + return False, [{"line": "Log file not found", "line_number": 0, "keyword": "FILE_NOT_FOUND"}] + + try: + with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: + for i, line in enumerate(f): + for keyword in error_keywords: + if keyword.lower() in line.lower(): + # Ignore certain false positives + if "ERROR" in keyword and ("NO ERROR" in line.upper() or "NO_ERROR" in line.upper()): + continue + errors.append({ + "line": line.strip(), + "line_number": i + 1, + "keyword": keyword + }) + break + except Exception as e: + logger.error(f"Error reading log file: {e}") + return False, [{"line": f"Error reading log file: {e}", "line_number": 0, "keyword": "FILE_READ_ERROR"}] + + return len(errors) == 0, errors + +def validate_log_file(log_file_path: str, required_phrases: List[str], direction: str = "", + error_keywords: Optional[List[str]] = None) -> Dict: + """ + Validate log file for required phrases and return validation information. + + Args: + log_file_path: Path to the log file + required_phrases: List of phrases to check for in order + direction: Optional string to identify the direction (e.g., 'Rx', 'Tx') + error_keywords: Optional list of error keywords to check for + + Returns: + Dictionary containing validation results: + { + 'is_pass': bool, + 'error_count': int, + 'validation_info': list of validation information strings, + 'missing_phrases': list of missing phrases, + 'context_lines': dict mapping phrases to context lines, + 'errors': list of error dictionaries (if error_keywords provided) + } + """ + validation_info = [] + + # Phrase order validation + log_pass, missing, context_lines = check_phrases_in_order(log_file_path, required_phrases) + error_count = len(missing) + + # Error keyword validation (optional) + error_check_pass = True + errors = [] + if error_keywords is not None: + error_check_pass, errors = check_for_errors(log_file_path, error_keywords) + error_count += len(errors) + + # Overall pass/fail status + is_pass = log_pass and error_check_pass + + # Build validation info + dir_prefix = f"{direction} " if direction else "" + validation_info.append(f"=== {dir_prefix}Log Validation ===") + validation_info.append(f"Log file: {log_file_path}") + validation_info.append(f"Validation result: {'PASS' if is_pass else 'FAIL'}") + validation_info.append(f"Total errors found: {error_count}") + + # Missing phrases info + if not log_pass: + validation_info.append(f"Missing or out-of-order phrases analysis:") + for phrase in missing: + validation_info.append(f"\n Expected phrase: \"{phrase}\"") + validation_info.append(f" Context in log file:") + if phrase in context_lines: + for line in context_lines[phrase]: + validation_info.append(f" {line}") + else: + validation_info.append(" ") + if missing: + logger.warning(f"{dir_prefix}process did not pass. First missing phrase: {missing[0]}") + + # Error keywords info + if errors: + validation_info.append(f"\nError keywords found:") + for error in errors: + validation_info.append(f" Line {error['line_number']} - {error['keyword']}: {error['line']}") + + return { + "is_pass": is_pass, + "error_count": error_count, + "validation_info": validation_info, + "missing_phrases": missing, + "context_lines": context_lines, + "errors": errors + } + +def save_validation_report(report_path: str, validation_info: List[str], overall_status: bool) -> None: + """ + Save validation report to a file. + + Args: + report_path: Path where to save the report + validation_info: List of validation information strings + overall_status: Overall pass/fail status + """ + try: + os.makedirs(os.path.dirname(report_path), exist_ok=True) + with open(report_path, 'w', encoding='utf-8') as f: + for line in validation_info: + f.write(f"{line}\n") + f.write(f"\n=== Overall Validation Summary ===\n") + f.write(f"Overall result: {'PASS' if overall_status else 'FAIL'}\n") + logger.info(f"Validation report saved to {report_path}") + except Exception as e: + logger.error(f"Error saving validation report: {e}") + +def output_validator(log_file_path: str, error_keywords: Optional[List[str]] = None) -> Dict[str, Any]: + """ + Simple validator that checks for error keywords in a log file. + + Args: + log_file_path: Path to the log file + error_keywords: List of keywords indicating errors + + Returns: + Dictionary with validation results: + { + 'is_pass': bool, + 'errors': list of error dictionaries, + 'phrase_mismatches': list of phrase mismatches (empty in this validator) + } + """ + is_pass, errors = check_for_errors(log_file_path, error_keywords) + + return { + "is_pass": is_pass, + "errors": errors, + "phrase_mismatches": [] # Not used in this simple validator + } diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index 1659160ce..883170111 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -17,7 +17,7 @@ from common.ffmpeg_handler.ffmpeg_io import FFmpegAudioIO from common.ffmpeg_handler.mcm_ffmpeg import FFmpegMcmMemifAudioIO -from Engine.rx_tx_app_file_validation_utils import cleanup_file +from Engine.const import FFMPEG_RUN_TIMEOUT logger = logging.getLogger(__name__) @@ -111,13 +111,21 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, lo mcm_rx_executor.start() mcm_tx_executor.start() + try: + if mcm_rx_executor._processes[0].running: + mcm_rx_executor._processes[0].wait(timeout=FFMPEG_RUN_TIMEOUT) + except Exception as e: + logging.warning(f"RX executor did not finish in time or error occurred: {e}") + mcm_rx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) mcm_tx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) + mcm_rx_executor.validate() + mcm_tx_executor.validate() + # TODO add validate() function to check if the output file is correct - success = cleanup_file(rx_host.connection, str(mcm_rx_outp.output_path)) - if success: - logger.debug(f"Cleaned up Rx output file: {mcm_rx_outp.output_path}") - else: - logger.warning(f"Failed to clean up Rx output file: {mcm_rx_outp.output_path}") + mcm_rx_executor.cleanup() + + assert mcm_tx_executor.is_pass is True, "TX FFmpeg process did not pass" + assert mcm_rx_executor.is_pass is True, "RX FFmpeg process did not pass" diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index 255f58901..2aac8f735 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -19,6 +19,7 @@ from common.ffmpeg_handler.ffmpeg_io import FFmpegVideoIO from common.ffmpeg_handler.mcm_ffmpeg import FFmpegMcmMemifVideoIO +from Engine.const import FFMPEG_RUN_TIMEOUT logger = logging.getLogger(__name__) @@ -111,13 +112,21 @@ def test_local_ffmpeg_video(media_proxy, hosts, test_config, file: str, log_path mcm_rx_executor.start() mcm_tx_executor.start() + try: + if mcm_rx_executor._processes[0].running: + mcm_rx_executor._processes[0].wait(timeout=FFMPEG_RUN_TIMEOUT) + except Exception as e: + logging.warning(f"RX executor did not finish in time or error occurred: {e}") + mcm_rx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) mcm_tx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) + mcm_rx_executor.validate() + mcm_tx_executor.validate() + # TODO add validate() function to check if the output file is correct - success = cleanup_file(rx_host.connection, str(mcm_rx_outp.output_path)) - if success: - logger.debug(f"Cleaned up Rx output file: {mcm_rx_outp.output_path}") - else: - logger.warning(f"Failed to clean up Rx output file: {mcm_rx_outp.output_path}") + mcm_rx_executor.cleanup() + + assert mcm_tx_executor.is_pass is True, "TX FFmpeg process did not pass" + assert mcm_rx_executor.is_pass is True, "RX FFmpeg process did not pass" From 9281324f06c2b17a70abd335be91b9df1243b1c8 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 25 Aug 2025 14:45:36 +0000 Subject: [PATCH 062/123] Reset bare-metal-build.yml to previous commit --- .github/workflows/bare-metal-build.yml | 142 +------------------------ 1 file changed, 2 insertions(+), 140 deletions(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index ad0bada70..709c65f83 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -52,24 +52,7 @@ jobs: with: ref: ${{ inputs.tag || inputs.branch }} - - name: 'Check OS level dependencies' - id: check_deps - run: | - # Check for key Ubuntu packages that indicate the system is set up - if dpkg -l | grep -q "\sbuild-essential\s" && \ - dpkg -l | grep -q "\slibnuma-dev\s" && \ - dpkg -l | grep -q "\slibssl-dev\s" && \ - dpkg -l | grep -q "\srdma-core\s" && \ - dpkg -l | grep -q "\slibpcap-dev\s"; then - echo "All key OS packages are already installed." - echo "packages_installed=true" >> $GITHUB_OUTPUT - else - echo "Missing some required OS packages." - echo "packages_installed=false" >> $GITHUB_OUTPUT - fi - - name: 'Install OS level dependencies' - if: steps.check_deps.outputs.packages_installed == 'false' run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' - name: "Check local dependencies build cache" @@ -78,70 +61,21 @@ jobs: with: path: ${{ env.BUILD_DIR }} key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} - - - name: "Check if build dependencies are already downloaded" - id: check_deps_download - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: | - # Check for key directories that would indicate dependencies are downloaded - if [ -d "${BUILD_DIR}/grpc" ] && [ -d "${BUILD_DIR}/xdp" ] && [ -d "${BUILD_DIR}/dpdk" ] && [ -d "${BUILD_DIR}/jpegxs" ]; then - echo "Build dependencies seem to be already downloaded." - echo "downloaded=true" >> $GITHUB_OUTPUT - else - echo "Build dependencies need to be downloaded." - echo "downloaded=false" >> $GITHUB_OUTPUT - fi - name: "Download, unpack and patch build dependencies" - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' && steps.check_deps_download.outputs.downloaded != 'true' - run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' - - - name: "Check if FFmpeg source is already cloned and patched" - id: check_ffmpeg_source if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: | - if [ -d "ffmpeg-plugin/6.1" ] && [ -d "ffmpeg-plugin/7.0" ]; then - echo "FFmpeg sources already cloned and patched." - echo "cloned=true" >> $GITHUB_OUTPUT - else - echo "FFmpeg sources need to be cloned and patched." - echo "cloned=false" >> $GITHUB_OUTPUT - fi + run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' - name: "Clone and patch ffmpeg 6.1 and 7.0" - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' && steps.check_ffmpeg_source.outputs.cloned != 'true' + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' run: | ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" - - name: "Check if xdp and libbpf are installed" - id: check_xdp - run: | - if [ -f "/usr/local/lib/libxdp.so" ] && [ -f "/usr/local/lib/libbpf.so" ]; then - echo "XDP and BPF libraries already installed." - echo "installed=true" >> $GITHUB_OUTPUT - else - echo "XDP and BPF libraries need to be installed." - echo "installed=false" >> $GITHUB_OUTPUT - fi - - name: "Build and Install xdp and libbpf" - if: steps.check_xdp.outputs.installed != 'true' run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' - - name: "Check if libfabric is installed" - id: check_libfabric - run: | - if [ -f "/usr/local/lib/libfabric.so" ]; then - echo "Libfabric already installed." - echo "installed=true" >> $GITHUB_OUTPUT - else - echo "Libfabric needs to be installed." - echo "installed=false" >> $GITHUB_OUTPUT - fi - - name: "Build and Install libfabric" - if: steps.check_libfabric.outputs.installed != 'true' run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' - name: "Build and Install the DPDK (skipped: already installed on runner)" @@ -202,97 +136,25 @@ jobs: echo "Skipping MTL build and install as it is already installed on the machine." # eval 'source scripts/setup_build_env.sh && lib_install_mtl' - - name: "Check if JPEG XS is installed" - id: check_jpegxs - run: | - if [ -d "/usr/local/include/svt-jpegxs" ] && [ -f "/usr/local/lib/libSvtJpegXS.so" ]; then - echo "JPEG XS already installed." - echo "installed=true" >> $GITHUB_OUTPUT - else - echo "JPEG XS needs to be installed." - echo "installed=false" >> $GITHUB_OUTPUT - fi - - name: "Build and Install JPEG XS" - if: steps.check_jpegxs.outputs.installed != 'true' run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' - - name: "Check if JPEG XS ffmpeg plugin is installed" - id: check_jpegxs_plugin - run: | - if [ -f "/usr/local/etc/imtl.json" ]; then - echo "JPEG XS ffmpeg plugin already installed." - echo "installed=true" >> $GITHUB_OUTPUT - else - echo "JPEG XS ffmpeg plugin needs to be installed." - echo "installed=false" >> $GITHUB_OUTPUT - fi - - name: "Build and Install JPEG XS ffmpeg plugin" - if: steps.check_jpegxs_plugin.outputs.installed != 'true' run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' - - name: "Check if gRPC is installed" - id: check_grpc - run: | - if [ -d "/usr/local/include/grpc" ] && [ -d "/usr/local/include/grpcpp" ]; then - echo "gRPC already installed." - echo "installed=true" >> $GITHUB_OUTPUT - else - echo "gRPC needs to be installed." - echo "installed=false" >> $GITHUB_OUTPUT - fi - - name: "Build gRPC and dependencies" - if: steps.check_grpc.outputs.installed != 'true' run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' - - name: "Check if MCM SDK and Media Proxy are built" - id: check_mcm - run: | - if [ -f "${BUILD_DIR}/mcm/bin/media_proxy" ] && [ -f "${BUILD_DIR}/mcm/bin/mesh-agent" ] && [ -f "${BUILD_DIR}/mcm/lib/libmcm_dp.so" ]; then - echo "MCM SDK and Media Proxy are already built." - echo "built=true" >> $GITHUB_OUTPUT - else - echo "MCM SDK and Media Proxy need to be built." - echo "built=false" >> $GITHUB_OUTPUT - fi - - name: "Build MCM SDK and Media Proxy" - if: steps.check_mcm.outputs.built != 'true' run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' - - name: "Check if FFmpeg 6.1 with MCM plugin is built" - id: check_ffmpeg_61 - run: | - if [ -f "${BUILD_DIR}/ffmpeg-6-1/ffmpeg" ]; then - echo "FFmpeg 6.1 with MCM plugin is already built." - echo "built=true" >> $GITHUB_OUTPUT - else - echo "FFmpeg 6.1 with MCM plugin needs to be built." - echo "built=false" >> $GITHUB_OUTPUT - fi - - name: "Build FFmpeg 6.1 with MCM plugin" - if: steps.check_ffmpeg_61.outputs.built != 'true' working-directory: ${{ github.workspace }}/ffmpeg-plugin run: | ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ ./build-ffmpeg.sh "6.1" - - name: "Check if FFmpeg 7.0 with MCM plugin is built" - id: check_ffmpeg_70 - run: | - if [ -f "${BUILD_DIR}/ffmpeg-7-0/ffmpeg" ]; then - echo "FFmpeg 7.0 with MCM plugin is already built." - echo "built=true" >> $GITHUB_OUTPUT - else - echo "FFmpeg 7.0 with MCM plugin needs to be built." - echo "built=false" >> $GITHUB_OUTPUT - fi - - name: "Build FFmpeg 7.0 with MCM plugin" - if: steps.check_ffmpeg_70.outputs.built != 'true' working-directory: ${{ github.workspace }}/ffmpeg-plugin run: | ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ From 916106e5f7eaa96b5ca885f8a8cc43590bab8e07 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Mon, 25 Aug 2025 16:11:37 +0000 Subject: [PATCH 063/123] Refactor media file paths and FFmpeg configurations in validation tests for improved organization and clarity --- .github/workflows/smoke_tests.yml | 5 ++++ tests/validation/configs/config_readme.md | 26 ++++++++++++++----- .../configs/test_config_workflow.yaml | 15 ----------- .../configs/topology_config_workflow.yaml | 15 ++++++++++- .../validation/configs/topology_template.yaml | 12 +++++++++ .../local/audio/test_ffmpeg_audio.py | 25 +++++++++--------- 6 files changed, 63 insertions(+), 35 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 1e13f3188..85fedc99e 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -142,8 +142,13 @@ jobs: echo "USER=${USER}" >> "$GITHUB_ENV" sed -i "s/{{ USER }}/root/g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml + # sed -i "s|{{ INTEGRITY_PATH }}|${{ env.BUILD_DIR }}/integrity/|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ OUTPUT_PATH }}|${{ github.workspace }}/received/|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ INPUT_PATH }}|/mnt/media/|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ MTL_PATH }}|${{ env.BUILD_DIR }}/mtl/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MCM_PATH }}|${{ github.workspace }}|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MCM_FFMPEG_7_0 }}|${{ env.BUILD_DIR }}/ffmpeg-7-0/|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ LD_LIBRARY_PATH }}|${{ env.BUILD_DIR }}/mcm/lib/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ OUTPUT_PATH }}|${{ github.workspace }}/received/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ NICCTL_PATH }}|${{ env.BUILD_DIR }}/mtl/script/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ LOG_PATH }}|${{ env.LOG_DIR }}|g" tests/validation/configs/test_config_workflow.yaml diff --git a/tests/validation/configs/config_readme.md b/tests/validation/configs/config_readme.md index da21fc72b..a72eb484b 100644 --- a/tests/validation/configs/config_readme.md +++ b/tests/validation/configs/config_readme.md @@ -25,6 +25,13 @@ hosts: extra_info: mtl_path: /opt/intel/mtl nicctl_path: /opt/intel/mtl/script + filepath: /mnt/media/ + output_path: /home/gta/received/ + ffmpeg_path: /opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build/bin/ffmpeg + prefix_variables: + LD_LIBRARY_PATH: /opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build/lib + NO_PROXY: 127.0.0.1,localhost + no_proxy: 127.0.0.1,localhost media_proxy: st2110: true sdk_port: 8002 @@ -66,6 +73,12 @@ A list of host definitions. Each host can have the following fields: - **integrity_path**: Path for integrity scripts on the host if you want to run integrity tests (optional). - **mtl_path**: Custom path to the MTL repo (optional) default is /mcm_path/_build/mtl. - **nicctl_path**: Path to `nicctl.sh` script (optional). + - **filepath**: Path to input media files for transmitter (e.g., `/mnt/media/`). + - **output_path**: Path where output files will be stored (e.g., `/home/gta/received/`). + - **ffmpeg_path**: Path to FFmpeg binary (e.g., `/opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build/bin/ffmpeg`). + - **prefix_variables**: Environment variables to set before running FFmpeg. + - **LD_LIBRARY_PATH**: Path to FFmpeg libraries. + - **NO_PROXY**, **no_proxy**: Proxy bypass settings. - **media_proxy**: (Optional) Media proxy configuration. DO NOT set this if you don't want to run media proxy process. - **st2110**: Set to `true` to use the ST2110 bridge. - **sdk_port**: Port for media proxy SDK. @@ -85,6 +98,7 @@ A list of host definitions. Each host can have the following fields: - Fields marked as optional can be omitted if default values are sufficient. - You can add more hosts or network interfaces as needed. - If a field is not set, the system will use default values or those set in the OS. +- FFmpeg and file path configurations are now recommended to be defined directly in the host's `extra_info` section rather than using the previously nested `tx` and `rx` structure, which has been deprecated. --- @@ -92,23 +106,23 @@ A list of host definitions. Each host can have the following fields: ## Test config Structure Overview ```yaml -# Mesh agent configuration +# Mesh agent configuration (if not defined in topology) mesh-agent: control_port: 8100 proxy_port: 50051 -# ST2110 configuration +# ST2110 configuration (if not defined in topology) st2110: sdk: 8002 -# RDMA configuration +# RDMA configuration (if not defined in topology) rdma: rdma_ports: 9100-9999 -# Path configurations -media_path: "/mnt/media/" +# Global path configurations (if not defined in host's extra_info) +# Note: It's now recommended to define these in the host's extra_info section +# of the topology file for better organization and host-specific settings input_path: "/opt/intel/input_path/" -output_path: "/opt/intel/output_path/" # Build configurations mcm_ffmpeg_rebuild: false # Set to true to rebuild FFmpeg with MCM plugin diff --git a/tests/validation/configs/test_config_workflow.yaml b/tests/validation/configs/test_config_workflow.yaml index 979991fb2..bdad2b126 100644 --- a/tests/validation/configs/test_config_workflow.yaml +++ b/tests/validation/configs/test_config_workflow.yaml @@ -1,18 +1,3 @@ -tx: - filepath: /mnt/media/ - p_sip: "192.168.39.39" - ffmpeg_path: ffmpeg - prefix_variables: - NO_PROXY: 127.0.0.1,localhost - no_proxy: 127.0.0.1,localhost -rx: - filepath: /dev/null/ - ffmpeg_path: ffmpeg - prefix_variables: - NO_PROXY: 127.0.0.1,localhost - no_proxy: 127.0.0.1,localhost - -# other broadcast_ip: '239.2.39.239' port: 20000 payload_type: 100 diff --git a/tests/validation/configs/topology_config_workflow.yaml b/tests/validation/configs/topology_config_workflow.yaml index a2292b3b0..9d3320274 100644 --- a/tests/validation/configs/topology_config_workflow.yaml +++ b/tests/validation/configs/topology_config_workflow.yaml @@ -17,8 +17,21 @@ hosts: password: None key_path: {{ KEY_PATH }} extra_info: + input_path: {{ INPUT_PATH }} + output_path: {{ OUTPUT_PATH }} + # integrity_path: {{ INTEGRITY_PATH }} + mtl_path: {{ MTL_PATH }} mcm_path: {{ MCM_PATH }} - ffmpeg_path: {{ MCM_FFMPEG_7_0 }} + mcm_ffmpeg_path: {{ MCM_FFMPEG_7_0 }} + mcm_prefix_variables: + LD_LIBRARY_PATH: {{ LD_LIBRARY_PATH }} + NO_PROXY: 127.0.0.1,localhost + no_proxy: 127.0.0.1,localhost + # mtl_ffmpeg_path: {{ MTL_FFMPEG_7_0 }} + # mtl_prefix_variables: + # LD_LIBRARY_PATH: /opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mtl_build/lib + # NO_PROXY: 127.0.0.1,localhost + # no_proxy: 127.0.0.1,localhost output_path: {{ OUTPUT_PATH }} nicctl_path: {{ NICCTL_PATH }} mesh_agent: diff --git a/tests/validation/configs/topology_template.yaml b/tests/validation/configs/topology_template.yaml index 72439ec1c..0cad0abd1 100644 --- a/tests/validation/configs/topology_template.yaml +++ b/tests/validation/configs/topology_template.yaml @@ -18,6 +18,18 @@ hosts: extra_info: # This is the list of extra information that will be used in the test for each host mtl_path: /opt/intel/mtl # this is custom path to mtl repo if you want to use different path than default in mcm _build directory nicctl_path: /opt/intel/mtl/script # this is path for nicctl.sh script if you want to use different path + + # File paths for tests + filepath: /mnt/media/ # Path to input media files for transmitter + output_path: /home/gta/received/ # Path where output files will be stored + + # FFmpeg configuration + ffmpeg_path: /opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build/bin/ffmpeg # Path to FFmpeg binary + prefix_variables: # Environment variables to set before running FFmpeg + LD_LIBRARY_PATH: /opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build/lib + NO_PROXY: 127.0.0.1,localhost + no_proxy: 127.0.0.1,localhost + media_proxy: # st2110 and rdma dev and ips are optional but remember that it overrites the default values and must be set in host st2110: {{ st2110|default(true) }} # set it to true if you want to use st2110 bridge for media proxy sdk_port: {{ sdk_port|default(8002) }} # this is the port that will be used for media proxy sdk diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index 883170111..578a886f6 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -37,14 +37,14 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, lo if len(host_list) < 1: pytest.skip("Local tests require at least 1 host") tx_host = rx_host = host_list[0] - tx_prefix_variables = test_config["tx"].get("prefix_variables", None) - rx_prefix_variables = test_config["rx"].get("prefix_variables", None) - tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( + # Access prefix_variables directly + if hasattr(tx_host.topology.extra_info, "prefix_variables"): + prefix_variables = dict(tx_host.topology.extra_info.prefix_variables) + else: + prefix_variables = {} + prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( tx_host.topology.extra_info.media_proxy["sdk_port"] ) - rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( - rx_host.topology.extra_info.media_proxy["sdk_port"] - ) audio_format = audio_file_format_to_format_dict( str(audio_files_25_03[audio_type]["format"]) @@ -65,7 +65,7 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, lo ac=int(audio_files_25_03[audio_type]["channels"]), ar=int(audio_files_25_03[audio_type]["sample_rate"]), stream_loop=False, - input_path=f'{test_config["tx"]["filepath"]}{audio_files_25_03[audio_type]["filename"]}', + input_path=f'{tx_host.topology.extra_info.filepath}{audio_files_25_03[audio_type]["filename"]}', ) mcm_tx_outp = FFmpegMcmMemifAudioIO( channels=int(audio_files_25_03[audio_type]["channels"]), @@ -74,13 +74,12 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, lo output_path="-", ) mcm_tx_ff = FFmpeg( - prefix_variables=tx_prefix_variables, - ffmpeg_path=test_config["tx"]["ffmpeg_path"], + prefix_variables=prefix_variables, + ffmpeg_path=tx_host.topology.extra_info.mcm_ffmpeg_path, ffmpeg_input=mcm_tx_inp, ffmpeg_output=mcm_tx_outp, yes_overwrite=False, ) - logger.debug(f"Tx command: {mcm_tx_ff.get_command()}") mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) @@ -96,11 +95,11 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, lo ac=int(audio_files_25_03[audio_type]["channels"]), ar=int(audio_files_25_03[audio_type]["sample_rate"]), channel_layout=audio_channel_layout, - output_path=f'{test_config["rx"]["filepath"]}test_{audio_files_25_03[audio_type]["filename"]}', + output_path=f'{rx_host.topology.extra_info.output_path}test_{audio_files_25_03[audio_type]["filename"]}', ) mcm_rx_ff = FFmpeg( - prefix_variables=rx_prefix_variables, - ffmpeg_path=test_config["rx"]["ffmpeg_path"], + prefix_variables=prefix_variables, + ffmpeg_path=rx_host.topology.extra_info.mcm_ffmpeg_path, ffmpeg_input=mcm_rx_inp, ffmpeg_output=mcm_rx_outp, yes_overwrite=True, From dc0117b709210c10daa5a673d0585ead9400e5c9 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 26 Aug 2025 15:01:09 +0000 Subject: [PATCH 064/123] Refactor log validation constants and remove unused log validation utilities --- tests/validation/Engine/const.py | 33 ------------- .../validation/Engine/log_validation_utils.py | 46 ------------------- tests/validation/common/log_constants.py | 6 --- .../configs/topology_config_workflow.yaml | 1 - .../local/audio/test_ffmpeg_audio.py | 19 ++++---- 5 files changed, 11 insertions(+), 94 deletions(-) delete mode 100644 tests/validation/Engine/log_validation_utils.py diff --git a/tests/validation/Engine/const.py b/tests/validation/Engine/const.py index 7efb732f9..58ec41f0b 100644 --- a/tests/validation/Engine/const.py +++ b/tests/validation/Engine/const.py @@ -2,39 +2,6 @@ # Copyright 2024-2025 Intel Corporation # Media Communications Mesh -# Required ordered log phrases for Rx validation -RX_REQUIRED_LOG_PHRASES = [ - '[RX] Launching RX App', - '[RX] Reading client configuration', - '[RX] Reading connection configuration', - '[DEBU] JSON client config:', - '[INFO] Media Communications Mesh SDK version', - '[DEBU] JSON conn config:', - '[RX] Fetched mesh data buffer', - '[RX] Saving buffer data to a file', - '[RX] Done reading the data', - '[RX] dropping connection to media-proxy', - '[RX] Shuting down connection', - 'INFO - memif disconnected!', - '[INFO] gRPC: connection deleted', - '[RX] Shutting down client', -] -TX_REQUIRED_LOG_PHRASES = [ - '[TX] Launching TX app', - '[TX] Reading client configuration', - '[TX] Reading connection configuration', - '[DEBU] JSON client config:', - '[INFO] Media Communications Mesh SDK version', - '[DEBU] JSON conn config:', - '[INFO] gRPC: connection created', - 'INFO - Create memif socket.', - 'INFO - Create memif interface.', - '[TX] Sending packet:', - '[TX] Shuting down connection', - '[INFO] gRPC: connection deleted', - '[TX] Shutting down client', -] - LOG_FOLDER = "logs" DEFAULT_MEDIA_PATH = "/mnt/media/" DEFAULT_INPUT_PATH = "/opt/intel/input_path/" diff --git a/tests/validation/Engine/log_validation_utils.py b/tests/validation/Engine/log_validation_utils.py deleted file mode 100644 index 43549f4ee..000000000 --- a/tests/validation/Engine/log_validation_utils.py +++ /dev/null @@ -1,46 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright 2024-2025 Intel Corporation -# Media Communications Mesh - -""" -Common log validation utilities for use by both rxtxapp and ffmpeg validation. -""" -import logging -from typing import List, Tuple, Dict - -logger = logging.getLogger(__name__) - -def check_phrases_in_order(log_path: str, phrases: List[str]) -> Tuple[bool, List[str], Dict[str, List[str]]]: - """ - Check that all required phrases appear in order in the log file. - Returns (all_found, missing_phrases, context_lines) - """ - found_indices = [] - missing_phrases = [] - lines_around_missing = {} - with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: - lines = [line.strip() for line in f] - - idx = 0 - for phrase_idx, phrase in enumerate(phrases): - found = False - phrase_stripped = phrase.strip() - start_idx = idx # Remember where we started searching - - while idx < len(lines): - line_stripped = lines[idx].strip() - if phrase_stripped in line_stripped: - found_indices.append(idx) - found = True - idx += 1 - break - idx += 1 - - if not found: - missing_phrases.append(phrase) - # Store context - lines around where we were searching - context_start = max(0, start_idx - 3) - context_end = min(len(lines), start_idx + 7) - lines_around_missing[phrase] = lines[context_start:context_end] - - return len(missing_phrases) == 0, missing_phrases, lines_around_missing diff --git a/tests/validation/common/log_constants.py b/tests/validation/common/log_constants.py index 3c9782c7f..ccfa61a60 100644 --- a/tests/validation/common/log_constants.py +++ b/tests/validation/common/log_constants.py @@ -8,7 +8,6 @@ # Required ordered log phrases for Rx validation RX_REQUIRED_LOG_PHRASES = [ - '[RX] Launching RX App', '[RX] Reading client configuration', '[RX] Reading connection configuration', '[DEBU] JSON client config:', @@ -18,15 +17,12 @@ '[RX] Saving buffer data to a file', '[RX] Done reading the data', '[RX] dropping connection to media-proxy', - '[RX] Shuting down connection', 'INFO - memif disconnected!', '[INFO] gRPC: connection deleted', - '[RX] Shutting down client', ] # Required ordered log phrases for Tx validation TX_REQUIRED_LOG_PHRASES = [ - '[TX] Launching TX app', '[TX] Reading client configuration', '[TX] Reading connection configuration', '[DEBU] JSON client config:', @@ -36,9 +32,7 @@ 'INFO - Create memif socket.', 'INFO - Create memif interface.', '[TX] Sending packet:', - '[TX] Shuting down connection', '[INFO] gRPC: connection deleted', - '[TX] Shutting down client', ] # Common error keywords to look for in logs diff --git a/tests/validation/configs/topology_config_workflow.yaml b/tests/validation/configs/topology_config_workflow.yaml index 9d3320274..7fbaaf30d 100644 --- a/tests/validation/configs/topology_config_workflow.yaml +++ b/tests/validation/configs/topology_config_workflow.yaml @@ -32,7 +32,6 @@ hosts: # LD_LIBRARY_PATH: /opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mtl_build/lib # NO_PROXY: 127.0.0.1,localhost # no_proxy: 127.0.0.1,localhost - output_path: {{ OUTPUT_PATH }} nicctl_path: {{ NICCTL_PATH }} mesh_agent: control_port: 8100 diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index 578a886f6..13aa3d7be 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -17,7 +17,10 @@ from common.ffmpeg_handler.ffmpeg_io import FFmpegAudioIO from common.ffmpeg_handler.mcm_ffmpeg import FFmpegMcmMemifAudioIO -from Engine.const import FFMPEG_RUN_TIMEOUT +from Engine.const import ( + FFMPEG_RUN_TIMEOUT, + DEFAULT_OUTPUT_PATH, +) logger = logging.getLogger(__name__) @@ -30,7 +33,7 @@ *[f for f in audio_files_25_03.keys() if f != "PCM16_48000_Stereo"], ], ) -def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, log_path) -> None: +def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, log_path, media_path) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts host_list = list(hosts.values()) @@ -38,8 +41,8 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, lo pytest.skip("Local tests require at least 1 host") tx_host = rx_host = host_list[0] # Access prefix_variables directly - if hasattr(tx_host.topology.extra_info, "prefix_variables"): - prefix_variables = dict(tx_host.topology.extra_info.prefix_variables) + if hasattr(tx_host.topology.extra_info, "mcm_prefix_variables"): + prefix_variables = dict(tx_host.topology.extra_info.mcm_prefix_variables) else: prefix_variables = {} prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( @@ -65,7 +68,7 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, lo ac=int(audio_files_25_03[audio_type]["channels"]), ar=int(audio_files_25_03[audio_type]["sample_rate"]), stream_loop=False, - input_path=f'{tx_host.topology.extra_info.filepath}{audio_files_25_03[audio_type]["filename"]}', + input_path=f'{media_path}{audio_files_25_03[audio_type]["filename"]}', ) mcm_tx_outp = FFmpegMcmMemifAudioIO( channels=int(audio_files_25_03[audio_type]["channels"]), @@ -75,7 +78,7 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, lo ) mcm_tx_ff = FFmpeg( prefix_variables=prefix_variables, - ffmpeg_path=tx_host.topology.extra_info.mcm_ffmpeg_path, + ffmpeg_path="/opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build/bin/ffmpeg", ffmpeg_input=mcm_tx_inp, ffmpeg_output=mcm_tx_outp, yes_overwrite=False, @@ -95,11 +98,11 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, lo ac=int(audio_files_25_03[audio_type]["channels"]), ar=int(audio_files_25_03[audio_type]["sample_rate"]), channel_layout=audio_channel_layout, - output_path=f'{rx_host.topology.extra_info.output_path}test_{audio_files_25_03[audio_type]["filename"]}', + output_path=f'{DEFAULT_OUTPUT_PATH}/test_{audio_files_25_03[audio_type]["filename"]}', ) mcm_rx_ff = FFmpeg( prefix_variables=prefix_variables, - ffmpeg_path=rx_host.topology.extra_info.mcm_ffmpeg_path, + ffmpeg_path="/opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build/bin/ffmpeg", ffmpeg_input=mcm_rx_inp, ffmpeg_output=mcm_rx_outp, yes_overwrite=True, From 5d664cc62f044b0bad26873c127b375caae5165b Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 26 Aug 2025 15:40:00 +0000 Subject: [PATCH 065/123] Update integrity and library paths in smoke tests configuration for accuracy --- .github/workflows/bare-metal-build.yml | 1 + .github/workflows/smoke_tests.yml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 709c65f83..468acdba0 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -170,3 +170,4 @@ jobs: ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg + ${{ env.BUILD_DIR }}/ffmpeg-7-0/lib/** diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 85fedc99e..5a36572ab 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -142,13 +142,13 @@ jobs: echo "USER=${USER}" >> "$GITHUB_ENV" sed -i "s/{{ USER }}/root/g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml - # sed -i "s|{{ INTEGRITY_PATH }}|${{ env.BUILD_DIR }}/integrity/|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ INTEGRITY_PATH }}|${{ github.workspace }}/tests/validation/common/integrity|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ OUTPUT_PATH }}|${{ github.workspace }}/received/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ INPUT_PATH }}|/mnt/media/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MTL_PATH }}|${{ env.BUILD_DIR }}/mtl/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MCM_PATH }}|${{ github.workspace }}|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MCM_FFMPEG_7_0 }}|${{ env.BUILD_DIR }}/ffmpeg-7-0/|g" tests/validation/configs/topology_config_workflow.yaml - sed -i "s|{{ LD_LIBRARY_PATH }}|${{ env.BUILD_DIR }}/mcm/lib/|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ LD_LIBRARY_PATH }}|${{ env.BUILD_DIR }}/ffmpeg-7-0/lib|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ OUTPUT_PATH }}|${{ github.workspace }}/received/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ NICCTL_PATH }}|${{ env.BUILD_DIR }}/mtl/script/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ LOG_PATH }}|${{ env.LOG_DIR }}|g" tests/validation/configs/test_config_workflow.yaml From 7d09459947cac4055eacfe05ef03ae98706c8027 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Tue, 26 Aug 2025 16:43:41 +0000 Subject: [PATCH 066/123] Update bare-metal build and smoke tests for FFmpeg 7.0 integration and environment variable consistency --- .github/workflows/bare-metal-build.yml | 12 ++--------- .github/workflows/smoke_tests.yml | 20 ++++++++++--------- .../configs/topology_config_workflow.yaml | 7 +------ 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 468acdba0..36710895e 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -66,10 +66,9 @@ jobs: if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' - - name: "Clone and patch ffmpeg 6.1 and 7.0" + - name: "Clone and patch ffmpeg 7.0" if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' run: | - ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" - name: "Build and Install xdp and libbpf" @@ -148,16 +147,10 @@ jobs: - name: "Build MCM SDK and Media Proxy" run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' - - name: "Build FFmpeg 6.1 with MCM plugin" - working-directory: ${{ github.workspace }}/ffmpeg-plugin - run: | - ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ - ./build-ffmpeg.sh "6.1" - - name: "Build FFmpeg 7.0 with MCM plugin" working-directory: ${{ github.workspace }}/ffmpeg-plugin run: | - ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ + ./configure-ffmpeg.sh "7.0" --prefix=${{ env.BUILD_DIR }}/ffmpeg-7-0 --disable-doc --disable-debug && \ ./build-ffmpeg.sh "7.0" - name: "upload media-proxy and mcm binaries" @@ -168,6 +161,5 @@ jobs: ${{ env.BUILD_DIR }}/mcm/bin/media_proxy ${{ env.BUILD_DIR }}/mcm/bin/mesh-agent ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* - ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg ${{ env.BUILD_DIR }}/ffmpeg-7-0/lib/** diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 5a36572ab..f56fea347 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -41,13 +41,15 @@ env: DPDK_REBUILD: "false" MEDIA_PATH: "/mnt/media" BUILD_DIR: "${{ github.workspace }}/_build" + INPUT_PATH: "/mnt/media/" + OUTPUT_PATH: "${{ github.workspace }}/received/" MCM_BINARIES_DIR: "./mcm-binaries" MEDIA_PROXY: "./mcm-binaries/media_proxy" MESH_AGENT: "./mcm-binaries/mesh-agent" - MCM_FFMPEG_7_0: ./mcm-binaries/ffmpeg-7-0/ffmpeg - MTL_FFMPEG_7_0: ./mtl-binaries/ffmpeg-7-0/ffmpeg - MCM_FFMPEG_6_1: ./mcm-binaries/ffmpeg-6-1/ffmpeg - MTL_FFMPEG_6_1: ./mtl-binaries/ffmpeg-6-1/ffmpeg + MCM_FFMPEG_7_0: "${{ github.workspace }}/_build/ffmpeg-7-0/bin/ffmpeg" + MCM_LD_LIBRARY_PATH: "${{ github.workspace }}/_build/ffmpeg-7-0/lib/" + MTL_FFMPEG_7_0: "./mtl-binaries/ffmpeg-7-0/ffmpeg" + INTEGRITY_PATH: "${{ github.workspace }}/tests/validation/common/integrity" LOG_DIR: "${{ github.workspace }}/logs" permissions: @@ -142,13 +144,13 @@ jobs: echo "USER=${USER}" >> "$GITHUB_ENV" sed -i "s/{{ USER }}/root/g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml - sed -i "s|{{ INTEGRITY_PATH }}|${{ github.workspace }}/tests/validation/common/integrity|g" tests/validation/configs/topology_config_workflow.yaml - sed -i "s|{{ OUTPUT_PATH }}|${{ github.workspace }}/received/|g" tests/validation/configs/topology_config_workflow.yaml - sed -i "s|{{ INPUT_PATH }}|/mnt/media/|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ INTEGRITY_PATH }}|${{ env.INTEGRITY_PATH }}|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ OUTPUT_PATH }}|${{ env.OUTPUT_PATH }}|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ INPUT_PATH }}|${{ env.INPUT_PATH }}|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MTL_PATH }}|${{ env.BUILD_DIR }}/mtl/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ MCM_PATH }}|${{ github.workspace }}|g" tests/validation/configs/topology_config_workflow.yaml - sed -i "s|{{ MCM_FFMPEG_7_0 }}|${{ env.BUILD_DIR }}/ffmpeg-7-0/|g" tests/validation/configs/topology_config_workflow.yaml - sed -i "s|{{ LD_LIBRARY_PATH }}|${{ env.BUILD_DIR }}/ffmpeg-7-0/lib|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ MCM_FFMPEG_7_0 }}|${{ env.MCM_FFMPEG_7_0 }}|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ LD_LIBRARY_PATH }}|${{ env.MCM_LD_LIBRARY_PATH }}|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ OUTPUT_PATH }}|${{ github.workspace }}/received/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ NICCTL_PATH }}|${{ env.BUILD_DIR }}/mtl/script/|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ LOG_PATH }}|${{ env.LOG_DIR }}|g" tests/validation/configs/test_config_workflow.yaml diff --git a/tests/validation/configs/topology_config_workflow.yaml b/tests/validation/configs/topology_config_workflow.yaml index 7fbaaf30d..2704a13c4 100644 --- a/tests/validation/configs/topology_config_workflow.yaml +++ b/tests/validation/configs/topology_config_workflow.yaml @@ -19,7 +19,7 @@ hosts: extra_info: input_path: {{ INPUT_PATH }} output_path: {{ OUTPUT_PATH }} - # integrity_path: {{ INTEGRITY_PATH }} + integrity_path: {{ INTEGRITY_PATH }} mtl_path: {{ MTL_PATH }} mcm_path: {{ MCM_PATH }} mcm_ffmpeg_path: {{ MCM_FFMPEG_7_0 }} @@ -27,11 +27,6 @@ hosts: LD_LIBRARY_PATH: {{ LD_LIBRARY_PATH }} NO_PROXY: 127.0.0.1,localhost no_proxy: 127.0.0.1,localhost - # mtl_ffmpeg_path: {{ MTL_FFMPEG_7_0 }} - # mtl_prefix_variables: - # LD_LIBRARY_PATH: /opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mtl_build/lib - # NO_PROXY: 127.0.0.1,localhost - # no_proxy: 127.0.0.1,localhost nicctl_path: {{ NICCTL_PATH }} mesh_agent: control_port: 8100 From ee25d296764a9833b7e7ee99bfbdb33e3a792645 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 08:39:45 +0000 Subject: [PATCH 067/123] log correction --- .github/workflows/smoke_tests.yml | 2 +- tests/validation/common/log_constants.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index f56fea347..e434c8421 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -46,7 +46,7 @@ env: MCM_BINARIES_DIR: "./mcm-binaries" MEDIA_PROXY: "./mcm-binaries/media_proxy" MESH_AGENT: "./mcm-binaries/mesh-agent" - MCM_FFMPEG_7_0: "${{ github.workspace }}/_build/ffmpeg-7-0/bin/ffmpeg" + MCM_FFMPEG_7_0: "${{ github.workspace }}/_build/ffmpeg-7-0/ffmpeg" MCM_LD_LIBRARY_PATH: "${{ github.workspace }}/_build/ffmpeg-7-0/lib/" MTL_FFMPEG_7_0: "./mtl-binaries/ffmpeg-7-0/ffmpeg" INTEGRITY_PATH: "${{ github.workspace }}/tests/validation/common/integrity" diff --git a/tests/validation/common/log_constants.py b/tests/validation/common/log_constants.py index ccfa61a60..1f4d64765 100644 --- a/tests/validation/common/log_constants.py +++ b/tests/validation/common/log_constants.py @@ -18,7 +18,6 @@ '[RX] Done reading the data', '[RX] dropping connection to media-proxy', 'INFO - memif disconnected!', - '[INFO] gRPC: connection deleted', ] # Required ordered log phrases for Tx validation @@ -31,8 +30,6 @@ '[INFO] gRPC: connection created', 'INFO - Create memif socket.', 'INFO - Create memif interface.', - '[TX] Sending packet:', - '[INFO] gRPC: connection deleted', ] # Common error keywords to look for in logs From aedc1014755f9318679bc4733aa00c9868d56520 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 09:29:55 +0000 Subject: [PATCH 068/123] linter fix and base build not run when not needed --- .github/workflows/bare-metal-build.yml | 244 +++++++++++----------- .github/workflows/base_build.yml | 172 ++++++++------- .github/workflows/build_docker_tpl.yml | 12 +- .github/workflows/coverity.yml | 158 +++++++------- .github/workflows/docker_build.yml | 12 +- .github/workflows/github_pages_update.yml | 4 +- .github/workflows/scorecard.yml | 4 +- .github/workflows/trivy.yml | 18 +- .github/workflows/validation-tests.yml | 74 +++---- 9 files changed, 358 insertions(+), 340 deletions(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 36710895e..8a410f452 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -36,128 +36,128 @@ jobs: runs-on: [Linux, self-hosted] timeout-minutes: 120 steps: - - name: "Harden Runner" - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 - with: - egress-policy: audit - - name: "Fix permissions before checkout" - run: | - if [ -d "${{ github.workspace }}" ]; then - sudo chown -R "${USER}" "${{ github.workspace }}" || true - sudo chmod -R u+w "${{ github.workspace }}" || true - fi - - - name: "Checkout repository" - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - ref: ${{ inputs.tag || inputs.branch }} - - - name: 'Install OS level dependencies' - run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' - - - name: "Check local dependencies build cache" - id: load-local-dependencies-cache - uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - with: - path: ${{ env.BUILD_DIR }} - key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} - - - name: "Download, unpack and patch build dependencies" - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' - - - name: "Clone and patch ffmpeg 7.0" - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: | - ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" - - - name: "Build and Install xdp and libbpf" - run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' - - - name: "Build and Install libfabric" - run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' - - - name: "Build and Install the DPDK (skipped: already installed on runner)" - if: always() && false # Always skip this step for now - run: | - echo "Skipping DPDK build and install as it is already installed on the machine." - # eval 'source scripts/setup_build_env.sh && lib_install_dpdk' - - - name: "Check if DPDK version needs to be updated" - id: check_dpdk_version - run: | - if grep -q "DPDK_VER=25.03" "${{ github.workspace }}/versions.env" && \ - grep -q "DPDK_VER=25.03" "${{ github.workspace }}/ffmpeg-plugin/versions.env" && \ - grep -q "DPDK_VER=25.03" "${{ github.workspace }}/media-proxy/versions.env" && \ - grep -q "DPDK_VER=25.03" "${{ github.workspace }}/sdk/versions.env"; then - echo "DPDK version is already set correctly." - echo "need_update=false" >> $GITHUB_OUTPUT - else - echo "DPDK version needs to be updated." - echo "need_update=true" >> $GITHUB_OUTPUT - fi - - - name: "Switch DPDK version to currently used on the machine" - if: steps.check_dpdk_version.outputs.need_update == 'true' - run: | - sed -i 's|DPDK_VER=23.11|DPDK_VER=25.03|g' \ - "${{ github.workspace }}/versions.env" \ - "${{ github.workspace }}/ffmpeg-plugin/versions.env" \ - "${{ github.workspace }}/media-proxy/versions.env" \ - "${{ github.workspace }}/sdk/versions.env" - - - name: "Check if MTL version needs to be updated" - id: check_mtl_version - run: | - if grep -q "MTL_VER=main" "${{ github.workspace }}/versions.env" && \ - grep -q "MTL_VER=main" "${{ github.workspace }}/ffmpeg-plugin/versions.env" && \ - grep -q "MTL_VER=main" "${{ github.workspace }}/media-proxy/versions.env" && \ - grep -q "MTL_VER=main" "${{ github.workspace }}/sdk/versions.env"; then - echo "MTL version is already set correctly." - echo "need_update=false" >> $GITHUB_OUTPUT - else - echo "MTL version needs to be updated." - echo "need_update=true" >> $GITHUB_OUTPUT - fi - - - name: "Switch MTL version to currently used on the machine" - if: steps.check_mtl_version.outputs.need_update == 'true' - run: | - sed -i 's|MTL_VER=v25.02|MTL_VER=main|g' \ - "${{ github.workspace }}/versions.env" \ - "${{ github.workspace }}/ffmpeg-plugin/versions.env" \ - "${{ github.workspace }}/media-proxy/versions.env" \ - "${{ github.workspace }}/sdk/versions.env" - - - name: "Build and Install the MTL(skipped: already installed on runner)" - if: always() && false # Always skip this step for now - run: | - echo "Skipping MTL build and install as it is already installed on the machine." - # eval 'source scripts/setup_build_env.sh && lib_install_mtl' - - - name: "Build and Install JPEG XS" - run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' - - - name: "Build and Install JPEG XS ffmpeg plugin" - run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' - - - name: "Build gRPC and dependencies" - run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' - - - name: "Build MCM SDK and Media Proxy" - run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' - - - name: "Build FFmpeg 7.0 with MCM plugin" - working-directory: ${{ github.workspace }}/ffmpeg-plugin - run: | - ./configure-ffmpeg.sh "7.0" --prefix=${{ env.BUILD_DIR }}/ffmpeg-7-0 --disable-doc --disable-debug && \ - ./build-ffmpeg.sh "7.0" - - - name: "upload media-proxy and mcm binaries" - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: mcm-build - path: | + - name: "Harden Runner" + uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + with: + egress-policy: audit + - name: "Fix permissions before checkout" + run: | + if [ -d "${{ github.workspace }}" ]; then + sudo chown -R "${USER}" "${{ github.workspace }}" || true + sudo chmod -R u+w "${{ github.workspace }}" || true + fi + + - name: "Checkout repository" + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ inputs.tag || inputs.branch }} + + - name: "Install OS level dependencies" + run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' + + - name: "Check local dependencies build cache" + id: load-local-dependencies-cache + uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ env.BUILD_DIR }} + key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} + + - name: "Download, unpack and patch build dependencies" + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' + + - name: "Clone and patch ffmpeg 7.0" + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: | + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" + + - name: "Build and Install xdp and libbpf" + run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' + + - name: "Build and Install libfabric" + run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' + + - name: "Build and Install the DPDK (skipped: already installed on runner)" + if: always() && false # Always skip this step for now + run: | + echo "Skipping DPDK build and install as it is already installed on the machine." + # eval 'source scripts/setup_build_env.sh && lib_install_dpdk' + + - name: "Check if DPDK version needs to be updated" + id: check_dpdk_version + run: | + if grep -q "DPDK_VER=25.03" "${{ github.workspace }}/versions.env" && \ + grep -q "DPDK_VER=25.03" "${{ github.workspace }}/ffmpeg-plugin/versions.env" && \ + grep -q "DPDK_VER=25.03" "${{ github.workspace }}/media-proxy/versions.env" && \ + grep -q "DPDK_VER=25.03" "${{ github.workspace }}/sdk/versions.env"; then + echo "DPDK version is already set correctly." + echo "need_update=false" >> "$GITHUB_OUTPUT" + else + echo "DPDK version needs to be updated." + echo "need_update=true" >> "$GITHUB_OUTPUT" + fi + + - name: "Switch DPDK version to currently used on the machine" + if: steps.check_dpdk_version.outputs.need_update == 'true' + run: | + sed -i 's|DPDK_VER=23.11|DPDK_VER=25.03|g' \ + "${{ github.workspace }}/versions.env" \ + "${{ github.workspace }}/ffmpeg-plugin/versions.env" \ + "${{ github.workspace }}/media-proxy/versions.env" \ + "${{ github.workspace }}/sdk/versions.env" + + - name: "Check if MTL version needs to be updated" + id: check_mtl_version + run: | + if grep -q "MTL_VER=main" "${{ github.workspace }}/versions.env" && \ + grep -q "MTL_VER=main" "${{ github.workspace }}/ffmpeg-plugin/versions.env" && \ + grep -q "MTL_VER=main" "${{ github.workspace }}/media-proxy/versions.env" && \ + grep -q "MTL_VER=main" "${{ github.workspace }}/sdk/versions.env"; then + echo "MTL version is already set correctly." + echo "need_update=false" >> "$GITHUB_OUTPUT" + else + echo "MTL version needs to be updated." + echo "need_update=true" >> "$GITHUB_OUTPUT" + fi + + - name: "Switch MTL version to currently used on the machine" + if: steps.check_mtl_version.outputs.need_update == 'true' + run: | + sed -i 's|MTL_VER=v25.02|MTL_VER=main|g' \ + "${{ github.workspace }}/versions.env" \ + "${{ github.workspace }}/ffmpeg-plugin/versions.env" \ + "${{ github.workspace }}/media-proxy/versions.env" \ + "${{ github.workspace }}/sdk/versions.env" + + - name: "Build and Install the MTL(skipped: already installed on runner)" + if: always() && false # Always skip this step for now + run: | + echo "Skipping MTL build and install as it is already installed on the machine." + # eval 'source scripts/setup_build_env.sh && lib_install_mtl' + + - name: "Build and Install JPEG XS" + run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' + + - name: "Build and Install JPEG XS ffmpeg plugin" + run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' + + - name: "Build gRPC and dependencies" + run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' + + - name: "Build MCM SDK and Media Proxy" + run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' + + - name: "Build FFmpeg 7.0 with MCM plugin" + working-directory: ${{ github.workspace }}/ffmpeg-plugin + run: | + ./configure-ffmpeg.sh "7.0" --prefix=${{ env.BUILD_DIR }}/ffmpeg-7-0 --disable-doc --disable-debug && \ + ./build-ffmpeg.sh "7.0" + + - name: "upload media-proxy and mcm binaries" + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: mcm-build + path: | ${{ env.BUILD_DIR }}/mcm/bin/media_proxy ${{ env.BUILD_DIR }}/mcm/bin/mesh-agent ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index 6abb7ee03..f1f4057bc 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -2,9 +2,23 @@ name: Base Build on: push: - branches: [ "main" ] + branches: ["main"] + paths-ignore: + - '**/*.md' + - 'tests/**' + - 'docs/**' + - 'LICENSE' + - '.gitignore' + - '.editorconfig' pull_request: - branches: [ "main" ] + branches: ["main"] + paths-ignore: + - '**/*.md' + - 'tests/**' + - 'docs/**' + - 'LICENSE' + - '.gitignore' + - '.editorconfig' workflow_dispatch: env: @@ -27,80 +41,84 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: 'ubuntu-22.04' + runs-on: "ubuntu-22.04" timeout-minutes: 120 steps: - - name: 'Harden Runner' - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 - with: - egress-policy: audit - - - name: 'Checkout repository' - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: 'Install OS level dependencies' - run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' - - - name: 'Check local dependencies build cache' - id: load-local-dependencies-cache - uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - with: - path: ${{ env.BUILD_DIR }} - key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} - - - name: 'Download, unpack and patch build dependencies' - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' - - - name: 'Clone and patch ffmpeg 6.1 and 7.0' - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: | - ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" - ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" - - - name: 'Build and Install xdp and libbpf' - run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' - - - name: 'Build and Install libfabric' - run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' - - - name: 'Build and Install the DPDK' - run: eval 'source scripts/setup_build_env.sh && lib_install_dpdk' - - - name: 'Build and Install the MTL' - run: eval 'source scripts/setup_build_env.sh && lib_install_mtl' - - - name: 'Build and Install JPEG XS' - run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' - - - name: 'Build and Install JPEG XS ffmpeg plugin' - run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' - - - name: 'Build gRPC and dependencies' - run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' - - - name: 'Build MCM SDK and Media Proxy' - run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' - - - name: 'Build FFmpeg 6.1 with MCM plugin' - working-directory: ${{ github.workspace }}/ffmpeg-plugin - run: | - ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ - ./build-ffmpeg.sh "6.1" - - - name: 'Build FFmpeg 7.0 with MCM plugin' - working-directory: ${{ github.workspace }}/ffmpeg-plugin - run: | - ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ - ./build-ffmpeg.sh "7.0" - - - name: 'upload media-proxy and mcm binaries' - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: mcm-build - path: | - ${{ env.BUILD_DIR }}/mcm/bin/media_proxy - ${{ env.BUILD_DIR }}/mcm/bin/mesh-agent - ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* - ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg - ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg + - name: "Harden Runner" + uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + with: + egress-policy: audit + + - name: "Checkout repository" + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: "Install OS level dependencies" + run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' + + - name: "Check local dependencies build cache" + id: load-local-dependencies-cache + uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ env.BUILD_DIR }} + key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} + + - name: "Download, unpack and patch build dependencies" + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' + + - name: "Clone and patch ffmpeg 6.1 and 7.0" + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: | + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" + + - name: "Build and Install xdp and libbpf" + run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' + + - name: "Build and Install libfabric" + run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' + + - name: "Build and Install the DPDK" + run: eval 'source scripts/setup_build_env.sh && lib_install_dpdk' + + - name: "Build and Install the MTL" + run: eval 'source scripts/setup_build_env.sh && lib_install_mtl' + + - name: "Build and Install JPEG XS" + run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' + + - name: "Build and Install JPEG XS ffmpeg plugin" + run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' + + - name: "Build gRPC and dependencies" + run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' + + - name: "Build MCM SDK and Media Proxy" + run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' + + - name: "Build FFmpeg 6.1 with MCM plugin" + working-directory: ${{ github.workspace }}/ffmpeg-plugin + run: | + ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ + ./build-ffmpeg.sh "6.1" + + - name: "Build FFmpeg 7.0 with MCM plugin" + working-directory: ${{ github.workspace }}/ffmpeg-plugin + run: | + ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ + ./build-ffmpeg.sh "7.0" + + - name: "upload media-proxy and mcm binaries" + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: mcm-build + path: | + ${{ env.BUILD_DIR }}/mcm/bin/media_proxy + ${{ env.BUILD_DIR }}/mcm/bin/mesh-agent + ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* + ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg + ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg + ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg + ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg + ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg + ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg diff --git a/.github/workflows/build_docker_tpl.yml b/.github/workflows/build_docker_tpl.yml index 2571b14dc..ae26615aa 100644 --- a/.github/workflows/build_docker_tpl.yml +++ b/.github/workflows/build_docker_tpl.yml @@ -6,15 +6,15 @@ on: build_type: required: false type: string - default: 'Release' + default: "Release" docker_registry: required: false type: string - default: 'ghcr.io' + default: "ghcr.io" docker_registry_prefix: required: false type: string - default: 'openvisualcloud/media-communications-mesh' + default: "openvisualcloud/media-communications-mesh" docker_registry_login: required: false type: boolean @@ -26,11 +26,11 @@ on: docker_build_args: required: false type: string - default: '' + default: "" docker_build_platforms: required: false type: string - default: 'linux/amd64' + default: "linux/amd64" docker_image_tag: required: false type: string @@ -40,7 +40,7 @@ on: docker_file_path: required: false type: string - default: './Dockerfile' + default: "./Dockerfile" secrets: docker_registry_login: required: false diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index 6cea7daee..3da9ee595 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -2,12 +2,12 @@ name: Coverity Build on: schedule: - - cron: '0 18 * * *' + - cron: "0 18 * * *" workflow_dispatch: inputs: branch: - description: 'Branch to run scans on' - default: 'main' + description: "Branch to run scans on" + default: "main" type: string env: @@ -26,81 +26,81 @@ concurrency: jobs: coverity: - runs-on: 'ubuntu-22.04' + runs-on: "ubuntu-22.04" timeout-minutes: 90 steps: - - name: 'Harden Runner' - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 - with: - egress-policy: audit - - - name: 'Checkout repository' - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - with: - ref: ${{ inputs.branch }} - - - name: 'Install OS level dependencies' - run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' - - - name: 'Check local dependencies build cache' - id: load-local-dependencies-cache - uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 - with: - path: ${{ env.BUILD_DIR }} - key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} - - - name: 'Download, unpack and patch build dependencies' - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' - - - name: 'Clone and patch ffmpeg 6.1 and 7.0' - if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' - run: | - ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" - ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" - - - name: 'Build and Install xdp and libbpf' - run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' - - - name: 'Build and Install libfabric' - run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' - - - name: 'Build and Install the DPDK' - run: eval 'source scripts/setup_build_env.sh && lib_install_dpdk' - - - name: 'Build and Install the MTL' - run: eval 'source scripts/setup_build_env.sh && lib_install_mtl' - - - name: 'Build and Install JPEG XS' - run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' - - - name: 'Build and Install JPEG XS ffmpeg plugin' - run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' - - - name: 'Build gRPC and dependencies' - run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' - - - name: 'Configure ffmpeg and dependencies' - run: | - sed -i 's/strlen (MEMIF_DEFAULT_APP_NAME)/(sizeof(MEMIF_DEFAULT_APP_NAME) - 1)/g' ${{ github.workspace }}/sdk/3rdparty/libmemif/src/memif_private.h && \ - ${{ github.workspace }}/build.sh && \ - ${{ github.workspace }}/ffmpeg-plugin/configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ - ${{ github.workspace }}/ffmpeg-plugin/configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ - rm -rf ${{ github.workspace }}/_build/mcm - echo "\"${{ github.workspace }}/ffmpeg-plugin/build-ffmpeg.sh\" \"6.1\"" > ${{ github.workspace }}/build.sh - echo "\"${{ github.workspace }}/ffmpeg-plugin/build-ffmpeg.sh\" \"7.0\"" > ${{ github.workspace }}/build.sh - - - name: 'Run coverity' - uses: vapier/coverity-scan-action@2068473c7bdf8c2fb984a6a40ae76ee7facd7a85 # v1.8.0 - with: - project: 'Media-Communications-Mesh' - email: ${{ secrets.COVERITY_SCAN_EMAIL }} - token: ${{ secrets.COVERITY_SCAN_TOKEN }} - build_language: 'cxx' - build_platform: 'linux64' - command: ${{ github.workspace }}/build.sh - - - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 - with: - name: coverity-reports - path: '${{ github.workspace }}/cov-int' + - name: "Harden Runner" + uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1 + with: + egress-policy: audit + + - name: "Checkout repository" + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ inputs.branch }} + + - name: "Install OS level dependencies" + run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' + + - name: "Check local dependencies build cache" + id: load-local-dependencies-cache + uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: ${{ env.BUILD_DIR }} + key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} + + - name: "Download, unpack and patch build dependencies" + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' + + - name: "Clone and patch ffmpeg 6.1 and 7.0" + if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' + run: | + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" + ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" + + - name: "Build and Install xdp and libbpf" + run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' + + - name: "Build and Install libfabric" + run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' + + - name: "Build and Install the DPDK" + run: eval 'source scripts/setup_build_env.sh && lib_install_dpdk' + + - name: "Build and Install the MTL" + run: eval 'source scripts/setup_build_env.sh && lib_install_mtl' + + - name: "Build and Install JPEG XS" + run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' + + - name: "Build and Install JPEG XS ffmpeg plugin" + run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' + + - name: "Build gRPC and dependencies" + run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' + + - name: "Configure ffmpeg and dependencies" + run: | + sed -i 's/strlen (MEMIF_DEFAULT_APP_NAME)/(sizeof(MEMIF_DEFAULT_APP_NAME) - 1)/g' ${{ github.workspace }}/sdk/3rdparty/libmemif/src/memif_private.h && \ + ${{ github.workspace }}/build.sh && \ + ${{ github.workspace }}/ffmpeg-plugin/configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ + ${{ github.workspace }}/ffmpeg-plugin/configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ + rm -rf ${{ github.workspace }}/_build/mcm + echo "\"${{ github.workspace }}/ffmpeg-plugin/build-ffmpeg.sh\" \"6.1\"" > ${{ github.workspace }}/build.sh + echo "\"${{ github.workspace }}/ffmpeg-plugin/build-ffmpeg.sh\" \"7.0\"" > ${{ github.workspace }}/build.sh + + - name: "Run coverity" + uses: vapier/coverity-scan-action@2068473c7bdf8c2fb984a6a40ae76ee7facd7a85 # v1.8.0 + with: + project: "Media-Communications-Mesh" + email: ${{ secrets.COVERITY_SCAN_EMAIL }} + token: ${{ secrets.COVERITY_SCAN_TOKEN }} + build_language: "cxx" + build_platform: "linux64" + command: ${{ github.workspace }}/build.sh + + - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: coverity-reports + path: "${{ github.workspace }}/cov-int" diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index 03e689ef0..e728ac86e 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -2,9 +2,9 @@ name: Docker Build on: pull_request: - branches: [ "main", "dev" ] + branches: ["main", "dev"] push: - branches: [ "main", "dev" ] + branches: ["main", "dev"] workflow_dispatch: permissions: @@ -19,14 +19,14 @@ jobs: name: Build sdk Docker Image uses: ./.github/workflows/build_docker_tpl.yml with: - docker_file_path: "sdk/Dockerfile" + docker_file_path: "sdk/Dockerfile" docker_image_name: "sdk" ffmpeg-6-1-image-build: name: Build ffmpeg v6.1 Docker Image uses: ./.github/workflows/build_docker_tpl.yml with: - docker_file_path: "ffmpeg-plugin/Dockerfile" + docker_file_path: "ffmpeg-plugin/Dockerfile" docker_image_name: "ffmpeg-6-1" docker_build_args: "FFMPEG_VER=6.1" @@ -34,7 +34,7 @@ jobs: name: Build ffmpeg v7.0 Docker Image uses: ./.github/workflows/build_docker_tpl.yml with: - docker_file_path: "ffmpeg-plugin/Dockerfile" + docker_file_path: "ffmpeg-plugin/Dockerfile" docker_image_name: "ffmpeg-7-0" docker_build_args: "FFMPEG_VER=7.0" @@ -42,5 +42,5 @@ jobs: name: Build Media-Proxy Docker Image uses: ./.github/workflows/build_docker_tpl.yml with: - docker_file_path: "media-proxy/Dockerfile" + docker_file_path: "media-proxy/Dockerfile" docker_image_name: "media-proxy" diff --git a/.github/workflows/github_pages_update.yml b/.github/workflows/github_pages_update.yml index 168255574..170e9f87d 100644 --- a/.github/workflows/github_pages_update.yml +++ b/.github/workflows/github_pages_update.yml @@ -3,7 +3,7 @@ on: workflow_call: workflow_dispatch: push: - branches: [ "main" ] + branches: ["main"] env: DEBIAN_FRONTEND: noninteractive @@ -45,7 +45,7 @@ jobs: run: python3 -m pip install sphinx_book_theme myst_parser sphinxcontrib.mermaid sphinx-copybutton - name: Build documentation - run: make -C docs/sphinx html + run: make -C docs/sphinx html - name: Upload GitHub Pages artifact uses: actions/upload-pages-artifact@v3.0.1 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 3e7f940f1..569bb5565 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -6,10 +6,10 @@ on: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - - cron: '0 18 * * *' + - cron: "0 18 * * *" workflow_dispatch: push: - branches: [ "main" ] + branches: ["main"] permissions: contents: read diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index c5192e927..d29e76a58 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,14 +1,14 @@ name: Trivy on: push: - branches: [ "main", "maint-*" ] + branches: ["main", "maint-*"] pull_request: - branches: [ "main", "maint-*" ] + branches: ["main", "maint-*"] workflow_dispatch: inputs: branch: - description: 'branch to run scans on' - default: 'main' + description: "branch to run scans on" + default: "main" type: string permissions: @@ -21,7 +21,7 @@ concurrency: jobs: scan: permissions: - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results runs-on: ubuntu-22.04 name: "Trivy: Perform scans job" steps: @@ -40,8 +40,8 @@ jobs: with: scan-type: config skip-dirs: deployment #helm charts not supported - exit-code: '0' - format: 'sarif' + exit-code: "0" + format: "sarif" output: "trivy-config-scan-results-${{ github.event.pull_request.number || github.sha }}.sarif" - name: "Trivy: Run vulnerability scanner for type=config (out=table)" @@ -50,8 +50,8 @@ jobs: with: scan-type: config skip-dirs: deployment #helm charts not supported - exit-code: '0' - format: 'table' + exit-code: "0" + format: "table" output: "trivy-config-scan-results-${{ github.event.pull_request.number || github.sha }}.txt" - name: "Trivy: Upload scan results to GitHub Security tab" diff --git a/.github/workflows/validation-tests.yml b/.github/workflows/validation-tests.yml index adcd1154e..479c528b5 100644 --- a/.github/workflows/validation-tests.yml +++ b/.github/workflows/validation-tests.yml @@ -6,13 +6,13 @@ on: inputs: branch-to-checkout: type: string - default: 'main' + default: "main" required: false - description: 'Branch name to use' + description: "Branch name to use" validation-iface-binding: type: choice required: true - description: 'Type of iface binding to use' + description: "Type of iface binding to use" options: - "create_vf" - "create_kvf" @@ -22,7 +22,7 @@ on: validation-test-port-p: type: choice required: true - description: 'Which to use as Test-Port-P' + description: "Which to use as Test-Port-P" options: - TEST_VF_PORT_P_0 - TEST_VF_PORT_P_1 @@ -37,7 +37,7 @@ on: validation-test-port-r: type: choice required: true - description: 'Which to use as Test-Port-R' + description: "Which to use as Test-Port-R" options: - TEST_VF_PORT_P_1 - TEST_VF_PORT_P_0 @@ -52,22 +52,22 @@ on: validation-no-fail-tests: type: choice required: false - description: 'Run all tests, non will fail' + description: "Run all tests, non will fail" options: - "true" - "false" validation-tests-1: type: string - default: 'single/video/pacing' + default: "single/video/pacing" required: true - description: '1st validation tests to run' + description: "1st validation tests to run" validation-tests-2: type: string - default: 'single/ancillary' + default: "single/ancillary" required: false - description: '2nd validation tests to run' + description: "2nd validation tests to run" validation-pre-release-1: - description: 'Select from pre-release group tests nr-1' + description: "Select from pre-release group tests nr-1" required: false type: choice options: @@ -82,7 +82,7 @@ on: - video - xdp validation-pre-release-2: - description: 'Select from pre-release group tests nr-2' + description: "Select from pre-release group tests nr-2" required: false type: choice options: @@ -96,7 +96,7 @@ on: - virtio-enable - wrong-parameter validation-pre-release-3: - description: 'Select from pre-release group tests nr-3' + description: "Select from pre-release group tests nr-3" required: false type: choice options: @@ -105,9 +105,9 @@ on: - gpu-enabling env: - BUILD_TYPE: 'Release' - DPDK_VERSION: '23.11' - DPDK_REBUILD: 'false' + BUILD_TYPE: "Release" + DPDK_VERSION: "23.11" + DPDK_REBUILD: "false" permissions: contents: read @@ -119,71 +119,71 @@ jobs: outputs: pipenv-activate: ${{ steps.pipenv-install.outputs.VIRTUAL_ENV }} steps: - - name: 'preparation: Harden Runner' + - name: "preparation: Harden Runner" uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - - name: 'Checkout repository' + - name: "Checkout repository" uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - name: 'Install OS level dependencies' + - name: "Install OS level dependencies" run: eval 'source scripts/setup_build_env.sh && install_package_dependencies' - - name: 'Check local dependencies build cache' + - name: "Check local dependencies build cache" id: load-local-dependencies-cache uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: ${{ env.BUILD_DIR }} key: ${{ runner.os }}-${{ hashFiles('versions.env') }}-${{ hashFiles('scripts/setup*.sh') }} - - name: 'Download, unpack and patch build dependencies' + - name: "Download, unpack and patch build dependencies" if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' run: eval 'source scripts/setup_build_env.sh && get_download_unpack_dependencies' - - name: 'Clone and patch ffmpeg 6.1 and 7.0' + - name: "Clone and patch ffmpeg 6.1 and 7.0" if: steps.load-local-dependencies-cache.outputs.cache-hit != 'true' run: | ffmpeg-plugin/clone-and-patch-ffmpeg.sh "6.1" ffmpeg-plugin/clone-and-patch-ffmpeg.sh "7.0" - - name: 'Build and Install xdp and libbpf' + - name: "Build and Install xdp and libbpf" run: eval 'source scripts/setup_build_env.sh && lib_install_xdp_bpf_tools' - - name: 'Build and Install libfabric' + - name: "Build and Install libfabric" run: eval 'source scripts/setup_build_env.sh && lib_install_fabrics' - - name: 'Build and Install the DPDK' + - name: "Build and Install the DPDK" run: eval 'source scripts/setup_build_env.sh && lib_install_dpdk' - - name: 'Build and Install the MTL' + - name: "Build and Install the MTL" run: eval 'source scripts/setup_build_env.sh && lib_install_mtl' - - name: 'Build and Install JPEG XS' + - name: "Build and Install JPEG XS" run: eval 'source scripts/setup_build_env.sh && lib_install_jpeg_xs' - - name: 'Build and Install JPEG XS ffmpeg plugin' + - name: "Build and Install JPEG XS ffmpeg plugin" run: eval 'source scripts/setup_build_env.sh && lib_install_mtl_jpeg_xs_plugin' - - name: 'Build gRPC and dependencies' + - name: "Build gRPC and dependencies" run: eval 'source scripts/setup_build_env.sh && lib_install_grpc' - - name: 'Build MCM SDK and Media Proxy' + - name: "Build MCM SDK and Media Proxy" run: eval 'source scripts/common.sh && ./build.sh "${PREFIX_DIR}"' - - name: 'Build FFmpeg 6.1 with MCM plugin' + - name: "Build FFmpeg 6.1 with MCM plugin" working-directory: ${{ github.workspace }}/ffmpeg-plugin run: | ./configure-ffmpeg.sh "6.1" --disable-doc --disable-debug && \ ./build-ffmpeg.sh "6.1" - - name: 'Build FFmpeg 7.0 with MCM plugin' + - name: "Build FFmpeg 7.0 with MCM plugin" working-directory: ${{ github.workspace }}/ffmpeg-plugin run: | ./configure-ffmpeg.sh "7.0" --disable-doc --disable-debug && \ ./build-ffmpeg.sh "7.0" - - name: 'installation: Install pipenv environment' + - name: "installation: Install pipenv environment" working-directory: tests/validation id: pipenv-install run: | @@ -197,16 +197,16 @@ jobs: runs-on: [Linux, self-hosted, DPDK] timeout-minutes: 720 env: - PYTEST_ALIAS: 'sudo --preserve-env python3 -m pipenv run pytest' + PYTEST_ALIAS: "sudo --preserve-env python3 -m pipenv run pytest" PYTEST_PARAMS: '--media=/mnt/media --build="../.."' - PYTEST_RETRIES: '3' + PYTEST_RETRIES: "3" steps: - - name: 'preparation: Harden Runner' + - name: "preparation: Harden Runner" uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - - name: 'cleanup: Generate runner summary' + - name: "cleanup: Generate runner summary" if: always() run: | { From 2eb15a7e423cac715aa2e30a212757dfa6f1276c Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 13:24:11 +0000 Subject: [PATCH 069/123] ffmpeg enable --- .../local/audio/test_ffmpeg_audio.py | 11 +++---- .../local/video/test_ffmpeg_video.py | 32 ++++++++++++------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index 13aa3d7be..fea7079fe 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -26,6 +26,7 @@ logger = logging.getLogger(__name__) +@pytest.mark.usefixtures("media_proxy") @pytest.mark.parametrize( "audio_type", [ @@ -33,14 +34,12 @@ *[f for f in audio_files_25_03.keys() if f != "PCM16_48000_Stereo"], ], ) -def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, log_path, media_path) -> None: - # media_proxy fixture used only to ensure that the media proxy is running - # Get TX and RX hosts +def test_local_ffmpeg_audio(hosts, test_config, audio_type: str, log_path, media_path) -> None: host_list = list(hosts.values()) if len(host_list) < 1: pytest.skip("Local tests require at least 1 host") tx_host = rx_host = host_list[0] - # Access prefix_variables directly + if hasattr(tx_host.topology.extra_info, "mcm_prefix_variables"): prefix_variables = dict(tx_host.topology.extra_info.mcm_prefix_variables) else: @@ -78,7 +77,7 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, lo ) mcm_tx_ff = FFmpeg( prefix_variables=prefix_variables, - ffmpeg_path="/opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build/bin/ffmpeg", + ffmpeg_path=tx_host.topology.extra_info.mcm_ffmpeg_path, ffmpeg_input=mcm_tx_inp, ffmpeg_output=mcm_tx_outp, yes_overwrite=False, @@ -102,7 +101,7 @@ def test_local_ffmpeg_audio(media_proxy, hosts, test_config, audio_type: str, lo ) mcm_rx_ff = FFmpeg( prefix_variables=prefix_variables, - ffmpeg_path="/opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build/bin/ffmpeg", + ffmpeg_path=rx_host.topology.extra_info.mcm_ffmpeg_path, ffmpeg_input=mcm_rx_inp, ffmpeg_output=mcm_rx_outp, yes_overwrite=True, diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index 2aac8f735..c95c9b44c 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -7,8 +7,6 @@ import pytest import logging -from Engine.rx_tx_app_file_validation_utils import cleanup_file - from Engine.media_files import video_files_25_03 from common.ffmpeg_handler.ffmpeg import FFmpeg, FFmpegExecutor @@ -19,11 +17,15 @@ from common.ffmpeg_handler.ffmpeg_io import FFmpegVideoIO from common.ffmpeg_handler.mcm_ffmpeg import FFmpegMcmMemifVideoIO -from Engine.const import FFMPEG_RUN_TIMEOUT +from Engine.const import ( + FFMPEG_RUN_TIMEOUT, + DEFAULT_OUTPUT_PATH, +) logger = logging.getLogger(__name__) +@pytest.mark.usefixtures("media_proxy") @pytest.mark.parametrize( "file", [ @@ -31,15 +33,21 @@ *[f for f in video_files_25_03.keys() if f != "FullHD_59.94"], ], ) -def test_local_ffmpeg_video(media_proxy, hosts, test_config, file: str, log_path) -> None: - # media_proxy fixture used only to ensure that the media proxy is running - # Get TX and RX hosts +def test_local_ffmpeg_video(hosts, test_config, file: str, log_path, media_path) -> None: host_list = list(hosts.values()) if len(host_list) < 1: pytest.skip("Local tests require at least 1 host") tx_host = rx_host = host_list[0] - tx_prefix_variables = test_config["tx"].get("prefix_variables", None) - rx_prefix_variables = test_config["rx"].get("prefix_variables", None) + + if hasattr(tx_host.topology.extra_info, "mcm_prefix_variables"): + tx_prefix_variables = dict(tx_host.topology.extra_info.mcm_prefix_variables) + else: + tx_prefix_variables = {} + if hasattr(rx_host.topology.extra_info, "mcm_prefix_variables"): + rx_prefix_variables = dict(rx_host.topology.extra_info.mcm_prefix_variables) + else: + rx_prefix_variables = {} + tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( tx_host.topology.extra_info.media_proxy["sdk_port"] ) @@ -62,7 +70,7 @@ def test_local_ffmpeg_video(media_proxy, hosts, test_config, file: str, log_path video_size=video_size, pixel_format=pixel_format, stream_loop=False, - input_path=f'{test_config["tx"]["filepath"]}{video_files_25_03[file]["filename"]}', + input_path=f'{media_path}{video_files_25_03[file]["filename"]}', ) mcm_tx_outp = FFmpegMcmMemifVideoIO( f="mcm", @@ -74,7 +82,7 @@ def test_local_ffmpeg_video(media_proxy, hosts, test_config, file: str, log_path ) mcm_tx_ff = FFmpeg( prefix_variables=tx_prefix_variables, - ffmpeg_path=test_config["tx"]["ffmpeg_path"], + ffmpeg_path=tx_host.topology.extra_info.mcm_ffmpeg_path, ffmpeg_input=mcm_tx_inp, ffmpeg_output=mcm_tx_outp, yes_overwrite=False, @@ -97,11 +105,11 @@ def test_local_ffmpeg_video(media_proxy, hosts, test_config, file: str, log_path framerate=frame_rate, video_size=video_size, pixel_format=pixel_format, - output_path=f'{test_config["rx"]["filepath"]}test_{video_files_25_03[file]["filename"]}', + output_path=f'{DEFAULT_OUTPUT_PATH}/test_{video_files_25_03[file]["filename"]}', ) mcm_rx_ff = FFmpeg( prefix_variables=rx_prefix_variables, - ffmpeg_path=test_config["rx"]["ffmpeg_path"], + ffmpeg_path=rx_host.topology.extra_info.mcm_ffmpeg_path, ffmpeg_input=mcm_rx_inp, ffmpeg_output=mcm_rx_outp, yes_overwrite=True, From c16c7aa674f32fa87ca2534141ce75f4c392137c Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 14:31:37 +0000 Subject: [PATCH 070/123] set to run on bcs-cicd-4 --- .github/workflows/bare-metal-build.yml | 2 +- .github/workflows/smoke_tests.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 8a410f452..59ba7e42b 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -33,7 +33,7 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: [Linux, self-hosted] + runs-on: [self-hosted, bcs-cicd-4] timeout-minutes: 120 steps: - name: "Harden Runner" diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index e434c8421..149a78ad6 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -64,7 +64,7 @@ jobs: tag: ${{ github.event.inputs.tag-to-checkout }} validation-prepare-setup-mcm: - runs-on: [Linux, self-hosted] + runs-on: [self-hosted, bcs-cicd-4] needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -157,7 +157,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: [Linux, self-hosted] + runs-on: [self-hosted, bcs-cicd-4] timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From 2c38cb0bbefbd794fe76c6050a8feaad5fce212e Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 15:11:45 +0000 Subject: [PATCH 071/123] add env.RUNNER --- .github/workflows/bare-metal-build.yml | 6 +++++- .github/workflows/smoke_tests.yml | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 59ba7e42b..7ba51c6eb 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -12,6 +12,10 @@ on: required: false type: string description: "Tag to checkout" + runner: + required: true + type: string + description: "Runner to use" env: BUILD_TYPE: Release @@ -33,7 +37,7 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: [self-hosted, bcs-cicd-4] + runs-on: ${{ inputs.runner }} timeout-minutes: 120 steps: - name: "Harden Runner" diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 149a78ad6..11801e108 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -51,6 +51,7 @@ env: MTL_FFMPEG_7_0: "./mtl-binaries/ffmpeg-7-0/ffmpeg" INTEGRITY_PATH: "${{ github.workspace }}/tests/validation/common/integrity" LOG_DIR: "${{ github.workspace }}/logs" + RUNNER: "bcs-cicd-4" permissions: contents: read @@ -62,9 +63,10 @@ jobs: with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} tag: ${{ github.event.inputs.tag-to-checkout }} + runner: ${{ env.RUNNER }} validation-prepare-setup-mcm: - runs-on: [self-hosted, bcs-cicd-4] + runs-on: ${{ env.RUNNER }} needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -157,7 +159,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: [self-hosted, bcs-cicd-4] + runs-on: ${{ env.RUNNER }} timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From 7969ed483d8fbadb4aa216afac9cf7b64e64dbab Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 15:14:02 +0000 Subject: [PATCH 072/123] runner: bcs-cicd-4 --- .github/workflows/bare-metal-build.yml | 2 +- .github/workflows/smoke_tests.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 7ba51c6eb..cf3de7f07 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -37,7 +37,7 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: ${{ inputs.runner }} + runs-on: bcs-cicd-4 timeout-minutes: 120 steps: - name: "Harden Runner" diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 11801e108..e630621fa 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -63,10 +63,10 @@ jobs: with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} tag: ${{ github.event.inputs.tag-to-checkout }} - runner: ${{ env.RUNNER }} + runner: bcs-cicd-4 validation-prepare-setup-mcm: - runs-on: ${{ env.RUNNER }} + runs-on: bcs-cicd-4 needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -159,7 +159,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: ${{ env.RUNNER }} + runs-on: bcs-cicd-4 timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From 82fe0ade74233f8410ff04664585bd2a39ac99c8 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 15:18:41 +0000 Subject: [PATCH 073/123] refactor: update runner configuration to use dynamic inputs --- .github/workflows/bare-metal-build.yml | 2 +- .github/workflows/smoke_tests.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index cf3de7f07..7ba51c6eb 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -37,7 +37,7 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: bcs-cicd-4 + runs-on: ${{ inputs.runner }} timeout-minutes: 120 steps: - name: "Harden Runner" diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index e630621fa..c848a9534 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -51,7 +51,7 @@ env: MTL_FFMPEG_7_0: "./mtl-binaries/ffmpeg-7-0/ffmpeg" INTEGRITY_PATH: "${{ github.workspace }}/tests/validation/common/integrity" LOG_DIR: "${{ github.workspace }}/logs" - RUNNER: "bcs-cicd-4" + RUNNER: [Linux, self-hosted] permissions: contents: read @@ -63,10 +63,10 @@ jobs: with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} tag: ${{ github.event.inputs.tag-to-checkout }} - runner: bcs-cicd-4 + runner: ${{ env.RUNNER }} validation-prepare-setup-mcm: - runs-on: bcs-cicd-4 + runs-on: ${{ env.RUNNER }} needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -159,7 +159,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: bcs-cicd-4 + runs-on: ${{ env.RUNNER }} timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From 6126e3efd3c0437f3a072b9e06113bf078a893f5 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 15:28:36 +0000 Subject: [PATCH 074/123] refactor: simplify runner configuration by removing env.RUNNER --- .github/workflows/smoke_tests.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index c848a9534..dd1bcc830 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -51,7 +51,6 @@ env: MTL_FFMPEG_7_0: "./mtl-binaries/ffmpeg-7-0/ffmpeg" INTEGRITY_PATH: "${{ github.workspace }}/tests/validation/common/integrity" LOG_DIR: "${{ github.workspace }}/logs" - RUNNER: [Linux, self-hosted] permissions: contents: read @@ -63,10 +62,10 @@ jobs: with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} tag: ${{ github.event.inputs.tag-to-checkout }} - runner: ${{ env.RUNNER }} + runner: [Linux, self-hosted] validation-prepare-setup-mcm: - runs-on: ${{ env.RUNNER }} + runs-on: [Linux, self-hosted] needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -159,7 +158,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: ${{ env.RUNNER }} + runs-on: [Linux, self-hosted] timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From 1bacd0000437eae388109edec8c056a245ddc142 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 15:30:34 +0000 Subject: [PATCH 075/123] refactor: remove runner input from workflows and set static runner configuration --- .github/workflows/bare-metal-build.yml | 6 +----- .github/workflows/smoke_tests.yml | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 7ba51c6eb..8a410f452 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -12,10 +12,6 @@ on: required: false type: string description: "Tag to checkout" - runner: - required: true - type: string - description: "Runner to use" env: BUILD_TYPE: Release @@ -37,7 +33,7 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: ${{ inputs.runner }} + runs-on: [Linux, self-hosted] timeout-minutes: 120 steps: - name: "Harden Runner" diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index dd1bcc830..e434c8421 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -62,7 +62,6 @@ jobs: with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} tag: ${{ github.event.inputs.tag-to-checkout }} - runner: [Linux, self-hosted] validation-prepare-setup-mcm: runs-on: [Linux, self-hosted] From 0cb485a6be3e874f1e204c96127f603b2d81da00 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 16:07:27 +0000 Subject: [PATCH 076/123] refactor: update output path in ffmpeg tests to use dynamic topology information --- tests/validation/functional/local/audio/test_ffmpeg_audio.py | 2 +- tests/validation/functional/local/video/test_ffmpeg_video.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index fea7079fe..abde2637c 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -97,7 +97,7 @@ def test_local_ffmpeg_audio(hosts, test_config, audio_type: str, log_path, media ac=int(audio_files_25_03[audio_type]["channels"]), ar=int(audio_files_25_03[audio_type]["sample_rate"]), channel_layout=audio_channel_layout, - output_path=f'{DEFAULT_OUTPUT_PATH}/test_{audio_files_25_03[audio_type]["filename"]}', + output_path=f'{getattr(rx_host.topology.extra_info, "output_path", DEFAULT_OUTPUT_PATH)}/test_{audio_files_25_03[audio_type]["filename"]}', ) mcm_rx_ff = FFmpeg( prefix_variables=prefix_variables, diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index c95c9b44c..04b2bb2f7 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -105,7 +105,7 @@ def test_local_ffmpeg_video(hosts, test_config, file: str, log_path, media_path) framerate=frame_rate, video_size=video_size, pixel_format=pixel_format, - output_path=f'{DEFAULT_OUTPUT_PATH}/test_{video_files_25_03[file]["filename"]}', + output_path=f'{getattr(rx_host.topology.extra_info, "output_path", DEFAULT_OUTPUT_PATH)}/test_{video_files_25_03[file]["filename"]}', ) mcm_rx_ff = FFmpeg( prefix_variables=rx_prefix_variables, From 16aac0c4076077c95dcaa651dc85b3b33dfcb6e1 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 16:49:53 +0000 Subject: [PATCH 077/123] linter fix --- .github/workflows/base_build.yml | 24 ++-- .github/workflows/validation-tests.yml | 4 +- .gitignore | 2 + tests/validation/Engine/mcm_apps.py | 44 ++---- .../Engine/rx_tx_app_client_json.py | 13 +- .../validation/Engine/rx_tx_app_connection.py | 4 +- .../Engine/rx_tx_app_connection_json.py | 4 +- ...rx_tx_app_connection_json_usage_example.py | 4 +- .../validation/Engine/rx_tx_app_engine_mcm.py | 113 ++++++---------- .../Engine/rx_tx_app_file_validation_utils.py | 12 +- tests/validation/common/__init__.py | 12 +- .../common/ffmpeg_handler/__init__.py | 8 +- .../common/ffmpeg_handler/ffmpeg.py | 118 +++++++---------- .../common/ffmpeg_handler/ffmpeg_enums.py | 4 +- .../common/ffmpeg_handler/log_constants.py | 35 ++--- .../common/ffmpeg_handler/mcm_ffmpeg.py | 4 +- .../common/integrity/blob_integrity.py | 47 ++----- .../common/integrity/integrity_runner.py | 97 ++++---------- .../common/integrity/test_blob_integrity.py | 4 +- .../common/integrity/video_integrity.py | 56 ++------ tests/validation/common/log_constants.py | 46 +++---- .../validation/common/log_validation_utils.py | 68 +++++----- tests/validation/common/nicctl.py | 12 +- .../common/visualisation/audio_graph.py | 80 +++-------- tests/validation/conftest.py | 125 +++++------------- .../cluster/audio/test_audio_25_03.py | 4 +- .../cluster/audio/test_ffmpeg_audio.py | 16 +-- .../cluster/blob/test_blob_25_03.py | 4 +- .../cluster/video/test_ffmpeg_video.py | 12 +- .../cluster/video/test_video_25_03.py | 4 +- .../local/audio/test_audio_25_03.py | 4 +- .../local/audio/test_ffmpeg_audio.py | 12 +- .../functional/local/blob/test_blob_25_03.py | 5 +- .../local/video/test_ffmpeg_video.py | 16 +-- .../local/video/test_video_25_03.py | 4 +- .../st20/test_3_2_st2110_standalone_video.py | 8 +- ...RxTxApp_mcm_to_mtl_video_multiple_nodes.py | 26 +--- ..._ffmpeg_mcm_to_mtl_video_multiple_nodes.py | 46 ++----- .../st20/test_6_1_st2110_ffmpeg_video.py | 4 +- .../st20/test_RxTxApp_mtl_to_mcm_video.py | 8 +- .../st20/test_ffmpeg_mcm_to_mtl_video.py | 18 +-- .../st20/test_ffmpeg_mtl_to_mcm_video.py | 12 +- ...mtl_to_RxTxApp_mcm_video_multiple_nodes.py | 16 +-- .../st30/test_3_2_st2110_standalone_audio.py | 8 +- ...RxTxApp_mcm_to_mtl_audio_multiple_nodes.py | 28 +--- ..._ffmpeg_mcm_to_mtl_audio_multiple_nodes.py | 48 ++----- .../st30/test_6_1_st2110_ffmpeg_audio.py | 8 +- .../st30/test_RxTxApp_mtl_to_mcm_audio.py | 16 +-- .../st30/test_ffmpeg_mcm_to_mtl_audio.py | 16 +-- .../st30/test_ffmpeg_mtl_to_mcm_audio.py | 20 +-- ...mtl_to_RxTxApp_mcm_audio_multiple_nodes.py | 24 +--- tests/validation/functional/test_demo.py | 101 ++++---------- 52 files changed, 424 insertions(+), 1004 deletions(-) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index f1f4057bc..2a5f5f9a5 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -4,21 +4,21 @@ on: push: branches: ["main"] paths-ignore: - - '**/*.md' - - 'tests/**' - - 'docs/**' - - 'LICENSE' - - '.gitignore' - - '.editorconfig' + - "**/*.md" + - "tests/**" + - "docs/**" + - "LICENSE" + - ".gitignore" + - ".editorconfig" pull_request: branches: ["main"] paths-ignore: - - '**/*.md' - - 'tests/**' - - 'docs/**' - - 'LICENSE' - - '.gitignore' - - '.editorconfig' + - "**/*.md" + - "tests/**" + - "docs/**" + - "LICENSE" + - ".gitignore" + - ".editorconfig" workflow_dispatch: env: diff --git a/.github/workflows/validation-tests.yml b/.github/workflows/validation-tests.yml index 479c528b5..cfc383093 100644 --- a/.github/workflows/validation-tests.yml +++ b/.github/workflows/validation-tests.yml @@ -114,7 +114,7 @@ permissions: jobs: validation-build-mtm: - runs-on: [Linux, self-hosted, DPDK] + runs-on: [Linux, self-hosted] timeout-minutes: 60 outputs: pipenv-activate: ${{ steps.pipenv-install.outputs.VIRTUAL_ENV }} @@ -194,7 +194,7 @@ jobs: # Timeout of this job is set to 12h [60m/h*12h=720m] validation-run-tests: needs: [validation-build-mtm] - runs-on: [Linux, self-hosted, DPDK] + runs-on: [Linux, self-hosted] timeout-minutes: 720 env: PYTEST_ALIAS: "sudo --preserve-env python3 -m pipenv run pytest" diff --git a/.gitignore b/.gitignore index cc9830ad5..b28288b0e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,5 @@ cov-int* # Autogenerated version files mcm-version.h mcm-version.go + +.venv* \ No newline at end of file diff --git a/tests/validation/Engine/mcm_apps.py b/tests/validation/Engine/mcm_apps.py index a7ec19d4c..c74ca03da 100644 --- a/tests/validation/Engine/mcm_apps.py +++ b/tests/validation/Engine/mcm_apps.py @@ -219,9 +219,7 @@ def __init__( self.log_path = log_path self.subdir = Path("media_proxy_logs", self.host.name) self.filename = "media_proxy.log" - self.log_file_path = Path( - log_path if log_path else LOG_FOLDER, self.subdir, self.filename - ) + self.log_file_path = Path(log_path if log_path else LOG_FOLDER, self.subdir, self.filename) def start(self): if not self.run_media_proxy_process or not self.run_media_proxy_process.running: @@ -246,14 +244,10 @@ def start(self): cmd += f" -p {self.p}" self.cmd = cmd - logger.info( - f"Starting media proxy on host {self.host.name} with command: {self.cmd}" - ) + logger.info(f"Starting media proxy on host {self.host.name} with command: {self.cmd}") try: logger.debug(f"Media proxy command on {self.host.name}: {self.cmd}") - self.run_media_proxy_process = connection.start_process( - self.cmd, stderr_to_stdout=True - ) + self.run_media_proxy_process = connection.start_process(self.cmd, stderr_to_stdout=True) # Start background logging thread def log_output(): @@ -268,17 +262,11 @@ def log_output(): threading.Thread(target=log_output, daemon=True).start() except Exception as e: - logger.error( - f"Failed to start media proxy process on host {self.host.name}" - ) - raise RuntimeError( - f"Failed to start media proxy process on host {self.host.name}: {e}" - ) + logger.error(f"Failed to start media proxy process on host {self.host.name}") + raise RuntimeError(f"Failed to start media proxy process on host {self.host.name}: {e}") # if self.use_sudo: # connection.disable_sudo() - logger.info( - f"Media proxy started on host {self.host.name}: {self.run_media_proxy_process.running}" - ) + logger.info(f"Media proxy started on host {self.host.name}: {self.run_media_proxy_process.running}") def stop(self, log_path=None): # Use the provided log_path or the one stored in the object @@ -342,13 +330,9 @@ def __init__(self, host, log_path=None): self.is_pass = False self.external = False self.log_path = log_path - self.subdir = Path( - "mesh_agent_logs", "mesh-agent" if host is None else host.name - ) + self.subdir = Path("mesh_agent_logs", "mesh-agent" if host is None else host.name) self.filename = "mesh_agent.log" - self.log_file_path = Path( - log_path if log_path else LOG_FOLDER, self.subdir, self.filename - ) + self.log_file_path = Path(log_path if log_path else LOG_FOLDER, self.subdir, self.filename) def start(self, c=None, p=None): if not self.mesh_agent_process or not self.mesh_agent_process.running: @@ -361,12 +345,8 @@ def start(self, c=None, p=None): cmd += f" -p {self.p}" self.cmd = cmd - logger.info( - f"Starting mesh agent on host {self.host.name} with command: {self.cmd}" - ) - self.mesh_agent_process = self.host.connection.start_process( - self.cmd, stderr_to_stdout=True - ) + logger.info(f"Starting mesh agent on host {self.host.name} with command: {self.cmd}") + self.mesh_agent_process = self.host.connection.start_process(self.cmd, stderr_to_stdout=True) # Start background logging thread def log_output(): @@ -382,9 +362,7 @@ def log_output(): threading.Thread(target=log_output, daemon=True).start() self.mesh_ip = self.host.connection.ip - logger.info( - f"Mesh agent started on host {self.host.name}: {self.mesh_agent_process.running}" - ) + logger.info(f"Mesh agent started on host {self.host.name}: {self.mesh_agent_process.running}") def stop(self, log_path=None): # Use the provided log_path or the one stored in the object diff --git a/tests/validation/Engine/rx_tx_app_client_json.py b/tests/validation/Engine/rx_tx_app_client_json.py index 4d0f13792..4bc161dc5 100644 --- a/tests/validation/Engine/rx_tx_app_client_json.py +++ b/tests/validation/Engine/rx_tx_app_client_json.py @@ -25,15 +25,11 @@ def __init__( def set_client(self, edits: dict) -> None: self.apiVersion = edits.get("apiVersion", self.apiVersion) - self.apiConnectionString = edits.get( - "apiConnectionString", self.apiConnectionString - ) + self.apiConnectionString = edits.get("apiConnectionString", self.apiConnectionString) self.apiDefaultTimeoutMicroseconds = edits.get( "apiDefaultTimeoutMicroseconds", self.apiDefaultTimeoutMicroseconds ) - self.maxMediaConnections = edits.get( - "maxMediaConnections", self.maxMediaConnections - ) + self.maxMediaConnections = edits.get("maxMediaConnections", self.maxMediaConnections) def to_json(self) -> str: json_dict = { @@ -49,15 +45,14 @@ def prepare_and_save_json(self, output_path: str = "client.json") -> None: json_content = self.to_json().replace('"', '\\"') f.write_text(json_content) - def copy_json_to_logs(self, log_path: str) -> None: """Copy the client.json file to the log path on runner.""" source_path = self.host.connection.path("client.json") dest_path = Path(log_path) / "client.json" - + # Create log directory if it doesn't exist Path(log_path).mkdir(parents=True, exist_ok=True) - + # Copy the client.json file to the log path with open(dest_path, "w") as dest_file: dest_file.write(self.to_json()) diff --git a/tests/validation/Engine/rx_tx_app_connection.py b/tests/validation/Engine/rx_tx_app_connection.py index c30be4eb0..0257ef57c 100644 --- a/tests/validation/Engine/rx_tx_app_connection.py +++ b/tests/validation/Engine/rx_tx_app_connection.py @@ -151,9 +151,7 @@ class ConnectionMode(Enum): class Rdma(RxTxAppConnection): """Prepares RDMA part of connection.json file""" - def __init__( - self, connectionMode=ConnectionMode.RC, maxLatencyNs=DEFAULT_RDMA_MAX_LATENCY_NS - ): + def __init__(self, connectionMode=ConnectionMode.RC, maxLatencyNs=DEFAULT_RDMA_MAX_LATENCY_NS): super().__init__(rx_tx_app_connection_type=RxTxAppConnectionType.RDMA) self.connectionMode = connectionMode self.maxLatencyNs = maxLatencyNs diff --git a/tests/validation/Engine/rx_tx_app_connection_json.py b/tests/validation/Engine/rx_tx_app_connection_json.py index ac7599673..9e38665f8 100644 --- a/tests/validation/Engine/rx_tx_app_connection_json.py +++ b/tests/validation/Engine/rx_tx_app_connection_json.py @@ -51,10 +51,10 @@ def copy_json_to_logs(self, log_path: str) -> None: """Copy the connection.json file to the log path on runner.""" source_path = self.host.connection.path("connection.json") dest_path = Path(log_path) / "connection.json" - + # Create log directory if it doesn't exist Path(log_path).mkdir(parents=True, exist_ok=True) - + # Copy the connection.json file to the log path with open(dest_path, "w") as dest_file: dest_file.write(self.to_json()) diff --git a/tests/validation/Engine/rx_tx_app_connection_json_usage_example.py b/tests/validation/Engine/rx_tx_app_connection_json_usage_example.py index f0755f482..ecea9e6ea 100644 --- a/tests/validation/Engine/rx_tx_app_connection_json_usage_example.py +++ b/tests/validation/Engine/rx_tx_app_connection_json_usage_example.py @@ -102,9 +102,7 @@ ) print("ConnectionJson:") -cj = ConnectionJson( - maxPayloadSize=1024, payload=pl, rx_tx_app_connection=rx_tx_app_connection_st2110 -) +cj = ConnectionJson(maxPayloadSize=1024, payload=pl, rx_tx_app_connection=rx_tx_app_connection_st2110) print( f"""Dict: diff --git a/tests/validation/Engine/rx_tx_app_engine_mcm.py b/tests/validation/Engine/rx_tx_app_engine_mcm.py index 53e31430f..7fadf4629 100644 --- a/tests/validation/Engine/rx_tx_app_engine_mcm.py +++ b/tests/validation/Engine/rx_tx_app_engine_mcm.py @@ -23,9 +23,7 @@ logger = logging.getLogger(__name__) -def create_client_json( - build: str, client: Engine.rx_tx_app_client_json.ClientJson, log_path: str = "" -) -> None: +def create_client_json(build: str, client: Engine.rx_tx_app_client_json.ClientJson, log_path: str = "") -> None: logger.debug("Client JSON:") for line in client.to_json().splitlines(): logger.debug(line) @@ -43,9 +41,7 @@ def create_connection_json( logger.debug("Connection JSON:") for line in rx_tx_app_connection.to_json().splitlines(): logger.debug(line) - output_path = str( - Path(build, "tests", "tools", "TestApp", "build", "connection.json") - ) + output_path = str(Path(build, "tests", "tools", "TestApp", "build", "connection.json")) logger.debug(f"Connection JSON path: {output_path}") rx_tx_app_connection.prepare_and_save_json(output_path=output_path) # Use provided log_path or default to LOG_FOLDER @@ -76,31 +72,21 @@ def __init__( self.file_dict = file_dict self.file = file self.rx_tx_app_connection = rx_tx_app_connection() - self.payload = payload_type.from_file_info( - media_path=self.media_path, file_info=file_dict - ) + self.payload = payload_type.from_file_info(media_path=self.media_path, file_info=file_dict) self.media_proxy_port = get_media_proxy_port(host) self.rx_tx_app_client_json = Engine.rx_tx_app_client_json.ClientJson( host, apiConnectionString=f"Server=127.0.0.1; Port={self.media_proxy_port}" ) - self.rx_tx_app_connection_json = ( - Engine.rx_tx_app_connection_json.ConnectionJson( - host=host, - rx_tx_app_connection=self.rx_tx_app_connection, - payload=self.payload, - ) - ) - self.app_path = host.connection.path( - self.mcm_path, "tests", "tools", "TestApp", "build" + self.rx_tx_app_connection_json = Engine.rx_tx_app_connection_json.ConnectionJson( + host=host, + rx_tx_app_connection=self.rx_tx_app_connection, + payload=self.payload, ) + self.app_path = host.connection.path(self.mcm_path, "tests", "tools", "TestApp", "build") self.client_cfg_file = host.connection.path(self.app_path, "client.json") - self.connection_cfg_file = host.connection.path( - self.app_path, "connection.json" - ) + self.connection_cfg_file = host.connection.path(self.app_path, "connection.json") self.input = input_file - self.output_path = getattr( - host.topology.extra_info, "output_path", DEFAULT_OUTPUT_PATH - ) + self.output_path = getattr(host.topology.extra_info, "output_path", DEFAULT_OUTPUT_PATH) # Handle output file path construction if output_file and output_path: @@ -148,9 +134,7 @@ def _ensure_output_directory_exists(self): if self.output and str(self.output) != "/dev/null": # Skip for /dev/null and other special files if "/dev/" in str(self.output): - logger.debug( - f"Skipping directory creation for special device path: {self.output}" - ) + logger.debug(f"Skipping directory creation for special device path: {self.output}") return output_dir = self.host.connection.path(self.output).parent @@ -200,27 +184,31 @@ def stop(self): app_log_validation_status = False app_log_error_count = 0 - + # Using common log validation utility from common.log_validation_utils import check_phrases_in_order if self.direction in ("Rx", "Tx"): from common.log_validation_utils import validate_log_file + required_phrases = RX_REQUIRED_LOG_PHRASES if self.direction == "Rx" else TX_REQUIRED_LOG_PHRASES - + # Use the common validation function validation_result = validate_log_file(log_file_path, required_phrases, self.direction) - - self.is_pass = validation_result['is_pass'] - app_log_validation_status = validation_result['is_pass'] - app_log_error_count = validation_result['error_count'] - validation_info.extend(validation_result['validation_info']) - + + self.is_pass = validation_result["is_pass"] + app_log_validation_status = validation_result["is_pass"] + app_log_error_count = validation_result["error_count"] + validation_info.extend(validation_result["validation_info"]) + # Additional logging if validation failed - if not validation_result['is_pass'] and validation_result['missing_phrases']: - print(f"{self.direction} process did not pass. First missing phrase: {validation_result['missing_phrases'][0]}") + if not validation_result["is_pass"] and validation_result["missing_phrases"]: + print( + f"{self.direction} process did not pass. First missing phrase: {validation_result['missing_phrases'][0]}" + ) else: from common.log_validation_utils import output_validator + result = output_validator( log_file_path=log_file_path, error_keywords=RX_TX_APP_ERROR_KEYWORDS, @@ -232,9 +220,7 @@ def stop(self): app_log_error_count = len(result["errors"]) validation_info.append(f"=== {self.direction} App Log Validation ===") validation_info.append(f"Log file: {log_file_path}") - validation_info.append( - f"Validation result: {'PASS' if result['is_pass'] else 'FAIL'}" - ) + validation_info.append(f"Validation result: {'PASS' if result['is_pass'] else 'FAIL'}") validation_info.append(f"Errors found: {len(result['errors'])}") if result["errors"]: validation_info.append("Error details:") @@ -243,39 +229,34 @@ def stop(self): if result["phrase_mismatches"]: validation_info.append("Phrase mismatches:") for phrase, found, expected in result["phrase_mismatches"]: - validation_info.append( - f" - {phrase}: found '{found}', expected '{expected}'" - ) + validation_info.append(f" - {phrase}: found '{found}', expected '{expected}'") # File validation for Rx only run if output path isn't "/dev/null" or doesn't start with "/dev/null/" - if self.direction == "Rx" and self.output and self.output_path and not str(self.output_path).startswith("/dev/null"): + if ( + self.direction == "Rx" + and self.output + and self.output_path + and not str(self.output_path).startswith("/dev/null") + ): validation_info.append(f"\n=== {self.direction} Output File Validation ===") validation_info.append(f"Expected output file: {self.output}") - file_info, file_validation_passed = validate_file( - self.host.connection, self.output, cleanup=False - ) + file_info, file_validation_passed = validate_file(self.host.connection, self.output, cleanup=False) validation_info.extend(file_info) self.is_pass = self.is_pass and file_validation_passed # Save validation report to file if validation_info: validation_info.append(f"\n=== Overall Validation Summary ===") - overall_status = ( - "PASS" if self.is_pass and file_validation_passed else "FAIL" - ) + overall_status = "PASS" if self.is_pass and file_validation_passed else "FAIL" validation_info.append(f"Overall validation: {overall_status}") - validation_info.append( - f"App log validation: {'PASS' if app_log_validation_status else 'FAIL'}" - ) + validation_info.append(f"App log validation: {'PASS' if app_log_validation_status else 'FAIL'}") if self.direction == "Rx": file_status = "PASS" if file_validation_passed else "FAIL" validation_info.append(f"File validation: {file_status}") # Add note about overall validation logic if not self.is_pass or not file_validation_passed: - validation_info.append( - "Note: Overall validation fails if either app log or file validation fails" - ) + validation_info.append("Note: Overall validation fails if either app log or file validation fails") log_dir = self.log_path if self.log_path is not None else LOG_FOLDER subdir = f"RxTx/{self.host.name}" @@ -306,13 +287,9 @@ def __init__(self, *args, **kwargs): def start(self): super().start() - logger.debug( - f"Starting Tx app with payload: {self.payload.payload_type} on {self.host}" - ) + logger.debug(f"Starting Tx app with payload: {self.payload.payload_type} on {self.host}") cmd = self._get_app_cmd("Tx") - self.process = self.host.connection.start_process( - cmd, shell=True, stderr_to_stdout=True, cwd=self.app_path - ) + self.process = self.host.connection.start_process(cmd, shell=True, stderr_to_stdout=True, cwd=self.app_path) def log_output(): log_dir = self.log_path if self.log_path is not None else LOG_FOLDER @@ -350,21 +327,15 @@ def _generate_output_file_path(self): output_filename = f"rx_{self.host.name}_{input_path.stem}_{timestamp}{input_path.suffix}" # Use the configured output path or default - self.output = self.host.connection.path( - self.output_path, output_filename - ) + self.output = self.host.connection.path(self.output_path, output_filename) logger.debug(f"Generated output path: {self.output}") def start(self): super().start() - logger.debug( - f"Starting Rx app with payload: {self.payload.payload_type} on {self.host}" - ) + logger.debug(f"Starting Rx app with payload: {self.payload.payload_type} on {self.host}") cmd = self._get_app_cmd("Rx") - self.process = self.host.connection.start_process( - cmd, shell=True, stderr_to_stdout=True, cwd=self.app_path - ) + self.process = self.host.connection.start_process(cmd, shell=True, stderr_to_stdout=True, cwd=self.app_path) def log_output(): log_dir = self.log_path if self.log_path is not None else LOG_FOLDER diff --git a/tests/validation/Engine/rx_tx_app_file_validation_utils.py b/tests/validation/Engine/rx_tx_app_file_validation_utils.py index e7a3efb64..1708ecf30 100644 --- a/tests/validation/Engine/rx_tx_app_file_validation_utils.py +++ b/tests/validation/Engine/rx_tx_app_file_validation_utils.py @@ -39,9 +39,7 @@ def validate_file(connection, file_path, cleanup=True): validation_info.append("File existence: PASS") # Execute a command to get the file size using ls - result = connection.execute_command( - f"ls -l {file_path}", expected_return_codes=None - ) + result = connection.execute_command(f"ls -l {file_path}", expected_return_codes=None) if result.return_code != 0: error_msg = f"Failed to retrieve file size for {file_path}." @@ -51,13 +49,9 @@ def validate_file(connection, file_path, cleanup=True): else: # Parse the output to get the file size file_info = result.stdout.strip().split() - file_size = int( - file_info[4] - ) # The size is the 5th element in the split output + file_size = int(file_info[4]) # The size is the 5th element in the split output - validation_info.append( - f"File size: {file_size} bytes (checked via ls -l: {file_info})" - ) + validation_info.append(f"File size: {file_size} bytes (checked via ls -l: {file_info})") if file_size == 0: error_msg = f"File size is 0: {file_path}" diff --git a/tests/validation/common/__init__.py b/tests/validation/common/__init__.py index 931d8daa9..7d414388d 100644 --- a/tests/validation/common/__init__.py +++ b/tests/validation/common/__init__.py @@ -10,10 +10,10 @@ from common.log_constants import RX_REQUIRED_LOG_PHRASES, TX_REQUIRED_LOG_PHRASES, RX_TX_APP_ERROR_KEYWORDS __all__ = [ - 'check_phrases_in_order', - 'validate_log_file', - 'output_validator', - 'RX_REQUIRED_LOG_PHRASES', - 'TX_REQUIRED_LOG_PHRASES', - 'RX_TX_APP_ERROR_KEYWORDS' + "check_phrases_in_order", + "validate_log_file", + "output_validator", + "RX_REQUIRED_LOG_PHRASES", + "TX_REQUIRED_LOG_PHRASES", + "RX_TX_APP_ERROR_KEYWORDS", ] diff --git a/tests/validation/common/ffmpeg_handler/__init__.py b/tests/validation/common/ffmpeg_handler/__init__.py index 08bda312e..14cfc7cc9 100644 --- a/tests/validation/common/ffmpeg_handler/__init__.py +++ b/tests/validation/common/ffmpeg_handler/__init__.py @@ -9,11 +9,7 @@ from common.ffmpeg_handler.log_constants import ( FFMPEG_RX_REQUIRED_LOG_PHRASES, FFMPEG_TX_REQUIRED_LOG_PHRASES, - FFMPEG_ERROR_KEYWORDS + FFMPEG_ERROR_KEYWORDS, ) -__all__ = [ - 'FFMPEG_RX_REQUIRED_LOG_PHRASES', - 'FFMPEG_TX_REQUIRED_LOG_PHRASES', - 'FFMPEG_ERROR_KEYWORDS' -] +__all__ = ["FFMPEG_RX_REQUIRED_LOG_PHRASES", "FFMPEG_TX_REQUIRED_LOG_PHRASES", "FFMPEG_ERROR_KEYWORDS"] diff --git a/tests/validation/common/ffmpeg_handler/ffmpeg.py b/tests/validation/common/ffmpeg_handler/ffmpeg.py index bea8ea524..627beadc3 100644 --- a/tests/validation/common/ffmpeg_handler/ffmpeg.py +++ b/tests/validation/common/ffmpeg_handler/ffmpeg.py @@ -80,37 +80,35 @@ def __init__(self, host, ffmpeg_instance: FFmpeg, log_path=None): def validate(self): """ Validates the FFmpeg process execution and output. - + Performs two types of validation: 1. Log validation - checks for required phrases and error keywords 2. File validation - checks if the output file exists and has expected characteristics - + Generates validation report files. - + Returns: bool: True if validation passed, False otherwise """ process_passed = True validation_info = [] - + for process in self._processes: if process.return_code != 0: logger.warning(f"FFmpeg process on {self.host.name} failed with return code {process.return_code}") process_passed = False - + # Determine if this is a receiver or transmitter is_receiver = False if self.ff.ffmpeg_input and self.ff.ffmpeg_output: input_path = getattr(self.ff.ffmpeg_input, "input_path", None) output_path = getattr(self.ff.ffmpeg_output, "output_path", None) - - if input_path == "-" or ( - output_path and output_path != "-" and "." in output_path - ): + + if input_path == "-" or (output_path and output_path != "-" and "." in output_path): is_receiver = True - + direction = "Rx" if is_receiver else "Tx" - + # Find the log file log_dir = self.log_path if self.log_path else LOG_FOLDER subdir = f"RxTx/{self.host.name}" @@ -119,29 +117,24 @@ def validate(self): input_class_name = self.ff.ffmpeg_input.__class__.__name__ prefix = "mtl_" if input_class_name and "Mtl" in input_class_name else "mcm_" log_filename = prefix + ("ffmpeg_rx.log" if is_receiver else "ffmpeg_tx.log") - + log_file_path = os.path.join(log_dir, subdir, log_filename) - + # Perform log validation from common.log_validation_utils import validate_log_file from common.ffmpeg_handler.log_constants import ( - FFMPEG_RX_REQUIRED_LOG_PHRASES, + FFMPEG_RX_REQUIRED_LOG_PHRASES, FFMPEG_TX_REQUIRED_LOG_PHRASES, - FFMPEG_ERROR_KEYWORDS + FFMPEG_ERROR_KEYWORDS, ) - + required_phrases = FFMPEG_RX_REQUIRED_LOG_PHRASES if is_receiver else FFMPEG_TX_REQUIRED_LOG_PHRASES - + if os.path.exists(log_file_path): - validation_result = validate_log_file( - log_file_path, - required_phrases, - direction, - FFMPEG_ERROR_KEYWORDS - ) - - log_validation_passed = validation_result['is_pass'] - validation_info.extend(validation_result['validation_info']) + validation_result = validate_log_file(log_file_path, required_phrases, direction, FFMPEG_ERROR_KEYWORDS) + + log_validation_passed = validation_result["is_pass"] + validation_info.extend(validation_result["validation_info"]) else: logger.warning(f"Log file not found at {log_file_path}") validation_info.append(f"=== {direction} Log Validation ===") @@ -150,7 +143,7 @@ def validate(self): validation_info.append(f"Errors found: 1") validation_info.append(f"Missing log file") log_validation_passed = False - + # File validation for Rx only run if output path isn't "/dev/null" or doesn't start with "/dev/null/" file_validation_passed = True if is_receiver and self.ff.ffmpeg_output and hasattr(self.ff.ffmpeg_output, "output_path"): @@ -158,16 +151,15 @@ def validate(self): if output_path and not str(output_path).startswith("/dev/null"): validation_info.append(f"\n=== {direction} Output File Validation ===") validation_info.append(f"Expected output file: {output_path}") - + from Engine.rx_tx_app_file_validation_utils import validate_file - file_info, file_validation_passed = validate_file( - self.host.connection, output_path, cleanup=False - ) + + file_info, file_validation_passed = validate_file(self.host.connection, output_path, cleanup=False) validation_info.extend(file_info) - + # Overall validation status self.is_pass = process_passed and log_validation_passed and file_validation_passed - + # Save validation report validation_info.append(f"\n=== Overall Validation Summary ===") validation_info.append(f"Overall validation: {'PASS' if self.is_pass else 'FAIL'}") @@ -176,20 +168,19 @@ def validate(self): if is_receiver: validation_info.append(f"File validation: {'PASS' if file_validation_passed else 'FAIL'}") validation_info.append(f"Note: Overall validation fails if any validation step fails") - + # Save validation report to a file from common.log_validation_utils import save_validation_report + validation_path = os.path.join(log_dir, subdir, f"{direction.lower()}_validation.log") save_validation_report(validation_path, validation_info, self.is_pass) - + return self.is_pass def start(self): """Starts the FFmpeg process on the host, waits for the process to start.""" cmd = self.ff.get_command() - ffmpeg_process = self.host.connection.start_process( - cmd, stderr_to_stdout=True, shell=True - ) + ffmpeg_process = self.host.connection.start_process(cmd, stderr_to_stdout=True, shell=True) self._processes.append(ffmpeg_process) CURRENT_RETRIES = RETRIES retries_counter = 0 @@ -198,13 +189,9 @@ def start(self): time.sleep(SLEEP_BETWEEN_CHECKS) # FIXME: Find a better way to check if the process is running; code below throws an error when the process is actually running sometimes if ffmpeg_process.running: - logger.info( - f"FFmpeg process started on {self.host.name} with command: {cmd}" - ) + logger.info(f"FFmpeg process started on {self.host.name} with command: {cmd}") else: - logger.debug( - f"FFmpeg process failed to start on {self.host.name} after {CURRENT_RETRIES} retries." - ) + logger.debug(f"FFmpeg process failed to start on {self.host.name} after {CURRENT_RETRIES} retries.") log_dir = self.log_path if self.log_path else LOG_FOLDER subdir = f"RxTx/{self.host.name}" @@ -215,9 +202,7 @@ def start(self): input_path = getattr(self.ff.ffmpeg_input, "input_path", None) output_path = getattr(self.ff.ffmpeg_output, "output_path", None) - if input_path == "-" or ( - output_path and output_path != "-" and "." in output_path - ): + if input_path == "-" or (output_path and output_path != "-" and "." in output_path): is_receiver = True filename = "ffmpeg_rx.log" if is_receiver else "ffmpeg_tx.log" @@ -258,25 +243,15 @@ def stop(self, wait: float = 0.0) -> float: if process.running: process.kill() except SSHRemoteProcessEndException as e: - logger.warning( - f"FFmpeg process on {self.host.name} was already stopped: {e}" - ) + logger.warning(f"FFmpeg process on {self.host.name} was already stopped: {e}") except RemoteProcessInvalidState as e: - logger.warning( - f"FFmpeg process on {self.host.name} is in an invalid state: {e}" - ) + logger.warning(f"FFmpeg process on {self.host.name} is in an invalid state: {e}") except Exception as e: - logger.error( - f"Error while stopping FFmpeg process on {self.host.name}: {e}" - ) + logger.error(f"Error while stopping FFmpeg process on {self.host.name}: {e}") raise e - logger.debug( - f">>> FFmpeg execution on '{self.host.name}' host returned:\n{process.stdout_text}" - ) + logger.debug(f">>> FFmpeg execution on '{self.host.name}' host returned:\n{process.stdout_text}") if process.return_code != 0: - logger.warning( - f"FFmpeg process on {self.host.name} return code is {process.return_code}" - ) + logger.warning(f"FFmpeg process on {self.host.name} return code is {process.return_code}") # assert process.return_code == 0 # Sometimes a different return code is returned for a graceful stop, so we do not assert it here else: logger.info("No FFmpeg process to stop!") @@ -298,18 +273,21 @@ def wait_with_timeout(self, timeout=None): def cleanup(self): """Clean up any resources or output files.""" - if (self.ff.ffmpeg_output and - hasattr(self.ff.ffmpeg_output, "output_path") and - self.ff.ffmpeg_output.output_path and - self.ff.ffmpeg_output.output_path != "-" and - not str(self.ff.ffmpeg_output.output_path).startswith("/dev/null")): - + if ( + self.ff.ffmpeg_output + and hasattr(self.ff.ffmpeg_output, "output_path") + and self.ff.ffmpeg_output.output_path + and self.ff.ffmpeg_output.output_path != "-" + and not str(self.ff.ffmpeg_output.output_path).startswith("/dev/null") + ): + success = cleanup_file(self.host.connection, str(self.ff.ffmpeg_output.output_path)) if success: logger.debug(f"Cleaned up output file: {self.ff.ffmpeg_output.output_path}") else: logger.warning(f"Failed to clean up output file: {self.ff.ffmpeg_output.output_path}") + def no_proxy_to_prefix_variables(host, prefix_variables: dict | None = None): """ Handles the no_proxy and NO_PROXY environment variables for FFmpeg execution. @@ -330,9 +308,7 @@ def no_proxy_to_prefix_variables(host, prefix_variables: dict | None = None): prefix_variables = prefix_variables if prefix_variables else {} try: if "no_proxy" not in prefix_variables.keys(): - prefix_variables["no_proxy"] = host.topology.extra_info.media_proxy.get( - "no_proxy" - ) + prefix_variables["no_proxy"] = host.topology.extra_info.media_proxy.get("no_proxy") if "NO_PROXY" not in prefix_variables.keys(): prefix_variables["NO_PROXY"] = host.topology.extra_info.media_proxy.get( "no_proxy" diff --git a/tests/validation/common/ffmpeg_handler/ffmpeg_enums.py b/tests/validation/common/ffmpeg_handler/ffmpeg_enums.py index dfc50b788..0799659eb 100644 --- a/tests/validation/common/ffmpeg_handler/ffmpeg_enums.py +++ b/tests/validation/common/ffmpeg_handler/ffmpeg_enums.py @@ -79,9 +79,7 @@ def audio_file_format_to_format_dict(audio_format: str) -> dict: elif audio_format == "pcm_s24be": return matching_audio_formats.get(FFmpegAudioFormat.pcm24, {}) elif audio_format == "pcm_s8": - raise Exception( - f"PCM 8 is not supported by Media Communications Mesh FFmpeg plugin!" - ) + raise Exception(f"PCM 8 is not supported by Media Communications Mesh FFmpeg plugin!") else: raise Exception(f"Not expected audio format {audio_format}") diff --git a/tests/validation/common/ffmpeg_handler/log_constants.py b/tests/validation/common/ffmpeg_handler/log_constants.py index b589e16f2..48a423823 100644 --- a/tests/validation/common/ffmpeg_handler/log_constants.py +++ b/tests/validation/common/ffmpeg_handler/log_constants.py @@ -8,32 +8,23 @@ # Required ordered log phrases for FFmpeg Rx validation FFMPEG_RX_REQUIRED_LOG_PHRASES = [ - '[DEBU] JSON client config:', - '[INFO] Media Communications Mesh SDK version', - '[DEBU] JSON conn config:', - '[INFO] gRPC: connection created', - 'INFO - Create memif socket.', - 'INFO - Create memif interface.', - 'INFO - memif connected!', - '[INFO] gRPC: connection active' + "[DEBU] JSON client config:", + "[INFO] Media Communications Mesh SDK version", + "[DEBU] JSON conn config:", + "[INFO] gRPC: connection created", + "INFO - Create memif socket.", + "INFO - Create memif interface.", + "INFO - memif connected!", + "[INFO] gRPC: connection active", ] # Required ordered log phrases for FFmpeg Tx validation FFMPEG_TX_REQUIRED_LOG_PHRASES = [ - '[DEBU] JSON client config:', - '[INFO] Media Communications Mesh SDK version', - '[DEBU] JSON conn config:', - '[DEBU] BUF PARTS' + "[DEBU] JSON client config:", + "[INFO] Media Communications Mesh SDK version", + "[DEBU] JSON conn config:", + "[DEBU] BUF PARTS", ] # Common error keywords to look for in logs -FFMPEG_ERROR_KEYWORDS = [ - "ERROR", - "FATAL", - "exception", - "segfault", - "core dumped", - "failed", - "FAIL", - "[error]" -] +FFMPEG_ERROR_KEYWORDS = ["ERROR", "FATAL", "exception", "segfault", "core dumped", "failed", "FAIL", "[error]"] diff --git a/tests/validation/common/ffmpeg_handler/mcm_ffmpeg.py b/tests/validation/common/ffmpeg_handler/mcm_ffmpeg.py index b93dde947..33015b21a 100644 --- a/tests/validation/common/ffmpeg_handler/mcm_ffmpeg.py +++ b/tests/validation/common/ffmpeg_handler/mcm_ffmpeg.py @@ -89,9 +89,7 @@ def __init__( self.payload_type = payload_type self.channels = channels if sample_rate and ptime not in matching_sample_rates[sample_rate]: - raise Exception( - f"Sample rate {sample_rate} Hz does not work with {ptime} packet time (ptime)." - ) + raise Exception(f"Sample rate {sample_rate} Hz does not work with {ptime} packet time (ptime).") self.sample_rate = sample_rate self.ptime = ptime self.f = f diff --git a/tests/validation/common/integrity/blob_integrity.py b/tests/validation/common/integrity/blob_integrity.py index e14a4cc59..ecb1a6c5c 100644 --- a/tests/validation/common/integrity/blob_integrity.py +++ b/tests/validation/common/integrity/blob_integrity.py @@ -12,12 +12,8 @@ DEFAULT_CHUNK_SIZE = 1024 * 1024 # 1MB chunks for processing -SEGMENT_DURATION_GRACE_PERIOD = ( - 1 # Grace period in seconds to allow for late-arriving files -) -FILE_HASH_CHUNK_SIZE = ( - 4096 # 4KB is optimal for file hashing (balances memory usage and disk I/O) -) +SEGMENT_DURATION_GRACE_PERIOD = 1 # Grace period in seconds to allow for late-arriving files +FILE_HASH_CHUNK_SIZE = 4096 # 4KB is optimal for file hashing (balances memory usage and disk I/O) def calculate_chunk_hashes(file_url: str, chunk_size: int = DEFAULT_CHUNK_SIZE) -> list: @@ -27,9 +23,7 @@ def calculate_chunk_hashes(file_url: str, chunk_size: int = DEFAULT_CHUNK_SIZE) chunk_index = 0 while chunk := f.read(chunk_size): if len(chunk) != chunk_size: - logging.debug( - f"CHUNK SIZE MISMATCH at index {chunk_index}: {len(chunk)} != {chunk_size}" - ) + logging.debug(f"CHUNK SIZE MISMATCH at index {chunk_index}: {len(chunk)} != {chunk_size}") chunk_sum = hashlib.md5(chunk).hexdigest() chunk_sums.append(chunk_sum) chunk_index += 1 @@ -93,16 +87,13 @@ def check_integrity_file(self, out_url) -> bool: no_src_chunks = len(self.src_chunk_sums) if no_out_chunks != no_src_chunks: self.logger.error( - f"Chunk count mismatch: source has {no_src_chunks} chunks, " - f"output has {no_out_chunks} chunks" + f"Chunk count mismatch: source has {no_src_chunks} chunks, " f"output has {no_out_chunks} chunks" ) bad_chunks += abs(no_out_chunks - no_src_chunks) if bad_chunks: self.bad_chunks_total += bad_chunks - self.logger.error( - f"Received {bad_chunks} bad chunks out of {no_out_chunks} total chunks." - ) + self.logger.error(f"Received {bad_chunks} bad chunks out of {no_out_chunks} total chunks.") return False self.logger.info(f"All {no_out_chunks} chunks in {out_url} are correct.") @@ -141,26 +132,20 @@ def __init__( delete_file: bool = True, ): super().__init__(logger, src_url, out_name, chunk_size, out_path, delete_file) - self.logger.info( - f"Output path {out_path}, segment duration: {segment_duration}" - ) + self.logger.info(f"Output path {out_path}, segment duration: {segment_duration}") self.segment_duration = segment_duration self.workers_count = workers_count def get_out_file(self, request_queue): """Monitor output path for new files and add them to processing queue.""" - self.logger.info( - f"Waiting for output files from {self.out_path} with prefix {self.out_name}" - ) + self.logger.info(f"Waiting for output files from {self.out_path} with prefix {self.out_name}") start = 0 waiting_for_files = True list_processed = [] out_files = [] # Add a grace period to segment duration to allow for late-arriving files - while ( - self.segment_duration + SEGMENT_DURATION_GRACE_PERIOD > start - ) or waiting_for_files: + while (self.segment_duration + SEGMENT_DURATION_GRACE_PERIOD > start) or waiting_for_files: gb = list(Path(self.out_path).glob(f"{self.out_name}*")) out_files = list(filter(lambda x: x not in list_processed, gb)) self.logger.debug(f"Received files: {out_files}") @@ -186,9 +171,7 @@ def start_workers(self, result_queue, request_queue, shared): """Create and start worker processes.""" processes = [] for number in range(self.workers_count): - p = multiprocessing.Process( - target=self.worker, args=(number, request_queue, result_queue, shared) - ) + p = multiprocessing.Process(target=self.worker, args=(number, request_queue, result_queue, shared)) p.start() processes.append(p) return processes @@ -200,9 +183,7 @@ def check_blob_integrity(self) -> bool: result_queue = multiprocessing.Queue() request_queue = multiprocessing.Queue() workers = self.start_workers(result_queue, request_queue, shared_data) - output_worker = multiprocessing.Process( - target=self.get_out_file, args=(request_queue,) - ) + output_worker = multiprocessing.Process(target=self.get_out_file, args=(request_queue,)) output_worker.start() results = [] while output_worker.is_alive(): @@ -239,9 +220,7 @@ def get_out_file(self): return gb[0] except IndexError: self.logger.error(f"File {self.out_name} not found!") - raise FileNotFoundError( - f"File {self.out_name} not found in {self.out_path}" - ) + raise FileNotFoundError(f"File {self.out_name} not found in {self.out_path}") def check_blob_integrity(self) -> bool: """Check integrity of a single blob file.""" @@ -276,9 +255,7 @@ def main(): formatter_class=argparse.RawDescriptionHelpFormatter, ) - subparsers = parser.add_subparsers( - dest="mode", help="Mode of operation: stream or file" - ) + subparsers = parser.add_subparsers(dest="mode", help="Mode of operation: stream or file") # Common arguments function to avoid repetition def add_common_arguments(parser): diff --git a/tests/validation/common/integrity/integrity_runner.py b/tests/validation/common/integrity/integrity_runner.py index c4e652f7b..22bc06926 100644 --- a/tests/validation/common/integrity/integrity_runner.py +++ b/tests/validation/common/integrity/integrity_runner.py @@ -42,27 +42,16 @@ def get_path(self, integrity_path): """ if integrity_path: return str(self.host.connection.path(integrity_path, self.module_name)) - return str( - self.host.connection.path( - self.test_repo_path, "tests", "common", "integrity", self.module_name - ) - ) + return str(self.host.connection.path(self.test_repo_path, "tests", "common", "integrity", self.module_name)) def setup(self): """ Setup method to prepare the environment for running the integrity check. This can include creating directories, checking dependencies, etc. """ - logger.info( - f"Setting up integrity check on {self.host.name} for {self.out_name}" - ) - self.host.connection.execute_command( - f"apt install tesseract-ocr -y", shell=True - ) - reqs = str( - self.host.connection.path(self.integrity_path).parents[0] - / "requirements.txt" - ) + logger.info(f"Setting up integrity check on {self.host.name} for {self.out_name}") + self.host.connection.execute_command(f"apt install tesseract-ocr -y", shell=True) + reqs = str(self.host.connection.path(self.integrity_path).parents[0] / "requirements.txt") for library in ["pytesseract", "opencv-python"]: cmd = f"{self.python_path} -m pip list | grep {library} || {self.python_path} -m pip install -r {reqs}" self.host.connection.execute_command(cmd, shell=True) @@ -110,9 +99,7 @@ def run(self): "--delete_file" if self.delete_file else "--no_delete_file", ] ) - logger.debug( - f"Running integrity check on {self.host.name} for {self.out_name} with command: {cmd}" - ) + logger.debug(f"Running integrity check on {self.host.name} for {self.out_name} with command: {cmd}") result = self.host.connection.execute_command( cmd, shell=True, stderr_to_stdout=True, expected_return_codes=(0, 1) ) @@ -120,9 +107,7 @@ def run(self): logger.error(f"Integrity check failed on {self.host.name}: {self.out_name}") logger.error(result.stdout) return False - logger.info( - f"Integrity check completed successfully on {self.host.name} for {self.out_name}" - ) + logger.info(f"Integrity check completed successfully on {self.host.name} for {self.out_name}") return True @@ -177,23 +162,15 @@ def run(self): str(self.workers), ] ) - logger.debug( - f"Running stream integrity check on {self.host.name} for {self.out_name} with command: {cmd}" - ) - self.process = self.host.connection.start_process( - cmd, shell=True, stderr_to_stdout=True - ) + logger.debug(f"Running stream integrity check on {self.host.name} for {self.out_name} with command: {cmd}") + self.process = self.host.connection.start_process(cmd, shell=True, stderr_to_stdout=True) def stop(self, timeout: int = 10): if self.process: self.process.wait(timeout) - logger.info( - f"Stream integrity check stopped on {self.host.name} for {self.out_name}" - ) + logger.info(f"Stream integrity check stopped on {self.host.name} for {self.out_name}") else: - logger.warning( - f"No active process to stop for {self.out_name} on {self.host.name}" - ) + logger.warning(f"No active process to stop for {self.out_name} on {self.host.name}") def stop_and_verify(self, timeout: int = 10): """ @@ -201,14 +178,10 @@ def stop_and_verify(self, timeout: int = 10): """ self.stop(timeout) if self.process and self.process.return_code != 0: - logger.error( - f"Stream integrity check failed on {self.host.name} for {self.out_name}" - ) + logger.error(f"Stream integrity check failed on {self.host.name} for {self.out_name}") logger.error(f"Process output: {self.process.stdout_text}") return False - logger.info( - f"Stream integrity check completed successfully on {self.host.name} for {self.out_name}" - ) + logger.info(f"Stream integrity check completed successfully on {self.host.name} for {self.out_name}") return True @@ -244,20 +217,14 @@ def get_path(self, integrity_path): """ if integrity_path: return str(self.host.connection.path(integrity_path, self.module_name)) - return str( - self.host.connection.path( - self.test_repo_path, "tests", "common", "integrity", self.module_name - ) - ) + return str(self.host.connection.path(self.test_repo_path, "tests", "common", "integrity", self.module_name)) def setup(self): """ Setup method to prepare the environment for running the blob integrity check. This is simpler than video integrity as it doesn't require OCR dependencies. """ - logger.info( - f"Setting up blob integrity check on {self.host.name} for {self.out_name}" - ) + logger.info(f"Setting up blob integrity check on {self.host.name} for {self.out_name}") # Blob integrity doesn't need special dependencies like tesseract or opencv @@ -301,21 +268,15 @@ def run(self): "--delete_file" if self.delete_file else "--no_delete_file", ] ) - logger.debug( - f"Running blob integrity check on {self.host.name} for {self.out_name} with command: {cmd}" - ) + logger.debug(f"Running blob integrity check on {self.host.name} for {self.out_name} with command: {cmd}") result = self.host.connection.execute_command( cmd, shell=True, stderr_to_stdout=True, expected_return_codes=(0, 1) ) if result.return_code > 0: - logger.error( - f"Blob integrity check failed on {self.host.name}: {self.out_name}" - ) + logger.error(f"Blob integrity check failed on {self.host.name}: {self.out_name}") logger.error(result.stdout) return False - logger.info( - f"Blob integrity check completed successfully on {self.host.name} for {self.out_name}" - ) + logger.info(f"Blob integrity check completed successfully on {self.host.name} for {self.out_name}") return True @@ -368,23 +329,15 @@ def run(self): str(self.workers), ] ) - logger.debug( - f"Running stream blob integrity check on {self.host.name} for {self.out_name} with command: {cmd}" - ) - self.process = self.host.connection.start_process( - cmd, shell=True, stderr_to_stdout=True - ) + logger.debug(f"Running stream blob integrity check on {self.host.name} for {self.out_name} with command: {cmd}") + self.process = self.host.connection.start_process(cmd, shell=True, stderr_to_stdout=True) def stop(self, timeout: int = 10): if self.process: self.process.wait(timeout) - logger.info( - f"Stream blob integrity check stopped on {self.host.name} for {self.out_name}" - ) + logger.info(f"Stream blob integrity check stopped on {self.host.name} for {self.out_name}") else: - logger.warning( - f"No active process to stop for {self.out_name} on {self.host.name}" - ) + logger.warning(f"No active process to stop for {self.out_name} on {self.host.name}") def stop_and_verify(self, timeout: int = 10): """ @@ -392,12 +345,8 @@ def stop_and_verify(self, timeout: int = 10): """ self.stop(timeout) if self.process and self.process.return_code != 0: - logger.error( - f"Stream blob integrity check failed on {self.host.name} for {self.out_name}" - ) + logger.error(f"Stream blob integrity check failed on {self.host.name} for {self.out_name}") logger.error(f"Process output: {self.process.stdout_text}") return False - logger.info( - f"Stream blob integrity check completed successfully on {self.host.name} for {self.out_name}" - ) + logger.info(f"Stream blob integrity check completed successfully on {self.host.name} for {self.out_name}") return True diff --git a/tests/validation/common/integrity/test_blob_integrity.py b/tests/validation/common/integrity/test_blob_integrity.py index c506915d4..e8ec21f1e 100644 --- a/tests/validation/common/integrity/test_blob_integrity.py +++ b/tests/validation/common/integrity/test_blob_integrity.py @@ -103,9 +103,7 @@ def test_corrupted_file_integrity(): ) result = integrator.check_blob_integrity() - print( - f"Corrupted file integrity check result: {'PASSED (UNEXPECTED!)' if result else 'FAILED (EXPECTED)'}" - ) + print(f"Corrupted file integrity check result: {'PASSED (UNEXPECTED!)' if result else 'FAILED (EXPECTED)'}") return not result # We expect this to fail, so return True if it failed diff --git a/tests/validation/common/integrity/video_integrity.py b/tests/validation/common/integrity/video_integrity.py index 83fd1d468..fec1ce6ac 100644 --- a/tests/validation/common/integrity/video_integrity.py +++ b/tests/validation/common/integrity/video_integrity.py @@ -30,9 +30,7 @@ def calculate_chunk_hashes(file_url: str, chunk_size: int) -> list: chunk_index = 0 while chunk := f.read(chunk_size): if len(chunk) != chunk_size: - logging.debug( - f"CHUNK SIZE MISMATCH at index {chunk_index}: {len(chunk)} != {chunk_size}" - ) + logging.debug(f"CHUNK SIZE MISMATCH at index {chunk_index}: {len(chunk)} != {chunk_size}") chunk_sum = hashlib.md5(chunk).hexdigest() chunk_sums.append(chunk_sum) chunk_index += 1 @@ -114,9 +112,7 @@ def shift_src_chunk_by_first_frame_no(self, out_file): for _ in range(self.shift - STARTING_FRAME): self.src_chunk_sums.append(self.src_chunk_sums.pop(0)) else: - raise ValueError( - f"No match found in the extracted text from first frame of {self.src_url}" - ) + raise ValueError(f"No match found in the extracted text from first frame of {self.src_url}") def check_integrity_file(self, out_url) -> bool: out_chunk_sums = calculate_chunk_hashes(out_url, self.frame_size) @@ -129,15 +125,11 @@ def check_integrity_file(self, out_url) -> bool: f"Chunk: {chunk_sum} received in position: {ids}, expected in {self.src_chunk_sums.index(chunk_sum)}" ) else: - self.logger.error( - f"Chunk: {chunk_sum} with ID: {ids} not found in src chunks checksums!" - ) + self.logger.error(f"Chunk: {chunk_sum} with ID: {ids} not found in src chunks checksums!") bad_frames += 1 if bad_frames: self.bad_frames_total += bad_frames - self.logger.error( - f"Received {bad_frames} bad frames out of {len(out_chunk_sums)} captured." - ) + self.logger.error(f"Received {bad_frames} bad frames out of {len(out_chunk_sums)} captured.") return False self.logger.info(f"All {len(out_chunk_sums)} frames in {out_url} are correct.") return True @@ -176,19 +168,13 @@ def __init__( workers_count=5, delete_file: bool = True, ): - super().__init__( - logger, src_url, out_name, resolution, file_format, out_path, delete_file - ) - self.logger.info( - f"Output path {out_path}, segment duration: {segment_duration}" - ) + super().__init__(logger, src_url, out_name, resolution, file_format, out_path, delete_file) + self.logger.info(f"Output path {out_path}, segment duration: {segment_duration}") self.segment_duration = segment_duration self.workers_count = workers_count def get_out_file(self, request_queue): - self.logger.info( - f"Waiting for output files from {self.out_path} with prefix {self.out_name}" - ) + self.logger.info(f"Waiting for output files from {self.out_path} with prefix {self.out_name}") start = 0 waiting_for_files = True list_processed = [] @@ -240,9 +226,7 @@ def start_workers(self, result_queue, request_queue, shared): # Create and start a separate process for each task processes = [] for number in range(self.workers_count): - p = multiprocessing.Process( - target=self.worker, args=(number, request_queue, result_queue, shared) - ) + p = multiprocessing.Process(target=self.worker, args=(number, request_queue, result_queue, shared)) p.start() processes.append(p) return processes @@ -253,9 +237,7 @@ def check_st20p_integrity(self) -> bool: result_queue = multiprocessing.Queue() request_queue = multiprocessing.Queue() workers = self.start_workers(result_queue, request_queue, shared_data) - output_worker = multiprocessing.Process( - target=self.get_out_file, args=(request_queue,) - ) + output_worker = multiprocessing.Process(target=self.get_out_file, args=(request_queue,)) output_worker.start() results = [] while output_worker.is_alive(): @@ -281,9 +263,7 @@ def __init__( out_path: str = "/mnt/ramdisk", delete_file: bool = True, ): - super().__init__( - logger, src_url, out_name, resolution, file_format, out_path, delete_file - ) + super().__init__(logger, src_url, out_name, resolution, file_format, out_path, delete_file) self.logger = logging.getLogger(__name__) def get_out_file(self): @@ -292,9 +272,7 @@ def get_out_file(self): return gb[0] except IndexError: self.logger.error(f"File {self.out_name} not found!") - raise FileNotFoundError( - f"File {self.out_name} not found in {self.out_path}" - ) + raise FileNotFoundError(f"File {self.out_name} not found in {self.out_path}") def check_st20p_integrity(self) -> bool: output_file = self.get_out_file() @@ -329,20 +307,14 @@ def main(): formatter_class=argparse.RawDescriptionHelpFormatter, ) - subparsers = parser.add_subparsers( - dest="mode", help="Mode of operation: stream or file" - ) + subparsers = parser.add_subparsers(dest="mode", help="Mode of operation: stream or file") # Common arguments function to avoid repetition def add_common_arguments(parser): parser.add_argument("src", type=str, help="Path to the source video file") parser.add_argument("out", type=str, help="Name/prefix of the output file") - parser.add_argument( - "res", type=str, help="Resolution of the video (e.g., 1920x1080)" - ) - parser.add_argument( - "fmt", type=str, help="Format of the video file (e.g., yuv422p10le)" - ) + parser.add_argument("res", type=str, help="Resolution of the video (e.g., 1920x1080)") + parser.add_argument("fmt", type=str, help="Format of the video file (e.g., yuv422p10le)") parser.add_argument( "--output_path", type=str, diff --git a/tests/validation/common/log_constants.py b/tests/validation/common/log_constants.py index 1f4d64765..78d75b07b 100644 --- a/tests/validation/common/log_constants.py +++ b/tests/validation/common/log_constants.py @@ -8,37 +8,29 @@ # Required ordered log phrases for Rx validation RX_REQUIRED_LOG_PHRASES = [ - '[RX] Reading client configuration', - '[RX] Reading connection configuration', - '[DEBU] JSON client config:', - '[INFO] Media Communications Mesh SDK version', - '[DEBU] JSON conn config:', - '[RX] Fetched mesh data buffer', - '[RX] Saving buffer data to a file', - '[RX] Done reading the data', - '[RX] dropping connection to media-proxy', - 'INFO - memif disconnected!', + "[RX] Reading client configuration", + "[RX] Reading connection configuration", + "[DEBU] JSON client config:", + "[INFO] Media Communications Mesh SDK version", + "[DEBU] JSON conn config:", + "[RX] Fetched mesh data buffer", + "[RX] Saving buffer data to a file", + "[RX] Done reading the data", + "[RX] dropping connection to media-proxy", + "INFO - memif disconnected!", ] # Required ordered log phrases for Tx validation TX_REQUIRED_LOG_PHRASES = [ - '[TX] Reading client configuration', - '[TX] Reading connection configuration', - '[DEBU] JSON client config:', - '[INFO] Media Communications Mesh SDK version', - '[DEBU] JSON conn config:', - '[INFO] gRPC: connection created', - 'INFO - Create memif socket.', - 'INFO - Create memif interface.', + "[TX] Reading client configuration", + "[TX] Reading connection configuration", + "[DEBU] JSON client config:", + "[INFO] Media Communications Mesh SDK version", + "[DEBU] JSON conn config:", + "[INFO] gRPC: connection created", + "INFO - Create memif socket.", + "INFO - Create memif interface.", ] # Common error keywords to look for in logs -RX_TX_APP_ERROR_KEYWORDS = [ - "ERROR", - "FATAL", - "exception", - "segfault", - "core dumped", - "failed", - "FAIL" -] +RX_TX_APP_ERROR_KEYWORDS = ["ERROR", "FATAL", "exception", "segfault", "core dumped", "failed", "FAIL"] diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py index 84a81965a..41838f708 100644 --- a/tests/validation/common/log_validation_utils.py +++ b/tests/validation/common/log_validation_utils.py @@ -13,6 +13,7 @@ logger = logging.getLogger(__name__) + def check_phrases_in_order(log_path: str, phrases: List[str]) -> Tuple[bool, List[str], Dict[str, List[str]]]: """ Check that all required phrases appear in order in the log file. @@ -21,7 +22,7 @@ def check_phrases_in_order(log_path: str, phrases: List[str]) -> Tuple[bool, Lis found_indices = [] missing_phrases = [] lines_around_missing = {} - with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: + with open(log_path, "r", encoding="utf-8", errors="ignore") as f: lines = [line.strip() for line in f] idx = 0 @@ -48,57 +49,56 @@ def check_phrases_in_order(log_path: str, phrases: List[str]) -> Tuple[bool, Lis return len(missing_phrases) == 0, missing_phrases, lines_around_missing + def check_for_errors(log_path: str, error_keywords: Optional[List[str]] = None) -> Tuple[bool, List[Dict[str, Any]]]: """ Check the log file for error keywords. - + Args: log_path: Path to the log file error_keywords: List of keywords indicating errors (default: RX_TX_APP_ERROR_KEYWORDS) - + Returns: Tuple of (is_pass, errors_found) errors_found is a list of dicts with 'line', 'line_number', and 'keyword' keys """ if error_keywords is None: error_keywords = RX_TX_APP_ERROR_KEYWORDS - + errors = [] if not os.path.exists(log_path): logger.error(f"Log file not found: {log_path}") return False, [{"line": "Log file not found", "line_number": 0, "keyword": "FILE_NOT_FOUND"}] - + try: - with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: + with open(log_path, "r", encoding="utf-8", errors="ignore") as f: for i, line in enumerate(f): for keyword in error_keywords: if keyword.lower() in line.lower(): # Ignore certain false positives if "ERROR" in keyword and ("NO ERROR" in line.upper() or "NO_ERROR" in line.upper()): continue - errors.append({ - "line": line.strip(), - "line_number": i + 1, - "keyword": keyword - }) + errors.append({"line": line.strip(), "line_number": i + 1, "keyword": keyword}) break except Exception as e: logger.error(f"Error reading log file: {e}") return False, [{"line": f"Error reading log file: {e}", "line_number": 0, "keyword": "FILE_READ_ERROR"}] - + return len(errors) == 0, errors -def validate_log_file(log_file_path: str, required_phrases: List[str], direction: str = "", - error_keywords: Optional[List[str]] = None) -> Dict: + +def validate_log_file( + log_file_path: str, required_phrases: List[str], direction: str = "", error_keywords: Optional[List[str]] = None +) -> Dict: """ Validate log file for required phrases and return validation information. - + Args: log_file_path: Path to the log file required_phrases: List of phrases to check for in order direction: Optional string to identify the direction (e.g., 'Rx', 'Tx') error_keywords: Optional list of error keywords to check for - + Returns: Dictionary containing validation results: { @@ -111,33 +111,33 @@ def validate_log_file(log_file_path: str, required_phrases: List[str], direction } """ validation_info = [] - + # Phrase order validation log_pass, missing, context_lines = check_phrases_in_order(log_file_path, required_phrases) error_count = len(missing) - + # Error keyword validation (optional) error_check_pass = True errors = [] if error_keywords is not None: error_check_pass, errors = check_for_errors(log_file_path, error_keywords) error_count += len(errors) - + # Overall pass/fail status is_pass = log_pass and error_check_pass - + # Build validation info dir_prefix = f"{direction} " if direction else "" validation_info.append(f"=== {dir_prefix}Log Validation ===") validation_info.append(f"Log file: {log_file_path}") validation_info.append(f"Validation result: {'PASS' if is_pass else 'FAIL'}") validation_info.append(f"Total errors found: {error_count}") - + # Missing phrases info if not log_pass: validation_info.append(f"Missing or out-of-order phrases analysis:") for phrase in missing: - validation_info.append(f"\n Expected phrase: \"{phrase}\"") + validation_info.append(f'\n Expected phrase: "{phrase}"') validation_info.append(f" Context in log file:") if phrase in context_lines: for line in context_lines[phrase]: @@ -146,26 +146,27 @@ def validate_log_file(log_file_path: str, required_phrases: List[str], direction validation_info.append(" ") if missing: logger.warning(f"{dir_prefix}process did not pass. First missing phrase: {missing[0]}") - + # Error keywords info if errors: validation_info.append(f"\nError keywords found:") for error in errors: validation_info.append(f" Line {error['line_number']} - {error['keyword']}: {error['line']}") - + return { "is_pass": is_pass, "error_count": error_count, "validation_info": validation_info, "missing_phrases": missing, "context_lines": context_lines, - "errors": errors + "errors": errors, } + def save_validation_report(report_path: str, validation_info: List[str], overall_status: bool) -> None: """ Save validation report to a file. - + Args: report_path: Path where to save the report validation_info: List of validation information strings @@ -173,7 +174,7 @@ def save_validation_report(report_path: str, validation_info: List[str], overall """ try: os.makedirs(os.path.dirname(report_path), exist_ok=True) - with open(report_path, 'w', encoding='utf-8') as f: + with open(report_path, "w", encoding="utf-8") as f: for line in validation_info: f.write(f"{line}\n") f.write(f"\n=== Overall Validation Summary ===\n") @@ -182,14 +183,15 @@ def save_validation_report(report_path: str, validation_info: List[str], overall except Exception as e: logger.error(f"Error saving validation report: {e}") + def output_validator(log_file_path: str, error_keywords: Optional[List[str]] = None) -> Dict[str, Any]: """ Simple validator that checks for error keywords in a log file. - + Args: log_file_path: Path to the log file error_keywords: List of keywords indicating errors - + Returns: Dictionary with validation results: { @@ -199,9 +201,5 @@ def output_validator(log_file_path: str, error_keywords: Optional[List[str]] = N } """ is_pass, errors = check_for_errors(log_file_path, error_keywords) - - return { - "is_pass": is_pass, - "errors": errors, - "phrase_mismatches": [] # Not used in this simple validator - } + + return {"is_pass": is_pass, "errors": errors, "phrase_mismatches": []} # Not used in this simple validator diff --git a/tests/validation/common/nicctl.py b/tests/validation/common/nicctl.py index 3fe3c0e00..0c3621ffb 100644 --- a/tests/validation/common/nicctl.py +++ b/tests/validation/common/nicctl.py @@ -31,9 +31,7 @@ def _parse_vf_list(self, output: str, all: bool = True) -> list: def vfio_list(self, pci_addr: str = "all") -> list: """Returns list of VFs created on host.""" - resp = self.connection.execute_command( - f"{self.nicctl} list {pci_addr}", shell=True - ) + resp = self.connection.execute_command(f"{self.nicctl} list {pci_addr}", shell=True) return self._parse_vf_list(resp.stdout, "all" in pci_addr) def create_vfs(self, pci_id: str, num_of_vfs: int = 6) -> list: @@ -42,18 +40,14 @@ def create_vfs(self, pci_id: str, num_of_vfs: int = 6) -> list: :param num_of_vfs: number of VFs to create :return: returns list of created vfs """ - resp = self.connection.execute_command( - f"{self.nicctl} create_vf {pci_id} {num_of_vfs}", shell=True - ) + resp = self.connection.execute_command(f"{self.nicctl} create_vf {pci_id} {num_of_vfs}", shell=True) return self._parse_vf_list(resp.stdout) def disable_vf(self, pci_id: str) -> None: """Remove VFs on NIC. :param pci_id: pci_id of the nic adapter """ - self.connection.execute_command( - self.nicctl + " disable_vf " + pci_id, shell=True - ) + self.connection.execute_command(self.nicctl + " disable_vf " + pci_id, shell=True) def prepare_vfs_for_test(self, nic: NetworkInterface) -> list: """Prepare VFs for test.""" diff --git a/tests/validation/common/visualisation/audio_graph.py b/tests/validation/common/visualisation/audio_graph.py index 09332a679..31d04163c 100644 --- a/tests/validation/common/visualisation/audio_graph.py +++ b/tests/validation/common/visualisation/audio_graph.py @@ -30,9 +30,7 @@ def read_pcm( """ with open(file_path, "rb") as f: if pcm_format == 8: - pcm_data = ( - np.frombuffer(f.read(), dtype=np.uint8) - 128 - ) # Center around zero + pcm_data = np.frombuffer(f.read(), dtype=np.uint8) - 128 # Center around zero elif pcm_format == 16: pcm_data = np.frombuffer(f.read(), dtype=np.int16) elif pcm_format == 24: @@ -45,9 +43,7 @@ def read_pcm( ) pcm_data = pcm_data - (pcm_data & 0x800000) * 2 # Sign extension else: - raise ValueError( - f"Unsupported PCM format '{pcm_format}'. Use 8, 16, or 24." - ) + raise ValueError(f"Unsupported PCM format '{pcm_format}'. Use 8, 16, or 24.") # Reshape the data to separate channels if there are multiple channels if num_channels > 1: @@ -110,14 +106,8 @@ def plot_waveforms( # If start_time or end_time is specified, plot only that subset if start_time is not None or end_time is not None: - start_sample = ( - int(start_time * effective_sample_rate) if start_time is not None else 0 - ) - end_sample = ( - int(end_time * effective_sample_rate) - if end_time is not None - else len(pcm_data1) - ) + start_sample = int(start_time * effective_sample_rate) if start_time is not None else 0 + end_sample = int(end_time * effective_sample_rate) if end_time is not None else len(pcm_data1) pcm_data1 = pcm_data1[start_sample:end_sample] pcm_data2 = pcm_data2[start_sample:end_sample] @@ -153,9 +143,7 @@ def plot_waveforms( label=f"{file_name1} - Right", ) else: - axs[0].plot( - time_axis, pcm_data1, color="red", linewidth=LINEWIDTH, label=file_name1 - ) + axs[0].plot(time_axis, pcm_data1, color="red", linewidth=LINEWIDTH, label=file_name1) if num_channels2 > 1: axs[0].plot( @@ -173,9 +161,7 @@ def plot_waveforms( label=f"{file_name2} - Right", ) else: - axs[0].plot( - time_axis, pcm_data2, color="blue", linewidth=LINEWIDTH, label=file_name2 - ) + axs[0].plot(time_axis, pcm_data2, color="blue", linewidth=LINEWIDTH, label=file_name2) axs[0].set_title("Combined Waveforms") axs[0].set_xlabel("Time [s]") @@ -186,18 +172,14 @@ def plot_waveforms( # Plot each channel separately plot_index = 1 if num_channels1 > 1: - axs[plot_index].plot( - time_axis, pcm_data1[:, 0], color="red", linewidth=LINEWIDTH - ) + axs[plot_index].plot(time_axis, pcm_data1[:, 0], color="red", linewidth=LINEWIDTH) axs[plot_index].set_title(f"{file_name1} - Left Channel") axs[plot_index].set_xlabel("Time [s]") axs[plot_index].set_ylabel("Amplitude") axs[plot_index].grid(True) plot_index += 1 - axs[plot_index].plot( - time_axis, pcm_data1[:, 1], color="black", linewidth=LINEWIDTH - ) + axs[plot_index].plot(time_axis, pcm_data1[:, 1], color="black", linewidth=LINEWIDTH) axs[plot_index].set_title(f"{file_name1} - Right Channel") axs[plot_index].set_xlabel("Time [s]") axs[plot_index].set_ylabel("Amplitude") @@ -212,18 +194,14 @@ def plot_waveforms( plot_index += 1 if num_channels2 > 1: - axs[plot_index].plot( - time_axis, pcm_data2[:, 0], color="blue", linewidth=LINEWIDTH - ) + axs[plot_index].plot(time_axis, pcm_data2[:, 0], color="blue", linewidth=LINEWIDTH) axs[plot_index].set_title(f"{file_name2} - Left Channel") axs[plot_index].set_xlabel("Time [s]") axs[plot_index].set_ylabel("Amplitude") axs[plot_index].grid(True) plot_index += 1 - axs[plot_index].plot( - time_axis, pcm_data2[:, 1], color="green", linewidth=LINEWIDTH - ) + axs[plot_index].plot(time_axis, pcm_data2[:, 1], color="green", linewidth=LINEWIDTH) axs[plot_index].set_title(f"{file_name2} - Right Channel") axs[plot_index].set_xlabel("Time [s]") axs[plot_index].set_ylabel("Amplitude") @@ -275,14 +253,8 @@ def plot_single_waveform( # If start_time or end_time is specified, plot only that subset if start_time is not None or end_time is not None: - start_sample = ( - int(start_time * effective_sample_rate) if start_time is not None else 0 - ) - end_sample = ( - int(end_time * effective_sample_rate) - if end_time is not None - else len(pcm_data) - ) + start_sample = int(start_time * effective_sample_rate) if start_time is not None else 0 + end_sample = int(end_time * effective_sample_rate) if end_time is not None else len(pcm_data) pcm_data = pcm_data[start_sample:end_sample] time_axis = np.arange(len(pcm_data)) / effective_sample_rate @@ -294,9 +266,7 @@ def plot_single_waveform( num_plots, 1, figsize=(10, 2 * num_plots), - gridspec_kw=( - {"height_ratios": [2] + [1] * (num_plots - 1)} if num_plots > 1 else None - ), + gridspec_kw=({"height_ratios": [2] + [1] * (num_plots - 1)} if num_plots > 1 else None), ) # If only one plot, convert axs to a list for consistent indexing @@ -316,9 +286,7 @@ def plot_single_waveform( label=f"{file_name} - {channel_name}", ) else: - axs[0].plot( - time_axis, pcm_data, color="red", linewidth=LINEWIDTH, label=file_name - ) + axs[0].plot(time_axis, pcm_data, color="red", linewidth=LINEWIDTH, label=file_name) axs[0].set_title("Waveform") axs[0].set_xlabel("Time [s]") @@ -376,14 +344,10 @@ def generate_waveform_plot( Returns: Absolute path to the generated output file. """ - pcm_data1 = read_pcm( - file_path1, sample_rate, pcm_format=pcm_format, num_channels=num_channels1 - ) + pcm_data1 = read_pcm(file_path1, sample_rate, pcm_format=pcm_format, num_channels=num_channels1) if file_path2: - pcm_data2 = read_pcm( - file_path2, sample_rate, pcm_format=pcm_format, num_channels=num_channels2 - ) + pcm_data2 = read_pcm(file_path2, sample_rate, pcm_format=pcm_format, num_channels=num_channels2) return plot_waveforms( file_path1, file_path2, @@ -428,9 +392,7 @@ def generate_waveform_plot( nargs="?", help="Path to the second PCM file (optional).", ) - parser.add_argument( - "--sample_rate", type=int, default=48000, help="Sample rate of the PCM files." - ) + parser.add_argument("--sample_rate", type=int, default=48000, help="Sample rate of the PCM files.") parser.add_argument( "--output_file", type=str, @@ -455,12 +417,8 @@ def generate_waveform_plot( default=10, help="Factor by which to downsample the data.", ) - parser.add_argument( - "--start_time", type=float, help="Start time in seconds for the plot." - ) - parser.add_argument( - "--end_time", type=float, help="End time in seconds for the plot." - ) + parser.add_argument("--start_time", type=float, help="Start time in seconds for the plot.") + parser.add_argument("--end_time", type=float, help="End time in seconds for the plot.") parser.add_argument( "--pcm_format", type=int, diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index fac402989..09501849f 100644 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -131,16 +131,12 @@ def mesh_agent(hosts, test_config, log_path): mesh_ip = test_config.get("mesh_ip", None) if not mesh_ip: - logger.error( - f"Host '{mesh_agent_name}' not found in topology.yaml and no mesh_ip provided." - ) + logger.error(f"Host '{mesh_agent_name}' not found in topology.yaml and no mesh_ip provided.") raise RuntimeError( f"No mesh-agent name '{mesh_agent_name}' found in hosts and no mesh_ip provided in test_config." ) else: - logger.info( - f"Assumed that mesh agent is running, getting IP from topology config: {mesh_ip}" - ) + logger.info(f"Assumed that mesh agent is running, getting IP from topology config: {mesh_ip}") mesh_agent.external = True mesh_agent.mesh_ip = mesh_ip mesh_agent.p = test_config.get("mesh_port", mesh_agent.p) @@ -239,9 +235,7 @@ def media_config(hosts: dict) -> None: if host.topology.extra_info.media_proxy.get("st2110", False): pf_addr = host.network_interfaces[if_idx].pci_address.lspci vfs = nicctl.vfio_list(pf_addr) - host.st2110_dev = host.topology.extra_info.media_proxy.get( - "st2110_dev", None - ) + host.st2110_dev = host.topology.extra_info.media_proxy.get("st2110_dev", None) if not host.st2110_dev and not vfs: nicctl.create_vfs(pf_addr) vfs = nicctl.vfio_list(pf_addr) @@ -256,44 +250,25 @@ def media_config(hosts: dict) -> None: f"Still no VFs on interface {host.network_interfaces[if_idx].pci_address_lspci} even after creating VFs!" ) host.vfs = vfs - host.st2110_ip = host.topology.extra_info.media_proxy.get( - "st2110_ip", f"192.168.0.{last_oct}" - ) + host.st2110_ip = host.topology.extra_info.media_proxy.get("st2110_ip", f"192.168.0.{last_oct}") if_idx += 1 if host.topology.extra_info.media_proxy.get("rdma", False): - if ( - int( - host.network_interfaces[if_idx].virtualization.get_current_vfs() - ) - > 0 - ): - nicctl.disable_vf( - str(host.network_interfaces[if_idx].pci_address.lspci) - ) + if int(host.network_interfaces[if_idx].virtualization.get_current_vfs()) > 0: + nicctl.disable_vf(str(host.network_interfaces[if_idx].pci_address.lspci)) net_adap_ips = host.network_interfaces[if_idx].ip.get_ips().v4 rdma_ip = host.topology.extra_info.media_proxy.get("rdma_ip", False) if rdma_ip: - rdma_ip = IPv4Interface( - f"{rdma_ip}" if "/" in rdma_ip else f"{rdma_ip}/24" - ) + rdma_ip = IPv4Interface(f"{rdma_ip}" if "/" in rdma_ip else f"{rdma_ip}/24") elif net_adap_ips and not rdma_ip: rdma_ip = net_adap_ips[0] if not rdma_ip or (rdma_ip not in net_adap_ips): - rdma_ip = ( - IPv4Interface(f"192.168.1.{last_oct}/24") - if not rdma_ip - else rdma_ip - ) - logger.info( - f"IP {rdma_ip} not found on RDMA network interface, setting: {rdma_ip}" - ) + rdma_ip = IPv4Interface(f"192.168.1.{last_oct}/24") if not rdma_ip else rdma_ip + logger.info(f"IP {rdma_ip} not found on RDMA network interface, setting: {rdma_ip}") host.network_interfaces[if_idx].ip.add_ip(rdma_ip) host.rdma_ip = str(rdma_ip.ip) logger.info(f"VFs on {host.name} are: {host.vfs}") except IndexError: - raise IndexError( - f"Not enough network adapters available for tests! Expected: {if_idx+1}" - ) + raise IndexError(f"Not enough network adapters available for tests! Expected: {if_idx+1}") except AttributeError: logger.warning( f"Extra info media proxy in topology config for {host.name} is not set, skipping media config setup for this host." @@ -316,17 +291,11 @@ def cleanup_processes(hosts: dict) -> None: logger.warning(f"Failed to check/kill {proc} on {host.name}: {e}") for pattern in ["^Rx[A-Za-z]+App$", "^Tx[A-Za-z]+App$"]: try: - connection.execute_command( - f"pgrep -f '{pattern}'", stderr_to_stdout=True - ) - connection.execute_command( - f"pkill -9 -f '{pattern}'", stderr_to_stdout=True - ) + connection.execute_command(f"pgrep -f '{pattern}'", stderr_to_stdout=True) + connection.execute_command(f"pkill -9 -f '{pattern}'", stderr_to_stdout=True) except Exception as e: if not (hasattr(e, "returncode") and e.returncode == 1): - logger.warning( - f"Failed to check/kill processes matching {pattern} on {host.name}: {e}" - ) + logger.warning(f"Failed to check/kill processes matching {pattern} on {host.name}: {e}") logger.info("Cleanup of processes completed.") @@ -361,9 +330,7 @@ def build_mcm_ffmpeg(hosts, test_config: dict): return True # Use constants from Engine/const.py - mcm_ffmpeg_version = test_config.get( - "mcm_ffmpeg_version", DEFAULT_MCM_FFMPEG_VERSION - ) + mcm_ffmpeg_version = test_config.get("mcm_ffmpeg_version", DEFAULT_MCM_FFMPEG_VERSION) if not mcm_ffmpeg_version in ALLOWED_FFMPEG_VERSIONS: logger.error(f"Invalid mcm_ffmpeg_version: {mcm_ffmpeg_version}") return False @@ -383,16 +350,12 @@ def build_mcm_ffmpeg(hosts, test_config: dict): ).stdout.strip() if check_dir == "exists": - host.connection.execute_command( - f"rm -rf {DEFAULT_MCM_FFMPEG_PATH}", shell=True - ) + host.connection.execute_command(f"rm -rf {DEFAULT_MCM_FFMPEG_PATH}", shell=True) logger.debug("Step 1: Clone and patch FFmpeg") clone_patch_script = f"{ffmpeg_tools_path}/clone-and-patch-ffmpeg.sh" cmd = f"bash {clone_patch_script} {mcm_ffmpeg_version}" - res = host.connection.execute_command( - cmd, cwd=ffmpeg_tools_path, shell=True, timeout=60, stderr_to_stdout=True - ) + res = host.connection.execute_command(cmd, cwd=ffmpeg_tools_path, shell=True, timeout=60, stderr_to_stdout=True) logger.debug(f"Command {cmd} output: {res.stdout}") if res.return_code != 0: logger.error(f"Command {cmd} failed with return code {res.return_code}.") @@ -401,9 +364,7 @@ def build_mcm_ffmpeg(hosts, test_config: dict): logger.debug("Step 2: Run FFmpeg configuration tool") configure_script = f"{ffmpeg_tools_path}/configure-ffmpeg.sh" cmd = f"{configure_script} {mcm_ffmpeg_version} --prefix={DEFAULT_MCM_FFMPEG_PATH}" - res = host.connection.execute_command( - cmd, cwd=ffmpeg_tools_path, shell=True, timeout=60, stderr_to_stdout=True - ) + res = host.connection.execute_command(cmd, cwd=ffmpeg_tools_path, shell=True, timeout=60, stderr_to_stdout=True) logger.debug(f"Command {cmd} output: {res.stdout}") if res.return_code != 0: logger.error(f"Command {cmd} failed with return code {res.return_code}.") @@ -420,9 +381,7 @@ def build_mcm_ffmpeg(hosts, test_config: dict): logger.error(f"Command {cmd} failed with return code {res.return_code}.") return False - logger.info( - f"Successfully built MCM FFmpeg {mcm_ffmpeg_version} at {DEFAULT_MCM_FFMPEG_PATH}" - ) + logger.info(f"Successfully built MCM FFmpeg {mcm_ffmpeg_version} at {DEFAULT_MCM_FFMPEG_PATH}") return True @@ -465,9 +424,7 @@ def build_openh264(host: Host, work_dir: str) -> bool: if check_dir == "not exists": # Try to clone the repository - logger.info( - f"OpenH264 repository not found at {openh264_repo_dir}. Attempting to clone..." - ) + logger.info(f"OpenH264 repository not found at {openh264_repo_dir}. Attempting to clone...") res = host.connection.execute_command( f"git clone https://github.com/cisco/openh264.git {openh264_repo_dir}", shell=True, @@ -506,9 +463,7 @@ def build_openh264(host: Host, work_dir: str) -> bool: logger.info(f"Building and installing openh264 to {openh264_install_dir}") # Create install directory if it doesn't exist - host.connection.execute_command( - f"sudo mkdir -p {openh264_install_dir}", shell=True, timeout=10 - ) + host.connection.execute_command(f"sudo mkdir -p {openh264_install_dir}", shell=True, timeout=10) # Build with custom prefix res = host.connection.execute_command( @@ -536,9 +491,7 @@ def build_mtl_ffmpeg(hosts, test_config: dict): if not test_config.get("mtl_ffmpeg_rebuild", False): return True - mtl_ffmpeg_version = test_config.get( - "mtl_ffmpeg_version", DEFAULT_MTL_FFMPEG_VERSION - ) + mtl_ffmpeg_version = test_config.get("mtl_ffmpeg_version", DEFAULT_MTL_FFMPEG_VERSION) if not mtl_ffmpeg_version in ALLOWED_FFMPEG_VERSIONS: logger.error(f"Invalid mtl_ffmpeg_version: {mtl_ffmpeg_version}") return False @@ -561,9 +514,7 @@ def build_mtl_ffmpeg(hosts, test_config: dict): ).stdout.strip() if check_dir == "exists": - host.connection.execute_command( - f"rm -rf {DEFAULT_MTL_FFMPEG_PATH}", shell=True - ) + host.connection.execute_command(f"rm -rf {DEFAULT_MTL_FFMPEG_PATH}", shell=True) # Step 1: Build openh264 logger.info("Step 1: Building openh264 dependency") @@ -657,9 +608,7 @@ def build_mtl_ffmpeg(hosts, test_config: dict): ) if res.return_code == 0: logger.info(f"Found GPU headers at {gpu_path}") - configure_options.append( - f'--extra-cflags="-DMTL_GPU_DIRECT_ENABLED -I{gpu_path}"' - ) + configure_options.append(f'--extra-cflags="-DMTL_GPU_DIRECT_ENABLED -I{gpu_path}"') gpu_headers_found = True break except Exception as e: @@ -667,9 +616,7 @@ def build_mtl_ffmpeg(hosts, test_config: dict): continue if not gpu_headers_found: - logger.warning( - "GPU direct support requested but headers not found. Continuing without GPU support." - ) + logger.warning("GPU direct support requested but headers not found. Continuing without GPU support.") enable_gpu_direct = False # Join configure options and continue with build @@ -720,9 +667,7 @@ def check_iommu(hosts: dict[str, Host]) -> None: iommu_not_enabled_hosts = [] for host in hosts.values(): try: - output = host.connection.execute_command( - "ls -1 /sys/kernel/iommu_groups | wc -l", shell=True, timeout=10 - ) + output = host.connection.execute_command("ls -1 /sys/kernel/iommu_groups | wc -l", shell=True, timeout=10) if int(output.stdout.strip()) == 0: logger.error(f"IOMMU is not enabled on host {host.name}.") iommu_not_enabled_hosts.append(host.name) @@ -730,9 +675,7 @@ def check_iommu(hosts: dict[str, Host]) -> None: logger.exception(f"Failed to check IOMMU status on host {host.name}.") iommu_not_enabled_hosts.append(host.name) if iommu_not_enabled_hosts: - pytest.exit( - f"IOMMU is not enabled on hosts: {', '.join(iommu_not_enabled_hosts)}. Aborting test session." - ) + pytest.exit(f"IOMMU is not enabled on hosts: {', '.join(iommu_not_enabled_hosts)}. Aborting test session.") else: logger.info("IOMMU is enabled on all hosts.") @@ -745,20 +688,14 @@ def enable_hugepages(hosts: dict[str, Host]) -> None: for host in hosts.values(): if not _check_hugepages(host): try: - host.connection.execute_command( - "sudo sysctl -w vm.nr_hugepages=2048", shell=True, timeout=10 - ) + host.connection.execute_command("sudo sysctl -w vm.nr_hugepages=2048", shell=True, timeout=10) logger.info(f"Hugepages enabled on host {host.name}.") except (RemoteProcessTimeoutExpired, ConnectionCalledProcessError): logger.exception(f"Failed to enable hugepages on host {host.name}.") - pytest.exit( - f"Failed to enable hugepages on host {host.name}. Aborting test session." - ) + pytest.exit(f"Failed to enable hugepages on host {host.name}. Aborting test session.") if not _check_hugepages(host): logger.error(f"Hugepages could not be enabled on host {host.name}.") - pytest.exit( - f"Hugepages could not be enabled on host {host.name}. Aborting test session." - ) + pytest.exit(f"Hugepages could not be enabled on host {host.name}. Aborting test session.") else: logger.info(f"Hugepages are already enabled on host {host.name}.") @@ -768,9 +705,7 @@ def _check_hugepages(host: Host) -> bool: Check if hugepages are enabled on the host. """ try: - output = host.connection.execute_command( - "cat /proc/sys/vm/nr_hugepages", shell=True, timeout=10 - ) + output = host.connection.execute_command("cat /proc/sys/vm/nr_hugepages", shell=True, timeout=10) return int(output.stdout.strip()) > 0 except (RemoteProcessTimeoutExpired, ConnectionCalledProcessError): logger.exception(f"Failed to check hugepages status on host {host.name}.") diff --git a/tests/validation/functional/cluster/audio/test_audio_25_03.py b/tests/validation/functional/cluster/audio/test_audio_25_03.py index cb58eef48..943ebb296 100644 --- a/tests/validation/functional/cluster/audio/test_audio_25_03.py +++ b/tests/validation/functional/cluster/audio/test_audio_25_03.py @@ -21,9 +21,7 @@ @pytest.mark.parametrize("file", audio_files_25_03.keys()) -def test_audio_25_03_2_2_standalone( - build_TestApp, hosts, media_proxy, media_path, file, log_path -) -> None: +def test_audio_25_03_2_2_standalone(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: # Get TX and RX hosts host_list = list(hosts.values()) diff --git a/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py b/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py index 3a5b25d00..cb6981196 100644 --- a/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py @@ -32,25 +32,17 @@ def test_cluster_ffmpeg_audio(hosts, media_proxy, test_config, audio_type: str, rx_host = host_list[1] tx_prefix_variables = test_config["tx"].get("prefix_variables", {}) rx_prefix_variables = test_config["rx"].get("prefix_variables", {}) - tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( - tx_host.topology.extra_info.media_proxy["sdk_port"] - ) - rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( - rx_host.topology.extra_info.media_proxy["sdk_port"] - ) + tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy["sdk_port"] + rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = rx_host.topology.extra_info.media_proxy["sdk_port"] - audio_format = audio_file_format_to_format_dict( - str(audio_files[audio_type]["format"]) - ) # audio format + audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) # audio format audio_channel_layout = audio_files[audio_type].get( "channel_layout", audio_channel_number_to_layout(int(audio_files[audio_type]["channels"])), ) if audio_files[audio_type]["sample_rate"] not in [48000, 44100, 96000]: - raise Exception( - f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" - ) + raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") # >>>>> MCM Tx mcm_tx_inp = FFmpegAudioIO( diff --git a/tests/validation/functional/cluster/blob/test_blob_25_03.py b/tests/validation/functional/cluster/blob/test_blob_25_03.py index 8966fec27..e434a1ab6 100644 --- a/tests/validation/functional/cluster/blob/test_blob_25_03.py +++ b/tests/validation/functional/cluster/blob/test_blob_25_03.py @@ -19,9 +19,7 @@ @pytest.mark.parametrize("file", [file for file in blob_files_25_03.keys()]) -def test_blob_25_03( - build_TestApp, hosts, media_proxy, media_path, file, log_path -) -> None: +def test_blob_25_03(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: # Get TX and RX hosts host_list = list(hosts.values()) diff --git a/tests/validation/functional/cluster/video/test_ffmpeg_video.py b/tests/validation/functional/cluster/video/test_ffmpeg_video.py index 7d47298ce..633021cf7 100644 --- a/tests/validation/functional/cluster/video/test_ffmpeg_video.py +++ b/tests/validation/functional/cluster/video/test_ffmpeg_video.py @@ -29,18 +29,12 @@ def test_cluster_ffmpeg_video(hosts, media_proxy, test_config, video_type: str, rx_host = host_list[1] tx_prefix_variables = test_config["tx"].get("prefix_variables", {}) rx_prefix_variables = test_config["rx"].get("prefix_variables", {}) - tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( - tx_host.topology.extra_info.media_proxy["sdk_port"] - ) - rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( - rx_host.topology.extra_info.media_proxy["sdk_port"] - ) + tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy["sdk_port"] + rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = rx_host.topology.extra_info.media_proxy["sdk_port"] frame_rate = str(yuv_files[video_type]["fps"]) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - pixel_format = video_file_format_to_payload_format( - str(yuv_files[video_type]["file_format"]) - ) + pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) conn_type = McmConnectionType.mpg.value # >>>>> MCM Tx diff --git a/tests/validation/functional/cluster/video/test_video_25_03.py b/tests/validation/functional/cluster/video/test_video_25_03.py index a09bff772..3c967284d 100644 --- a/tests/validation/functional/cluster/video/test_video_25_03.py +++ b/tests/validation/functional/cluster/video/test_video_25_03.py @@ -19,9 +19,7 @@ @pytest.mark.parametrize("file", [file for file in video_files_25_03.keys()]) -def test_video_25_03_2_2_standalone( - build_TestApp, hosts, media_proxy, media_path, file, log_path -) -> None: +def test_video_25_03_2_2_standalone(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: # Get TX and RX hosts host_list = list(hosts.values()) diff --git a/tests/validation/functional/local/audio/test_audio_25_03.py b/tests/validation/functional/local/audio/test_audio_25_03.py index 8e81e44eb..825a80f3b 100644 --- a/tests/validation/functional/local/audio/test_audio_25_03.py +++ b/tests/validation/functional/local/audio/test_audio_25_03.py @@ -24,9 +24,7 @@ *[f for f in audio_files_25_03.keys() if f != "PCM16_48000_Mono"], ], ) -def test_audio_25_03( - build_TestApp, hosts, media_proxy, media_path, file, log_path -) -> None: +def test_audio_25_03(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: # Get TX and RX hosts host_list = list(hosts.values()) diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index abde2637c..dd90772e8 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -44,22 +44,16 @@ def test_local_ffmpeg_audio(hosts, test_config, audio_type: str, log_path, media prefix_variables = dict(tx_host.topology.extra_info.mcm_prefix_variables) else: prefix_variables = {} - prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( - tx_host.topology.extra_info.media_proxy["sdk_port"] - ) + prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy["sdk_port"] - audio_format = audio_file_format_to_format_dict( - str(audio_files_25_03[audio_type]["format"]) - ) # audio format + audio_format = audio_file_format_to_format_dict(str(audio_files_25_03[audio_type]["format"])) # audio format audio_channel_layout = audio_files_25_03[audio_type].get( "channel_layout", audio_channel_number_to_layout(int(audio_files_25_03[audio_type]["channels"])), ) if audio_files_25_03[audio_type]["sample_rate"] not in [48000, 44100, 96000]: - raise Exception( - f"Not expected audio sample rate of {audio_files_25_03[audio_type]['sample_rate']}!" - ) + raise Exception(f"Not expected audio sample rate of {audio_files_25_03[audio_type]['sample_rate']}!") # >>>>> MCM Tx mcm_tx_inp = FFmpegAudioIO( diff --git a/tests/validation/functional/local/blob/test_blob_25_03.py b/tests/validation/functional/local/blob/test_blob_25_03.py index a767351c1..1977d1bd7 100644 --- a/tests/validation/functional/local/blob/test_blob_25_03.py +++ b/tests/validation/functional/local/blob/test_blob_25_03.py @@ -17,11 +17,10 @@ ) from Engine.media_files import blob_files_25_03 + @pytest.mark.smoke @pytest.mark.parametrize("file", [file for file in blob_files_25_03.keys()]) -def test_blob_25_03( - build_TestApp, hosts, media_proxy, media_path, file, log_path -) -> None: +def test_blob_25_03(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: # Get TX and RX hosts host_list = list(hosts.values()) diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index 04b2bb2f7..179eed14c 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -48,20 +48,12 @@ def test_local_ffmpeg_video(hosts, test_config, file: str, log_path, media_path) else: rx_prefix_variables = {} - tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( - tx_host.topology.extra_info.media_proxy["sdk_port"] - ) - rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( - rx_host.topology.extra_info.media_proxy["sdk_port"] - ) + tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy["sdk_port"] + rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = rx_host.topology.extra_info.media_proxy["sdk_port"] frame_rate = str(video_files_25_03[file]["fps"]) - video_size = ( - f'{video_files_25_03[file]["width"]}x{video_files_25_03[file]["height"]}' - ) - pixel_format = video_file_format_to_payload_format( - str(video_files_25_03[file]["file_format"]) - ) + video_size = f'{video_files_25_03[file]["width"]}x{video_files_25_03[file]["height"]}' + pixel_format = video_file_format_to_payload_format(str(video_files_25_03[file]["file_format"])) conn_type = McmConnectionType.mpg.value # >>>>> MCM Tx diff --git a/tests/validation/functional/local/video/test_video_25_03.py b/tests/validation/functional/local/video/test_video_25_03.py index d5d868b79..289f364ea 100644 --- a/tests/validation/functional/local/video/test_video_25_03.py +++ b/tests/validation/functional/local/video/test_video_25_03.py @@ -25,9 +25,7 @@ *[f for f in video_files_25_03.keys() if f != "FullHD_60"], ], ) -def test_video_25_03( - build_TestApp, hosts, media_proxy, media_path, file, log_path -) -> None: +def test_video_25_03(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: # Get TX and RX hosts host_list = list(hosts.values()) diff --git a/tests/validation/functional/st2110/st20/test_3_2_st2110_standalone_video.py b/tests/validation/functional/st2110/st20/test_3_2_st2110_standalone_video.py index b98b960d0..a7e2f0a07 100644 --- a/tests/validation/functional/st2110/st20/test_3_2_st2110_standalone_video.py +++ b/tests/validation/functional/st2110/st20/test_3_2_st2110_standalone_video.py @@ -58,9 +58,7 @@ def test_3_2_st2110_standalone_video(hosts, test_config, video_type, log_path): fps = str(yuv_files[video_type]["fps"]) size = f"{yuv_files[video_type]['width']}x{yuv_files[video_type]['height']}" - pixel_format = video_file_format_to_payload_format( - yuv_files[video_type]["file_format"] - ) + pixel_format = video_file_format_to_payload_format(yuv_files[video_type]["file_format"]) rx_ffmpeg_input = FFmpegMtlSt20pRx( video_size=size, @@ -80,9 +78,7 @@ def test_3_2_st2110_standalone_video(hosts, test_config, video_type, log_path): ) rx_ffmpeg_output = FFmpegVideoIO( video_size=size, - pixel_format=( - "yuv422p10le" if pixel_format == "yuv422p10rfc4175" else pixel_format - ), + pixel_format=("yuv422p10le" if pixel_format == "yuv422p10rfc4175" else pixel_format), f=FFmpegVideoFormat.raw.value, output_path=f'{test_config["rx"]["filepath"]}test_{yuv_files[video_type]["filename"]}_{size}at{yuv_files[video_type]["fps"]}fps.yuv', pix_fmt=None, diff --git a/tests/validation/functional/st2110/st20/test_4_1_RxTxApp_mcm_to_mtl_video_multiple_nodes.py b/tests/validation/functional/st2110/st20/test_4_1_RxTxApp_mcm_to_mtl_video_multiple_nodes.py index 32d1bcd3e..852dbbc1f 100644 --- a/tests/validation/functional/st2110/st20/test_4_1_RxTxApp_mcm_to_mtl_video_multiple_nodes.py +++ b/tests/validation/functional/st2110/st20/test_4_1_RxTxApp_mcm_to_mtl_video_multiple_nodes.py @@ -70,9 +70,7 @@ def test_st2110_rttxapp_mcm_to_mtl_video( frame_rate = str(yuv_files[video_type]["fps"]) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - video_pixel_format = video_file_format_to_payload_format( - str(yuv_files[video_type]["file_format"]) - ) + video_pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) rx_nicctl = Nicctl( mtl_path=rx_mtl_path, @@ -97,11 +95,7 @@ def test_st2110_rttxapp_mcm_to_mtl_video( ) mtl_rx_outp = FFmpegVideoIO( video_size=video_size, - pixel_format=( - "yuv422p10le" - if video_pixel_format == "yuv422p10rfc4175" - else video_pixel_format - ), + pixel_format=("yuv422p10le" if video_pixel_format == "yuv422p10rfc4175" else video_pixel_format), f=FFmpegVideoFormat.raw.value, output_path=f'{test_config["rx"]["filepath"]}test_{yuv_files[video_type]["filename"]}_{video_size}at{yuv_files[video_type]["fps"]}fps.yuv', pix_fmt=None, # this is required to overwrite the default @@ -113,12 +107,8 @@ def test_st2110_rttxapp_mcm_to_mtl_video( ffmpeg_output=mtl_rx_outp, yes_overwrite=True, ) - logger.debug( - f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}" - ) - mtl_rx_executor = FFmpegExecutor( - rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path - ) + logger.debug(f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}") + mtl_rx_executor = FFmpegExecutor(rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path) rx_executor_a = utils.LapkaExecutor.Rx( host=rx_host_a, @@ -172,9 +162,5 @@ def test_st2110_rttxapp_mcm_to_mtl_video( mtl_rx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) tx_executor.stop() - assert ( - rx_executor_a.is_pass - ), "Receiver A validation failed. Check logs for details." - assert ( - rx_executor_b.is_pass - ), "Receiver B validation failed. Check logs for details." + assert rx_executor_a.is_pass, "Receiver A validation failed. Check logs for details." + assert rx_executor_b.is_pass, "Receiver B validation failed. Check logs for details." diff --git a/tests/validation/functional/st2110/st20/test_4_1_ffmpeg_mcm_to_mtl_video_multiple_nodes.py b/tests/validation/functional/st2110/st20/test_4_1_ffmpeg_mcm_to_mtl_video_multiple_nodes.py index c5d83135d..3db873968 100644 --- a/tests/validation/functional/st2110/st20/test_4_1_ffmpeg_mcm_to_mtl_video_multiple_nodes.py +++ b/tests/validation/functional/st2110/st20/test_4_1_ffmpeg_mcm_to_mtl_video_multiple_nodes.py @@ -32,9 +32,7 @@ logger = logging.getLogger(__name__) -EARLY_STOP_THRESHOLD_PERCENTAGE = ( - 20 # percentage of max_test_time to consider an early stop -) +EARLY_STOP_THRESHOLD_PERCENTAGE = 20 # percentage of max_test_time to consider an early stop @pytest.mark.parametrize("video_type", [k for k in yuv_files.keys() if "4K" not in k]) @@ -59,9 +57,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( frame_rate = str(yuv_files[video_type]["fps"]) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - video_pixel_format = video_file_format_to_payload_format( - str(yuv_files[video_type]["file_format"]) - ) + video_pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) conn_type = McmConnectionType.st.value # MCM FFmpeg Tx @@ -91,12 +87,8 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( ffmpeg_output=mcm_tx_outp, yes_overwrite=False, ) - logger.debug( - f"MCM Tx command executed on {tx_host.name}: {mcm_tx_ff.get_command()}" - ) - mcm_tx_executor = FFmpegExecutor( - tx_host, ffmpeg_instance=mcm_tx_ff, log_path=log_path - ) + logger.debug(f"MCM Tx command executed on {tx_host.name}: {mcm_tx_ff.get_command()}") + mcm_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mcm_tx_ff, log_path=log_path) rx_mtl_path = get_mtl_path(rx_mtl_host) @@ -124,11 +116,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( ) mtl_rx_outp = FFmpegVideoIO( video_size=video_size, - pixel_format=( - "yuv422p10le" - if video_pixel_format == "yuv422p10rfc4175" - else video_pixel_format - ), + pixel_format=("yuv422p10le" if video_pixel_format == "yuv422p10rfc4175" else video_pixel_format), f=FFmpegVideoFormat.raw.value, output_path=f'{getattr(rx_mtl_host.topology.extra_info, "output_path", DEFAULT_OUTPUT_PATH)}/test_{yuv_files[video_type]["filename"]}_{video_size}at{yuv_files[video_type]["fps"]}fps.yuv', pix_fmt=None, # this is required to overwrite the default @@ -140,12 +128,8 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( ffmpeg_output=mtl_rx_outp, yes_overwrite=True, ) - logger.debug( - f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}" - ) - mtl_rx_executor = FFmpegExecutor( - rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path - ) + logger.debug(f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}") + mtl_rx_executor = FFmpegExecutor(rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path) # MCM FFmpeg Rx A mcm_rx_a_inp = FFmpegMcmST2110VideoRx( @@ -175,12 +159,8 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( ffmpeg_output=mcm_rx_a_outp, yes_overwrite=True, ) - logger.debug( - f"MCM Rx A command executed on {rx_host_a.name}: {mcm_rx_a_ff.get_command()}" - ) - mcm_rx_a_executor = FFmpegExecutor( - rx_host_a, ffmpeg_instance=mcm_rx_a_ff, log_path=log_path - ) + logger.debug(f"MCM Rx A command executed on {rx_host_a.name}: {mcm_rx_a_ff.get_command()}") + mcm_rx_a_executor = FFmpegExecutor(rx_host_a, ffmpeg_instance=mcm_rx_a_ff, log_path=log_path) # MCM FFmpeg Rx B mcm_rx_b_inp = FFmpegMcmST2110VideoRx( @@ -210,12 +190,8 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( ffmpeg_output=mcm_rx_b_outp, yes_overwrite=True, ) - logger.debug( - f"MCM Rx B command executed on {rx_host_b.name}: {mcm_rx_b_ff.get_command()}" - ) - mcm_rx_b_executor = FFmpegExecutor( - rx_host_b, ffmpeg_instance=mcm_rx_b_ff, log_path=log_path - ) + logger.debug(f"MCM Rx B command executed on {rx_host_b.name}: {mcm_rx_b_ff.get_command()}") + mcm_rx_b_executor = FFmpegExecutor(rx_host_b, ffmpeg_instance=mcm_rx_b_ff, log_path=log_path) mcm_tx_executor.start() sleep(MCM_ESTABLISH_TIMEOUT) diff --git a/tests/validation/functional/st2110/st20/test_6_1_st2110_ffmpeg_video.py b/tests/validation/functional/st2110/st20/test_6_1_st2110_ffmpeg_video.py index c8040e81d..46eb0fd62 100644 --- a/tests/validation/functional/st2110/st20/test_6_1_st2110_ffmpeg_video.py +++ b/tests/validation/functional/st2110/st20/test_6_1_st2110_ffmpeg_video.py @@ -27,9 +27,7 @@ def test_6_1_st2110_ffmpeg_video(hosts, test_config, video_file, log_path): rx_b_host = hosts["client"] video_size = f'{video_file["width"]}x{video_file["height"]}' - video_pixel_format = ffmpeg_enums.video_file_format_to_payload_format( - video_file["file_format"] - ) + video_pixel_format = ffmpeg_enums.video_file_format_to_payload_format(video_file["file_format"]) video_frame_rate = video_file["fps"] # Host A --- MTL FFmpeg Tx diff --git a/tests/validation/functional/st2110/st20/test_RxTxApp_mtl_to_mcm_video.py b/tests/validation/functional/st2110/st20/test_RxTxApp_mtl_to_mcm_video.py index 5978089f9..350d2ca9c 100644 --- a/tests/validation/functional/st2110/st20/test_RxTxApp_mtl_to_mcm_video.py +++ b/tests/validation/functional/st2110/st20/test_RxTxApp_mtl_to_mcm_video.py @@ -52,9 +52,7 @@ def test_st2110_rttxapp_mtl_to_mcm_video( tx_mtl_path = get_mtl_path(tx_host) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - video_pixel_format = video_file_format_to_payload_format( - str(yuv_files[video_type]["file_format"]) - ) + video_pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) tx_nicctl = Nicctl( host=tx_host, @@ -87,9 +85,7 @@ def test_st2110_rttxapp_mtl_to_mcm_video( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mtl_tx_ff.get_command()}") - mtl_tx_executor = FFmpegExecutor( - tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path - ) + mtl_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path) rx_connection = Engine.rx_tx_app_connection.St2110_20( remoteIpAddr=test_config.get("broadcast_ip", DEFAULT_REMOTE_IP_ADDR), diff --git a/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py b/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py index c0d008398..82048d05c 100644 --- a/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py +++ b/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py @@ -28,9 +28,7 @@ @pytest.mark.parametrize("video_type", [k for k in yuv_files.keys()]) -def test_st2110_ffmpeg_mcm_to_mtl_video( - media_proxy, hosts, test_config, video_type: str, log_path -) -> None: +def test_st2110_ffmpeg_mcm_to_mtl_video(media_proxy, hosts, test_config, video_type: str, log_path) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts host_list = list(hosts.values()) @@ -40,9 +38,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( tx_host = host_list[0] tx_prefix_variables = test_config["tx"].get("prefix_variables", {}) tx_prefix_variables = no_proxy_to_prefix_variables(tx_host, tx_prefix_variables) - tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( - tx_host.topology.extra_info.media_proxy.get("sdk_port") - ) + tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy.get("sdk_port") logger.debug(f"tx_prefix_variables: {tx_prefix_variables}") # RX configuration @@ -54,9 +50,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( # Video configuration frame_rate = str(yuv_files[video_type]["fps"]) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - video_pixel_format = video_file_format_to_payload_format( - str(yuv_files[video_type]["file_format"]) - ) + video_pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) conn_type = McmConnectionType.st.value # Prepare Rx VFs @@ -119,11 +113,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( ) mtl_rx_outp = FFmpegVideoIO( video_size=video_size, - pixel_format=( - "yuv422p10le" - if video_pixel_format == "yuv422p10rfc4175" - else video_pixel_format - ), + pixel_format=("yuv422p10le" if video_pixel_format == "yuv422p10rfc4175" else video_pixel_format), f=FFmpegVideoFormat.raw.value, output_path=f'{test_config["rx"]["filepath"]}test_{yuv_files[video_type]["filename"]}_{video_size}at{yuv_files[video_type]["fps"]}fps.yuv', pix_fmt=None, # this is required to overwrite the default diff --git a/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py b/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py index b3f85111c..a3fc35a2d 100644 --- a/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py +++ b/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py @@ -26,9 +26,7 @@ logger = logging.getLogger(__name__) MAX_TEST_TIME_DEFAULT = 60 # seconds -EARLY_STOP_THRESHOLD_PERCENTAGE = ( - 20 # percentage of max_test_time to consider an early stop -) +EARLY_STOP_THRESHOLD_PERCENTAGE = 20 # percentage of max_test_time to consider an early stop @pytest.mark.parametrize("video_type", [k for k in yuv_files.keys()]) @@ -48,9 +46,7 @@ def test_st2110_ffmpeg_video(media_proxy, hosts, test_config, video_type: str, l frame_rate = str(yuv_files[video_type]["fps"]) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - video_pixel_format = video_file_format_to_payload_format( - str(yuv_files[video_type]["file_format"]) - ) + video_pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) conn_type = McmConnectionType.st.value # Prepare Tx VFs @@ -78,9 +74,7 @@ def test_st2110_ffmpeg_video(media_proxy, hosts, test_config, video_type: str, l ) mtl_tx_outp = FFmpegMtlSt20pTx( # TODO: Add -filter option (to FFmpegIO?) - p_port=str( - tx_vfs[0] if tx_vfs else tx_pf - ), # use VF or PF if no VFs are available + p_port=str(tx_vfs[0] if tx_vfs else tx_pf), # use VF or PF if no VFs are available p_sip=test_config["tx"]["p_sip"], p_tx_ip=test_config["broadcast_ip"], udp_port=test_config["port"], diff --git a/tests/validation/functional/st2110/st20/test_mtl_to_RxTxApp_mcm_video_multiple_nodes.py b/tests/validation/functional/st2110/st20/test_mtl_to_RxTxApp_mcm_video_multiple_nodes.py index 334cfa860..e16e119ef 100644 --- a/tests/validation/functional/st2110/st20/test_mtl_to_RxTxApp_mcm_video_multiple_nodes.py +++ b/tests/validation/functional/st2110/st20/test_mtl_to_RxTxApp_mcm_video_multiple_nodes.py @@ -53,9 +53,7 @@ def test_st2110_rttxapp_mtl_to_mcm_video( tx_mtl_path = get_mtl_path(tx_host) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - video_pixel_format = video_file_format_to_payload_format( - str(yuv_files[video_type]["file_format"]) - ) + video_pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) tx_nicctl = Nicctl( mtl_path=tx_mtl_path, @@ -87,9 +85,7 @@ def test_st2110_rttxapp_mtl_to_mcm_video( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mtl_tx_ff.get_command()}") - mtl_tx_executor = FFmpegExecutor( - tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path - ) + mtl_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path) rx_connection = Engine.rx_tx_app_connection.St2110_20( remoteIpAddr=test_config.get("broadcast_ip", DEFAULT_REMOTE_IP_ADDR), @@ -141,9 +137,5 @@ def test_st2110_rttxapp_mtl_to_mcm_video( rx_executor_a.cleanup() rx_executor_b.cleanup() - assert ( - rx_executor_a.is_pass - ), "Receiver A validation failed. Check logs for details." - assert ( - rx_executor_b.is_pass - ), "Receiver B validation failed. Check logs for details." + assert rx_executor_a.is_pass, "Receiver A validation failed. Check logs for details." + assert rx_executor_b.is_pass, "Receiver B validation failed. Check logs for details." diff --git a/tests/validation/functional/st2110/st30/test_3_2_st2110_standalone_audio.py b/tests/validation/functional/st2110/st30/test_3_2_st2110_standalone_audio.py index 5b938afa9..225e6aca3 100644 --- a/tests/validation/functional/st2110/st30/test_3_2_st2110_standalone_audio.py +++ b/tests/validation/functional/st2110/st30/test_3_2_st2110_standalone_audio.py @@ -27,9 +27,7 @@ @pytest.mark.parametrize("audio_type", list(audio_files.keys())) def test_3_2_st2110_standalone_audio(hosts, test_config, audio_type, log_path): try: - audio_format = audio_file_format_to_format_dict( - str(audio_files[audio_type]["format"]) - ) + audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) except: pytest.skip(f"Unsupported audio format: {audio_files[audio_type]['format']}") @@ -40,9 +38,7 @@ def test_3_2_st2110_standalone_audio(hosts, test_config, audio_type, log_path): audio_sample_rate = int(audio_files[audio_type]["sample_rate"]) if audio_sample_rate not in [ar.value for ar in FFmpegAudioRate]: - raise Exception( - f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" - ) + raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") tx_host = hosts["mesh-agent"] rx_host = hosts["client"] diff --git a/tests/validation/functional/st2110/st30/test_4_1_RxTxApp_mcm_to_mtl_audio_multiple_nodes.py b/tests/validation/functional/st2110/st30/test_4_1_RxTxApp_mcm_to_mtl_audio_multiple_nodes.py index 39f36aaa9..98d8c64ec 100644 --- a/tests/validation/functional/st2110/st30/test_4_1_RxTxApp_mcm_to_mtl_audio_multiple_nodes.py +++ b/tests/validation/functional/st2110/st30/test_4_1_RxTxApp_mcm_to_mtl_audio_multiple_nodes.py @@ -36,9 +36,7 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize( - "audio_type", [k for k in audio_files.keys() if "PCM8" not in k] -) +@pytest.mark.parametrize("audio_type", [k for k in audio_files.keys() if "PCM8" not in k]) def test_st2110_rttxapp_mcm_to_mtl_audio( build_TestApp, hosts, media_proxy, media_path, test_config, audio_type, log_path ) -> None: @@ -70,15 +68,11 @@ def test_st2110_rttxapp_mcm_to_mtl_audio( rx_prefix_variables = test_config["rx"].get("prefix_variables", {}) rx_mtl_path = get_mtl_path(rx_mtl_host) - audio_format = audio_file_format_to_format_dict( - str(audio_files[audio_type]["format"]) - ) + audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) audio_sample_rate = int(audio_files[audio_type]["sample_rate"]) if audio_sample_rate not in [ar.value for ar in FFmpegAudioRate]: - raise Exception( - f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" - ) + raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") rx_nicctl = Nicctl( mtl_path=rx_mtl_path, @@ -116,12 +110,8 @@ def test_st2110_rttxapp_mcm_to_mtl_audio( ffmpeg_output=mtl_rx_outp, yes_overwrite=True, ) - logger.debug( - f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}" - ) - mtl_rx_executor = FFmpegExecutor( - host=rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path - ) + logger.debug(f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}") + mtl_rx_executor = FFmpegExecutor(host=rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path) rx_executor_a = utils.LapkaExecutor.Rx( host=rx_host_a, @@ -173,9 +163,5 @@ def test_st2110_rttxapp_mcm_to_mtl_audio( mtl_rx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) tx_executor.stop() - assert ( - rx_executor_a.is_pass - ), "Receiver A validation failed. Check logs for details." - assert ( - rx_executor_b.is_pass - ), "Receiver B validation failed. Check logs for details." + assert rx_executor_a.is_pass, "Receiver A validation failed. Check logs for details." + assert rx_executor_b.is_pass, "Receiver B validation failed. Check logs for details." diff --git a/tests/validation/functional/st2110/st30/test_4_1_ffmpeg_mcm_to_mtl_audio_multiple_nodes.py b/tests/validation/functional/st2110/st30/test_4_1_ffmpeg_mcm_to_mtl_audio_multiple_nodes.py index 8dadfc99f..995b634ec 100644 --- a/tests/validation/functional/st2110/st30/test_4_1_ffmpeg_mcm_to_mtl_audio_multiple_nodes.py +++ b/tests/validation/functional/st2110/st30/test_4_1_ffmpeg_mcm_to_mtl_audio_multiple_nodes.py @@ -32,14 +32,10 @@ logger = logging.getLogger(__name__) -EARLY_STOP_THRESHOLD_PERCENTAGE = ( - 20 # percentage of max_test_time to consider an early stop -) +EARLY_STOP_THRESHOLD_PERCENTAGE = 20 # percentage of max_test_time to consider an early stop -@pytest.mark.parametrize( - "audio_type", [k for k in audio_files.keys() if "PCM8" not in k] -) +@pytest.mark.parametrize("audio_type", [k for k in audio_files.keys() if "PCM8" not in k]) def test_st2110_ffmpeg_mcm_to_mtl_audio( build_TestApp, hosts, media_proxy, media_path, test_config, audio_type, log_path ) -> None: @@ -59,9 +55,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = get_media_proxy_port(rx_host_b) rx_mtl_prefix_variables["MCM_MEDIA_PROXY_PORT"] = get_media_proxy_port(rx_mtl_host) - audio_format = audio_file_format_to_format_dict( - str(audio_files[audio_type]["format"]) - ) + audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) audio_channel_layout = audio_files[audio_type].get( "channel_layout", audio_channel_number_to_layout(int(audio_files[audio_type]["channels"])), @@ -70,9 +64,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( # Set audio sample rate based on the audio file sample rate audio_sample_rate = int(audio_files[audio_type]["sample_rate"]) if audio_sample_rate not in [ar.value for ar in FFmpegAudioRate]: - raise Exception( - f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" - ) + raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") conn_type = McmConnectionType.st.value @@ -102,12 +94,8 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( ffmpeg_output=mcm_tx_outp, yes_overwrite=False, ) - logger.debug( - f"MCM Tx command executed on {tx_host.name}: {mcm_tx_ff.get_command()}" - ) - mcm_tx_executor = FFmpegExecutor( - tx_host, ffmpeg_instance=mcm_tx_ff, log_path=log_path - ) + logger.debug(f"MCM Tx command executed on {tx_host.name}: {mcm_tx_ff.get_command()}") + mcm_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mcm_tx_ff, log_path=log_path) rx_mtl_path = get_mtl_path(rx_mtl_host) @@ -147,12 +135,8 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( ffmpeg_output=mtl_rx_outp, yes_overwrite=True, ) - logger.debug( - f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}" - ) - mtl_rx_executor = FFmpegExecutor( - rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path - ) + logger.debug(f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}") + mtl_rx_executor = FFmpegExecutor(rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path) # MCM FFmpeg Rx A mcm_rx_a_inp = FFmpegMcmST2110AudioRx( @@ -180,12 +164,8 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( ffmpeg_output=mcm_rx_a_outp, yes_overwrite=True, ) - logger.debug( - f"MCM Rx A command executed on {rx_host_a.name}: {mcm_rx_a_ff.get_command()}" - ) - mcm_rx_a_executor = FFmpegExecutor( - rx_host_a, ffmpeg_instance=mcm_rx_a_ff, log_path=log_path - ) + logger.debug(f"MCM Rx A command executed on {rx_host_a.name}: {mcm_rx_a_ff.get_command()}") + mcm_rx_a_executor = FFmpegExecutor(rx_host_a, ffmpeg_instance=mcm_rx_a_ff, log_path=log_path) # MCM FFmpeg Rx B mcm_rx_b_inp = FFmpegMcmST2110AudioRx( @@ -213,12 +193,8 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( ffmpeg_output=mcm_rx_b_outp, yes_overwrite=True, ) - logger.debug( - f"MCM Rx B command executed on {rx_host_b.name}: {mcm_rx_b_ff.get_command()}" - ) - mcm_rx_b_executor = FFmpegExecutor( - rx_host_b, ffmpeg_instance=mcm_rx_b_ff, log_path=log_path - ) + logger.debug(f"MCM Rx B command executed on {rx_host_b.name}: {mcm_rx_b_ff.get_command()}") + mcm_rx_b_executor = FFmpegExecutor(rx_host_b, ffmpeg_instance=mcm_rx_b_ff, log_path=log_path) mcm_tx_executor.start() sleep(MCM_ESTABLISH_TIMEOUT) diff --git a/tests/validation/functional/st2110/st30/test_6_1_st2110_ffmpeg_audio.py b/tests/validation/functional/st2110/st30/test_6_1_st2110_ffmpeg_audio.py index 45ba18339..3b0c46965 100644 --- a/tests/validation/functional/st2110/st30/test_6_1_st2110_ffmpeg_audio.py +++ b/tests/validation/functional/st2110/st30/test_6_1_st2110_ffmpeg_audio.py @@ -27,18 +27,14 @@ def test_6_1_st2110_ffmpeg_audio(hosts, test_config, audio_file, log_path): rx_b_host = hosts["client"] try: - audio_format = ffmpeg_enums.audio_file_format_to_format_dict( - str(audio_file["format"]) - ) + audio_format = ffmpeg_enums.audio_file_format_to_format_dict(str(audio_file["format"])) except: pytest.skip(f"Unsupported audio format: {audio_file['format']}") audio_sample_rate = int(audio_file["sample_rate"]) if audio_sample_rate not in [ar.value for ar in ffmpeg_enums.FFmpegAudioRate]: - raise Exception( - f"Not expected audio sample rate of {audio_file['sample_rate']}!" - ) + raise Exception(f"Not expected audio sample rate of {audio_file['sample_rate']}!") # Host A --- MTL FFmpeg Tx diff --git a/tests/validation/functional/st2110/st30/test_RxTxApp_mtl_to_mcm_audio.py b/tests/validation/functional/st2110/st30/test_RxTxApp_mtl_to_mcm_audio.py index e5cfefa7a..a987b491e 100644 --- a/tests/validation/functional/st2110/st30/test_RxTxApp_mtl_to_mcm_audio.py +++ b/tests/validation/functional/st2110/st30/test_RxTxApp_mtl_to_mcm_audio.py @@ -35,9 +35,7 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize( - "audio_type", [k for k in audio_files.keys() if "PCM8" not in k] -) +@pytest.mark.parametrize("audio_type", [k for k in audio_files.keys() if "PCM8" not in k]) def test_st2110_rttxapp_mtl_to_mcm_audio( build_TestApp, hosts, media_proxy, media_path, test_config, audio_type, log_path ) -> None: @@ -51,15 +49,11 @@ def test_st2110_rttxapp_mtl_to_mcm_audio( tx_prefix_variables = test_config["tx"].get("prefix_variables", {}) tx_mtl_path = get_mtl_path(tx_host) - audio_format = audio_file_format_to_format_dict( - str(audio_files[audio_type]["format"]) - ) + audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) audio_sample_rate = int(audio_files[audio_type]["sample_rate"]) if audio_sample_rate not in [ar.value for ar in FFmpegAudioRate]: - raise Exception( - f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" - ) + raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") tx_nicctl = Nicctl( host=tx_host, @@ -96,9 +90,7 @@ def test_st2110_rttxapp_mtl_to_mcm_audio( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mtl_tx_ff.get_command()}") - mtl_tx_executor = FFmpegExecutor( - tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path - ) + mtl_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path) rx_connection = Engine.rx_tx_app_connection.St2110_30( remoteIpAddr=test_config.get("broadcast_ip", DEFAULT_REMOTE_IP_ADDR), diff --git a/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py b/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py index 232f109b7..0ce95d3a3 100644 --- a/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py +++ b/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py @@ -29,9 +29,7 @@ @pytest.mark.parametrize("audio_type", [k for k in audio_files.keys()]) -def test_st2110_ffmpeg_mcm_to_mtl_audio( - media_proxy, hosts, test_config, audio_type: str, log_path -) -> None: +def test_st2110_ffmpeg_mcm_to_mtl_audio(media_proxy, hosts, test_config, audio_type: str, log_path) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts host_list = list(hosts.values()) @@ -41,9 +39,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( tx_host = host_list[0] tx_prefix_variables = test_config["tx"].get("prefix_variables", {}) tx_prefix_variables = no_proxy_to_prefix_variables(tx_host, tx_prefix_variables) - tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( - tx_host.topology.extra_info.media_proxy.get("sdk_port") - ) + tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy.get("sdk_port") # RX configuration rx_host = host_list[1] @@ -52,9 +48,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( rx_mtl_path = rx_host.topology.extra_info.mtl_path # Audio configuration - audio_format = audio_file_format_to_format_dict( - str(audio_files[audio_type]["format"]) - ) # audio format + audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) # audio format audio_channel_layout = audio_files[audio_type].get( "channel_layout", audio_channel_number_to_layout(int(audio_files[audio_type]["channels"])), @@ -67,9 +61,7 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( elif int(audio_files[audio_type]["sample_rate"]) == 96000: audio_sample_rate = FFmpegAudioRate.k96.value else: - pytest.skip( - f"Skipping test due to unsupported audio sample rate: {audio_files[audio_type]['sample_rate']}" - ) + pytest.skip(f"Skipping test due to unsupported audio sample rate: {audio_files[audio_type]['sample_rate']}") # Prepare Rx VFs rx_nicctl = Nicctl( diff --git a/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py b/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py index f793906f4..c6ab7bd59 100644 --- a/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py +++ b/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py @@ -29,15 +29,11 @@ logger = logging.getLogger(__name__) MAX_TEST_TIME_DEFAULT = 60 # seconds -EARLY_STOP_THRESHOLD_PERCENTAGE = ( - 20 # percentage of max_test_time to consider an early stop -) +EARLY_STOP_THRESHOLD_PERCENTAGE = 20 # percentage of max_test_time to consider an early stop @pytest.mark.parametrize("audio_type", [k for k in audio_files.keys()]) -def test_st2110_ffmpeg_mtl_to_mcm_audio( - media_proxy, hosts, test_config, audio_type: str, log_path -) -> None: +def test_st2110_ffmpeg_mtl_to_mcm_audio(media_proxy, hosts, test_config, audio_type: str, log_path) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts host_list = list(hosts.values()) @@ -51,9 +47,7 @@ def test_st2110_ffmpeg_mtl_to_mcm_audio( tx_mtl_path = get_mtl_path(tx_host) rx_mtl_path = get_mtl_path(rx_host) - audio_format = audio_file_format_to_format_dict( - str(audio_files[audio_type]["format"]) - ) # audio format + audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) # audio format audio_channel_layout = audio_files[audio_type].get( "channel_layout", audio_channel_number_to_layout(int(audio_files[audio_type]["channels"])), @@ -62,9 +56,7 @@ def test_st2110_ffmpeg_mtl_to_mcm_audio( # Set audio sample rate based on the audio file sample rate audio_sample_rate = int(audio_files[audio_type]["sample_rate"]) if audio_sample_rate not in [ar.value for ar in FFmpegAudioRate]: - raise Exception( - f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" - ) + raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") # Prepare Tx VFs tx_nicctl = Nicctl( @@ -92,9 +84,7 @@ def test_st2110_ffmpeg_mtl_to_mcm_audio( ) mtl_tx_outp = FFmpegMtlSt30pTx( ptime=PacketTime.pt_1ms.value, - p_port=str( - tx_vfs[0] if tx_vfs else tx_pf - ), # use VF or PF if no VFs are available + p_port=str(tx_vfs[0] if tx_vfs else tx_pf), # use VF or PF if no VFs are available p_sip=test_config["tx"]["p_sip"], p_tx_ip=test_config["broadcast_ip"], udp_port=test_config["port"], diff --git a/tests/validation/functional/st2110/st30/test_mtl_to_RxTxApp_mcm_audio_multiple_nodes.py b/tests/validation/functional/st2110/st30/test_mtl_to_RxTxApp_mcm_audio_multiple_nodes.py index d7adf48ca..e84a50dd0 100644 --- a/tests/validation/functional/st2110/st30/test_mtl_to_RxTxApp_mcm_audio_multiple_nodes.py +++ b/tests/validation/functional/st2110/st30/test_mtl_to_RxTxApp_mcm_audio_multiple_nodes.py @@ -36,9 +36,7 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize( - "audio_type", [k for k in audio_files.keys() if "PCM8" not in k] -) +@pytest.mark.parametrize("audio_type", [k for k in audio_files.keys() if "PCM8" not in k]) def test_st2110_rttxapp_mtl_to_mcm_audio( build_TestApp, hosts, media_proxy, media_path, test_config, audio_type, log_path ) -> None: @@ -54,15 +52,11 @@ def test_st2110_rttxapp_mtl_to_mcm_audio( tx_prefix_variables = test_config["tx"].get("mtl_prefix_variables", {}) tx_mtl_path = get_mtl_path(tx_host) - audio_format = audio_file_format_to_format_dict( - str(audio_files[audio_type]["format"]) - ) + audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) audio_sample_rate = int(audio_files[audio_type]["sample_rate"]) if audio_sample_rate not in [ar.value for ar in FFmpegAudioRate]: - raise Exception( - f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" - ) + raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") tx_nicctl = Nicctl( mtl_path=tx_mtl_path, @@ -100,9 +94,7 @@ def test_st2110_rttxapp_mtl_to_mcm_audio( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mtl_tx_ff.get_command()}") - mtl_tx_executor = FFmpegExecutor( - tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path - ) + mtl_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path) rx_connection = Engine.rx_tx_app_connection.St2110_30( remoteIpAddr=test_config.get("broadcast_ip", DEFAULT_REMOTE_IP_ADDR), @@ -153,9 +145,5 @@ def test_st2110_rttxapp_mtl_to_mcm_audio( rx_executor_a.cleanup() rx_executor_b.cleanup() - assert ( - rx_executor_a.is_pass - ), "Receiver A validation failed. Check logs for details." - assert ( - rx_executor_b.is_pass - ), "Receiver B validation failed. Check logs for details." + assert rx_executor_a.is_pass, "Receiver A validation failed. Check logs for details." + assert rx_executor_b.is_pass, "Receiver B validation failed. Check logs for details." diff --git a/tests/validation/functional/test_demo.py b/tests/validation/functional/test_demo.py index 3c5f05f2e..164c229de 100644 --- a/tests/validation/functional/test_demo.py +++ b/tests/validation/functional/test_demo.py @@ -81,9 +81,7 @@ def test_list_command_on_sut(hosts): def test_mesh_agent_lifecycle(mesh_agent, logging): """Test starting and stopping the mesh agent.""" logger.info("Testing mesh_agent lifecycle") - assert ( - mesh_agent.mesh_agent_process is not None - ), "Mesh agent process was not started." + assert mesh_agent.mesh_agent_process is not None, "Mesh agent process was not started." assert mesh_agent.mesh_agent_process.running, "Mesh agent process is not running." logger.info("Mesh agent lifecycle test completed successfully.") @@ -108,9 +106,7 @@ def test_sudo_command(hosts): result = connection.execute_command("ls /", stderr_to_stdout=True) logger.info(f"Command output: {result.stdout}") logger.info(f"Command error (if any): {result.stderr}") - assert ( - result.return_code == 0 - ), f"Sudo command failed with return code {result.return_code}" + assert result.return_code == 0, f"Sudo command failed with return code {result.return_code}" # connection.disable_sudo() logger.info("Sudo command execution test completed") @@ -120,12 +116,8 @@ def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config, log_ tx_host = rx_host = list(hosts.values())[0] prefix_variables = test_config.get("prefix_variables", {}) if tx_host.topology.extra_info.media_proxy.get("no_proxy", None): - prefix_variables["NO_PROXY"] = tx_host.topology.extra_info.media_proxy[ - "no_proxy" - ] - prefix_variables["no_proxy"] = tx_host.topology.extra_info.media_proxy[ - "no_proxy" - ] + prefix_variables["NO_PROXY"] = tx_host.topology.extra_info.media_proxy["no_proxy"] + prefix_variables["no_proxy"] = tx_host.topology.extra_info.media_proxy["no_proxy"] sdk_port = MEDIA_PROXY_PORT if tx_host.name in media_proxy and media_proxy[tx_host.name].p is not None: sdk_port = media_proxy[tx_host.name].t @@ -136,11 +128,7 @@ def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config, log_ pixel_format = "yuv422p10le" conn_type = McmConnectionType.mpg.value - input_path = str( - tx_host.connection.path( - test_config["input_path"], "180fr_1920x1080_yuv422p.yuv" - ) - ) + input_path = str(tx_host.connection.path(test_config["input_path"], "180fr_1920x1080_yuv422p.yuv")) # >>>>> MCM Tx mcm_tx_inp = FFmpegVideoIO( @@ -183,9 +171,7 @@ def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config, log_ framerate=frame_rate, video_size=video_size, pixel_format=pixel_format, - output_path=str( - tx_host.connection.path(test_config["output_path"], "output_vid.yuv") - ), + output_path=str(tx_host.connection.path(test_config["output_path"], "output_vid.yuv")), ) mcm_rx_ff = FFmpeg( prefix_variables=prefix_variables, @@ -224,12 +210,8 @@ def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config, log_pat tx_host = rx_host = list(hosts.values())[0] prefix_variables = test_config.get("prefix_variables", {}) if tx_host.topology.extra_info.media_proxy.get("no_proxy", None): - prefix_variables["NO_PROXY"] = tx_host.topology.extra_info.media_proxy[ - "no_proxy" - ] - prefix_variables["no_proxy"] = tx_host.topology.extra_info.media_proxy[ - "no_proxy" - ] + prefix_variables["NO_PROXY"] = tx_host.topology.extra_info.media_proxy["no_proxy"] + prefix_variables["no_proxy"] = tx_host.topology.extra_info.media_proxy["no_proxy"] sdk_port = MEDIA_PROXY_PORT if tx_host.name in media_proxy and media_proxy[tx_host.name].p is not None: sdk_port = media_proxy[tx_host.name].t @@ -240,11 +222,7 @@ def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config, log_pat pixel_format = "yuv422p10le" conn_type = McmConnectionType.mpg.value - input_path = str( - tx_host.connection.path( - test_config["input_path"], "180fr_1920x1080_yuv422p.yuv" - ) - ) + input_path = str(tx_host.connection.path(test_config["input_path"], "180fr_1920x1080_yuv422p.yuv")) # >>>>> MCM Tx mcm_tx_inp = FFmpegVideoIO( @@ -321,16 +299,12 @@ def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config, log_pat time.sleep(3) # Ensure the receiver is ready before starting the transmitter mcm_tx_executor.start() mcm_rx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) - mcm_tx_executor.stop( - wait=3 - ) # Tx should stop just after Rx stop so wait timeout can be shorter here + mcm_tx_executor.stop(wait=3) # Tx should stop just after Rx stop so wait timeout can be shorter here assert integrator.stop_and_verify(timeout=20), "Stream integrity check failed" -def test_demo_local_blob_integrity( - build_TestApp, hosts, media_proxy, media_path, log_path -) -> None: +def test_demo_local_blob_integrity(build_TestApp, hosts, media_proxy, media_path, log_path) -> None: """Test blob integrity checking with a single blob file transfer using LapkaExecutor.""" # Get TX and RX hosts host_list = list(hosts.values()) @@ -386,19 +360,12 @@ def test_demo_local_blob_integrity( assert rx_executor.is_pass is True, "RX process did not pass" # Check if the output file actually exists - output_exists = ( - rx_host.connection.execute_command( - f"test -f {rx_executor.output}", shell=True - ).return_code - == 0 - ) + output_exists = rx_host.connection.execute_command(f"test -f {rx_executor.output}", shell=True).return_code == 0 logger.info(f"Output file exists: {output_exists}") if output_exists: # Get actual file size for debugging - file_size_result = rx_host.connection.execute_command( - f"stat -c%s {rx_executor.output}", shell=True - ) + file_size_result = rx_host.connection.execute_command(f"stat -c%s {rx_executor.output}", shell=True) logger.info(f"Output file size: {file_size_result.stdout.strip()} bytes") # Setup blob integrity checker @@ -407,9 +374,7 @@ def test_demo_local_blob_integrity( test_repo_path=None, src_url=str(tx_executor.input), # Use the actual input path from executor out_name=rx_executor.output.name, # Use the actual output filename - chunk_size=int( - file_dict["max_payload_size"] - ), # Use payload size as chunk size + chunk_size=int(file_dict["max_payload_size"]), # Use payload size as chunk size out_path=str(rx_executor.output.parent), # Use the output directory delete_file=False, integrity_path="/opt/intel/val_tests/validation/common/integrity/", @@ -430,52 +395,33 @@ def test_demo_local_blob_integrity( logger.error("=== Debugging integrity check failure ===") # Check source file details - src_exists = ( - tx_host.connection.execute_command( - f"test -f {tx_executor.input}", shell=True - ).return_code - == 0 - ) + src_exists = tx_host.connection.execute_command(f"test -f {tx_executor.input}", shell=True).return_code == 0 if src_exists: - src_size_result = tx_host.connection.execute_command( - f"stat -c%s {tx_executor.input}", shell=True - ) - logger.error( - f"Source file size: {src_size_result.stdout.strip()} bytes" - ) + src_size_result = tx_host.connection.execute_command(f"stat -c%s {tx_executor.input}", shell=True) + logger.error(f"Source file size: {src_size_result.stdout.strip()} bytes") else: logger.error(f"Source file {tx_executor.input} does not exist") # Check output file details again - out_size_result = rx_host.connection.execute_command( - f"stat -c%s {rx_executor.output}", shell=True - ) + out_size_result = rx_host.connection.execute_command(f"stat -c%s {rx_executor.output}", shell=True) logger.error(f"Output file size: {out_size_result.stdout.strip()} bytes") # Compare sizes - logger.error( - f"Max payload size (chunk size): {file_dict['max_payload_size']}" - ) + logger.error(f"Max payload size (chunk size): {file_dict['max_payload_size']}") # Try to get some hex dump of both files for comparison logger.error("First 32 bytes of source file:") - src_hex = tx_host.connection.execute_command( - f"hexdump -C {tx_executor.input} | head -3", shell=True - ) + src_hex = tx_host.connection.execute_command(f"hexdump -C {tx_executor.input} | head -3", shell=True) logger.error(src_hex.stdout) logger.error("First 32 bytes of output file:") - out_hex = rx_host.connection.execute_command( - f"hexdump -C {rx_executor.output} | head -3", shell=True - ) + out_hex = rx_host.connection.execute_command(f"hexdump -C {rx_executor.output} | head -3", shell=True) logger.error(out_hex.stdout) assert result, "Blob integrity check failed" else: # List files in the output directory to see what was actually created - ls_result = rx_host.connection.execute_command( - f"ls -la {rx_executor.output.parent}/", shell=True - ) + ls_result = rx_host.connection.execute_command(f"ls -la {rx_executor.output.parent}/", shell=True) logger.error(f"Output directory contents:\n{ls_result.stdout}") # Find any files that might match our pattern @@ -505,8 +451,9 @@ def test_build_mtl_ffmpeg(build_mtl_ffmpeg, hosts, test_config): logger.info("Testing MTL FFmpeg build process") assert build_mtl_ffmpeg, "MTL FFmpeg build failed" + def test_simple(log_path_dir, log_path, request): # For this test, log_path will be based on "test_simple" logging.info(f"Log path dir for test_simple: {log_path_dir}") logging.info(f"Log path for test_simple: {log_path}") - logging.info(f"Request: {request.node.name}") \ No newline at end of file + logging.info(f"Request: {request.node.name}") From 542588b594263d784a5043fa28a98970cb6f6e45 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 16:58:41 +0000 Subject: [PATCH 078/123] Revert "linter fix" This reverts commit 16aac0c4076077c95dcaa651dc85b3b33dfcb6e1. --- .github/workflows/base_build.yml | 24 ++-- .github/workflows/validation-tests.yml | 4 +- .gitignore | 2 - tests/validation/Engine/mcm_apps.py | 44 ++++-- .../Engine/rx_tx_app_client_json.py | 13 +- .../validation/Engine/rx_tx_app_connection.py | 4 +- .../Engine/rx_tx_app_connection_json.py | 4 +- ...rx_tx_app_connection_json_usage_example.py | 4 +- .../validation/Engine/rx_tx_app_engine_mcm.py | 113 ++++++++++------ .../Engine/rx_tx_app_file_validation_utils.py | 12 +- tests/validation/common/__init__.py | 12 +- .../common/ffmpeg_handler/__init__.py | 8 +- .../common/ffmpeg_handler/ffmpeg.py | 118 ++++++++++------- .../common/ffmpeg_handler/ffmpeg_enums.py | 4 +- .../common/ffmpeg_handler/log_constants.py | 35 +++-- .../common/ffmpeg_handler/mcm_ffmpeg.py | 4 +- .../common/integrity/blob_integrity.py | 47 +++++-- .../common/integrity/integrity_runner.py | 97 ++++++++++---- .../common/integrity/test_blob_integrity.py | 4 +- .../common/integrity/video_integrity.py | 56 ++++++-- tests/validation/common/log_constants.py | 46 ++++--- .../validation/common/log_validation_utils.py | 68 +++++----- tests/validation/common/nicctl.py | 12 +- .../common/visualisation/audio_graph.py | 80 ++++++++--- tests/validation/conftest.py | 125 +++++++++++++----- .../cluster/audio/test_audio_25_03.py | 4 +- .../cluster/audio/test_ffmpeg_audio.py | 16 ++- .../cluster/blob/test_blob_25_03.py | 4 +- .../cluster/video/test_ffmpeg_video.py | 12 +- .../cluster/video/test_video_25_03.py | 4 +- .../local/audio/test_audio_25_03.py | 4 +- .../local/audio/test_ffmpeg_audio.py | 12 +- .../functional/local/blob/test_blob_25_03.py | 5 +- .../local/video/test_ffmpeg_video.py | 16 ++- .../local/video/test_video_25_03.py | 4 +- .../st20/test_3_2_st2110_standalone_video.py | 8 +- ...RxTxApp_mcm_to_mtl_video_multiple_nodes.py | 26 +++- ..._ffmpeg_mcm_to_mtl_video_multiple_nodes.py | 46 +++++-- .../st20/test_6_1_st2110_ffmpeg_video.py | 4 +- .../st20/test_RxTxApp_mtl_to_mcm_video.py | 8 +- .../st20/test_ffmpeg_mcm_to_mtl_video.py | 18 ++- .../st20/test_ffmpeg_mtl_to_mcm_video.py | 12 +- ...mtl_to_RxTxApp_mcm_video_multiple_nodes.py | 16 ++- .../st30/test_3_2_st2110_standalone_audio.py | 8 +- ...RxTxApp_mcm_to_mtl_audio_multiple_nodes.py | 28 +++- ..._ffmpeg_mcm_to_mtl_audio_multiple_nodes.py | 48 +++++-- .../st30/test_6_1_st2110_ffmpeg_audio.py | 8 +- .../st30/test_RxTxApp_mtl_to_mcm_audio.py | 16 ++- .../st30/test_ffmpeg_mcm_to_mtl_audio.py | 16 ++- .../st30/test_ffmpeg_mtl_to_mcm_audio.py | 20 ++- ...mtl_to_RxTxApp_mcm_audio_multiple_nodes.py | 24 +++- tests/validation/functional/test_demo.py | 101 ++++++++++---- 52 files changed, 1004 insertions(+), 424 deletions(-) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index 2a5f5f9a5..f1f4057bc 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -4,21 +4,21 @@ on: push: branches: ["main"] paths-ignore: - - "**/*.md" - - "tests/**" - - "docs/**" - - "LICENSE" - - ".gitignore" - - ".editorconfig" + - '**/*.md' + - 'tests/**' + - 'docs/**' + - 'LICENSE' + - '.gitignore' + - '.editorconfig' pull_request: branches: ["main"] paths-ignore: - - "**/*.md" - - "tests/**" - - "docs/**" - - "LICENSE" - - ".gitignore" - - ".editorconfig" + - '**/*.md' + - 'tests/**' + - 'docs/**' + - 'LICENSE' + - '.gitignore' + - '.editorconfig' workflow_dispatch: env: diff --git a/.github/workflows/validation-tests.yml b/.github/workflows/validation-tests.yml index cfc383093..479c528b5 100644 --- a/.github/workflows/validation-tests.yml +++ b/.github/workflows/validation-tests.yml @@ -114,7 +114,7 @@ permissions: jobs: validation-build-mtm: - runs-on: [Linux, self-hosted] + runs-on: [Linux, self-hosted, DPDK] timeout-minutes: 60 outputs: pipenv-activate: ${{ steps.pipenv-install.outputs.VIRTUAL_ENV }} @@ -194,7 +194,7 @@ jobs: # Timeout of this job is set to 12h [60m/h*12h=720m] validation-run-tests: needs: [validation-build-mtm] - runs-on: [Linux, self-hosted] + runs-on: [Linux, self-hosted, DPDK] timeout-minutes: 720 env: PYTEST_ALIAS: "sudo --preserve-env python3 -m pipenv run pytest" diff --git a/.gitignore b/.gitignore index b28288b0e..cc9830ad5 100644 --- a/.gitignore +++ b/.gitignore @@ -41,5 +41,3 @@ cov-int* # Autogenerated version files mcm-version.h mcm-version.go - -.venv* \ No newline at end of file diff --git a/tests/validation/Engine/mcm_apps.py b/tests/validation/Engine/mcm_apps.py index c74ca03da..a7ec19d4c 100644 --- a/tests/validation/Engine/mcm_apps.py +++ b/tests/validation/Engine/mcm_apps.py @@ -219,7 +219,9 @@ def __init__( self.log_path = log_path self.subdir = Path("media_proxy_logs", self.host.name) self.filename = "media_proxy.log" - self.log_file_path = Path(log_path if log_path else LOG_FOLDER, self.subdir, self.filename) + self.log_file_path = Path( + log_path if log_path else LOG_FOLDER, self.subdir, self.filename + ) def start(self): if not self.run_media_proxy_process or not self.run_media_proxy_process.running: @@ -244,10 +246,14 @@ def start(self): cmd += f" -p {self.p}" self.cmd = cmd - logger.info(f"Starting media proxy on host {self.host.name} with command: {self.cmd}") + logger.info( + f"Starting media proxy on host {self.host.name} with command: {self.cmd}" + ) try: logger.debug(f"Media proxy command on {self.host.name}: {self.cmd}") - self.run_media_proxy_process = connection.start_process(self.cmd, stderr_to_stdout=True) + self.run_media_proxy_process = connection.start_process( + self.cmd, stderr_to_stdout=True + ) # Start background logging thread def log_output(): @@ -262,11 +268,17 @@ def log_output(): threading.Thread(target=log_output, daemon=True).start() except Exception as e: - logger.error(f"Failed to start media proxy process on host {self.host.name}") - raise RuntimeError(f"Failed to start media proxy process on host {self.host.name}: {e}") + logger.error( + f"Failed to start media proxy process on host {self.host.name}" + ) + raise RuntimeError( + f"Failed to start media proxy process on host {self.host.name}: {e}" + ) # if self.use_sudo: # connection.disable_sudo() - logger.info(f"Media proxy started on host {self.host.name}: {self.run_media_proxy_process.running}") + logger.info( + f"Media proxy started on host {self.host.name}: {self.run_media_proxy_process.running}" + ) def stop(self, log_path=None): # Use the provided log_path or the one stored in the object @@ -330,9 +342,13 @@ def __init__(self, host, log_path=None): self.is_pass = False self.external = False self.log_path = log_path - self.subdir = Path("mesh_agent_logs", "mesh-agent" if host is None else host.name) + self.subdir = Path( + "mesh_agent_logs", "mesh-agent" if host is None else host.name + ) self.filename = "mesh_agent.log" - self.log_file_path = Path(log_path if log_path else LOG_FOLDER, self.subdir, self.filename) + self.log_file_path = Path( + log_path if log_path else LOG_FOLDER, self.subdir, self.filename + ) def start(self, c=None, p=None): if not self.mesh_agent_process or not self.mesh_agent_process.running: @@ -345,8 +361,12 @@ def start(self, c=None, p=None): cmd += f" -p {self.p}" self.cmd = cmd - logger.info(f"Starting mesh agent on host {self.host.name} with command: {self.cmd}") - self.mesh_agent_process = self.host.connection.start_process(self.cmd, stderr_to_stdout=True) + logger.info( + f"Starting mesh agent on host {self.host.name} with command: {self.cmd}" + ) + self.mesh_agent_process = self.host.connection.start_process( + self.cmd, stderr_to_stdout=True + ) # Start background logging thread def log_output(): @@ -362,7 +382,9 @@ def log_output(): threading.Thread(target=log_output, daemon=True).start() self.mesh_ip = self.host.connection.ip - logger.info(f"Mesh agent started on host {self.host.name}: {self.mesh_agent_process.running}") + logger.info( + f"Mesh agent started on host {self.host.name}: {self.mesh_agent_process.running}" + ) def stop(self, log_path=None): # Use the provided log_path or the one stored in the object diff --git a/tests/validation/Engine/rx_tx_app_client_json.py b/tests/validation/Engine/rx_tx_app_client_json.py index 4bc161dc5..4d0f13792 100644 --- a/tests/validation/Engine/rx_tx_app_client_json.py +++ b/tests/validation/Engine/rx_tx_app_client_json.py @@ -25,11 +25,15 @@ def __init__( def set_client(self, edits: dict) -> None: self.apiVersion = edits.get("apiVersion", self.apiVersion) - self.apiConnectionString = edits.get("apiConnectionString", self.apiConnectionString) + self.apiConnectionString = edits.get( + "apiConnectionString", self.apiConnectionString + ) self.apiDefaultTimeoutMicroseconds = edits.get( "apiDefaultTimeoutMicroseconds", self.apiDefaultTimeoutMicroseconds ) - self.maxMediaConnections = edits.get("maxMediaConnections", self.maxMediaConnections) + self.maxMediaConnections = edits.get( + "maxMediaConnections", self.maxMediaConnections + ) def to_json(self) -> str: json_dict = { @@ -45,14 +49,15 @@ def prepare_and_save_json(self, output_path: str = "client.json") -> None: json_content = self.to_json().replace('"', '\\"') f.write_text(json_content) + def copy_json_to_logs(self, log_path: str) -> None: """Copy the client.json file to the log path on runner.""" source_path = self.host.connection.path("client.json") dest_path = Path(log_path) / "client.json" - + # Create log directory if it doesn't exist Path(log_path).mkdir(parents=True, exist_ok=True) - + # Copy the client.json file to the log path with open(dest_path, "w") as dest_file: dest_file.write(self.to_json()) diff --git a/tests/validation/Engine/rx_tx_app_connection.py b/tests/validation/Engine/rx_tx_app_connection.py index 0257ef57c..c30be4eb0 100644 --- a/tests/validation/Engine/rx_tx_app_connection.py +++ b/tests/validation/Engine/rx_tx_app_connection.py @@ -151,7 +151,9 @@ class ConnectionMode(Enum): class Rdma(RxTxAppConnection): """Prepares RDMA part of connection.json file""" - def __init__(self, connectionMode=ConnectionMode.RC, maxLatencyNs=DEFAULT_RDMA_MAX_LATENCY_NS): + def __init__( + self, connectionMode=ConnectionMode.RC, maxLatencyNs=DEFAULT_RDMA_MAX_LATENCY_NS + ): super().__init__(rx_tx_app_connection_type=RxTxAppConnectionType.RDMA) self.connectionMode = connectionMode self.maxLatencyNs = maxLatencyNs diff --git a/tests/validation/Engine/rx_tx_app_connection_json.py b/tests/validation/Engine/rx_tx_app_connection_json.py index 9e38665f8..ac7599673 100644 --- a/tests/validation/Engine/rx_tx_app_connection_json.py +++ b/tests/validation/Engine/rx_tx_app_connection_json.py @@ -51,10 +51,10 @@ def copy_json_to_logs(self, log_path: str) -> None: """Copy the connection.json file to the log path on runner.""" source_path = self.host.connection.path("connection.json") dest_path = Path(log_path) / "connection.json" - + # Create log directory if it doesn't exist Path(log_path).mkdir(parents=True, exist_ok=True) - + # Copy the connection.json file to the log path with open(dest_path, "w") as dest_file: dest_file.write(self.to_json()) diff --git a/tests/validation/Engine/rx_tx_app_connection_json_usage_example.py b/tests/validation/Engine/rx_tx_app_connection_json_usage_example.py index ecea9e6ea..f0755f482 100644 --- a/tests/validation/Engine/rx_tx_app_connection_json_usage_example.py +++ b/tests/validation/Engine/rx_tx_app_connection_json_usage_example.py @@ -102,7 +102,9 @@ ) print("ConnectionJson:") -cj = ConnectionJson(maxPayloadSize=1024, payload=pl, rx_tx_app_connection=rx_tx_app_connection_st2110) +cj = ConnectionJson( + maxPayloadSize=1024, payload=pl, rx_tx_app_connection=rx_tx_app_connection_st2110 +) print( f"""Dict: diff --git a/tests/validation/Engine/rx_tx_app_engine_mcm.py b/tests/validation/Engine/rx_tx_app_engine_mcm.py index 7fadf4629..53e31430f 100644 --- a/tests/validation/Engine/rx_tx_app_engine_mcm.py +++ b/tests/validation/Engine/rx_tx_app_engine_mcm.py @@ -23,7 +23,9 @@ logger = logging.getLogger(__name__) -def create_client_json(build: str, client: Engine.rx_tx_app_client_json.ClientJson, log_path: str = "") -> None: +def create_client_json( + build: str, client: Engine.rx_tx_app_client_json.ClientJson, log_path: str = "" +) -> None: logger.debug("Client JSON:") for line in client.to_json().splitlines(): logger.debug(line) @@ -41,7 +43,9 @@ def create_connection_json( logger.debug("Connection JSON:") for line in rx_tx_app_connection.to_json().splitlines(): logger.debug(line) - output_path = str(Path(build, "tests", "tools", "TestApp", "build", "connection.json")) + output_path = str( + Path(build, "tests", "tools", "TestApp", "build", "connection.json") + ) logger.debug(f"Connection JSON path: {output_path}") rx_tx_app_connection.prepare_and_save_json(output_path=output_path) # Use provided log_path or default to LOG_FOLDER @@ -72,21 +76,31 @@ def __init__( self.file_dict = file_dict self.file = file self.rx_tx_app_connection = rx_tx_app_connection() - self.payload = payload_type.from_file_info(media_path=self.media_path, file_info=file_dict) + self.payload = payload_type.from_file_info( + media_path=self.media_path, file_info=file_dict + ) self.media_proxy_port = get_media_proxy_port(host) self.rx_tx_app_client_json = Engine.rx_tx_app_client_json.ClientJson( host, apiConnectionString=f"Server=127.0.0.1; Port={self.media_proxy_port}" ) - self.rx_tx_app_connection_json = Engine.rx_tx_app_connection_json.ConnectionJson( - host=host, - rx_tx_app_connection=self.rx_tx_app_connection, - payload=self.payload, + self.rx_tx_app_connection_json = ( + Engine.rx_tx_app_connection_json.ConnectionJson( + host=host, + rx_tx_app_connection=self.rx_tx_app_connection, + payload=self.payload, + ) + ) + self.app_path = host.connection.path( + self.mcm_path, "tests", "tools", "TestApp", "build" ) - self.app_path = host.connection.path(self.mcm_path, "tests", "tools", "TestApp", "build") self.client_cfg_file = host.connection.path(self.app_path, "client.json") - self.connection_cfg_file = host.connection.path(self.app_path, "connection.json") + self.connection_cfg_file = host.connection.path( + self.app_path, "connection.json" + ) self.input = input_file - self.output_path = getattr(host.topology.extra_info, "output_path", DEFAULT_OUTPUT_PATH) + self.output_path = getattr( + host.topology.extra_info, "output_path", DEFAULT_OUTPUT_PATH + ) # Handle output file path construction if output_file and output_path: @@ -134,7 +148,9 @@ def _ensure_output_directory_exists(self): if self.output and str(self.output) != "/dev/null": # Skip for /dev/null and other special files if "/dev/" in str(self.output): - logger.debug(f"Skipping directory creation for special device path: {self.output}") + logger.debug( + f"Skipping directory creation for special device path: {self.output}" + ) return output_dir = self.host.connection.path(self.output).parent @@ -184,31 +200,27 @@ def stop(self): app_log_validation_status = False app_log_error_count = 0 - + # Using common log validation utility from common.log_validation_utils import check_phrases_in_order if self.direction in ("Rx", "Tx"): from common.log_validation_utils import validate_log_file - required_phrases = RX_REQUIRED_LOG_PHRASES if self.direction == "Rx" else TX_REQUIRED_LOG_PHRASES - + # Use the common validation function validation_result = validate_log_file(log_file_path, required_phrases, self.direction) - - self.is_pass = validation_result["is_pass"] - app_log_validation_status = validation_result["is_pass"] - app_log_error_count = validation_result["error_count"] - validation_info.extend(validation_result["validation_info"]) - + + self.is_pass = validation_result['is_pass'] + app_log_validation_status = validation_result['is_pass'] + app_log_error_count = validation_result['error_count'] + validation_info.extend(validation_result['validation_info']) + # Additional logging if validation failed - if not validation_result["is_pass"] and validation_result["missing_phrases"]: - print( - f"{self.direction} process did not pass. First missing phrase: {validation_result['missing_phrases'][0]}" - ) + if not validation_result['is_pass'] and validation_result['missing_phrases']: + print(f"{self.direction} process did not pass. First missing phrase: {validation_result['missing_phrases'][0]}") else: from common.log_validation_utils import output_validator - result = output_validator( log_file_path=log_file_path, error_keywords=RX_TX_APP_ERROR_KEYWORDS, @@ -220,7 +232,9 @@ def stop(self): app_log_error_count = len(result["errors"]) validation_info.append(f"=== {self.direction} App Log Validation ===") validation_info.append(f"Log file: {log_file_path}") - validation_info.append(f"Validation result: {'PASS' if result['is_pass'] else 'FAIL'}") + validation_info.append( + f"Validation result: {'PASS' if result['is_pass'] else 'FAIL'}" + ) validation_info.append(f"Errors found: {len(result['errors'])}") if result["errors"]: validation_info.append("Error details:") @@ -229,34 +243,39 @@ def stop(self): if result["phrase_mismatches"]: validation_info.append("Phrase mismatches:") for phrase, found, expected in result["phrase_mismatches"]: - validation_info.append(f" - {phrase}: found '{found}', expected '{expected}'") + validation_info.append( + f" - {phrase}: found '{found}', expected '{expected}'" + ) # File validation for Rx only run if output path isn't "/dev/null" or doesn't start with "/dev/null/" - if ( - self.direction == "Rx" - and self.output - and self.output_path - and not str(self.output_path).startswith("/dev/null") - ): + if self.direction == "Rx" and self.output and self.output_path and not str(self.output_path).startswith("/dev/null"): validation_info.append(f"\n=== {self.direction} Output File Validation ===") validation_info.append(f"Expected output file: {self.output}") - file_info, file_validation_passed = validate_file(self.host.connection, self.output, cleanup=False) + file_info, file_validation_passed = validate_file( + self.host.connection, self.output, cleanup=False + ) validation_info.extend(file_info) self.is_pass = self.is_pass and file_validation_passed # Save validation report to file if validation_info: validation_info.append(f"\n=== Overall Validation Summary ===") - overall_status = "PASS" if self.is_pass and file_validation_passed else "FAIL" + overall_status = ( + "PASS" if self.is_pass and file_validation_passed else "FAIL" + ) validation_info.append(f"Overall validation: {overall_status}") - validation_info.append(f"App log validation: {'PASS' if app_log_validation_status else 'FAIL'}") + validation_info.append( + f"App log validation: {'PASS' if app_log_validation_status else 'FAIL'}" + ) if self.direction == "Rx": file_status = "PASS" if file_validation_passed else "FAIL" validation_info.append(f"File validation: {file_status}") # Add note about overall validation logic if not self.is_pass or not file_validation_passed: - validation_info.append("Note: Overall validation fails if either app log or file validation fails") + validation_info.append( + "Note: Overall validation fails if either app log or file validation fails" + ) log_dir = self.log_path if self.log_path is not None else LOG_FOLDER subdir = f"RxTx/{self.host.name}" @@ -287,9 +306,13 @@ def __init__(self, *args, **kwargs): def start(self): super().start() - logger.debug(f"Starting Tx app with payload: {self.payload.payload_type} on {self.host}") + logger.debug( + f"Starting Tx app with payload: {self.payload.payload_type} on {self.host}" + ) cmd = self._get_app_cmd("Tx") - self.process = self.host.connection.start_process(cmd, shell=True, stderr_to_stdout=True, cwd=self.app_path) + self.process = self.host.connection.start_process( + cmd, shell=True, stderr_to_stdout=True, cwd=self.app_path + ) def log_output(): log_dir = self.log_path if self.log_path is not None else LOG_FOLDER @@ -327,15 +350,21 @@ def _generate_output_file_path(self): output_filename = f"rx_{self.host.name}_{input_path.stem}_{timestamp}{input_path.suffix}" # Use the configured output path or default - self.output = self.host.connection.path(self.output_path, output_filename) + self.output = self.host.connection.path( + self.output_path, output_filename + ) logger.debug(f"Generated output path: {self.output}") def start(self): super().start() - logger.debug(f"Starting Rx app with payload: {self.payload.payload_type} on {self.host}") + logger.debug( + f"Starting Rx app with payload: {self.payload.payload_type} on {self.host}" + ) cmd = self._get_app_cmd("Rx") - self.process = self.host.connection.start_process(cmd, shell=True, stderr_to_stdout=True, cwd=self.app_path) + self.process = self.host.connection.start_process( + cmd, shell=True, stderr_to_stdout=True, cwd=self.app_path + ) def log_output(): log_dir = self.log_path if self.log_path is not None else LOG_FOLDER diff --git a/tests/validation/Engine/rx_tx_app_file_validation_utils.py b/tests/validation/Engine/rx_tx_app_file_validation_utils.py index 1708ecf30..e7a3efb64 100644 --- a/tests/validation/Engine/rx_tx_app_file_validation_utils.py +++ b/tests/validation/Engine/rx_tx_app_file_validation_utils.py @@ -39,7 +39,9 @@ def validate_file(connection, file_path, cleanup=True): validation_info.append("File existence: PASS") # Execute a command to get the file size using ls - result = connection.execute_command(f"ls -l {file_path}", expected_return_codes=None) + result = connection.execute_command( + f"ls -l {file_path}", expected_return_codes=None + ) if result.return_code != 0: error_msg = f"Failed to retrieve file size for {file_path}." @@ -49,9 +51,13 @@ def validate_file(connection, file_path, cleanup=True): else: # Parse the output to get the file size file_info = result.stdout.strip().split() - file_size = int(file_info[4]) # The size is the 5th element in the split output + file_size = int( + file_info[4] + ) # The size is the 5th element in the split output - validation_info.append(f"File size: {file_size} bytes (checked via ls -l: {file_info})") + validation_info.append( + f"File size: {file_size} bytes (checked via ls -l: {file_info})" + ) if file_size == 0: error_msg = f"File size is 0: {file_path}" diff --git a/tests/validation/common/__init__.py b/tests/validation/common/__init__.py index 7d414388d..931d8daa9 100644 --- a/tests/validation/common/__init__.py +++ b/tests/validation/common/__init__.py @@ -10,10 +10,10 @@ from common.log_constants import RX_REQUIRED_LOG_PHRASES, TX_REQUIRED_LOG_PHRASES, RX_TX_APP_ERROR_KEYWORDS __all__ = [ - "check_phrases_in_order", - "validate_log_file", - "output_validator", - "RX_REQUIRED_LOG_PHRASES", - "TX_REQUIRED_LOG_PHRASES", - "RX_TX_APP_ERROR_KEYWORDS", + 'check_phrases_in_order', + 'validate_log_file', + 'output_validator', + 'RX_REQUIRED_LOG_PHRASES', + 'TX_REQUIRED_LOG_PHRASES', + 'RX_TX_APP_ERROR_KEYWORDS' ] diff --git a/tests/validation/common/ffmpeg_handler/__init__.py b/tests/validation/common/ffmpeg_handler/__init__.py index 14cfc7cc9..08bda312e 100644 --- a/tests/validation/common/ffmpeg_handler/__init__.py +++ b/tests/validation/common/ffmpeg_handler/__init__.py @@ -9,7 +9,11 @@ from common.ffmpeg_handler.log_constants import ( FFMPEG_RX_REQUIRED_LOG_PHRASES, FFMPEG_TX_REQUIRED_LOG_PHRASES, - FFMPEG_ERROR_KEYWORDS, + FFMPEG_ERROR_KEYWORDS ) -__all__ = ["FFMPEG_RX_REQUIRED_LOG_PHRASES", "FFMPEG_TX_REQUIRED_LOG_PHRASES", "FFMPEG_ERROR_KEYWORDS"] +__all__ = [ + 'FFMPEG_RX_REQUIRED_LOG_PHRASES', + 'FFMPEG_TX_REQUIRED_LOG_PHRASES', + 'FFMPEG_ERROR_KEYWORDS' +] diff --git a/tests/validation/common/ffmpeg_handler/ffmpeg.py b/tests/validation/common/ffmpeg_handler/ffmpeg.py index 627beadc3..bea8ea524 100644 --- a/tests/validation/common/ffmpeg_handler/ffmpeg.py +++ b/tests/validation/common/ffmpeg_handler/ffmpeg.py @@ -80,35 +80,37 @@ def __init__(self, host, ffmpeg_instance: FFmpeg, log_path=None): def validate(self): """ Validates the FFmpeg process execution and output. - + Performs two types of validation: 1. Log validation - checks for required phrases and error keywords 2. File validation - checks if the output file exists and has expected characteristics - + Generates validation report files. - + Returns: bool: True if validation passed, False otherwise """ process_passed = True validation_info = [] - + for process in self._processes: if process.return_code != 0: logger.warning(f"FFmpeg process on {self.host.name} failed with return code {process.return_code}") process_passed = False - + # Determine if this is a receiver or transmitter is_receiver = False if self.ff.ffmpeg_input and self.ff.ffmpeg_output: input_path = getattr(self.ff.ffmpeg_input, "input_path", None) output_path = getattr(self.ff.ffmpeg_output, "output_path", None) - - if input_path == "-" or (output_path and output_path != "-" and "." in output_path): + + if input_path == "-" or ( + output_path and output_path != "-" and "." in output_path + ): is_receiver = True - + direction = "Rx" if is_receiver else "Tx" - + # Find the log file log_dir = self.log_path if self.log_path else LOG_FOLDER subdir = f"RxTx/{self.host.name}" @@ -117,24 +119,29 @@ def validate(self): input_class_name = self.ff.ffmpeg_input.__class__.__name__ prefix = "mtl_" if input_class_name and "Mtl" in input_class_name else "mcm_" log_filename = prefix + ("ffmpeg_rx.log" if is_receiver else "ffmpeg_tx.log") - + log_file_path = os.path.join(log_dir, subdir, log_filename) - + # Perform log validation from common.log_validation_utils import validate_log_file from common.ffmpeg_handler.log_constants import ( - FFMPEG_RX_REQUIRED_LOG_PHRASES, + FFMPEG_RX_REQUIRED_LOG_PHRASES, FFMPEG_TX_REQUIRED_LOG_PHRASES, - FFMPEG_ERROR_KEYWORDS, + FFMPEG_ERROR_KEYWORDS ) - + required_phrases = FFMPEG_RX_REQUIRED_LOG_PHRASES if is_receiver else FFMPEG_TX_REQUIRED_LOG_PHRASES - + if os.path.exists(log_file_path): - validation_result = validate_log_file(log_file_path, required_phrases, direction, FFMPEG_ERROR_KEYWORDS) - - log_validation_passed = validation_result["is_pass"] - validation_info.extend(validation_result["validation_info"]) + validation_result = validate_log_file( + log_file_path, + required_phrases, + direction, + FFMPEG_ERROR_KEYWORDS + ) + + log_validation_passed = validation_result['is_pass'] + validation_info.extend(validation_result['validation_info']) else: logger.warning(f"Log file not found at {log_file_path}") validation_info.append(f"=== {direction} Log Validation ===") @@ -143,7 +150,7 @@ def validate(self): validation_info.append(f"Errors found: 1") validation_info.append(f"Missing log file") log_validation_passed = False - + # File validation for Rx only run if output path isn't "/dev/null" or doesn't start with "/dev/null/" file_validation_passed = True if is_receiver and self.ff.ffmpeg_output and hasattr(self.ff.ffmpeg_output, "output_path"): @@ -151,15 +158,16 @@ def validate(self): if output_path and not str(output_path).startswith("/dev/null"): validation_info.append(f"\n=== {direction} Output File Validation ===") validation_info.append(f"Expected output file: {output_path}") - + from Engine.rx_tx_app_file_validation_utils import validate_file - - file_info, file_validation_passed = validate_file(self.host.connection, output_path, cleanup=False) + file_info, file_validation_passed = validate_file( + self.host.connection, output_path, cleanup=False + ) validation_info.extend(file_info) - + # Overall validation status self.is_pass = process_passed and log_validation_passed and file_validation_passed - + # Save validation report validation_info.append(f"\n=== Overall Validation Summary ===") validation_info.append(f"Overall validation: {'PASS' if self.is_pass else 'FAIL'}") @@ -168,19 +176,20 @@ def validate(self): if is_receiver: validation_info.append(f"File validation: {'PASS' if file_validation_passed else 'FAIL'}") validation_info.append(f"Note: Overall validation fails if any validation step fails") - + # Save validation report to a file from common.log_validation_utils import save_validation_report - validation_path = os.path.join(log_dir, subdir, f"{direction.lower()}_validation.log") save_validation_report(validation_path, validation_info, self.is_pass) - + return self.is_pass def start(self): """Starts the FFmpeg process on the host, waits for the process to start.""" cmd = self.ff.get_command() - ffmpeg_process = self.host.connection.start_process(cmd, stderr_to_stdout=True, shell=True) + ffmpeg_process = self.host.connection.start_process( + cmd, stderr_to_stdout=True, shell=True + ) self._processes.append(ffmpeg_process) CURRENT_RETRIES = RETRIES retries_counter = 0 @@ -189,9 +198,13 @@ def start(self): time.sleep(SLEEP_BETWEEN_CHECKS) # FIXME: Find a better way to check if the process is running; code below throws an error when the process is actually running sometimes if ffmpeg_process.running: - logger.info(f"FFmpeg process started on {self.host.name} with command: {cmd}") + logger.info( + f"FFmpeg process started on {self.host.name} with command: {cmd}" + ) else: - logger.debug(f"FFmpeg process failed to start on {self.host.name} after {CURRENT_RETRIES} retries.") + logger.debug( + f"FFmpeg process failed to start on {self.host.name} after {CURRENT_RETRIES} retries." + ) log_dir = self.log_path if self.log_path else LOG_FOLDER subdir = f"RxTx/{self.host.name}" @@ -202,7 +215,9 @@ def start(self): input_path = getattr(self.ff.ffmpeg_input, "input_path", None) output_path = getattr(self.ff.ffmpeg_output, "output_path", None) - if input_path == "-" or (output_path and output_path != "-" and "." in output_path): + if input_path == "-" or ( + output_path and output_path != "-" and "." in output_path + ): is_receiver = True filename = "ffmpeg_rx.log" if is_receiver else "ffmpeg_tx.log" @@ -243,15 +258,25 @@ def stop(self, wait: float = 0.0) -> float: if process.running: process.kill() except SSHRemoteProcessEndException as e: - logger.warning(f"FFmpeg process on {self.host.name} was already stopped: {e}") + logger.warning( + f"FFmpeg process on {self.host.name} was already stopped: {e}" + ) except RemoteProcessInvalidState as e: - logger.warning(f"FFmpeg process on {self.host.name} is in an invalid state: {e}") + logger.warning( + f"FFmpeg process on {self.host.name} is in an invalid state: {e}" + ) except Exception as e: - logger.error(f"Error while stopping FFmpeg process on {self.host.name}: {e}") + logger.error( + f"Error while stopping FFmpeg process on {self.host.name}: {e}" + ) raise e - logger.debug(f">>> FFmpeg execution on '{self.host.name}' host returned:\n{process.stdout_text}") + logger.debug( + f">>> FFmpeg execution on '{self.host.name}' host returned:\n{process.stdout_text}" + ) if process.return_code != 0: - logger.warning(f"FFmpeg process on {self.host.name} return code is {process.return_code}") + logger.warning( + f"FFmpeg process on {self.host.name} return code is {process.return_code}" + ) # assert process.return_code == 0 # Sometimes a different return code is returned for a graceful stop, so we do not assert it here else: logger.info("No FFmpeg process to stop!") @@ -273,21 +298,18 @@ def wait_with_timeout(self, timeout=None): def cleanup(self): """Clean up any resources or output files.""" - if ( - self.ff.ffmpeg_output - and hasattr(self.ff.ffmpeg_output, "output_path") - and self.ff.ffmpeg_output.output_path - and self.ff.ffmpeg_output.output_path != "-" - and not str(self.ff.ffmpeg_output.output_path).startswith("/dev/null") - ): - + if (self.ff.ffmpeg_output and + hasattr(self.ff.ffmpeg_output, "output_path") and + self.ff.ffmpeg_output.output_path and + self.ff.ffmpeg_output.output_path != "-" and + not str(self.ff.ffmpeg_output.output_path).startswith("/dev/null")): + success = cleanup_file(self.host.connection, str(self.ff.ffmpeg_output.output_path)) if success: logger.debug(f"Cleaned up output file: {self.ff.ffmpeg_output.output_path}") else: logger.warning(f"Failed to clean up output file: {self.ff.ffmpeg_output.output_path}") - def no_proxy_to_prefix_variables(host, prefix_variables: dict | None = None): """ Handles the no_proxy and NO_PROXY environment variables for FFmpeg execution. @@ -308,7 +330,9 @@ def no_proxy_to_prefix_variables(host, prefix_variables: dict | None = None): prefix_variables = prefix_variables if prefix_variables else {} try: if "no_proxy" not in prefix_variables.keys(): - prefix_variables["no_proxy"] = host.topology.extra_info.media_proxy.get("no_proxy") + prefix_variables["no_proxy"] = host.topology.extra_info.media_proxy.get( + "no_proxy" + ) if "NO_PROXY" not in prefix_variables.keys(): prefix_variables["NO_PROXY"] = host.topology.extra_info.media_proxy.get( "no_proxy" diff --git a/tests/validation/common/ffmpeg_handler/ffmpeg_enums.py b/tests/validation/common/ffmpeg_handler/ffmpeg_enums.py index 0799659eb..dfc50b788 100644 --- a/tests/validation/common/ffmpeg_handler/ffmpeg_enums.py +++ b/tests/validation/common/ffmpeg_handler/ffmpeg_enums.py @@ -79,7 +79,9 @@ def audio_file_format_to_format_dict(audio_format: str) -> dict: elif audio_format == "pcm_s24be": return matching_audio_formats.get(FFmpegAudioFormat.pcm24, {}) elif audio_format == "pcm_s8": - raise Exception(f"PCM 8 is not supported by Media Communications Mesh FFmpeg plugin!") + raise Exception( + f"PCM 8 is not supported by Media Communications Mesh FFmpeg plugin!" + ) else: raise Exception(f"Not expected audio format {audio_format}") diff --git a/tests/validation/common/ffmpeg_handler/log_constants.py b/tests/validation/common/ffmpeg_handler/log_constants.py index 48a423823..b589e16f2 100644 --- a/tests/validation/common/ffmpeg_handler/log_constants.py +++ b/tests/validation/common/ffmpeg_handler/log_constants.py @@ -8,23 +8,32 @@ # Required ordered log phrases for FFmpeg Rx validation FFMPEG_RX_REQUIRED_LOG_PHRASES = [ - "[DEBU] JSON client config:", - "[INFO] Media Communications Mesh SDK version", - "[DEBU] JSON conn config:", - "[INFO] gRPC: connection created", - "INFO - Create memif socket.", - "INFO - Create memif interface.", - "INFO - memif connected!", - "[INFO] gRPC: connection active", + '[DEBU] JSON client config:', + '[INFO] Media Communications Mesh SDK version', + '[DEBU] JSON conn config:', + '[INFO] gRPC: connection created', + 'INFO - Create memif socket.', + 'INFO - Create memif interface.', + 'INFO - memif connected!', + '[INFO] gRPC: connection active' ] # Required ordered log phrases for FFmpeg Tx validation FFMPEG_TX_REQUIRED_LOG_PHRASES = [ - "[DEBU] JSON client config:", - "[INFO] Media Communications Mesh SDK version", - "[DEBU] JSON conn config:", - "[DEBU] BUF PARTS", + '[DEBU] JSON client config:', + '[INFO] Media Communications Mesh SDK version', + '[DEBU] JSON conn config:', + '[DEBU] BUF PARTS' ] # Common error keywords to look for in logs -FFMPEG_ERROR_KEYWORDS = ["ERROR", "FATAL", "exception", "segfault", "core dumped", "failed", "FAIL", "[error]"] +FFMPEG_ERROR_KEYWORDS = [ + "ERROR", + "FATAL", + "exception", + "segfault", + "core dumped", + "failed", + "FAIL", + "[error]" +] diff --git a/tests/validation/common/ffmpeg_handler/mcm_ffmpeg.py b/tests/validation/common/ffmpeg_handler/mcm_ffmpeg.py index 33015b21a..b93dde947 100644 --- a/tests/validation/common/ffmpeg_handler/mcm_ffmpeg.py +++ b/tests/validation/common/ffmpeg_handler/mcm_ffmpeg.py @@ -89,7 +89,9 @@ def __init__( self.payload_type = payload_type self.channels = channels if sample_rate and ptime not in matching_sample_rates[sample_rate]: - raise Exception(f"Sample rate {sample_rate} Hz does not work with {ptime} packet time (ptime).") + raise Exception( + f"Sample rate {sample_rate} Hz does not work with {ptime} packet time (ptime)." + ) self.sample_rate = sample_rate self.ptime = ptime self.f = f diff --git a/tests/validation/common/integrity/blob_integrity.py b/tests/validation/common/integrity/blob_integrity.py index ecb1a6c5c..e14a4cc59 100644 --- a/tests/validation/common/integrity/blob_integrity.py +++ b/tests/validation/common/integrity/blob_integrity.py @@ -12,8 +12,12 @@ DEFAULT_CHUNK_SIZE = 1024 * 1024 # 1MB chunks for processing -SEGMENT_DURATION_GRACE_PERIOD = 1 # Grace period in seconds to allow for late-arriving files -FILE_HASH_CHUNK_SIZE = 4096 # 4KB is optimal for file hashing (balances memory usage and disk I/O) +SEGMENT_DURATION_GRACE_PERIOD = ( + 1 # Grace period in seconds to allow for late-arriving files +) +FILE_HASH_CHUNK_SIZE = ( + 4096 # 4KB is optimal for file hashing (balances memory usage and disk I/O) +) def calculate_chunk_hashes(file_url: str, chunk_size: int = DEFAULT_CHUNK_SIZE) -> list: @@ -23,7 +27,9 @@ def calculate_chunk_hashes(file_url: str, chunk_size: int = DEFAULT_CHUNK_SIZE) chunk_index = 0 while chunk := f.read(chunk_size): if len(chunk) != chunk_size: - logging.debug(f"CHUNK SIZE MISMATCH at index {chunk_index}: {len(chunk)} != {chunk_size}") + logging.debug( + f"CHUNK SIZE MISMATCH at index {chunk_index}: {len(chunk)} != {chunk_size}" + ) chunk_sum = hashlib.md5(chunk).hexdigest() chunk_sums.append(chunk_sum) chunk_index += 1 @@ -87,13 +93,16 @@ def check_integrity_file(self, out_url) -> bool: no_src_chunks = len(self.src_chunk_sums) if no_out_chunks != no_src_chunks: self.logger.error( - f"Chunk count mismatch: source has {no_src_chunks} chunks, " f"output has {no_out_chunks} chunks" + f"Chunk count mismatch: source has {no_src_chunks} chunks, " + f"output has {no_out_chunks} chunks" ) bad_chunks += abs(no_out_chunks - no_src_chunks) if bad_chunks: self.bad_chunks_total += bad_chunks - self.logger.error(f"Received {bad_chunks} bad chunks out of {no_out_chunks} total chunks.") + self.logger.error( + f"Received {bad_chunks} bad chunks out of {no_out_chunks} total chunks." + ) return False self.logger.info(f"All {no_out_chunks} chunks in {out_url} are correct.") @@ -132,20 +141,26 @@ def __init__( delete_file: bool = True, ): super().__init__(logger, src_url, out_name, chunk_size, out_path, delete_file) - self.logger.info(f"Output path {out_path}, segment duration: {segment_duration}") + self.logger.info( + f"Output path {out_path}, segment duration: {segment_duration}" + ) self.segment_duration = segment_duration self.workers_count = workers_count def get_out_file(self, request_queue): """Monitor output path for new files and add them to processing queue.""" - self.logger.info(f"Waiting for output files from {self.out_path} with prefix {self.out_name}") + self.logger.info( + f"Waiting for output files from {self.out_path} with prefix {self.out_name}" + ) start = 0 waiting_for_files = True list_processed = [] out_files = [] # Add a grace period to segment duration to allow for late-arriving files - while (self.segment_duration + SEGMENT_DURATION_GRACE_PERIOD > start) or waiting_for_files: + while ( + self.segment_duration + SEGMENT_DURATION_GRACE_PERIOD > start + ) or waiting_for_files: gb = list(Path(self.out_path).glob(f"{self.out_name}*")) out_files = list(filter(lambda x: x not in list_processed, gb)) self.logger.debug(f"Received files: {out_files}") @@ -171,7 +186,9 @@ def start_workers(self, result_queue, request_queue, shared): """Create and start worker processes.""" processes = [] for number in range(self.workers_count): - p = multiprocessing.Process(target=self.worker, args=(number, request_queue, result_queue, shared)) + p = multiprocessing.Process( + target=self.worker, args=(number, request_queue, result_queue, shared) + ) p.start() processes.append(p) return processes @@ -183,7 +200,9 @@ def check_blob_integrity(self) -> bool: result_queue = multiprocessing.Queue() request_queue = multiprocessing.Queue() workers = self.start_workers(result_queue, request_queue, shared_data) - output_worker = multiprocessing.Process(target=self.get_out_file, args=(request_queue,)) + output_worker = multiprocessing.Process( + target=self.get_out_file, args=(request_queue,) + ) output_worker.start() results = [] while output_worker.is_alive(): @@ -220,7 +239,9 @@ def get_out_file(self): return gb[0] except IndexError: self.logger.error(f"File {self.out_name} not found!") - raise FileNotFoundError(f"File {self.out_name} not found in {self.out_path}") + raise FileNotFoundError( + f"File {self.out_name} not found in {self.out_path}" + ) def check_blob_integrity(self) -> bool: """Check integrity of a single blob file.""" @@ -255,7 +276,9 @@ def main(): formatter_class=argparse.RawDescriptionHelpFormatter, ) - subparsers = parser.add_subparsers(dest="mode", help="Mode of operation: stream or file") + subparsers = parser.add_subparsers( + dest="mode", help="Mode of operation: stream or file" + ) # Common arguments function to avoid repetition def add_common_arguments(parser): diff --git a/tests/validation/common/integrity/integrity_runner.py b/tests/validation/common/integrity/integrity_runner.py index 22bc06926..c4e652f7b 100644 --- a/tests/validation/common/integrity/integrity_runner.py +++ b/tests/validation/common/integrity/integrity_runner.py @@ -42,16 +42,27 @@ def get_path(self, integrity_path): """ if integrity_path: return str(self.host.connection.path(integrity_path, self.module_name)) - return str(self.host.connection.path(self.test_repo_path, "tests", "common", "integrity", self.module_name)) + return str( + self.host.connection.path( + self.test_repo_path, "tests", "common", "integrity", self.module_name + ) + ) def setup(self): """ Setup method to prepare the environment for running the integrity check. This can include creating directories, checking dependencies, etc. """ - logger.info(f"Setting up integrity check on {self.host.name} for {self.out_name}") - self.host.connection.execute_command(f"apt install tesseract-ocr -y", shell=True) - reqs = str(self.host.connection.path(self.integrity_path).parents[0] / "requirements.txt") + logger.info( + f"Setting up integrity check on {self.host.name} for {self.out_name}" + ) + self.host.connection.execute_command( + f"apt install tesseract-ocr -y", shell=True + ) + reqs = str( + self.host.connection.path(self.integrity_path).parents[0] + / "requirements.txt" + ) for library in ["pytesseract", "opencv-python"]: cmd = f"{self.python_path} -m pip list | grep {library} || {self.python_path} -m pip install -r {reqs}" self.host.connection.execute_command(cmd, shell=True) @@ -99,7 +110,9 @@ def run(self): "--delete_file" if self.delete_file else "--no_delete_file", ] ) - logger.debug(f"Running integrity check on {self.host.name} for {self.out_name} with command: {cmd}") + logger.debug( + f"Running integrity check on {self.host.name} for {self.out_name} with command: {cmd}" + ) result = self.host.connection.execute_command( cmd, shell=True, stderr_to_stdout=True, expected_return_codes=(0, 1) ) @@ -107,7 +120,9 @@ def run(self): logger.error(f"Integrity check failed on {self.host.name}: {self.out_name}") logger.error(result.stdout) return False - logger.info(f"Integrity check completed successfully on {self.host.name} for {self.out_name}") + logger.info( + f"Integrity check completed successfully on {self.host.name} for {self.out_name}" + ) return True @@ -162,15 +177,23 @@ def run(self): str(self.workers), ] ) - logger.debug(f"Running stream integrity check on {self.host.name} for {self.out_name} with command: {cmd}") - self.process = self.host.connection.start_process(cmd, shell=True, stderr_to_stdout=True) + logger.debug( + f"Running stream integrity check on {self.host.name} for {self.out_name} with command: {cmd}" + ) + self.process = self.host.connection.start_process( + cmd, shell=True, stderr_to_stdout=True + ) def stop(self, timeout: int = 10): if self.process: self.process.wait(timeout) - logger.info(f"Stream integrity check stopped on {self.host.name} for {self.out_name}") + logger.info( + f"Stream integrity check stopped on {self.host.name} for {self.out_name}" + ) else: - logger.warning(f"No active process to stop for {self.out_name} on {self.host.name}") + logger.warning( + f"No active process to stop for {self.out_name} on {self.host.name}" + ) def stop_and_verify(self, timeout: int = 10): """ @@ -178,10 +201,14 @@ def stop_and_verify(self, timeout: int = 10): """ self.stop(timeout) if self.process and self.process.return_code != 0: - logger.error(f"Stream integrity check failed on {self.host.name} for {self.out_name}") + logger.error( + f"Stream integrity check failed on {self.host.name} for {self.out_name}" + ) logger.error(f"Process output: {self.process.stdout_text}") return False - logger.info(f"Stream integrity check completed successfully on {self.host.name} for {self.out_name}") + logger.info( + f"Stream integrity check completed successfully on {self.host.name} for {self.out_name}" + ) return True @@ -217,14 +244,20 @@ def get_path(self, integrity_path): """ if integrity_path: return str(self.host.connection.path(integrity_path, self.module_name)) - return str(self.host.connection.path(self.test_repo_path, "tests", "common", "integrity", self.module_name)) + return str( + self.host.connection.path( + self.test_repo_path, "tests", "common", "integrity", self.module_name + ) + ) def setup(self): """ Setup method to prepare the environment for running the blob integrity check. This is simpler than video integrity as it doesn't require OCR dependencies. """ - logger.info(f"Setting up blob integrity check on {self.host.name} for {self.out_name}") + logger.info( + f"Setting up blob integrity check on {self.host.name} for {self.out_name}" + ) # Blob integrity doesn't need special dependencies like tesseract or opencv @@ -268,15 +301,21 @@ def run(self): "--delete_file" if self.delete_file else "--no_delete_file", ] ) - logger.debug(f"Running blob integrity check on {self.host.name} for {self.out_name} with command: {cmd}") + logger.debug( + f"Running blob integrity check on {self.host.name} for {self.out_name} with command: {cmd}" + ) result = self.host.connection.execute_command( cmd, shell=True, stderr_to_stdout=True, expected_return_codes=(0, 1) ) if result.return_code > 0: - logger.error(f"Blob integrity check failed on {self.host.name}: {self.out_name}") + logger.error( + f"Blob integrity check failed on {self.host.name}: {self.out_name}" + ) logger.error(result.stdout) return False - logger.info(f"Blob integrity check completed successfully on {self.host.name} for {self.out_name}") + logger.info( + f"Blob integrity check completed successfully on {self.host.name} for {self.out_name}" + ) return True @@ -329,15 +368,23 @@ def run(self): str(self.workers), ] ) - logger.debug(f"Running stream blob integrity check on {self.host.name} for {self.out_name} with command: {cmd}") - self.process = self.host.connection.start_process(cmd, shell=True, stderr_to_stdout=True) + logger.debug( + f"Running stream blob integrity check on {self.host.name} for {self.out_name} with command: {cmd}" + ) + self.process = self.host.connection.start_process( + cmd, shell=True, stderr_to_stdout=True + ) def stop(self, timeout: int = 10): if self.process: self.process.wait(timeout) - logger.info(f"Stream blob integrity check stopped on {self.host.name} for {self.out_name}") + logger.info( + f"Stream blob integrity check stopped on {self.host.name} for {self.out_name}" + ) else: - logger.warning(f"No active process to stop for {self.out_name} on {self.host.name}") + logger.warning( + f"No active process to stop for {self.out_name} on {self.host.name}" + ) def stop_and_verify(self, timeout: int = 10): """ @@ -345,8 +392,12 @@ def stop_and_verify(self, timeout: int = 10): """ self.stop(timeout) if self.process and self.process.return_code != 0: - logger.error(f"Stream blob integrity check failed on {self.host.name} for {self.out_name}") + logger.error( + f"Stream blob integrity check failed on {self.host.name} for {self.out_name}" + ) logger.error(f"Process output: {self.process.stdout_text}") return False - logger.info(f"Stream blob integrity check completed successfully on {self.host.name} for {self.out_name}") + logger.info( + f"Stream blob integrity check completed successfully on {self.host.name} for {self.out_name}" + ) return True diff --git a/tests/validation/common/integrity/test_blob_integrity.py b/tests/validation/common/integrity/test_blob_integrity.py index e8ec21f1e..c506915d4 100644 --- a/tests/validation/common/integrity/test_blob_integrity.py +++ b/tests/validation/common/integrity/test_blob_integrity.py @@ -103,7 +103,9 @@ def test_corrupted_file_integrity(): ) result = integrator.check_blob_integrity() - print(f"Corrupted file integrity check result: {'PASSED (UNEXPECTED!)' if result else 'FAILED (EXPECTED)'}") + print( + f"Corrupted file integrity check result: {'PASSED (UNEXPECTED!)' if result else 'FAILED (EXPECTED)'}" + ) return not result # We expect this to fail, so return True if it failed diff --git a/tests/validation/common/integrity/video_integrity.py b/tests/validation/common/integrity/video_integrity.py index fec1ce6ac..83fd1d468 100644 --- a/tests/validation/common/integrity/video_integrity.py +++ b/tests/validation/common/integrity/video_integrity.py @@ -30,7 +30,9 @@ def calculate_chunk_hashes(file_url: str, chunk_size: int) -> list: chunk_index = 0 while chunk := f.read(chunk_size): if len(chunk) != chunk_size: - logging.debug(f"CHUNK SIZE MISMATCH at index {chunk_index}: {len(chunk)} != {chunk_size}") + logging.debug( + f"CHUNK SIZE MISMATCH at index {chunk_index}: {len(chunk)} != {chunk_size}" + ) chunk_sum = hashlib.md5(chunk).hexdigest() chunk_sums.append(chunk_sum) chunk_index += 1 @@ -112,7 +114,9 @@ def shift_src_chunk_by_first_frame_no(self, out_file): for _ in range(self.shift - STARTING_FRAME): self.src_chunk_sums.append(self.src_chunk_sums.pop(0)) else: - raise ValueError(f"No match found in the extracted text from first frame of {self.src_url}") + raise ValueError( + f"No match found in the extracted text from first frame of {self.src_url}" + ) def check_integrity_file(self, out_url) -> bool: out_chunk_sums = calculate_chunk_hashes(out_url, self.frame_size) @@ -125,11 +129,15 @@ def check_integrity_file(self, out_url) -> bool: f"Chunk: {chunk_sum} received in position: {ids}, expected in {self.src_chunk_sums.index(chunk_sum)}" ) else: - self.logger.error(f"Chunk: {chunk_sum} with ID: {ids} not found in src chunks checksums!") + self.logger.error( + f"Chunk: {chunk_sum} with ID: {ids} not found in src chunks checksums!" + ) bad_frames += 1 if bad_frames: self.bad_frames_total += bad_frames - self.logger.error(f"Received {bad_frames} bad frames out of {len(out_chunk_sums)} captured.") + self.logger.error( + f"Received {bad_frames} bad frames out of {len(out_chunk_sums)} captured." + ) return False self.logger.info(f"All {len(out_chunk_sums)} frames in {out_url} are correct.") return True @@ -168,13 +176,19 @@ def __init__( workers_count=5, delete_file: bool = True, ): - super().__init__(logger, src_url, out_name, resolution, file_format, out_path, delete_file) - self.logger.info(f"Output path {out_path}, segment duration: {segment_duration}") + super().__init__( + logger, src_url, out_name, resolution, file_format, out_path, delete_file + ) + self.logger.info( + f"Output path {out_path}, segment duration: {segment_duration}" + ) self.segment_duration = segment_duration self.workers_count = workers_count def get_out_file(self, request_queue): - self.logger.info(f"Waiting for output files from {self.out_path} with prefix {self.out_name}") + self.logger.info( + f"Waiting for output files from {self.out_path} with prefix {self.out_name}" + ) start = 0 waiting_for_files = True list_processed = [] @@ -226,7 +240,9 @@ def start_workers(self, result_queue, request_queue, shared): # Create and start a separate process for each task processes = [] for number in range(self.workers_count): - p = multiprocessing.Process(target=self.worker, args=(number, request_queue, result_queue, shared)) + p = multiprocessing.Process( + target=self.worker, args=(number, request_queue, result_queue, shared) + ) p.start() processes.append(p) return processes @@ -237,7 +253,9 @@ def check_st20p_integrity(self) -> bool: result_queue = multiprocessing.Queue() request_queue = multiprocessing.Queue() workers = self.start_workers(result_queue, request_queue, shared_data) - output_worker = multiprocessing.Process(target=self.get_out_file, args=(request_queue,)) + output_worker = multiprocessing.Process( + target=self.get_out_file, args=(request_queue,) + ) output_worker.start() results = [] while output_worker.is_alive(): @@ -263,7 +281,9 @@ def __init__( out_path: str = "/mnt/ramdisk", delete_file: bool = True, ): - super().__init__(logger, src_url, out_name, resolution, file_format, out_path, delete_file) + super().__init__( + logger, src_url, out_name, resolution, file_format, out_path, delete_file + ) self.logger = logging.getLogger(__name__) def get_out_file(self): @@ -272,7 +292,9 @@ def get_out_file(self): return gb[0] except IndexError: self.logger.error(f"File {self.out_name} not found!") - raise FileNotFoundError(f"File {self.out_name} not found in {self.out_path}") + raise FileNotFoundError( + f"File {self.out_name} not found in {self.out_path}" + ) def check_st20p_integrity(self) -> bool: output_file = self.get_out_file() @@ -307,14 +329,20 @@ def main(): formatter_class=argparse.RawDescriptionHelpFormatter, ) - subparsers = parser.add_subparsers(dest="mode", help="Mode of operation: stream or file") + subparsers = parser.add_subparsers( + dest="mode", help="Mode of operation: stream or file" + ) # Common arguments function to avoid repetition def add_common_arguments(parser): parser.add_argument("src", type=str, help="Path to the source video file") parser.add_argument("out", type=str, help="Name/prefix of the output file") - parser.add_argument("res", type=str, help="Resolution of the video (e.g., 1920x1080)") - parser.add_argument("fmt", type=str, help="Format of the video file (e.g., yuv422p10le)") + parser.add_argument( + "res", type=str, help="Resolution of the video (e.g., 1920x1080)" + ) + parser.add_argument( + "fmt", type=str, help="Format of the video file (e.g., yuv422p10le)" + ) parser.add_argument( "--output_path", type=str, diff --git a/tests/validation/common/log_constants.py b/tests/validation/common/log_constants.py index 78d75b07b..1f4d64765 100644 --- a/tests/validation/common/log_constants.py +++ b/tests/validation/common/log_constants.py @@ -8,29 +8,37 @@ # Required ordered log phrases for Rx validation RX_REQUIRED_LOG_PHRASES = [ - "[RX] Reading client configuration", - "[RX] Reading connection configuration", - "[DEBU] JSON client config:", - "[INFO] Media Communications Mesh SDK version", - "[DEBU] JSON conn config:", - "[RX] Fetched mesh data buffer", - "[RX] Saving buffer data to a file", - "[RX] Done reading the data", - "[RX] dropping connection to media-proxy", - "INFO - memif disconnected!", + '[RX] Reading client configuration', + '[RX] Reading connection configuration', + '[DEBU] JSON client config:', + '[INFO] Media Communications Mesh SDK version', + '[DEBU] JSON conn config:', + '[RX] Fetched mesh data buffer', + '[RX] Saving buffer data to a file', + '[RX] Done reading the data', + '[RX] dropping connection to media-proxy', + 'INFO - memif disconnected!', ] # Required ordered log phrases for Tx validation TX_REQUIRED_LOG_PHRASES = [ - "[TX] Reading client configuration", - "[TX] Reading connection configuration", - "[DEBU] JSON client config:", - "[INFO] Media Communications Mesh SDK version", - "[DEBU] JSON conn config:", - "[INFO] gRPC: connection created", - "INFO - Create memif socket.", - "INFO - Create memif interface.", + '[TX] Reading client configuration', + '[TX] Reading connection configuration', + '[DEBU] JSON client config:', + '[INFO] Media Communications Mesh SDK version', + '[DEBU] JSON conn config:', + '[INFO] gRPC: connection created', + 'INFO - Create memif socket.', + 'INFO - Create memif interface.', ] # Common error keywords to look for in logs -RX_TX_APP_ERROR_KEYWORDS = ["ERROR", "FATAL", "exception", "segfault", "core dumped", "failed", "FAIL"] +RX_TX_APP_ERROR_KEYWORDS = [ + "ERROR", + "FATAL", + "exception", + "segfault", + "core dumped", + "failed", + "FAIL" +] diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py index 41838f708..84a81965a 100644 --- a/tests/validation/common/log_validation_utils.py +++ b/tests/validation/common/log_validation_utils.py @@ -13,7 +13,6 @@ logger = logging.getLogger(__name__) - def check_phrases_in_order(log_path: str, phrases: List[str]) -> Tuple[bool, List[str], Dict[str, List[str]]]: """ Check that all required phrases appear in order in the log file. @@ -22,7 +21,7 @@ def check_phrases_in_order(log_path: str, phrases: List[str]) -> Tuple[bool, Lis found_indices = [] missing_phrases = [] lines_around_missing = {} - with open(log_path, "r", encoding="utf-8", errors="ignore") as f: + with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: lines = [line.strip() for line in f] idx = 0 @@ -49,56 +48,57 @@ def check_phrases_in_order(log_path: str, phrases: List[str]) -> Tuple[bool, Lis return len(missing_phrases) == 0, missing_phrases, lines_around_missing - def check_for_errors(log_path: str, error_keywords: Optional[List[str]] = None) -> Tuple[bool, List[Dict[str, Any]]]: """ Check the log file for error keywords. - + Args: log_path: Path to the log file error_keywords: List of keywords indicating errors (default: RX_TX_APP_ERROR_KEYWORDS) - + Returns: Tuple of (is_pass, errors_found) errors_found is a list of dicts with 'line', 'line_number', and 'keyword' keys """ if error_keywords is None: error_keywords = RX_TX_APP_ERROR_KEYWORDS - + errors = [] if not os.path.exists(log_path): logger.error(f"Log file not found: {log_path}") return False, [{"line": "Log file not found", "line_number": 0, "keyword": "FILE_NOT_FOUND"}] - + try: - with open(log_path, "r", encoding="utf-8", errors="ignore") as f: + with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: for i, line in enumerate(f): for keyword in error_keywords: if keyword.lower() in line.lower(): # Ignore certain false positives if "ERROR" in keyword and ("NO ERROR" in line.upper() or "NO_ERROR" in line.upper()): continue - errors.append({"line": line.strip(), "line_number": i + 1, "keyword": keyword}) + errors.append({ + "line": line.strip(), + "line_number": i + 1, + "keyword": keyword + }) break except Exception as e: logger.error(f"Error reading log file: {e}") return False, [{"line": f"Error reading log file: {e}", "line_number": 0, "keyword": "FILE_READ_ERROR"}] - + return len(errors) == 0, errors - -def validate_log_file( - log_file_path: str, required_phrases: List[str], direction: str = "", error_keywords: Optional[List[str]] = None -) -> Dict: +def validate_log_file(log_file_path: str, required_phrases: List[str], direction: str = "", + error_keywords: Optional[List[str]] = None) -> Dict: """ Validate log file for required phrases and return validation information. - + Args: log_file_path: Path to the log file required_phrases: List of phrases to check for in order direction: Optional string to identify the direction (e.g., 'Rx', 'Tx') error_keywords: Optional list of error keywords to check for - + Returns: Dictionary containing validation results: { @@ -111,33 +111,33 @@ def validate_log_file( } """ validation_info = [] - + # Phrase order validation log_pass, missing, context_lines = check_phrases_in_order(log_file_path, required_phrases) error_count = len(missing) - + # Error keyword validation (optional) error_check_pass = True errors = [] if error_keywords is not None: error_check_pass, errors = check_for_errors(log_file_path, error_keywords) error_count += len(errors) - + # Overall pass/fail status is_pass = log_pass and error_check_pass - + # Build validation info dir_prefix = f"{direction} " if direction else "" validation_info.append(f"=== {dir_prefix}Log Validation ===") validation_info.append(f"Log file: {log_file_path}") validation_info.append(f"Validation result: {'PASS' if is_pass else 'FAIL'}") validation_info.append(f"Total errors found: {error_count}") - + # Missing phrases info if not log_pass: validation_info.append(f"Missing or out-of-order phrases analysis:") for phrase in missing: - validation_info.append(f'\n Expected phrase: "{phrase}"') + validation_info.append(f"\n Expected phrase: \"{phrase}\"") validation_info.append(f" Context in log file:") if phrase in context_lines: for line in context_lines[phrase]: @@ -146,27 +146,26 @@ def validate_log_file( validation_info.append(" ") if missing: logger.warning(f"{dir_prefix}process did not pass. First missing phrase: {missing[0]}") - + # Error keywords info if errors: validation_info.append(f"\nError keywords found:") for error in errors: validation_info.append(f" Line {error['line_number']} - {error['keyword']}: {error['line']}") - + return { "is_pass": is_pass, "error_count": error_count, "validation_info": validation_info, "missing_phrases": missing, "context_lines": context_lines, - "errors": errors, + "errors": errors } - def save_validation_report(report_path: str, validation_info: List[str], overall_status: bool) -> None: """ Save validation report to a file. - + Args: report_path: Path where to save the report validation_info: List of validation information strings @@ -174,7 +173,7 @@ def save_validation_report(report_path: str, validation_info: List[str], overall """ try: os.makedirs(os.path.dirname(report_path), exist_ok=True) - with open(report_path, "w", encoding="utf-8") as f: + with open(report_path, 'w', encoding='utf-8') as f: for line in validation_info: f.write(f"{line}\n") f.write(f"\n=== Overall Validation Summary ===\n") @@ -183,15 +182,14 @@ def save_validation_report(report_path: str, validation_info: List[str], overall except Exception as e: logger.error(f"Error saving validation report: {e}") - def output_validator(log_file_path: str, error_keywords: Optional[List[str]] = None) -> Dict[str, Any]: """ Simple validator that checks for error keywords in a log file. - + Args: log_file_path: Path to the log file error_keywords: List of keywords indicating errors - + Returns: Dictionary with validation results: { @@ -201,5 +199,9 @@ def output_validator(log_file_path: str, error_keywords: Optional[List[str]] = N } """ is_pass, errors = check_for_errors(log_file_path, error_keywords) - - return {"is_pass": is_pass, "errors": errors, "phrase_mismatches": []} # Not used in this simple validator + + return { + "is_pass": is_pass, + "errors": errors, + "phrase_mismatches": [] # Not used in this simple validator + } diff --git a/tests/validation/common/nicctl.py b/tests/validation/common/nicctl.py index 0c3621ffb..3fe3c0e00 100644 --- a/tests/validation/common/nicctl.py +++ b/tests/validation/common/nicctl.py @@ -31,7 +31,9 @@ def _parse_vf_list(self, output: str, all: bool = True) -> list: def vfio_list(self, pci_addr: str = "all") -> list: """Returns list of VFs created on host.""" - resp = self.connection.execute_command(f"{self.nicctl} list {pci_addr}", shell=True) + resp = self.connection.execute_command( + f"{self.nicctl} list {pci_addr}", shell=True + ) return self._parse_vf_list(resp.stdout, "all" in pci_addr) def create_vfs(self, pci_id: str, num_of_vfs: int = 6) -> list: @@ -40,14 +42,18 @@ def create_vfs(self, pci_id: str, num_of_vfs: int = 6) -> list: :param num_of_vfs: number of VFs to create :return: returns list of created vfs """ - resp = self.connection.execute_command(f"{self.nicctl} create_vf {pci_id} {num_of_vfs}", shell=True) + resp = self.connection.execute_command( + f"{self.nicctl} create_vf {pci_id} {num_of_vfs}", shell=True + ) return self._parse_vf_list(resp.stdout) def disable_vf(self, pci_id: str) -> None: """Remove VFs on NIC. :param pci_id: pci_id of the nic adapter """ - self.connection.execute_command(self.nicctl + " disable_vf " + pci_id, shell=True) + self.connection.execute_command( + self.nicctl + " disable_vf " + pci_id, shell=True + ) def prepare_vfs_for_test(self, nic: NetworkInterface) -> list: """Prepare VFs for test.""" diff --git a/tests/validation/common/visualisation/audio_graph.py b/tests/validation/common/visualisation/audio_graph.py index 31d04163c..09332a679 100644 --- a/tests/validation/common/visualisation/audio_graph.py +++ b/tests/validation/common/visualisation/audio_graph.py @@ -30,7 +30,9 @@ def read_pcm( """ with open(file_path, "rb") as f: if pcm_format == 8: - pcm_data = np.frombuffer(f.read(), dtype=np.uint8) - 128 # Center around zero + pcm_data = ( + np.frombuffer(f.read(), dtype=np.uint8) - 128 + ) # Center around zero elif pcm_format == 16: pcm_data = np.frombuffer(f.read(), dtype=np.int16) elif pcm_format == 24: @@ -43,7 +45,9 @@ def read_pcm( ) pcm_data = pcm_data - (pcm_data & 0x800000) * 2 # Sign extension else: - raise ValueError(f"Unsupported PCM format '{pcm_format}'. Use 8, 16, or 24.") + raise ValueError( + f"Unsupported PCM format '{pcm_format}'. Use 8, 16, or 24." + ) # Reshape the data to separate channels if there are multiple channels if num_channels > 1: @@ -106,8 +110,14 @@ def plot_waveforms( # If start_time or end_time is specified, plot only that subset if start_time is not None or end_time is not None: - start_sample = int(start_time * effective_sample_rate) if start_time is not None else 0 - end_sample = int(end_time * effective_sample_rate) if end_time is not None else len(pcm_data1) + start_sample = ( + int(start_time * effective_sample_rate) if start_time is not None else 0 + ) + end_sample = ( + int(end_time * effective_sample_rate) + if end_time is not None + else len(pcm_data1) + ) pcm_data1 = pcm_data1[start_sample:end_sample] pcm_data2 = pcm_data2[start_sample:end_sample] @@ -143,7 +153,9 @@ def plot_waveforms( label=f"{file_name1} - Right", ) else: - axs[0].plot(time_axis, pcm_data1, color="red", linewidth=LINEWIDTH, label=file_name1) + axs[0].plot( + time_axis, pcm_data1, color="red", linewidth=LINEWIDTH, label=file_name1 + ) if num_channels2 > 1: axs[0].plot( @@ -161,7 +173,9 @@ def plot_waveforms( label=f"{file_name2} - Right", ) else: - axs[0].plot(time_axis, pcm_data2, color="blue", linewidth=LINEWIDTH, label=file_name2) + axs[0].plot( + time_axis, pcm_data2, color="blue", linewidth=LINEWIDTH, label=file_name2 + ) axs[0].set_title("Combined Waveforms") axs[0].set_xlabel("Time [s]") @@ -172,14 +186,18 @@ def plot_waveforms( # Plot each channel separately plot_index = 1 if num_channels1 > 1: - axs[plot_index].plot(time_axis, pcm_data1[:, 0], color="red", linewidth=LINEWIDTH) + axs[plot_index].plot( + time_axis, pcm_data1[:, 0], color="red", linewidth=LINEWIDTH + ) axs[plot_index].set_title(f"{file_name1} - Left Channel") axs[plot_index].set_xlabel("Time [s]") axs[plot_index].set_ylabel("Amplitude") axs[plot_index].grid(True) plot_index += 1 - axs[plot_index].plot(time_axis, pcm_data1[:, 1], color="black", linewidth=LINEWIDTH) + axs[plot_index].plot( + time_axis, pcm_data1[:, 1], color="black", linewidth=LINEWIDTH + ) axs[plot_index].set_title(f"{file_name1} - Right Channel") axs[plot_index].set_xlabel("Time [s]") axs[plot_index].set_ylabel("Amplitude") @@ -194,14 +212,18 @@ def plot_waveforms( plot_index += 1 if num_channels2 > 1: - axs[plot_index].plot(time_axis, pcm_data2[:, 0], color="blue", linewidth=LINEWIDTH) + axs[plot_index].plot( + time_axis, pcm_data2[:, 0], color="blue", linewidth=LINEWIDTH + ) axs[plot_index].set_title(f"{file_name2} - Left Channel") axs[plot_index].set_xlabel("Time [s]") axs[plot_index].set_ylabel("Amplitude") axs[plot_index].grid(True) plot_index += 1 - axs[plot_index].plot(time_axis, pcm_data2[:, 1], color="green", linewidth=LINEWIDTH) + axs[plot_index].plot( + time_axis, pcm_data2[:, 1], color="green", linewidth=LINEWIDTH + ) axs[plot_index].set_title(f"{file_name2} - Right Channel") axs[plot_index].set_xlabel("Time [s]") axs[plot_index].set_ylabel("Amplitude") @@ -253,8 +275,14 @@ def plot_single_waveform( # If start_time or end_time is specified, plot only that subset if start_time is not None or end_time is not None: - start_sample = int(start_time * effective_sample_rate) if start_time is not None else 0 - end_sample = int(end_time * effective_sample_rate) if end_time is not None else len(pcm_data) + start_sample = ( + int(start_time * effective_sample_rate) if start_time is not None else 0 + ) + end_sample = ( + int(end_time * effective_sample_rate) + if end_time is not None + else len(pcm_data) + ) pcm_data = pcm_data[start_sample:end_sample] time_axis = np.arange(len(pcm_data)) / effective_sample_rate @@ -266,7 +294,9 @@ def plot_single_waveform( num_plots, 1, figsize=(10, 2 * num_plots), - gridspec_kw=({"height_ratios": [2] + [1] * (num_plots - 1)} if num_plots > 1 else None), + gridspec_kw=( + {"height_ratios": [2] + [1] * (num_plots - 1)} if num_plots > 1 else None + ), ) # If only one plot, convert axs to a list for consistent indexing @@ -286,7 +316,9 @@ def plot_single_waveform( label=f"{file_name} - {channel_name}", ) else: - axs[0].plot(time_axis, pcm_data, color="red", linewidth=LINEWIDTH, label=file_name) + axs[0].plot( + time_axis, pcm_data, color="red", linewidth=LINEWIDTH, label=file_name + ) axs[0].set_title("Waveform") axs[0].set_xlabel("Time [s]") @@ -344,10 +376,14 @@ def generate_waveform_plot( Returns: Absolute path to the generated output file. """ - pcm_data1 = read_pcm(file_path1, sample_rate, pcm_format=pcm_format, num_channels=num_channels1) + pcm_data1 = read_pcm( + file_path1, sample_rate, pcm_format=pcm_format, num_channels=num_channels1 + ) if file_path2: - pcm_data2 = read_pcm(file_path2, sample_rate, pcm_format=pcm_format, num_channels=num_channels2) + pcm_data2 = read_pcm( + file_path2, sample_rate, pcm_format=pcm_format, num_channels=num_channels2 + ) return plot_waveforms( file_path1, file_path2, @@ -392,7 +428,9 @@ def generate_waveform_plot( nargs="?", help="Path to the second PCM file (optional).", ) - parser.add_argument("--sample_rate", type=int, default=48000, help="Sample rate of the PCM files.") + parser.add_argument( + "--sample_rate", type=int, default=48000, help="Sample rate of the PCM files." + ) parser.add_argument( "--output_file", type=str, @@ -417,8 +455,12 @@ def generate_waveform_plot( default=10, help="Factor by which to downsample the data.", ) - parser.add_argument("--start_time", type=float, help="Start time in seconds for the plot.") - parser.add_argument("--end_time", type=float, help="End time in seconds for the plot.") + parser.add_argument( + "--start_time", type=float, help="Start time in seconds for the plot." + ) + parser.add_argument( + "--end_time", type=float, help="End time in seconds for the plot." + ) parser.add_argument( "--pcm_format", type=int, diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index 09501849f..fac402989 100644 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -131,12 +131,16 @@ def mesh_agent(hosts, test_config, log_path): mesh_ip = test_config.get("mesh_ip", None) if not mesh_ip: - logger.error(f"Host '{mesh_agent_name}' not found in topology.yaml and no mesh_ip provided.") + logger.error( + f"Host '{mesh_agent_name}' not found in topology.yaml and no mesh_ip provided." + ) raise RuntimeError( f"No mesh-agent name '{mesh_agent_name}' found in hosts and no mesh_ip provided in test_config." ) else: - logger.info(f"Assumed that mesh agent is running, getting IP from topology config: {mesh_ip}") + logger.info( + f"Assumed that mesh agent is running, getting IP from topology config: {mesh_ip}" + ) mesh_agent.external = True mesh_agent.mesh_ip = mesh_ip mesh_agent.p = test_config.get("mesh_port", mesh_agent.p) @@ -235,7 +239,9 @@ def media_config(hosts: dict) -> None: if host.topology.extra_info.media_proxy.get("st2110", False): pf_addr = host.network_interfaces[if_idx].pci_address.lspci vfs = nicctl.vfio_list(pf_addr) - host.st2110_dev = host.topology.extra_info.media_proxy.get("st2110_dev", None) + host.st2110_dev = host.topology.extra_info.media_proxy.get( + "st2110_dev", None + ) if not host.st2110_dev and not vfs: nicctl.create_vfs(pf_addr) vfs = nicctl.vfio_list(pf_addr) @@ -250,25 +256,44 @@ def media_config(hosts: dict) -> None: f"Still no VFs on interface {host.network_interfaces[if_idx].pci_address_lspci} even after creating VFs!" ) host.vfs = vfs - host.st2110_ip = host.topology.extra_info.media_proxy.get("st2110_ip", f"192.168.0.{last_oct}") + host.st2110_ip = host.topology.extra_info.media_proxy.get( + "st2110_ip", f"192.168.0.{last_oct}" + ) if_idx += 1 if host.topology.extra_info.media_proxy.get("rdma", False): - if int(host.network_interfaces[if_idx].virtualization.get_current_vfs()) > 0: - nicctl.disable_vf(str(host.network_interfaces[if_idx].pci_address.lspci)) + if ( + int( + host.network_interfaces[if_idx].virtualization.get_current_vfs() + ) + > 0 + ): + nicctl.disable_vf( + str(host.network_interfaces[if_idx].pci_address.lspci) + ) net_adap_ips = host.network_interfaces[if_idx].ip.get_ips().v4 rdma_ip = host.topology.extra_info.media_proxy.get("rdma_ip", False) if rdma_ip: - rdma_ip = IPv4Interface(f"{rdma_ip}" if "/" in rdma_ip else f"{rdma_ip}/24") + rdma_ip = IPv4Interface( + f"{rdma_ip}" if "/" in rdma_ip else f"{rdma_ip}/24" + ) elif net_adap_ips and not rdma_ip: rdma_ip = net_adap_ips[0] if not rdma_ip or (rdma_ip not in net_adap_ips): - rdma_ip = IPv4Interface(f"192.168.1.{last_oct}/24") if not rdma_ip else rdma_ip - logger.info(f"IP {rdma_ip} not found on RDMA network interface, setting: {rdma_ip}") + rdma_ip = ( + IPv4Interface(f"192.168.1.{last_oct}/24") + if not rdma_ip + else rdma_ip + ) + logger.info( + f"IP {rdma_ip} not found on RDMA network interface, setting: {rdma_ip}" + ) host.network_interfaces[if_idx].ip.add_ip(rdma_ip) host.rdma_ip = str(rdma_ip.ip) logger.info(f"VFs on {host.name} are: {host.vfs}") except IndexError: - raise IndexError(f"Not enough network adapters available for tests! Expected: {if_idx+1}") + raise IndexError( + f"Not enough network adapters available for tests! Expected: {if_idx+1}" + ) except AttributeError: logger.warning( f"Extra info media proxy in topology config for {host.name} is not set, skipping media config setup for this host." @@ -291,11 +316,17 @@ def cleanup_processes(hosts: dict) -> None: logger.warning(f"Failed to check/kill {proc} on {host.name}: {e}") for pattern in ["^Rx[A-Za-z]+App$", "^Tx[A-Za-z]+App$"]: try: - connection.execute_command(f"pgrep -f '{pattern}'", stderr_to_stdout=True) - connection.execute_command(f"pkill -9 -f '{pattern}'", stderr_to_stdout=True) + connection.execute_command( + f"pgrep -f '{pattern}'", stderr_to_stdout=True + ) + connection.execute_command( + f"pkill -9 -f '{pattern}'", stderr_to_stdout=True + ) except Exception as e: if not (hasattr(e, "returncode") and e.returncode == 1): - logger.warning(f"Failed to check/kill processes matching {pattern} on {host.name}: {e}") + logger.warning( + f"Failed to check/kill processes matching {pattern} on {host.name}: {e}" + ) logger.info("Cleanup of processes completed.") @@ -330,7 +361,9 @@ def build_mcm_ffmpeg(hosts, test_config: dict): return True # Use constants from Engine/const.py - mcm_ffmpeg_version = test_config.get("mcm_ffmpeg_version", DEFAULT_MCM_FFMPEG_VERSION) + mcm_ffmpeg_version = test_config.get( + "mcm_ffmpeg_version", DEFAULT_MCM_FFMPEG_VERSION + ) if not mcm_ffmpeg_version in ALLOWED_FFMPEG_VERSIONS: logger.error(f"Invalid mcm_ffmpeg_version: {mcm_ffmpeg_version}") return False @@ -350,12 +383,16 @@ def build_mcm_ffmpeg(hosts, test_config: dict): ).stdout.strip() if check_dir == "exists": - host.connection.execute_command(f"rm -rf {DEFAULT_MCM_FFMPEG_PATH}", shell=True) + host.connection.execute_command( + f"rm -rf {DEFAULT_MCM_FFMPEG_PATH}", shell=True + ) logger.debug("Step 1: Clone and patch FFmpeg") clone_patch_script = f"{ffmpeg_tools_path}/clone-and-patch-ffmpeg.sh" cmd = f"bash {clone_patch_script} {mcm_ffmpeg_version}" - res = host.connection.execute_command(cmd, cwd=ffmpeg_tools_path, shell=True, timeout=60, stderr_to_stdout=True) + res = host.connection.execute_command( + cmd, cwd=ffmpeg_tools_path, shell=True, timeout=60, stderr_to_stdout=True + ) logger.debug(f"Command {cmd} output: {res.stdout}") if res.return_code != 0: logger.error(f"Command {cmd} failed with return code {res.return_code}.") @@ -364,7 +401,9 @@ def build_mcm_ffmpeg(hosts, test_config: dict): logger.debug("Step 2: Run FFmpeg configuration tool") configure_script = f"{ffmpeg_tools_path}/configure-ffmpeg.sh" cmd = f"{configure_script} {mcm_ffmpeg_version} --prefix={DEFAULT_MCM_FFMPEG_PATH}" - res = host.connection.execute_command(cmd, cwd=ffmpeg_tools_path, shell=True, timeout=60, stderr_to_stdout=True) + res = host.connection.execute_command( + cmd, cwd=ffmpeg_tools_path, shell=True, timeout=60, stderr_to_stdout=True + ) logger.debug(f"Command {cmd} output: {res.stdout}") if res.return_code != 0: logger.error(f"Command {cmd} failed with return code {res.return_code}.") @@ -381,7 +420,9 @@ def build_mcm_ffmpeg(hosts, test_config: dict): logger.error(f"Command {cmd} failed with return code {res.return_code}.") return False - logger.info(f"Successfully built MCM FFmpeg {mcm_ffmpeg_version} at {DEFAULT_MCM_FFMPEG_PATH}") + logger.info( + f"Successfully built MCM FFmpeg {mcm_ffmpeg_version} at {DEFAULT_MCM_FFMPEG_PATH}" + ) return True @@ -424,7 +465,9 @@ def build_openh264(host: Host, work_dir: str) -> bool: if check_dir == "not exists": # Try to clone the repository - logger.info(f"OpenH264 repository not found at {openh264_repo_dir}. Attempting to clone...") + logger.info( + f"OpenH264 repository not found at {openh264_repo_dir}. Attempting to clone..." + ) res = host.connection.execute_command( f"git clone https://github.com/cisco/openh264.git {openh264_repo_dir}", shell=True, @@ -463,7 +506,9 @@ def build_openh264(host: Host, work_dir: str) -> bool: logger.info(f"Building and installing openh264 to {openh264_install_dir}") # Create install directory if it doesn't exist - host.connection.execute_command(f"sudo mkdir -p {openh264_install_dir}", shell=True, timeout=10) + host.connection.execute_command( + f"sudo mkdir -p {openh264_install_dir}", shell=True, timeout=10 + ) # Build with custom prefix res = host.connection.execute_command( @@ -491,7 +536,9 @@ def build_mtl_ffmpeg(hosts, test_config: dict): if not test_config.get("mtl_ffmpeg_rebuild", False): return True - mtl_ffmpeg_version = test_config.get("mtl_ffmpeg_version", DEFAULT_MTL_FFMPEG_VERSION) + mtl_ffmpeg_version = test_config.get( + "mtl_ffmpeg_version", DEFAULT_MTL_FFMPEG_VERSION + ) if not mtl_ffmpeg_version in ALLOWED_FFMPEG_VERSIONS: logger.error(f"Invalid mtl_ffmpeg_version: {mtl_ffmpeg_version}") return False @@ -514,7 +561,9 @@ def build_mtl_ffmpeg(hosts, test_config: dict): ).stdout.strip() if check_dir == "exists": - host.connection.execute_command(f"rm -rf {DEFAULT_MTL_FFMPEG_PATH}", shell=True) + host.connection.execute_command( + f"rm -rf {DEFAULT_MTL_FFMPEG_PATH}", shell=True + ) # Step 1: Build openh264 logger.info("Step 1: Building openh264 dependency") @@ -608,7 +657,9 @@ def build_mtl_ffmpeg(hosts, test_config: dict): ) if res.return_code == 0: logger.info(f"Found GPU headers at {gpu_path}") - configure_options.append(f'--extra-cflags="-DMTL_GPU_DIRECT_ENABLED -I{gpu_path}"') + configure_options.append( + f'--extra-cflags="-DMTL_GPU_DIRECT_ENABLED -I{gpu_path}"' + ) gpu_headers_found = True break except Exception as e: @@ -616,7 +667,9 @@ def build_mtl_ffmpeg(hosts, test_config: dict): continue if not gpu_headers_found: - logger.warning("GPU direct support requested but headers not found. Continuing without GPU support.") + logger.warning( + "GPU direct support requested but headers not found. Continuing without GPU support." + ) enable_gpu_direct = False # Join configure options and continue with build @@ -667,7 +720,9 @@ def check_iommu(hosts: dict[str, Host]) -> None: iommu_not_enabled_hosts = [] for host in hosts.values(): try: - output = host.connection.execute_command("ls -1 /sys/kernel/iommu_groups | wc -l", shell=True, timeout=10) + output = host.connection.execute_command( + "ls -1 /sys/kernel/iommu_groups | wc -l", shell=True, timeout=10 + ) if int(output.stdout.strip()) == 0: logger.error(f"IOMMU is not enabled on host {host.name}.") iommu_not_enabled_hosts.append(host.name) @@ -675,7 +730,9 @@ def check_iommu(hosts: dict[str, Host]) -> None: logger.exception(f"Failed to check IOMMU status on host {host.name}.") iommu_not_enabled_hosts.append(host.name) if iommu_not_enabled_hosts: - pytest.exit(f"IOMMU is not enabled on hosts: {', '.join(iommu_not_enabled_hosts)}. Aborting test session.") + pytest.exit( + f"IOMMU is not enabled on hosts: {', '.join(iommu_not_enabled_hosts)}. Aborting test session." + ) else: logger.info("IOMMU is enabled on all hosts.") @@ -688,14 +745,20 @@ def enable_hugepages(hosts: dict[str, Host]) -> None: for host in hosts.values(): if not _check_hugepages(host): try: - host.connection.execute_command("sudo sysctl -w vm.nr_hugepages=2048", shell=True, timeout=10) + host.connection.execute_command( + "sudo sysctl -w vm.nr_hugepages=2048", shell=True, timeout=10 + ) logger.info(f"Hugepages enabled on host {host.name}.") except (RemoteProcessTimeoutExpired, ConnectionCalledProcessError): logger.exception(f"Failed to enable hugepages on host {host.name}.") - pytest.exit(f"Failed to enable hugepages on host {host.name}. Aborting test session.") + pytest.exit( + f"Failed to enable hugepages on host {host.name}. Aborting test session." + ) if not _check_hugepages(host): logger.error(f"Hugepages could not be enabled on host {host.name}.") - pytest.exit(f"Hugepages could not be enabled on host {host.name}. Aborting test session.") + pytest.exit( + f"Hugepages could not be enabled on host {host.name}. Aborting test session." + ) else: logger.info(f"Hugepages are already enabled on host {host.name}.") @@ -705,7 +768,9 @@ def _check_hugepages(host: Host) -> bool: Check if hugepages are enabled on the host. """ try: - output = host.connection.execute_command("cat /proc/sys/vm/nr_hugepages", shell=True, timeout=10) + output = host.connection.execute_command( + "cat /proc/sys/vm/nr_hugepages", shell=True, timeout=10 + ) return int(output.stdout.strip()) > 0 except (RemoteProcessTimeoutExpired, ConnectionCalledProcessError): logger.exception(f"Failed to check hugepages status on host {host.name}.") diff --git a/tests/validation/functional/cluster/audio/test_audio_25_03.py b/tests/validation/functional/cluster/audio/test_audio_25_03.py index 943ebb296..cb58eef48 100644 --- a/tests/validation/functional/cluster/audio/test_audio_25_03.py +++ b/tests/validation/functional/cluster/audio/test_audio_25_03.py @@ -21,7 +21,9 @@ @pytest.mark.parametrize("file", audio_files_25_03.keys()) -def test_audio_25_03_2_2_standalone(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: +def test_audio_25_03_2_2_standalone( + build_TestApp, hosts, media_proxy, media_path, file, log_path +) -> None: # Get TX and RX hosts host_list = list(hosts.values()) diff --git a/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py b/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py index cb6981196..3a5b25d00 100644 --- a/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py @@ -32,17 +32,25 @@ def test_cluster_ffmpeg_audio(hosts, media_proxy, test_config, audio_type: str, rx_host = host_list[1] tx_prefix_variables = test_config["tx"].get("prefix_variables", {}) rx_prefix_variables = test_config["rx"].get("prefix_variables", {}) - tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy["sdk_port"] - rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = rx_host.topology.extra_info.media_proxy["sdk_port"] + tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( + tx_host.topology.extra_info.media_proxy["sdk_port"] + ) + rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( + rx_host.topology.extra_info.media_proxy["sdk_port"] + ) - audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) # audio format + audio_format = audio_file_format_to_format_dict( + str(audio_files[audio_type]["format"]) + ) # audio format audio_channel_layout = audio_files[audio_type].get( "channel_layout", audio_channel_number_to_layout(int(audio_files[audio_type]["channels"])), ) if audio_files[audio_type]["sample_rate"] not in [48000, 44100, 96000]: - raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") + raise Exception( + f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" + ) # >>>>> MCM Tx mcm_tx_inp = FFmpegAudioIO( diff --git a/tests/validation/functional/cluster/blob/test_blob_25_03.py b/tests/validation/functional/cluster/blob/test_blob_25_03.py index e434a1ab6..8966fec27 100644 --- a/tests/validation/functional/cluster/blob/test_blob_25_03.py +++ b/tests/validation/functional/cluster/blob/test_blob_25_03.py @@ -19,7 +19,9 @@ @pytest.mark.parametrize("file", [file for file in blob_files_25_03.keys()]) -def test_blob_25_03(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: +def test_blob_25_03( + build_TestApp, hosts, media_proxy, media_path, file, log_path +) -> None: # Get TX and RX hosts host_list = list(hosts.values()) diff --git a/tests/validation/functional/cluster/video/test_ffmpeg_video.py b/tests/validation/functional/cluster/video/test_ffmpeg_video.py index 633021cf7..7d47298ce 100644 --- a/tests/validation/functional/cluster/video/test_ffmpeg_video.py +++ b/tests/validation/functional/cluster/video/test_ffmpeg_video.py @@ -29,12 +29,18 @@ def test_cluster_ffmpeg_video(hosts, media_proxy, test_config, video_type: str, rx_host = host_list[1] tx_prefix_variables = test_config["tx"].get("prefix_variables", {}) rx_prefix_variables = test_config["rx"].get("prefix_variables", {}) - tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy["sdk_port"] - rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = rx_host.topology.extra_info.media_proxy["sdk_port"] + tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( + tx_host.topology.extra_info.media_proxy["sdk_port"] + ) + rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( + rx_host.topology.extra_info.media_proxy["sdk_port"] + ) frame_rate = str(yuv_files[video_type]["fps"]) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) + pixel_format = video_file_format_to_payload_format( + str(yuv_files[video_type]["file_format"]) + ) conn_type = McmConnectionType.mpg.value # >>>>> MCM Tx diff --git a/tests/validation/functional/cluster/video/test_video_25_03.py b/tests/validation/functional/cluster/video/test_video_25_03.py index 3c967284d..a09bff772 100644 --- a/tests/validation/functional/cluster/video/test_video_25_03.py +++ b/tests/validation/functional/cluster/video/test_video_25_03.py @@ -19,7 +19,9 @@ @pytest.mark.parametrize("file", [file for file in video_files_25_03.keys()]) -def test_video_25_03_2_2_standalone(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: +def test_video_25_03_2_2_standalone( + build_TestApp, hosts, media_proxy, media_path, file, log_path +) -> None: # Get TX and RX hosts host_list = list(hosts.values()) diff --git a/tests/validation/functional/local/audio/test_audio_25_03.py b/tests/validation/functional/local/audio/test_audio_25_03.py index 825a80f3b..8e81e44eb 100644 --- a/tests/validation/functional/local/audio/test_audio_25_03.py +++ b/tests/validation/functional/local/audio/test_audio_25_03.py @@ -24,7 +24,9 @@ *[f for f in audio_files_25_03.keys() if f != "PCM16_48000_Mono"], ], ) -def test_audio_25_03(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: +def test_audio_25_03( + build_TestApp, hosts, media_proxy, media_path, file, log_path +) -> None: # Get TX and RX hosts host_list = list(hosts.values()) diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index dd90772e8..abde2637c 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -44,16 +44,22 @@ def test_local_ffmpeg_audio(hosts, test_config, audio_type: str, log_path, media prefix_variables = dict(tx_host.topology.extra_info.mcm_prefix_variables) else: prefix_variables = {} - prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy["sdk_port"] + prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( + tx_host.topology.extra_info.media_proxy["sdk_port"] + ) - audio_format = audio_file_format_to_format_dict(str(audio_files_25_03[audio_type]["format"])) # audio format + audio_format = audio_file_format_to_format_dict( + str(audio_files_25_03[audio_type]["format"]) + ) # audio format audio_channel_layout = audio_files_25_03[audio_type].get( "channel_layout", audio_channel_number_to_layout(int(audio_files_25_03[audio_type]["channels"])), ) if audio_files_25_03[audio_type]["sample_rate"] not in [48000, 44100, 96000]: - raise Exception(f"Not expected audio sample rate of {audio_files_25_03[audio_type]['sample_rate']}!") + raise Exception( + f"Not expected audio sample rate of {audio_files_25_03[audio_type]['sample_rate']}!" + ) # >>>>> MCM Tx mcm_tx_inp = FFmpegAudioIO( diff --git a/tests/validation/functional/local/blob/test_blob_25_03.py b/tests/validation/functional/local/blob/test_blob_25_03.py index 1977d1bd7..a767351c1 100644 --- a/tests/validation/functional/local/blob/test_blob_25_03.py +++ b/tests/validation/functional/local/blob/test_blob_25_03.py @@ -17,10 +17,11 @@ ) from Engine.media_files import blob_files_25_03 - @pytest.mark.smoke @pytest.mark.parametrize("file", [file for file in blob_files_25_03.keys()]) -def test_blob_25_03(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: +def test_blob_25_03( + build_TestApp, hosts, media_proxy, media_path, file, log_path +) -> None: # Get TX and RX hosts host_list = list(hosts.values()) diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index 179eed14c..04b2bb2f7 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -48,12 +48,20 @@ def test_local_ffmpeg_video(hosts, test_config, file: str, log_path, media_path) else: rx_prefix_variables = {} - tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy["sdk_port"] - rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = rx_host.topology.extra_info.media_proxy["sdk_port"] + tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( + tx_host.topology.extra_info.media_proxy["sdk_port"] + ) + rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( + rx_host.topology.extra_info.media_proxy["sdk_port"] + ) frame_rate = str(video_files_25_03[file]["fps"]) - video_size = f'{video_files_25_03[file]["width"]}x{video_files_25_03[file]["height"]}' - pixel_format = video_file_format_to_payload_format(str(video_files_25_03[file]["file_format"])) + video_size = ( + f'{video_files_25_03[file]["width"]}x{video_files_25_03[file]["height"]}' + ) + pixel_format = video_file_format_to_payload_format( + str(video_files_25_03[file]["file_format"]) + ) conn_type = McmConnectionType.mpg.value # >>>>> MCM Tx diff --git a/tests/validation/functional/local/video/test_video_25_03.py b/tests/validation/functional/local/video/test_video_25_03.py index 289f364ea..d5d868b79 100644 --- a/tests/validation/functional/local/video/test_video_25_03.py +++ b/tests/validation/functional/local/video/test_video_25_03.py @@ -25,7 +25,9 @@ *[f for f in video_files_25_03.keys() if f != "FullHD_60"], ], ) -def test_video_25_03(build_TestApp, hosts, media_proxy, media_path, file, log_path) -> None: +def test_video_25_03( + build_TestApp, hosts, media_proxy, media_path, file, log_path +) -> None: # Get TX and RX hosts host_list = list(hosts.values()) diff --git a/tests/validation/functional/st2110/st20/test_3_2_st2110_standalone_video.py b/tests/validation/functional/st2110/st20/test_3_2_st2110_standalone_video.py index a7e2f0a07..b98b960d0 100644 --- a/tests/validation/functional/st2110/st20/test_3_2_st2110_standalone_video.py +++ b/tests/validation/functional/st2110/st20/test_3_2_st2110_standalone_video.py @@ -58,7 +58,9 @@ def test_3_2_st2110_standalone_video(hosts, test_config, video_type, log_path): fps = str(yuv_files[video_type]["fps"]) size = f"{yuv_files[video_type]['width']}x{yuv_files[video_type]['height']}" - pixel_format = video_file_format_to_payload_format(yuv_files[video_type]["file_format"]) + pixel_format = video_file_format_to_payload_format( + yuv_files[video_type]["file_format"] + ) rx_ffmpeg_input = FFmpegMtlSt20pRx( video_size=size, @@ -78,7 +80,9 @@ def test_3_2_st2110_standalone_video(hosts, test_config, video_type, log_path): ) rx_ffmpeg_output = FFmpegVideoIO( video_size=size, - pixel_format=("yuv422p10le" if pixel_format == "yuv422p10rfc4175" else pixel_format), + pixel_format=( + "yuv422p10le" if pixel_format == "yuv422p10rfc4175" else pixel_format + ), f=FFmpegVideoFormat.raw.value, output_path=f'{test_config["rx"]["filepath"]}test_{yuv_files[video_type]["filename"]}_{size}at{yuv_files[video_type]["fps"]}fps.yuv', pix_fmt=None, diff --git a/tests/validation/functional/st2110/st20/test_4_1_RxTxApp_mcm_to_mtl_video_multiple_nodes.py b/tests/validation/functional/st2110/st20/test_4_1_RxTxApp_mcm_to_mtl_video_multiple_nodes.py index 852dbbc1f..32d1bcd3e 100644 --- a/tests/validation/functional/st2110/st20/test_4_1_RxTxApp_mcm_to_mtl_video_multiple_nodes.py +++ b/tests/validation/functional/st2110/st20/test_4_1_RxTxApp_mcm_to_mtl_video_multiple_nodes.py @@ -70,7 +70,9 @@ def test_st2110_rttxapp_mcm_to_mtl_video( frame_rate = str(yuv_files[video_type]["fps"]) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - video_pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) + video_pixel_format = video_file_format_to_payload_format( + str(yuv_files[video_type]["file_format"]) + ) rx_nicctl = Nicctl( mtl_path=rx_mtl_path, @@ -95,7 +97,11 @@ def test_st2110_rttxapp_mcm_to_mtl_video( ) mtl_rx_outp = FFmpegVideoIO( video_size=video_size, - pixel_format=("yuv422p10le" if video_pixel_format == "yuv422p10rfc4175" else video_pixel_format), + pixel_format=( + "yuv422p10le" + if video_pixel_format == "yuv422p10rfc4175" + else video_pixel_format + ), f=FFmpegVideoFormat.raw.value, output_path=f'{test_config["rx"]["filepath"]}test_{yuv_files[video_type]["filename"]}_{video_size}at{yuv_files[video_type]["fps"]}fps.yuv', pix_fmt=None, # this is required to overwrite the default @@ -107,8 +113,12 @@ def test_st2110_rttxapp_mcm_to_mtl_video( ffmpeg_output=mtl_rx_outp, yes_overwrite=True, ) - logger.debug(f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}") - mtl_rx_executor = FFmpegExecutor(rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path) + logger.debug( + f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}" + ) + mtl_rx_executor = FFmpegExecutor( + rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path + ) rx_executor_a = utils.LapkaExecutor.Rx( host=rx_host_a, @@ -162,5 +172,9 @@ def test_st2110_rttxapp_mcm_to_mtl_video( mtl_rx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) tx_executor.stop() - assert rx_executor_a.is_pass, "Receiver A validation failed. Check logs for details." - assert rx_executor_b.is_pass, "Receiver B validation failed. Check logs for details." + assert ( + rx_executor_a.is_pass + ), "Receiver A validation failed. Check logs for details." + assert ( + rx_executor_b.is_pass + ), "Receiver B validation failed. Check logs for details." diff --git a/tests/validation/functional/st2110/st20/test_4_1_ffmpeg_mcm_to_mtl_video_multiple_nodes.py b/tests/validation/functional/st2110/st20/test_4_1_ffmpeg_mcm_to_mtl_video_multiple_nodes.py index 3db873968..c5d83135d 100644 --- a/tests/validation/functional/st2110/st20/test_4_1_ffmpeg_mcm_to_mtl_video_multiple_nodes.py +++ b/tests/validation/functional/st2110/st20/test_4_1_ffmpeg_mcm_to_mtl_video_multiple_nodes.py @@ -32,7 +32,9 @@ logger = logging.getLogger(__name__) -EARLY_STOP_THRESHOLD_PERCENTAGE = 20 # percentage of max_test_time to consider an early stop +EARLY_STOP_THRESHOLD_PERCENTAGE = ( + 20 # percentage of max_test_time to consider an early stop +) @pytest.mark.parametrize("video_type", [k for k in yuv_files.keys() if "4K" not in k]) @@ -57,7 +59,9 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( frame_rate = str(yuv_files[video_type]["fps"]) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - video_pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) + video_pixel_format = video_file_format_to_payload_format( + str(yuv_files[video_type]["file_format"]) + ) conn_type = McmConnectionType.st.value # MCM FFmpeg Tx @@ -87,8 +91,12 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( ffmpeg_output=mcm_tx_outp, yes_overwrite=False, ) - logger.debug(f"MCM Tx command executed on {tx_host.name}: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mcm_tx_ff, log_path=log_path) + logger.debug( + f"MCM Tx command executed on {tx_host.name}: {mcm_tx_ff.get_command()}" + ) + mcm_tx_executor = FFmpegExecutor( + tx_host, ffmpeg_instance=mcm_tx_ff, log_path=log_path + ) rx_mtl_path = get_mtl_path(rx_mtl_host) @@ -116,7 +124,11 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( ) mtl_rx_outp = FFmpegVideoIO( video_size=video_size, - pixel_format=("yuv422p10le" if video_pixel_format == "yuv422p10rfc4175" else video_pixel_format), + pixel_format=( + "yuv422p10le" + if video_pixel_format == "yuv422p10rfc4175" + else video_pixel_format + ), f=FFmpegVideoFormat.raw.value, output_path=f'{getattr(rx_mtl_host.topology.extra_info, "output_path", DEFAULT_OUTPUT_PATH)}/test_{yuv_files[video_type]["filename"]}_{video_size}at{yuv_files[video_type]["fps"]}fps.yuv', pix_fmt=None, # this is required to overwrite the default @@ -128,8 +140,12 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( ffmpeg_output=mtl_rx_outp, yes_overwrite=True, ) - logger.debug(f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}") - mtl_rx_executor = FFmpegExecutor(rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path) + logger.debug( + f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}" + ) + mtl_rx_executor = FFmpegExecutor( + rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path + ) # MCM FFmpeg Rx A mcm_rx_a_inp = FFmpegMcmST2110VideoRx( @@ -159,8 +175,12 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( ffmpeg_output=mcm_rx_a_outp, yes_overwrite=True, ) - logger.debug(f"MCM Rx A command executed on {rx_host_a.name}: {mcm_rx_a_ff.get_command()}") - mcm_rx_a_executor = FFmpegExecutor(rx_host_a, ffmpeg_instance=mcm_rx_a_ff, log_path=log_path) + logger.debug( + f"MCM Rx A command executed on {rx_host_a.name}: {mcm_rx_a_ff.get_command()}" + ) + mcm_rx_a_executor = FFmpegExecutor( + rx_host_a, ffmpeg_instance=mcm_rx_a_ff, log_path=log_path + ) # MCM FFmpeg Rx B mcm_rx_b_inp = FFmpegMcmST2110VideoRx( @@ -190,8 +210,12 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( ffmpeg_output=mcm_rx_b_outp, yes_overwrite=True, ) - logger.debug(f"MCM Rx B command executed on {rx_host_b.name}: {mcm_rx_b_ff.get_command()}") - mcm_rx_b_executor = FFmpegExecutor(rx_host_b, ffmpeg_instance=mcm_rx_b_ff, log_path=log_path) + logger.debug( + f"MCM Rx B command executed on {rx_host_b.name}: {mcm_rx_b_ff.get_command()}" + ) + mcm_rx_b_executor = FFmpegExecutor( + rx_host_b, ffmpeg_instance=mcm_rx_b_ff, log_path=log_path + ) mcm_tx_executor.start() sleep(MCM_ESTABLISH_TIMEOUT) diff --git a/tests/validation/functional/st2110/st20/test_6_1_st2110_ffmpeg_video.py b/tests/validation/functional/st2110/st20/test_6_1_st2110_ffmpeg_video.py index 46eb0fd62..c8040e81d 100644 --- a/tests/validation/functional/st2110/st20/test_6_1_st2110_ffmpeg_video.py +++ b/tests/validation/functional/st2110/st20/test_6_1_st2110_ffmpeg_video.py @@ -27,7 +27,9 @@ def test_6_1_st2110_ffmpeg_video(hosts, test_config, video_file, log_path): rx_b_host = hosts["client"] video_size = f'{video_file["width"]}x{video_file["height"]}' - video_pixel_format = ffmpeg_enums.video_file_format_to_payload_format(video_file["file_format"]) + video_pixel_format = ffmpeg_enums.video_file_format_to_payload_format( + video_file["file_format"] + ) video_frame_rate = video_file["fps"] # Host A --- MTL FFmpeg Tx diff --git a/tests/validation/functional/st2110/st20/test_RxTxApp_mtl_to_mcm_video.py b/tests/validation/functional/st2110/st20/test_RxTxApp_mtl_to_mcm_video.py index 350d2ca9c..5978089f9 100644 --- a/tests/validation/functional/st2110/st20/test_RxTxApp_mtl_to_mcm_video.py +++ b/tests/validation/functional/st2110/st20/test_RxTxApp_mtl_to_mcm_video.py @@ -52,7 +52,9 @@ def test_st2110_rttxapp_mtl_to_mcm_video( tx_mtl_path = get_mtl_path(tx_host) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - video_pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) + video_pixel_format = video_file_format_to_payload_format( + str(yuv_files[video_type]["file_format"]) + ) tx_nicctl = Nicctl( host=tx_host, @@ -85,7 +87,9 @@ def test_st2110_rttxapp_mtl_to_mcm_video( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mtl_tx_ff.get_command()}") - mtl_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path) + mtl_tx_executor = FFmpegExecutor( + tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path + ) rx_connection = Engine.rx_tx_app_connection.St2110_20( remoteIpAddr=test_config.get("broadcast_ip", DEFAULT_REMOTE_IP_ADDR), diff --git a/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py b/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py index 82048d05c..c0d008398 100644 --- a/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py +++ b/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py @@ -28,7 +28,9 @@ @pytest.mark.parametrize("video_type", [k for k in yuv_files.keys()]) -def test_st2110_ffmpeg_mcm_to_mtl_video(media_proxy, hosts, test_config, video_type: str, log_path) -> None: +def test_st2110_ffmpeg_mcm_to_mtl_video( + media_proxy, hosts, test_config, video_type: str, log_path +) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts host_list = list(hosts.values()) @@ -38,7 +40,9 @@ def test_st2110_ffmpeg_mcm_to_mtl_video(media_proxy, hosts, test_config, video_t tx_host = host_list[0] tx_prefix_variables = test_config["tx"].get("prefix_variables", {}) tx_prefix_variables = no_proxy_to_prefix_variables(tx_host, tx_prefix_variables) - tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy.get("sdk_port") + tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( + tx_host.topology.extra_info.media_proxy.get("sdk_port") + ) logger.debug(f"tx_prefix_variables: {tx_prefix_variables}") # RX configuration @@ -50,7 +54,9 @@ def test_st2110_ffmpeg_mcm_to_mtl_video(media_proxy, hosts, test_config, video_t # Video configuration frame_rate = str(yuv_files[video_type]["fps"]) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - video_pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) + video_pixel_format = video_file_format_to_payload_format( + str(yuv_files[video_type]["file_format"]) + ) conn_type = McmConnectionType.st.value # Prepare Rx VFs @@ -113,7 +119,11 @@ def test_st2110_ffmpeg_mcm_to_mtl_video(media_proxy, hosts, test_config, video_t ) mtl_rx_outp = FFmpegVideoIO( video_size=video_size, - pixel_format=("yuv422p10le" if video_pixel_format == "yuv422p10rfc4175" else video_pixel_format), + pixel_format=( + "yuv422p10le" + if video_pixel_format == "yuv422p10rfc4175" + else video_pixel_format + ), f=FFmpegVideoFormat.raw.value, output_path=f'{test_config["rx"]["filepath"]}test_{yuv_files[video_type]["filename"]}_{video_size}at{yuv_files[video_type]["fps"]}fps.yuv', pix_fmt=None, # this is required to overwrite the default diff --git a/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py b/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py index a3fc35a2d..b3f85111c 100644 --- a/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py +++ b/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py @@ -26,7 +26,9 @@ logger = logging.getLogger(__name__) MAX_TEST_TIME_DEFAULT = 60 # seconds -EARLY_STOP_THRESHOLD_PERCENTAGE = 20 # percentage of max_test_time to consider an early stop +EARLY_STOP_THRESHOLD_PERCENTAGE = ( + 20 # percentage of max_test_time to consider an early stop +) @pytest.mark.parametrize("video_type", [k for k in yuv_files.keys()]) @@ -46,7 +48,9 @@ def test_st2110_ffmpeg_video(media_proxy, hosts, test_config, video_type: str, l frame_rate = str(yuv_files[video_type]["fps"]) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - video_pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) + video_pixel_format = video_file_format_to_payload_format( + str(yuv_files[video_type]["file_format"]) + ) conn_type = McmConnectionType.st.value # Prepare Tx VFs @@ -74,7 +78,9 @@ def test_st2110_ffmpeg_video(media_proxy, hosts, test_config, video_type: str, l ) mtl_tx_outp = FFmpegMtlSt20pTx( # TODO: Add -filter option (to FFmpegIO?) - p_port=str(tx_vfs[0] if tx_vfs else tx_pf), # use VF or PF if no VFs are available + p_port=str( + tx_vfs[0] if tx_vfs else tx_pf + ), # use VF or PF if no VFs are available p_sip=test_config["tx"]["p_sip"], p_tx_ip=test_config["broadcast_ip"], udp_port=test_config["port"], diff --git a/tests/validation/functional/st2110/st20/test_mtl_to_RxTxApp_mcm_video_multiple_nodes.py b/tests/validation/functional/st2110/st20/test_mtl_to_RxTxApp_mcm_video_multiple_nodes.py index e16e119ef..334cfa860 100644 --- a/tests/validation/functional/st2110/st20/test_mtl_to_RxTxApp_mcm_video_multiple_nodes.py +++ b/tests/validation/functional/st2110/st20/test_mtl_to_RxTxApp_mcm_video_multiple_nodes.py @@ -53,7 +53,9 @@ def test_st2110_rttxapp_mtl_to_mcm_video( tx_mtl_path = get_mtl_path(tx_host) video_size = f'{yuv_files[video_type]["width"]}x{yuv_files[video_type]["height"]}' - video_pixel_format = video_file_format_to_payload_format(str(yuv_files[video_type]["file_format"])) + video_pixel_format = video_file_format_to_payload_format( + str(yuv_files[video_type]["file_format"]) + ) tx_nicctl = Nicctl( mtl_path=tx_mtl_path, @@ -85,7 +87,9 @@ def test_st2110_rttxapp_mtl_to_mcm_video( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mtl_tx_ff.get_command()}") - mtl_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path) + mtl_tx_executor = FFmpegExecutor( + tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path + ) rx_connection = Engine.rx_tx_app_connection.St2110_20( remoteIpAddr=test_config.get("broadcast_ip", DEFAULT_REMOTE_IP_ADDR), @@ -137,5 +141,9 @@ def test_st2110_rttxapp_mtl_to_mcm_video( rx_executor_a.cleanup() rx_executor_b.cleanup() - assert rx_executor_a.is_pass, "Receiver A validation failed. Check logs for details." - assert rx_executor_b.is_pass, "Receiver B validation failed. Check logs for details." + assert ( + rx_executor_a.is_pass + ), "Receiver A validation failed. Check logs for details." + assert ( + rx_executor_b.is_pass + ), "Receiver B validation failed. Check logs for details." diff --git a/tests/validation/functional/st2110/st30/test_3_2_st2110_standalone_audio.py b/tests/validation/functional/st2110/st30/test_3_2_st2110_standalone_audio.py index 225e6aca3..5b938afa9 100644 --- a/tests/validation/functional/st2110/st30/test_3_2_st2110_standalone_audio.py +++ b/tests/validation/functional/st2110/st30/test_3_2_st2110_standalone_audio.py @@ -27,7 +27,9 @@ @pytest.mark.parametrize("audio_type", list(audio_files.keys())) def test_3_2_st2110_standalone_audio(hosts, test_config, audio_type, log_path): try: - audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) + audio_format = audio_file_format_to_format_dict( + str(audio_files[audio_type]["format"]) + ) except: pytest.skip(f"Unsupported audio format: {audio_files[audio_type]['format']}") @@ -38,7 +40,9 @@ def test_3_2_st2110_standalone_audio(hosts, test_config, audio_type, log_path): audio_sample_rate = int(audio_files[audio_type]["sample_rate"]) if audio_sample_rate not in [ar.value for ar in FFmpegAudioRate]: - raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") + raise Exception( + f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" + ) tx_host = hosts["mesh-agent"] rx_host = hosts["client"] diff --git a/tests/validation/functional/st2110/st30/test_4_1_RxTxApp_mcm_to_mtl_audio_multiple_nodes.py b/tests/validation/functional/st2110/st30/test_4_1_RxTxApp_mcm_to_mtl_audio_multiple_nodes.py index 98d8c64ec..39f36aaa9 100644 --- a/tests/validation/functional/st2110/st30/test_4_1_RxTxApp_mcm_to_mtl_audio_multiple_nodes.py +++ b/tests/validation/functional/st2110/st30/test_4_1_RxTxApp_mcm_to_mtl_audio_multiple_nodes.py @@ -36,7 +36,9 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize("audio_type", [k for k in audio_files.keys() if "PCM8" not in k]) +@pytest.mark.parametrize( + "audio_type", [k for k in audio_files.keys() if "PCM8" not in k] +) def test_st2110_rttxapp_mcm_to_mtl_audio( build_TestApp, hosts, media_proxy, media_path, test_config, audio_type, log_path ) -> None: @@ -68,11 +70,15 @@ def test_st2110_rttxapp_mcm_to_mtl_audio( rx_prefix_variables = test_config["rx"].get("prefix_variables", {}) rx_mtl_path = get_mtl_path(rx_mtl_host) - audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) + audio_format = audio_file_format_to_format_dict( + str(audio_files[audio_type]["format"]) + ) audio_sample_rate = int(audio_files[audio_type]["sample_rate"]) if audio_sample_rate not in [ar.value for ar in FFmpegAudioRate]: - raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") + raise Exception( + f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" + ) rx_nicctl = Nicctl( mtl_path=rx_mtl_path, @@ -110,8 +116,12 @@ def test_st2110_rttxapp_mcm_to_mtl_audio( ffmpeg_output=mtl_rx_outp, yes_overwrite=True, ) - logger.debug(f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}") - mtl_rx_executor = FFmpegExecutor(host=rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path) + logger.debug( + f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}" + ) + mtl_rx_executor = FFmpegExecutor( + host=rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path + ) rx_executor_a = utils.LapkaExecutor.Rx( host=rx_host_a, @@ -163,5 +173,9 @@ def test_st2110_rttxapp_mcm_to_mtl_audio( mtl_rx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) tx_executor.stop() - assert rx_executor_a.is_pass, "Receiver A validation failed. Check logs for details." - assert rx_executor_b.is_pass, "Receiver B validation failed. Check logs for details." + assert ( + rx_executor_a.is_pass + ), "Receiver A validation failed. Check logs for details." + assert ( + rx_executor_b.is_pass + ), "Receiver B validation failed. Check logs for details." diff --git a/tests/validation/functional/st2110/st30/test_4_1_ffmpeg_mcm_to_mtl_audio_multiple_nodes.py b/tests/validation/functional/st2110/st30/test_4_1_ffmpeg_mcm_to_mtl_audio_multiple_nodes.py index 995b634ec..8dadfc99f 100644 --- a/tests/validation/functional/st2110/st30/test_4_1_ffmpeg_mcm_to_mtl_audio_multiple_nodes.py +++ b/tests/validation/functional/st2110/st30/test_4_1_ffmpeg_mcm_to_mtl_audio_multiple_nodes.py @@ -32,10 +32,14 @@ logger = logging.getLogger(__name__) -EARLY_STOP_THRESHOLD_PERCENTAGE = 20 # percentage of max_test_time to consider an early stop +EARLY_STOP_THRESHOLD_PERCENTAGE = ( + 20 # percentage of max_test_time to consider an early stop +) -@pytest.mark.parametrize("audio_type", [k for k in audio_files.keys() if "PCM8" not in k]) +@pytest.mark.parametrize( + "audio_type", [k for k in audio_files.keys() if "PCM8" not in k] +) def test_st2110_ffmpeg_mcm_to_mtl_audio( build_TestApp, hosts, media_proxy, media_path, test_config, audio_type, log_path ) -> None: @@ -55,7 +59,9 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( rx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = get_media_proxy_port(rx_host_b) rx_mtl_prefix_variables["MCM_MEDIA_PROXY_PORT"] = get_media_proxy_port(rx_mtl_host) - audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) + audio_format = audio_file_format_to_format_dict( + str(audio_files[audio_type]["format"]) + ) audio_channel_layout = audio_files[audio_type].get( "channel_layout", audio_channel_number_to_layout(int(audio_files[audio_type]["channels"])), @@ -64,7 +70,9 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( # Set audio sample rate based on the audio file sample rate audio_sample_rate = int(audio_files[audio_type]["sample_rate"]) if audio_sample_rate not in [ar.value for ar in FFmpegAudioRate]: - raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") + raise Exception( + f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" + ) conn_type = McmConnectionType.st.value @@ -94,8 +102,12 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( ffmpeg_output=mcm_tx_outp, yes_overwrite=False, ) - logger.debug(f"MCM Tx command executed on {tx_host.name}: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mcm_tx_ff, log_path=log_path) + logger.debug( + f"MCM Tx command executed on {tx_host.name}: {mcm_tx_ff.get_command()}" + ) + mcm_tx_executor = FFmpegExecutor( + tx_host, ffmpeg_instance=mcm_tx_ff, log_path=log_path + ) rx_mtl_path = get_mtl_path(rx_mtl_host) @@ -135,8 +147,12 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( ffmpeg_output=mtl_rx_outp, yes_overwrite=True, ) - logger.debug(f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}") - mtl_rx_executor = FFmpegExecutor(rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path) + logger.debug( + f"Mtl rx command executed on {rx_mtl_host.name}: {mtl_rx_ff.get_command()}" + ) + mtl_rx_executor = FFmpegExecutor( + rx_mtl_host, ffmpeg_instance=mtl_rx_ff, log_path=log_path + ) # MCM FFmpeg Rx A mcm_rx_a_inp = FFmpegMcmST2110AudioRx( @@ -164,8 +180,12 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( ffmpeg_output=mcm_rx_a_outp, yes_overwrite=True, ) - logger.debug(f"MCM Rx A command executed on {rx_host_a.name}: {mcm_rx_a_ff.get_command()}") - mcm_rx_a_executor = FFmpegExecutor(rx_host_a, ffmpeg_instance=mcm_rx_a_ff, log_path=log_path) + logger.debug( + f"MCM Rx A command executed on {rx_host_a.name}: {mcm_rx_a_ff.get_command()}" + ) + mcm_rx_a_executor = FFmpegExecutor( + rx_host_a, ffmpeg_instance=mcm_rx_a_ff, log_path=log_path + ) # MCM FFmpeg Rx B mcm_rx_b_inp = FFmpegMcmST2110AudioRx( @@ -193,8 +213,12 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( ffmpeg_output=mcm_rx_b_outp, yes_overwrite=True, ) - logger.debug(f"MCM Rx B command executed on {rx_host_b.name}: {mcm_rx_b_ff.get_command()}") - mcm_rx_b_executor = FFmpegExecutor(rx_host_b, ffmpeg_instance=mcm_rx_b_ff, log_path=log_path) + logger.debug( + f"MCM Rx B command executed on {rx_host_b.name}: {mcm_rx_b_ff.get_command()}" + ) + mcm_rx_b_executor = FFmpegExecutor( + rx_host_b, ffmpeg_instance=mcm_rx_b_ff, log_path=log_path + ) mcm_tx_executor.start() sleep(MCM_ESTABLISH_TIMEOUT) diff --git a/tests/validation/functional/st2110/st30/test_6_1_st2110_ffmpeg_audio.py b/tests/validation/functional/st2110/st30/test_6_1_st2110_ffmpeg_audio.py index 3b0c46965..45ba18339 100644 --- a/tests/validation/functional/st2110/st30/test_6_1_st2110_ffmpeg_audio.py +++ b/tests/validation/functional/st2110/st30/test_6_1_st2110_ffmpeg_audio.py @@ -27,14 +27,18 @@ def test_6_1_st2110_ffmpeg_audio(hosts, test_config, audio_file, log_path): rx_b_host = hosts["client"] try: - audio_format = ffmpeg_enums.audio_file_format_to_format_dict(str(audio_file["format"])) + audio_format = ffmpeg_enums.audio_file_format_to_format_dict( + str(audio_file["format"]) + ) except: pytest.skip(f"Unsupported audio format: {audio_file['format']}") audio_sample_rate = int(audio_file["sample_rate"]) if audio_sample_rate not in [ar.value for ar in ffmpeg_enums.FFmpegAudioRate]: - raise Exception(f"Not expected audio sample rate of {audio_file['sample_rate']}!") + raise Exception( + f"Not expected audio sample rate of {audio_file['sample_rate']}!" + ) # Host A --- MTL FFmpeg Tx diff --git a/tests/validation/functional/st2110/st30/test_RxTxApp_mtl_to_mcm_audio.py b/tests/validation/functional/st2110/st30/test_RxTxApp_mtl_to_mcm_audio.py index a987b491e..e5cfefa7a 100644 --- a/tests/validation/functional/st2110/st30/test_RxTxApp_mtl_to_mcm_audio.py +++ b/tests/validation/functional/st2110/st30/test_RxTxApp_mtl_to_mcm_audio.py @@ -35,7 +35,9 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize("audio_type", [k for k in audio_files.keys() if "PCM8" not in k]) +@pytest.mark.parametrize( + "audio_type", [k for k in audio_files.keys() if "PCM8" not in k] +) def test_st2110_rttxapp_mtl_to_mcm_audio( build_TestApp, hosts, media_proxy, media_path, test_config, audio_type, log_path ) -> None: @@ -49,11 +51,15 @@ def test_st2110_rttxapp_mtl_to_mcm_audio( tx_prefix_variables = test_config["tx"].get("prefix_variables", {}) tx_mtl_path = get_mtl_path(tx_host) - audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) + audio_format = audio_file_format_to_format_dict( + str(audio_files[audio_type]["format"]) + ) audio_sample_rate = int(audio_files[audio_type]["sample_rate"]) if audio_sample_rate not in [ar.value for ar in FFmpegAudioRate]: - raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") + raise Exception( + f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" + ) tx_nicctl = Nicctl( host=tx_host, @@ -90,7 +96,9 @@ def test_st2110_rttxapp_mtl_to_mcm_audio( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mtl_tx_ff.get_command()}") - mtl_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path) + mtl_tx_executor = FFmpegExecutor( + tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path + ) rx_connection = Engine.rx_tx_app_connection.St2110_30( remoteIpAddr=test_config.get("broadcast_ip", DEFAULT_REMOTE_IP_ADDR), diff --git a/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py b/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py index 0ce95d3a3..232f109b7 100644 --- a/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py +++ b/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py @@ -29,7 +29,9 @@ @pytest.mark.parametrize("audio_type", [k for k in audio_files.keys()]) -def test_st2110_ffmpeg_mcm_to_mtl_audio(media_proxy, hosts, test_config, audio_type: str, log_path) -> None: +def test_st2110_ffmpeg_mcm_to_mtl_audio( + media_proxy, hosts, test_config, audio_type: str, log_path +) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts host_list = list(hosts.values()) @@ -39,7 +41,9 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio(media_proxy, hosts, test_config, audio_t tx_host = host_list[0] tx_prefix_variables = test_config["tx"].get("prefix_variables", {}) tx_prefix_variables = no_proxy_to_prefix_variables(tx_host, tx_prefix_variables) - tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy.get("sdk_port") + tx_prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( + tx_host.topology.extra_info.media_proxy.get("sdk_port") + ) # RX configuration rx_host = host_list[1] @@ -48,7 +52,9 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio(media_proxy, hosts, test_config, audio_t rx_mtl_path = rx_host.topology.extra_info.mtl_path # Audio configuration - audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) # audio format + audio_format = audio_file_format_to_format_dict( + str(audio_files[audio_type]["format"]) + ) # audio format audio_channel_layout = audio_files[audio_type].get( "channel_layout", audio_channel_number_to_layout(int(audio_files[audio_type]["channels"])), @@ -61,7 +67,9 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio(media_proxy, hosts, test_config, audio_t elif int(audio_files[audio_type]["sample_rate"]) == 96000: audio_sample_rate = FFmpegAudioRate.k96.value else: - pytest.skip(f"Skipping test due to unsupported audio sample rate: {audio_files[audio_type]['sample_rate']}") + pytest.skip( + f"Skipping test due to unsupported audio sample rate: {audio_files[audio_type]['sample_rate']}" + ) # Prepare Rx VFs rx_nicctl = Nicctl( diff --git a/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py b/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py index c6ab7bd59..f793906f4 100644 --- a/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py +++ b/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py @@ -29,11 +29,15 @@ logger = logging.getLogger(__name__) MAX_TEST_TIME_DEFAULT = 60 # seconds -EARLY_STOP_THRESHOLD_PERCENTAGE = 20 # percentage of max_test_time to consider an early stop +EARLY_STOP_THRESHOLD_PERCENTAGE = ( + 20 # percentage of max_test_time to consider an early stop +) @pytest.mark.parametrize("audio_type", [k for k in audio_files.keys()]) -def test_st2110_ffmpeg_mtl_to_mcm_audio(media_proxy, hosts, test_config, audio_type: str, log_path) -> None: +def test_st2110_ffmpeg_mtl_to_mcm_audio( + media_proxy, hosts, test_config, audio_type: str, log_path +) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts host_list = list(hosts.values()) @@ -47,7 +51,9 @@ def test_st2110_ffmpeg_mtl_to_mcm_audio(media_proxy, hosts, test_config, audio_t tx_mtl_path = get_mtl_path(tx_host) rx_mtl_path = get_mtl_path(rx_host) - audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) # audio format + audio_format = audio_file_format_to_format_dict( + str(audio_files[audio_type]["format"]) + ) # audio format audio_channel_layout = audio_files[audio_type].get( "channel_layout", audio_channel_number_to_layout(int(audio_files[audio_type]["channels"])), @@ -56,7 +62,9 @@ def test_st2110_ffmpeg_mtl_to_mcm_audio(media_proxy, hosts, test_config, audio_t # Set audio sample rate based on the audio file sample rate audio_sample_rate = int(audio_files[audio_type]["sample_rate"]) if audio_sample_rate not in [ar.value for ar in FFmpegAudioRate]: - raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") + raise Exception( + f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" + ) # Prepare Tx VFs tx_nicctl = Nicctl( @@ -84,7 +92,9 @@ def test_st2110_ffmpeg_mtl_to_mcm_audio(media_proxy, hosts, test_config, audio_t ) mtl_tx_outp = FFmpegMtlSt30pTx( ptime=PacketTime.pt_1ms.value, - p_port=str(tx_vfs[0] if tx_vfs else tx_pf), # use VF or PF if no VFs are available + p_port=str( + tx_vfs[0] if tx_vfs else tx_pf + ), # use VF or PF if no VFs are available p_sip=test_config["tx"]["p_sip"], p_tx_ip=test_config["broadcast_ip"], udp_port=test_config["port"], diff --git a/tests/validation/functional/st2110/st30/test_mtl_to_RxTxApp_mcm_audio_multiple_nodes.py b/tests/validation/functional/st2110/st30/test_mtl_to_RxTxApp_mcm_audio_multiple_nodes.py index e84a50dd0..d7adf48ca 100644 --- a/tests/validation/functional/st2110/st30/test_mtl_to_RxTxApp_mcm_audio_multiple_nodes.py +++ b/tests/validation/functional/st2110/st30/test_mtl_to_RxTxApp_mcm_audio_multiple_nodes.py @@ -36,7 +36,9 @@ logger = logging.getLogger(__name__) -@pytest.mark.parametrize("audio_type", [k for k in audio_files.keys() if "PCM8" not in k]) +@pytest.mark.parametrize( + "audio_type", [k for k in audio_files.keys() if "PCM8" not in k] +) def test_st2110_rttxapp_mtl_to_mcm_audio( build_TestApp, hosts, media_proxy, media_path, test_config, audio_type, log_path ) -> None: @@ -52,11 +54,15 @@ def test_st2110_rttxapp_mtl_to_mcm_audio( tx_prefix_variables = test_config["tx"].get("mtl_prefix_variables", {}) tx_mtl_path = get_mtl_path(tx_host) - audio_format = audio_file_format_to_format_dict(str(audio_files[audio_type]["format"])) + audio_format = audio_file_format_to_format_dict( + str(audio_files[audio_type]["format"]) + ) audio_sample_rate = int(audio_files[audio_type]["sample_rate"]) if audio_sample_rate not in [ar.value for ar in FFmpegAudioRate]: - raise Exception(f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!") + raise Exception( + f"Not expected audio sample rate of {audio_files[audio_type]['sample_rate']}!" + ) tx_nicctl = Nicctl( mtl_path=tx_mtl_path, @@ -94,7 +100,9 @@ def test_st2110_rttxapp_mtl_to_mcm_audio( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mtl_tx_ff.get_command()}") - mtl_tx_executor = FFmpegExecutor(tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path) + mtl_tx_executor = FFmpegExecutor( + tx_host, ffmpeg_instance=mtl_tx_ff, log_path=log_path + ) rx_connection = Engine.rx_tx_app_connection.St2110_30( remoteIpAddr=test_config.get("broadcast_ip", DEFAULT_REMOTE_IP_ADDR), @@ -145,5 +153,9 @@ def test_st2110_rttxapp_mtl_to_mcm_audio( rx_executor_a.cleanup() rx_executor_b.cleanup() - assert rx_executor_a.is_pass, "Receiver A validation failed. Check logs for details." - assert rx_executor_b.is_pass, "Receiver B validation failed. Check logs for details." + assert ( + rx_executor_a.is_pass + ), "Receiver A validation failed. Check logs for details." + assert ( + rx_executor_b.is_pass + ), "Receiver B validation failed. Check logs for details." diff --git a/tests/validation/functional/test_demo.py b/tests/validation/functional/test_demo.py index 164c229de..3c5f05f2e 100644 --- a/tests/validation/functional/test_demo.py +++ b/tests/validation/functional/test_demo.py @@ -81,7 +81,9 @@ def test_list_command_on_sut(hosts): def test_mesh_agent_lifecycle(mesh_agent, logging): """Test starting and stopping the mesh agent.""" logger.info("Testing mesh_agent lifecycle") - assert mesh_agent.mesh_agent_process is not None, "Mesh agent process was not started." + assert ( + mesh_agent.mesh_agent_process is not None + ), "Mesh agent process was not started." assert mesh_agent.mesh_agent_process.running, "Mesh agent process is not running." logger.info("Mesh agent lifecycle test completed successfully.") @@ -106,7 +108,9 @@ def test_sudo_command(hosts): result = connection.execute_command("ls /", stderr_to_stdout=True) logger.info(f"Command output: {result.stdout}") logger.info(f"Command error (if any): {result.stderr}") - assert result.return_code == 0, f"Sudo command failed with return code {result.return_code}" + assert ( + result.return_code == 0 + ), f"Sudo command failed with return code {result.return_code}" # connection.disable_sudo() logger.info("Sudo command execution test completed") @@ -116,8 +120,12 @@ def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config, log_ tx_host = rx_host = list(hosts.values())[0] prefix_variables = test_config.get("prefix_variables", {}) if tx_host.topology.extra_info.media_proxy.get("no_proxy", None): - prefix_variables["NO_PROXY"] = tx_host.topology.extra_info.media_proxy["no_proxy"] - prefix_variables["no_proxy"] = tx_host.topology.extra_info.media_proxy["no_proxy"] + prefix_variables["NO_PROXY"] = tx_host.topology.extra_info.media_proxy[ + "no_proxy" + ] + prefix_variables["no_proxy"] = tx_host.topology.extra_info.media_proxy[ + "no_proxy" + ] sdk_port = MEDIA_PROXY_PORT if tx_host.name in media_proxy and media_proxy[tx_host.name].p is not None: sdk_port = media_proxy[tx_host.name].t @@ -128,7 +136,11 @@ def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config, log_ pixel_format = "yuv422p10le" conn_type = McmConnectionType.mpg.value - input_path = str(tx_host.connection.path(test_config["input_path"], "180fr_1920x1080_yuv422p.yuv")) + input_path = str( + tx_host.connection.path( + test_config["input_path"], "180fr_1920x1080_yuv422p.yuv" + ) + ) # >>>>> MCM Tx mcm_tx_inp = FFmpegVideoIO( @@ -171,7 +183,9 @@ def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config, log_ framerate=frame_rate, video_size=video_size, pixel_format=pixel_format, - output_path=str(tx_host.connection.path(test_config["output_path"], "output_vid.yuv")), + output_path=str( + tx_host.connection.path(test_config["output_path"], "output_vid.yuv") + ), ) mcm_rx_ff = FFmpeg( prefix_variables=prefix_variables, @@ -210,8 +224,12 @@ def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config, log_pat tx_host = rx_host = list(hosts.values())[0] prefix_variables = test_config.get("prefix_variables", {}) if tx_host.topology.extra_info.media_proxy.get("no_proxy", None): - prefix_variables["NO_PROXY"] = tx_host.topology.extra_info.media_proxy["no_proxy"] - prefix_variables["no_proxy"] = tx_host.topology.extra_info.media_proxy["no_proxy"] + prefix_variables["NO_PROXY"] = tx_host.topology.extra_info.media_proxy[ + "no_proxy" + ] + prefix_variables["no_proxy"] = tx_host.topology.extra_info.media_proxy[ + "no_proxy" + ] sdk_port = MEDIA_PROXY_PORT if tx_host.name in media_proxy and media_proxy[tx_host.name].p is not None: sdk_port = media_proxy[tx_host.name].t @@ -222,7 +240,11 @@ def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config, log_pat pixel_format = "yuv422p10le" conn_type = McmConnectionType.mpg.value - input_path = str(tx_host.connection.path(test_config["input_path"], "180fr_1920x1080_yuv422p.yuv")) + input_path = str( + tx_host.connection.path( + test_config["input_path"], "180fr_1920x1080_yuv422p.yuv" + ) + ) # >>>>> MCM Tx mcm_tx_inp = FFmpegVideoIO( @@ -299,12 +321,16 @@ def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config, log_pat time.sleep(3) # Ensure the receiver is ready before starting the transmitter mcm_tx_executor.start() mcm_rx_executor.stop(wait=test_config.get("test_time_sec", 0.0)) - mcm_tx_executor.stop(wait=3) # Tx should stop just after Rx stop so wait timeout can be shorter here + mcm_tx_executor.stop( + wait=3 + ) # Tx should stop just after Rx stop so wait timeout can be shorter here assert integrator.stop_and_verify(timeout=20), "Stream integrity check failed" -def test_demo_local_blob_integrity(build_TestApp, hosts, media_proxy, media_path, log_path) -> None: +def test_demo_local_blob_integrity( + build_TestApp, hosts, media_proxy, media_path, log_path +) -> None: """Test blob integrity checking with a single blob file transfer using LapkaExecutor.""" # Get TX and RX hosts host_list = list(hosts.values()) @@ -360,12 +386,19 @@ def test_demo_local_blob_integrity(build_TestApp, hosts, media_proxy, media_path assert rx_executor.is_pass is True, "RX process did not pass" # Check if the output file actually exists - output_exists = rx_host.connection.execute_command(f"test -f {rx_executor.output}", shell=True).return_code == 0 + output_exists = ( + rx_host.connection.execute_command( + f"test -f {rx_executor.output}", shell=True + ).return_code + == 0 + ) logger.info(f"Output file exists: {output_exists}") if output_exists: # Get actual file size for debugging - file_size_result = rx_host.connection.execute_command(f"stat -c%s {rx_executor.output}", shell=True) + file_size_result = rx_host.connection.execute_command( + f"stat -c%s {rx_executor.output}", shell=True + ) logger.info(f"Output file size: {file_size_result.stdout.strip()} bytes") # Setup blob integrity checker @@ -374,7 +407,9 @@ def test_demo_local_blob_integrity(build_TestApp, hosts, media_proxy, media_path test_repo_path=None, src_url=str(tx_executor.input), # Use the actual input path from executor out_name=rx_executor.output.name, # Use the actual output filename - chunk_size=int(file_dict["max_payload_size"]), # Use payload size as chunk size + chunk_size=int( + file_dict["max_payload_size"] + ), # Use payload size as chunk size out_path=str(rx_executor.output.parent), # Use the output directory delete_file=False, integrity_path="/opt/intel/val_tests/validation/common/integrity/", @@ -395,33 +430,52 @@ def test_demo_local_blob_integrity(build_TestApp, hosts, media_proxy, media_path logger.error("=== Debugging integrity check failure ===") # Check source file details - src_exists = tx_host.connection.execute_command(f"test -f {tx_executor.input}", shell=True).return_code == 0 + src_exists = ( + tx_host.connection.execute_command( + f"test -f {tx_executor.input}", shell=True + ).return_code + == 0 + ) if src_exists: - src_size_result = tx_host.connection.execute_command(f"stat -c%s {tx_executor.input}", shell=True) - logger.error(f"Source file size: {src_size_result.stdout.strip()} bytes") + src_size_result = tx_host.connection.execute_command( + f"stat -c%s {tx_executor.input}", shell=True + ) + logger.error( + f"Source file size: {src_size_result.stdout.strip()} bytes" + ) else: logger.error(f"Source file {tx_executor.input} does not exist") # Check output file details again - out_size_result = rx_host.connection.execute_command(f"stat -c%s {rx_executor.output}", shell=True) + out_size_result = rx_host.connection.execute_command( + f"stat -c%s {rx_executor.output}", shell=True + ) logger.error(f"Output file size: {out_size_result.stdout.strip()} bytes") # Compare sizes - logger.error(f"Max payload size (chunk size): {file_dict['max_payload_size']}") + logger.error( + f"Max payload size (chunk size): {file_dict['max_payload_size']}" + ) # Try to get some hex dump of both files for comparison logger.error("First 32 bytes of source file:") - src_hex = tx_host.connection.execute_command(f"hexdump -C {tx_executor.input} | head -3", shell=True) + src_hex = tx_host.connection.execute_command( + f"hexdump -C {tx_executor.input} | head -3", shell=True + ) logger.error(src_hex.stdout) logger.error("First 32 bytes of output file:") - out_hex = rx_host.connection.execute_command(f"hexdump -C {rx_executor.output} | head -3", shell=True) + out_hex = rx_host.connection.execute_command( + f"hexdump -C {rx_executor.output} | head -3", shell=True + ) logger.error(out_hex.stdout) assert result, "Blob integrity check failed" else: # List files in the output directory to see what was actually created - ls_result = rx_host.connection.execute_command(f"ls -la {rx_executor.output.parent}/", shell=True) + ls_result = rx_host.connection.execute_command( + f"ls -la {rx_executor.output.parent}/", shell=True + ) logger.error(f"Output directory contents:\n{ls_result.stdout}") # Find any files that might match our pattern @@ -451,9 +505,8 @@ def test_build_mtl_ffmpeg(build_mtl_ffmpeg, hosts, test_config): logger.info("Testing MTL FFmpeg build process") assert build_mtl_ffmpeg, "MTL FFmpeg build failed" - def test_simple(log_path_dir, log_path, request): # For this test, log_path will be based on "test_simple" logging.info(f"Log path dir for test_simple: {log_path_dir}") logging.info(f"Log path for test_simple: {log_path}") - logging.info(f"Request: {request.node.name}") + logging.info(f"Request: {request.node.name}") \ No newline at end of file From c9a6cdea89dca8b0b7844fca64988a2922795d04 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 17:01:13 +0000 Subject: [PATCH 079/123] linter fix --- .github/workflows/base_build.yml | 24 ++++++++++++------------ .github/workflows/validation-tests.yml | 4 ++-- .gitignore | 2 ++ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index f1f4057bc..2a5f5f9a5 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -4,21 +4,21 @@ on: push: branches: ["main"] paths-ignore: - - '**/*.md' - - 'tests/**' - - 'docs/**' - - 'LICENSE' - - '.gitignore' - - '.editorconfig' + - "**/*.md" + - "tests/**" + - "docs/**" + - "LICENSE" + - ".gitignore" + - ".editorconfig" pull_request: branches: ["main"] paths-ignore: - - '**/*.md' - - 'tests/**' - - 'docs/**' - - 'LICENSE' - - '.gitignore' - - '.editorconfig' + - "**/*.md" + - "tests/**" + - "docs/**" + - "LICENSE" + - ".gitignore" + - ".editorconfig" workflow_dispatch: env: diff --git a/.github/workflows/validation-tests.yml b/.github/workflows/validation-tests.yml index 479c528b5..cfc383093 100644 --- a/.github/workflows/validation-tests.yml +++ b/.github/workflows/validation-tests.yml @@ -114,7 +114,7 @@ permissions: jobs: validation-build-mtm: - runs-on: [Linux, self-hosted, DPDK] + runs-on: [Linux, self-hosted] timeout-minutes: 60 outputs: pipenv-activate: ${{ steps.pipenv-install.outputs.VIRTUAL_ENV }} @@ -194,7 +194,7 @@ jobs: # Timeout of this job is set to 12h [60m/h*12h=720m] validation-run-tests: needs: [validation-build-mtm] - runs-on: [Linux, self-hosted, DPDK] + runs-on: [Linux, self-hosted] timeout-minutes: 720 env: PYTEST_ALIAS: "sudo --preserve-env python3 -m pipenv run pytest" diff --git a/.gitignore b/.gitignore index cc9830ad5..b28288b0e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,5 @@ cov-int* # Autogenerated version files mcm-version.h mcm-version.go + +.venv* \ No newline at end of file From 5c39b97158671123f3f17234471a477ec7f63ab9 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 17:10:53 +0000 Subject: [PATCH 080/123] remove gaps --- .../Engine/rx_tx_app_client_json.py | 4 +-- .../Engine/rx_tx_app_connection_json.py | 4 +-- tests/validation/common/__init__.py | 2 +- .../common/ffmpeg_handler/__init__.py | 2 +- .../common/ffmpeg_handler/ffmpeg.py | 32 ++++++++--------- .../common/ffmpeg_handler/log_constants.py | 4 +-- .../validation/common/log_validation_utils.py | 36 +++++++++---------- .../common/visualisation/audio_graph.py | 2 +- .../validation/configs/topology_template.yaml | 6 ++-- 9 files changed, 46 insertions(+), 46 deletions(-) diff --git a/tests/validation/Engine/rx_tx_app_client_json.py b/tests/validation/Engine/rx_tx_app_client_json.py index 4d0f13792..f0fe538e6 100644 --- a/tests/validation/Engine/rx_tx_app_client_json.py +++ b/tests/validation/Engine/rx_tx_app_client_json.py @@ -54,10 +54,10 @@ def copy_json_to_logs(self, log_path: str) -> None: """Copy the client.json file to the log path on runner.""" source_path = self.host.connection.path("client.json") dest_path = Path(log_path) / "client.json" - + # Create log directory if it doesn't exist Path(log_path).mkdir(parents=True, exist_ok=True) - + # Copy the client.json file to the log path with open(dest_path, "w") as dest_file: dest_file.write(self.to_json()) diff --git a/tests/validation/Engine/rx_tx_app_connection_json.py b/tests/validation/Engine/rx_tx_app_connection_json.py index ac7599673..9e38665f8 100644 --- a/tests/validation/Engine/rx_tx_app_connection_json.py +++ b/tests/validation/Engine/rx_tx_app_connection_json.py @@ -51,10 +51,10 @@ def copy_json_to_logs(self, log_path: str) -> None: """Copy the connection.json file to the log path on runner.""" source_path = self.host.connection.path("connection.json") dest_path = Path(log_path) / "connection.json" - + # Create log directory if it doesn't exist Path(log_path).mkdir(parents=True, exist_ok=True) - + # Copy the connection.json file to the log path with open(dest_path, "w") as dest_file: dest_file.write(self.to_json()) diff --git a/tests/validation/common/__init__.py b/tests/validation/common/__init__.py index 931d8daa9..2bb8b3764 100644 --- a/tests/validation/common/__init__.py +++ b/tests/validation/common/__init__.py @@ -15,5 +15,5 @@ 'output_validator', 'RX_REQUIRED_LOG_PHRASES', 'TX_REQUIRED_LOG_PHRASES', - 'RX_TX_APP_ERROR_KEYWORDS' + 'RX_TX_APP_ERROR_KEYWORDS', ] diff --git a/tests/validation/common/ffmpeg_handler/__init__.py b/tests/validation/common/ffmpeg_handler/__init__.py index 08bda312e..f9a8f7cea 100644 --- a/tests/validation/common/ffmpeg_handler/__init__.py +++ b/tests/validation/common/ffmpeg_handler/__init__.py @@ -15,5 +15,5 @@ __all__ = [ 'FFMPEG_RX_REQUIRED_LOG_PHRASES', 'FFMPEG_TX_REQUIRED_LOG_PHRASES', - 'FFMPEG_ERROR_KEYWORDS' + 'FFMPEG_ERROR_KEYWORDS', ] diff --git a/tests/validation/common/ffmpeg_handler/ffmpeg.py b/tests/validation/common/ffmpeg_handler/ffmpeg.py index bea8ea524..c64715854 100644 --- a/tests/validation/common/ffmpeg_handler/ffmpeg.py +++ b/tests/validation/common/ffmpeg_handler/ffmpeg.py @@ -80,24 +80,24 @@ def __init__(self, host, ffmpeg_instance: FFmpeg, log_path=None): def validate(self): """ Validates the FFmpeg process execution and output. - + Performs two types of validation: 1. Log validation - checks for required phrases and error keywords 2. File validation - checks if the output file exists and has expected characteristics - + Generates validation report files. - + Returns: bool: True if validation passed, False otherwise """ process_passed = True validation_info = [] - + for process in self._processes: if process.return_code != 0: logger.warning(f"FFmpeg process on {self.host.name} failed with return code {process.return_code}") process_passed = False - + # Determine if this is a receiver or transmitter is_receiver = False if self.ff.ffmpeg_input and self.ff.ffmpeg_output: @@ -108,9 +108,9 @@ def validate(self): output_path and output_path != "-" and "." in output_path ): is_receiver = True - + direction = "Rx" if is_receiver else "Tx" - + # Find the log file log_dir = self.log_path if self.log_path else LOG_FOLDER subdir = f"RxTx/{self.host.name}" @@ -119,9 +119,9 @@ def validate(self): input_class_name = self.ff.ffmpeg_input.__class__.__name__ prefix = "mtl_" if input_class_name and "Mtl" in input_class_name else "mcm_" log_filename = prefix + ("ffmpeg_rx.log" if is_receiver else "ffmpeg_tx.log") - + log_file_path = os.path.join(log_dir, subdir, log_filename) - + # Perform log validation from common.log_validation_utils import validate_log_file from common.ffmpeg_handler.log_constants import ( @@ -129,9 +129,9 @@ def validate(self): FFMPEG_TX_REQUIRED_LOG_PHRASES, FFMPEG_ERROR_KEYWORDS ) - + required_phrases = FFMPEG_RX_REQUIRED_LOG_PHRASES if is_receiver else FFMPEG_TX_REQUIRED_LOG_PHRASES - + if os.path.exists(log_file_path): validation_result = validate_log_file( log_file_path, @@ -150,7 +150,7 @@ def validate(self): validation_info.append(f"Errors found: 1") validation_info.append(f"Missing log file") log_validation_passed = False - + # File validation for Rx only run if output path isn't "/dev/null" or doesn't start with "/dev/null/" file_validation_passed = True if is_receiver and self.ff.ffmpeg_output and hasattr(self.ff.ffmpeg_output, "output_path"): @@ -164,10 +164,10 @@ def validate(self): self.host.connection, output_path, cleanup=False ) validation_info.extend(file_info) - + # Overall validation status self.is_pass = process_passed and log_validation_passed and file_validation_passed - + # Save validation report validation_info.append(f"\n=== Overall Validation Summary ===") validation_info.append(f"Overall validation: {'PASS' if self.is_pass else 'FAIL'}") @@ -176,12 +176,12 @@ def validate(self): if is_receiver: validation_info.append(f"File validation: {'PASS' if file_validation_passed else 'FAIL'}") validation_info.append(f"Note: Overall validation fails if any validation step fails") - + # Save validation report to a file from common.log_validation_utils import save_validation_report validation_path = os.path.join(log_dir, subdir, f"{direction.lower()}_validation.log") save_validation_report(validation_path, validation_info, self.is_pass) - + return self.is_pass def start(self): diff --git a/tests/validation/common/ffmpeg_handler/log_constants.py b/tests/validation/common/ffmpeg_handler/log_constants.py index b589e16f2..2c151b2a1 100644 --- a/tests/validation/common/ffmpeg_handler/log_constants.py +++ b/tests/validation/common/ffmpeg_handler/log_constants.py @@ -15,7 +15,7 @@ 'INFO - Create memif socket.', 'INFO - Create memif interface.', 'INFO - memif connected!', - '[INFO] gRPC: connection active' + '[INFO] gRPC: connection active', ] # Required ordered log phrases for FFmpeg Tx validation @@ -23,7 +23,7 @@ '[DEBU] JSON client config:', '[INFO] Media Communications Mesh SDK version', '[DEBU] JSON conn config:', - '[DEBU] BUF PARTS' + '[DEBU] BUF PARTS', ] # Common error keywords to look for in logs diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py index 84a81965a..41359bfef 100644 --- a/tests/validation/common/log_validation_utils.py +++ b/tests/validation/common/log_validation_utils.py @@ -51,23 +51,23 @@ def check_phrases_in_order(log_path: str, phrases: List[str]) -> Tuple[bool, Lis def check_for_errors(log_path: str, error_keywords: Optional[List[str]] = None) -> Tuple[bool, List[Dict[str, Any]]]: """ Check the log file for error keywords. - + Args: log_path: Path to the log file error_keywords: List of keywords indicating errors (default: RX_TX_APP_ERROR_KEYWORDS) - + Returns: Tuple of (is_pass, errors_found) errors_found is a list of dicts with 'line', 'line_number', and 'keyword' keys """ if error_keywords is None: error_keywords = RX_TX_APP_ERROR_KEYWORDS - + errors = [] if not os.path.exists(log_path): logger.error(f"Log file not found: {log_path}") return False, [{"line": "Log file not found", "line_number": 0, "keyword": "FILE_NOT_FOUND"}] - + try: with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: for i, line in enumerate(f): @@ -85,20 +85,20 @@ def check_for_errors(log_path: str, error_keywords: Optional[List[str]] = None) except Exception as e: logger.error(f"Error reading log file: {e}") return False, [{"line": f"Error reading log file: {e}", "line_number": 0, "keyword": "FILE_READ_ERROR"}] - + return len(errors) == 0, errors def validate_log_file(log_file_path: str, required_phrases: List[str], direction: str = "", error_keywords: Optional[List[str]] = None) -> Dict: """ Validate log file for required phrases and return validation information. - + Args: log_file_path: Path to the log file required_phrases: List of phrases to check for in order direction: Optional string to identify the direction (e.g., 'Rx', 'Tx') error_keywords: Optional list of error keywords to check for - + Returns: Dictionary containing validation results: { @@ -111,28 +111,28 @@ def validate_log_file(log_file_path: str, required_phrases: List[str], direction } """ validation_info = [] - + # Phrase order validation log_pass, missing, context_lines = check_phrases_in_order(log_file_path, required_phrases) error_count = len(missing) - + # Error keyword validation (optional) error_check_pass = True errors = [] if error_keywords is not None: error_check_pass, errors = check_for_errors(log_file_path, error_keywords) error_count += len(errors) - + # Overall pass/fail status is_pass = log_pass and error_check_pass - + # Build validation info dir_prefix = f"{direction} " if direction else "" validation_info.append(f"=== {dir_prefix}Log Validation ===") validation_info.append(f"Log file: {log_file_path}") validation_info.append(f"Validation result: {'PASS' if is_pass else 'FAIL'}") validation_info.append(f"Total errors found: {error_count}") - + # Missing phrases info if not log_pass: validation_info.append(f"Missing or out-of-order phrases analysis:") @@ -146,13 +146,13 @@ def validate_log_file(log_file_path: str, required_phrases: List[str], direction validation_info.append(" ") if missing: logger.warning(f"{dir_prefix}process did not pass. First missing phrase: {missing[0]}") - + # Error keywords info if errors: validation_info.append(f"\nError keywords found:") for error in errors: validation_info.append(f" Line {error['line_number']} - {error['keyword']}: {error['line']}") - + return { "is_pass": is_pass, "error_count": error_count, @@ -165,7 +165,7 @@ def validate_log_file(log_file_path: str, required_phrases: List[str], direction def save_validation_report(report_path: str, validation_info: List[str], overall_status: bool) -> None: """ Save validation report to a file. - + Args: report_path: Path where to save the report validation_info: List of validation information strings @@ -185,11 +185,11 @@ def save_validation_report(report_path: str, validation_info: List[str], overall def output_validator(log_file_path: str, error_keywords: Optional[List[str]] = None) -> Dict[str, Any]: """ Simple validator that checks for error keywords in a log file. - + Args: log_file_path: Path to the log file error_keywords: List of keywords indicating errors - + Returns: Dictionary with validation results: { @@ -199,7 +199,7 @@ def output_validator(log_file_path: str, error_keywords: Optional[List[str]] = N } """ is_pass, errors = check_for_errors(log_file_path, error_keywords) - + return { "is_pass": is_pass, "errors": errors, diff --git a/tests/validation/common/visualisation/audio_graph.py b/tests/validation/common/visualisation/audio_graph.py index 09332a679..6fe53f3b0 100644 --- a/tests/validation/common/visualisation/audio_graph.py +++ b/tests/validation/common/visualisation/audio_graph.py @@ -416,7 +416,7 @@ def generate_waveform_plot( epilog=""" Example usage: python3 audio_graph.py /path/to/file1.pcm /path/to/file2.pcm --sample_rate 44100 --output_file output.png --num_channels1 1 --num_channels2 1 --downsample_factor 10 --start_time 0 --end_time 0.03 --pcm_format 16 - + For single file: python3 audio_graph.py /path/to/file1.pcm --sample_rate 44100 --output_file output.png --num_channels1 2 --downsample_factor 10 --pcm_format 16 """, diff --git a/tests/validation/configs/topology_template.yaml b/tests/validation/configs/topology_template.yaml index 0cad0abd1..9351c2f0e 100644 --- a/tests/validation/configs/topology_template.yaml +++ b/tests/validation/configs/topology_template.yaml @@ -18,18 +18,18 @@ hosts: extra_info: # This is the list of extra information that will be used in the test for each host mtl_path: /opt/intel/mtl # this is custom path to mtl repo if you want to use different path than default in mcm _build directory nicctl_path: /opt/intel/mtl/script # this is path for nicctl.sh script if you want to use different path - + # File paths for tests filepath: /mnt/media/ # Path to input media files for transmitter output_path: /home/gta/received/ # Path where output files will be stored - + # FFmpeg configuration ffmpeg_path: /opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build/bin/ffmpeg # Path to FFmpeg binary prefix_variables: # Environment variables to set before running FFmpeg LD_LIBRARY_PATH: /opt/intel/_build/ffmpeg-7.0/ffmpeg-7-0_mcm_build/lib NO_PROXY: 127.0.0.1,localhost no_proxy: 127.0.0.1,localhost - + media_proxy: # st2110 and rdma dev and ips are optional but remember that it overrites the default values and must be set in host st2110: {{ st2110|default(true) }} # set it to true if you want to use st2110 bridge for media proxy sdk_port: {{ sdk_port|default(8002) }} # this is the port that will be used for media proxy sdk From d547f62f1ad839841822b3ee7a66eea46cb5a3a5 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 17:44:40 +0000 Subject: [PATCH 081/123] linter black fix --- .../Engine/rx_tx_app_client_json.py | 1 - .../validation/Engine/rx_tx_app_engine_mcm.py | 42 +++++--- tests/validation/common/__init__.py | 24 +++-- .../common/ffmpeg_handler/__init__.py | 8 +- .../common/ffmpeg_handler/ffmpeg.py | 100 ++++++++++++------ .../common/ffmpeg_handler/log_constants.py | 26 ++--- tests/validation/common/log_constants.py | 38 +++---- .../validation/common/log_validation_utils.py | 8 +- tests/validation/conftest.py | 8 +- .../cluster/video/test_ffmpeg_video.py | 12 ++- .../local/audio/test_ffmpeg_audio.py | 18 ++-- .../functional/local/blob/test_blob_25_03.py | 1 + .../local/video/test_ffmpeg_video.py | 12 ++- .../st20/test_ffmpeg_mcm_to_mtl_video.py | 8 +- .../st20/test_ffmpeg_mtl_to_mcm_video.py | 12 ++- .../st30/test_ffmpeg_mcm_to_mtl_audio.py | 8 +- .../st30/test_ffmpeg_mtl_to_mcm_audio.py | 8 +- tests/validation/functional/test_demo.py | 25 +++-- 18 files changed, 238 insertions(+), 121 deletions(-) diff --git a/tests/validation/Engine/rx_tx_app_client_json.py b/tests/validation/Engine/rx_tx_app_client_json.py index f0fe538e6..f0bbe9722 100644 --- a/tests/validation/Engine/rx_tx_app_client_json.py +++ b/tests/validation/Engine/rx_tx_app_client_json.py @@ -49,7 +49,6 @@ def prepare_and_save_json(self, output_path: str = "client.json") -> None: json_content = self.to_json().replace('"', '\\"') f.write_text(json_content) - def copy_json_to_logs(self, log_path: str) -> None: """Copy the client.json file to the log path on runner.""" source_path = self.host.connection.path("client.json") diff --git a/tests/validation/Engine/rx_tx_app_engine_mcm.py b/tests/validation/Engine/rx_tx_app_engine_mcm.py index 53e31430f..f68035261 100644 --- a/tests/validation/Engine/rx_tx_app_engine_mcm.py +++ b/tests/validation/Engine/rx_tx_app_engine_mcm.py @@ -12,7 +12,11 @@ import Engine.rx_tx_app_client_json import Engine.rx_tx_app_connection_json from Engine.const import LOG_FOLDER, DEFAULT_OUTPUT_PATH -from common.log_constants import RX_REQUIRED_LOG_PHRASES, TX_REQUIRED_LOG_PHRASES, RX_TX_APP_ERROR_KEYWORDS +from common.log_constants import ( + RX_REQUIRED_LOG_PHRASES, + TX_REQUIRED_LOG_PHRASES, + RX_TX_APP_ERROR_KEYWORDS, +) from Engine.mcm_apps import ( get_media_proxy_port, save_process_log, @@ -207,20 +211,27 @@ def stop(self): if self.direction in ("Rx", "Tx"): from common.log_validation_utils import validate_log_file required_phrases = RX_REQUIRED_LOG_PHRASES if self.direction == "Rx" else TX_REQUIRED_LOG_PHRASES - - # Use the common validation function - validation_result = validate_log_file(log_file_path, required_phrases, self.direction) - - self.is_pass = validation_result['is_pass'] - app_log_validation_status = validation_result['is_pass'] - app_log_error_count = validation_result['error_count'] - validation_info.extend(validation_result['validation_info']) - + + validation_result = validate_log_file( + log_file_path, required_phrases, self.direction + ) + + self.is_pass = validation_result["is_pass"] + app_log_validation_status = validation_result["is_pass"] + app_log_error_count = validation_result["error_count"] + validation_info.extend(validation_result["validation_info"]) + # Additional logging if validation failed - if not validation_result['is_pass'] and validation_result['missing_phrases']: - print(f"{self.direction} process did not pass. First missing phrase: {validation_result['missing_phrases'][0]}") + if ( + not validation_result["is_pass"] + and validation_result["missing_phrases"] + ): + print( + f"{self.direction} process did not pass. First missing phrase: {validation_result['missing_phrases'][0]}" + ) else: from common.log_validation_utils import output_validator + result = output_validator( log_file_path=log_file_path, error_keywords=RX_TX_APP_ERROR_KEYWORDS, @@ -248,7 +259,12 @@ def stop(self): ) # File validation for Rx only run if output path isn't "/dev/null" or doesn't start with "/dev/null/" - if self.direction == "Rx" and self.output and self.output_path and not str(self.output_path).startswith("/dev/null"): + if ( + self.direction == "Rx" + and self.output + and self.output_path + and not str(self.output_path).startswith("/dev/null") + ): validation_info.append(f"\n=== {self.direction} Output File Validation ===") validation_info.append(f"Expected output file: {self.output}") diff --git a/tests/validation/common/__init__.py b/tests/validation/common/__init__.py index 2bb8b3764..a91ab25b6 100644 --- a/tests/validation/common/__init__.py +++ b/tests/validation/common/__init__.py @@ -6,14 +6,22 @@ Common utilities package for validation code. """ -from common.log_validation_utils import check_phrases_in_order, validate_log_file, output_validator -from common.log_constants import RX_REQUIRED_LOG_PHRASES, TX_REQUIRED_LOG_PHRASES, RX_TX_APP_ERROR_KEYWORDS +from common.log_validation_utils import ( + check_phrases_in_order, + validate_log_file, + output_validator, +) +from common.log_constants import ( + RX_REQUIRED_LOG_PHRASES, + TX_REQUIRED_LOG_PHRASES, + RX_TX_APP_ERROR_KEYWORDS, +) __all__ = [ - 'check_phrases_in_order', - 'validate_log_file', - 'output_validator', - 'RX_REQUIRED_LOG_PHRASES', - 'TX_REQUIRED_LOG_PHRASES', - 'RX_TX_APP_ERROR_KEYWORDS', + "check_phrases_in_order", + "validate_log_file", + "output_validator", + "RX_REQUIRED_LOG_PHRASES", + "TX_REQUIRED_LOG_PHRASES", + "RX_TX_APP_ERROR_KEYWORDS", ] diff --git a/tests/validation/common/ffmpeg_handler/__init__.py b/tests/validation/common/ffmpeg_handler/__init__.py index f9a8f7cea..4e77f3267 100644 --- a/tests/validation/common/ffmpeg_handler/__init__.py +++ b/tests/validation/common/ffmpeg_handler/__init__.py @@ -9,11 +9,11 @@ from common.ffmpeg_handler.log_constants import ( FFMPEG_RX_REQUIRED_LOG_PHRASES, FFMPEG_TX_REQUIRED_LOG_PHRASES, - FFMPEG_ERROR_KEYWORDS + FFMPEG_ERROR_KEYWORDS, ) __all__ = [ - 'FFMPEG_RX_REQUIRED_LOG_PHRASES', - 'FFMPEG_TX_REQUIRED_LOG_PHRASES', - 'FFMPEG_ERROR_KEYWORDS', + "FFMPEG_RX_REQUIRED_LOG_PHRASES", + "FFMPEG_TX_REQUIRED_LOG_PHRASES", + "FFMPEG_ERROR_KEYWORDS", ] diff --git a/tests/validation/common/ffmpeg_handler/ffmpeg.py b/tests/validation/common/ffmpeg_handler/ffmpeg.py index c64715854..e59ea68d0 100644 --- a/tests/validation/common/ffmpeg_handler/ffmpeg.py +++ b/tests/validation/common/ffmpeg_handler/ffmpeg.py @@ -95,7 +95,9 @@ def validate(self): for process in self._processes: if process.return_code != 0: - logger.warning(f"FFmpeg process on {self.host.name} failed with return code {process.return_code}") + logger.warning( + f"FFmpeg process on {self.host.name} failed with return code {process.return_code}" + ) process_passed = False # Determine if this is a receiver or transmitter @@ -103,7 +105,7 @@ def validate(self): if self.ff.ffmpeg_input and self.ff.ffmpeg_output: input_path = getattr(self.ff.ffmpeg_input, "input_path", None) output_path = getattr(self.ff.ffmpeg_output, "output_path", None) - + if input_path == "-" or ( output_path and output_path != "-" and "." in output_path ): @@ -125,23 +127,24 @@ def validate(self): # Perform log validation from common.log_validation_utils import validate_log_file from common.ffmpeg_handler.log_constants import ( - FFMPEG_RX_REQUIRED_LOG_PHRASES, + FFMPEG_RX_REQUIRED_LOG_PHRASES, FFMPEG_TX_REQUIRED_LOG_PHRASES, - FFMPEG_ERROR_KEYWORDS + FFMPEG_ERROR_KEYWORDS, ) - required_phrases = FFMPEG_RX_REQUIRED_LOG_PHRASES if is_receiver else FFMPEG_TX_REQUIRED_LOG_PHRASES + required_phrases = ( + FFMPEG_RX_REQUIRED_LOG_PHRASES + if is_receiver + else FFMPEG_TX_REQUIRED_LOG_PHRASES + ) if os.path.exists(log_file_path): validation_result = validate_log_file( - log_file_path, - required_phrases, - direction, - FFMPEG_ERROR_KEYWORDS + log_file_path, required_phrases, direction, FFMPEG_ERROR_KEYWORDS ) - - log_validation_passed = validation_result['is_pass'] - validation_info.extend(validation_result['validation_info']) + + log_validation_passed = validation_result["is_pass"] + validation_info.extend(validation_result["validation_info"]) else: logger.warning(f"Log file not found at {log_file_path}") validation_info.append(f"=== {direction} Log Validation ===") @@ -153,33 +156,53 @@ def validate(self): # File validation for Rx only run if output path isn't "/dev/null" or doesn't start with "/dev/null/" file_validation_passed = True - if is_receiver and self.ff.ffmpeg_output and hasattr(self.ff.ffmpeg_output, "output_path"): + if ( + is_receiver + and self.ff.ffmpeg_output + and hasattr(self.ff.ffmpeg_output, "output_path") + ): output_path = self.ff.ffmpeg_output.output_path if output_path and not str(output_path).startswith("/dev/null"): validation_info.append(f"\n=== {direction} Output File Validation ===") validation_info.append(f"Expected output file: {output_path}") - + from Engine.rx_tx_app_file_validation_utils import validate_file + file_info, file_validation_passed = validate_file( self.host.connection, output_path, cleanup=False ) validation_info.extend(file_info) # Overall validation status - self.is_pass = process_passed and log_validation_passed and file_validation_passed + self.is_pass = ( + process_passed and log_validation_passed and file_validation_passed + ) # Save validation report validation_info.append(f"\n=== Overall Validation Summary ===") - validation_info.append(f"Overall validation: {'PASS' if self.is_pass else 'FAIL'}") - validation_info.append(f"Process validation: {'PASS' if process_passed else 'FAIL'}") - validation_info.append(f"Log validation: {'PASS' if log_validation_passed else 'FAIL'}") + validation_info.append( + f"Overall validation: {'PASS' if self.is_pass else 'FAIL'}" + ) + validation_info.append( + f"Process validation: {'PASS' if process_passed else 'FAIL'}" + ) + validation_info.append( + f"Log validation: {'PASS' if log_validation_passed else 'FAIL'}" + ) if is_receiver: - validation_info.append(f"File validation: {'PASS' if file_validation_passed else 'FAIL'}") - validation_info.append(f"Note: Overall validation fails if any validation step fails") + validation_info.append( + f"File validation: {'PASS' if file_validation_passed else 'FAIL'}" + ) + validation_info.append( + f"Note: Overall validation fails if any validation step fails" + ) # Save validation report to a file from common.log_validation_utils import save_validation_report - validation_path = os.path.join(log_dir, subdir, f"{direction.lower()}_validation.log") + + validation_path = os.path.join( + log_dir, subdir, f"{direction.lower()}_validation.log" + ) save_validation_report(validation_path, validation_info, self.is_pass) return self.is_pass @@ -285,30 +308,43 @@ def stop(self, wait: float = 0.0) -> float: def wait_with_timeout(self, timeout=None): """Wait for the process to complete with a timeout.""" if not timeout: - timeout = 60 # Default timeout or use a constant like MCM_RXTXAPP_RUN_TIMEOUT + timeout = ( + 60 # Default timeout or use a constant like MCM_RXTXAPP_RUN_TIMEOUT + ) try: for process in self._processes: if process.running: process.wait(timeout=timeout) except Exception as e: - logger.warning(f"FFmpeg process did not finish in time or error occurred: {e}") + logger.warning( + f"FFmpeg process did not finish in time or error occurred: {e}" + ) return False return True def cleanup(self): """Clean up any resources or output files.""" - if (self.ff.ffmpeg_output and - hasattr(self.ff.ffmpeg_output, "output_path") and - self.ff.ffmpeg_output.output_path and - self.ff.ffmpeg_output.output_path != "-" and - not str(self.ff.ffmpeg_output.output_path).startswith("/dev/null")): - - success = cleanup_file(self.host.connection, str(self.ff.ffmpeg_output.output_path)) + if ( + self.ff.ffmpeg_output + and hasattr(self.ff.ffmpeg_output, "output_path") + and self.ff.ffmpeg_output.output_path + and self.ff.ffmpeg_output.output_path != "-" + and not str(self.ff.ffmpeg_output.output_path).startswith("/dev/null") + ): + + success = cleanup_file( + self.host.connection, str(self.ff.ffmpeg_output.output_path) + ) if success: - logger.debug(f"Cleaned up output file: {self.ff.ffmpeg_output.output_path}") + logger.debug( + f"Cleaned up output file: {self.ff.ffmpeg_output.output_path}" + ) else: - logger.warning(f"Failed to clean up output file: {self.ff.ffmpeg_output.output_path}") + logger.warning( + f"Failed to clean up output file: {self.ff.ffmpeg_output.output_path}" + ) + def no_proxy_to_prefix_variables(host, prefix_variables: dict | None = None): """ diff --git a/tests/validation/common/ffmpeg_handler/log_constants.py b/tests/validation/common/ffmpeg_handler/log_constants.py index 2c151b2a1..5fd82d4b1 100644 --- a/tests/validation/common/ffmpeg_handler/log_constants.py +++ b/tests/validation/common/ffmpeg_handler/log_constants.py @@ -8,22 +8,22 @@ # Required ordered log phrases for FFmpeg Rx validation FFMPEG_RX_REQUIRED_LOG_PHRASES = [ - '[DEBU] JSON client config:', - '[INFO] Media Communications Mesh SDK version', - '[DEBU] JSON conn config:', - '[INFO] gRPC: connection created', - 'INFO - Create memif socket.', - 'INFO - Create memif interface.', - 'INFO - memif connected!', - '[INFO] gRPC: connection active', + "[DEBU] JSON client config:", + "[INFO] Media Communications Mesh SDK version", + "[DEBU] JSON conn config:", + "[INFO] gRPC: connection created", + "INFO - Create memif socket.", + "INFO - Create memif interface.", + "INFO - memif connected!", + "[INFO] gRPC: connection active", ] # Required ordered log phrases for FFmpeg Tx validation FFMPEG_TX_REQUIRED_LOG_PHRASES = [ - '[DEBU] JSON client config:', - '[INFO] Media Communications Mesh SDK version', - '[DEBU] JSON conn config:', - '[DEBU] BUF PARTS', + "[DEBU] JSON client config:", + "[INFO] Media Communications Mesh SDK version", + "[DEBU] JSON conn config:", + "[DEBU] BUF PARTS", ] # Common error keywords to look for in logs @@ -35,5 +35,5 @@ "core dumped", "failed", "FAIL", - "[error]" + "[error]", ] diff --git a/tests/validation/common/log_constants.py b/tests/validation/common/log_constants.py index 1f4d64765..351f45176 100644 --- a/tests/validation/common/log_constants.py +++ b/tests/validation/common/log_constants.py @@ -8,28 +8,28 @@ # Required ordered log phrases for Rx validation RX_REQUIRED_LOG_PHRASES = [ - '[RX] Reading client configuration', - '[RX] Reading connection configuration', - '[DEBU] JSON client config:', - '[INFO] Media Communications Mesh SDK version', - '[DEBU] JSON conn config:', - '[RX] Fetched mesh data buffer', - '[RX] Saving buffer data to a file', - '[RX] Done reading the data', - '[RX] dropping connection to media-proxy', - 'INFO - memif disconnected!', + "[RX] Reading client configuration", + "[RX] Reading connection configuration", + "[DEBU] JSON client config:", + "[INFO] Media Communications Mesh SDK version", + "[DEBU] JSON conn config:", + "[RX] Fetched mesh data buffer", + "[RX] Saving buffer data to a file", + "[RX] Done reading the data", + "[RX] dropping connection to media-proxy", + "INFO - memif disconnected!", ] # Required ordered log phrases for Tx validation TX_REQUIRED_LOG_PHRASES = [ - '[TX] Reading client configuration', - '[TX] Reading connection configuration', - '[DEBU] JSON client config:', - '[INFO] Media Communications Mesh SDK version', - '[DEBU] JSON conn config:', - '[INFO] gRPC: connection created', - 'INFO - Create memif socket.', - 'INFO - Create memif interface.', + "[TX] Reading client configuration", + "[TX] Reading connection configuration", + "[DEBU] JSON client config:", + "[INFO] Media Communications Mesh SDK version", + "[DEBU] JSON conn config:", + "[INFO] gRPC: connection created", + "INFO - Create memif socket.", + "INFO - Create memif interface.", ] # Common error keywords to look for in logs @@ -40,5 +40,5 @@ "segfault", "core dumped", "failed", - "FAIL" + "FAIL", ] diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py index 41359bfef..b4d1cb9a1 100644 --- a/tests/validation/common/log_validation_utils.py +++ b/tests/validation/common/log_validation_utils.py @@ -13,7 +13,9 @@ logger = logging.getLogger(__name__) -def check_phrases_in_order(log_path: str, phrases: List[str]) -> Tuple[bool, List[str], Dict[str, List[str]]]: +def check_phrases_in_order( + log_path: str, phrases: List[str] +) -> Tuple[bool, List[str], Dict[str, List[str]]]: """ Check that all required phrases appear in order in the log file. Returns (all_found, missing_phrases, context_lines) @@ -21,7 +23,7 @@ def check_phrases_in_order(log_path: str, phrases: List[str]) -> Tuple[bool, Lis found_indices = [] missing_phrases = [] lines_around_missing = {} - with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: + with open(log_path, "r", encoding="utf-8", errors="ignore") as f: lines = [line.strip() for line in f] idx = 0 @@ -203,5 +205,5 @@ def output_validator(log_file_path: str, error_keywords: Optional[List[str]] = N return { "is_pass": is_pass, "errors": errors, - "phrase_mismatches": [] # Not used in this simple validator + "phrase_mismatches": [], # Not used in this simple validator } diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index fac402989..e8ecf346e 100644 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -32,7 +32,13 @@ MTL_BUILD_PATH, OPENH264_VERSION_TAG, ) -from Engine.mcm_apps import MediaProxy, MeshAgent, get_mcm_path, get_mtl_path, get_log_folder_path +from Engine.mcm_apps import ( + MediaProxy, + MeshAgent, + get_mcm_path, + get_mtl_path, + get_log_folder_path, +) from datetime import datetime import re diff --git a/tests/validation/functional/cluster/video/test_ffmpeg_video.py b/tests/validation/functional/cluster/video/test_ffmpeg_video.py index 7d47298ce..0ab9e2cff 100644 --- a/tests/validation/functional/cluster/video/test_ffmpeg_video.py +++ b/tests/validation/functional/cluster/video/test_ffmpeg_video.py @@ -20,7 +20,9 @@ @pytest.mark.parametrize("video_type", [k for k in yuv_files.keys()]) -def test_cluster_ffmpeg_video(hosts, media_proxy, test_config, video_type: str, log_path) -> None: +def test_cluster_ffmpeg_video( + hosts, media_proxy, test_config, video_type: str, log_path +) -> None: # Get TX and RX hosts host_list = list(hosts.values()) if len(host_list) < 2: @@ -68,7 +70,9 @@ def test_cluster_ffmpeg_video(hosts, media_proxy, test_config, video_type: str, ) logger.debug(f"Tx command on {tx_host.name}: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor( + tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff + ) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMultipointGroupVideoIO( @@ -95,7 +99,9 @@ def test_cluster_ffmpeg_video(hosts, media_proxy, test_config, video_type: str, ) logger.debug(f"Rx command on {rx_host.name}: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor( + rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff + ) mcm_rx_executor.start() mcm_tx_executor.start() diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index abde2637c..48dd64335 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -34,7 +34,9 @@ *[f for f in audio_files_25_03.keys() if f != "PCM16_48000_Stereo"], ], ) -def test_local_ffmpeg_audio(hosts, test_config, audio_type: str, log_path, media_path) -> None: +def test_local_ffmpeg_audio( + hosts, test_config, audio_type: str, log_path, media_path +) -> None: host_list = list(hosts.values()) if len(host_list) < 1: pytest.skip("Local tests require at least 1 host") @@ -44,9 +46,9 @@ def test_local_ffmpeg_audio(hosts, test_config, audio_type: str, log_path, media prefix_variables = dict(tx_host.topology.extra_info.mcm_prefix_variables) else: prefix_variables = {} - prefix_variables["MCM_MEDIA_PROXY_PORT"] = ( - tx_host.topology.extra_info.media_proxy["sdk_port"] - ) + prefix_variables["MCM_MEDIA_PROXY_PORT"] = tx_host.topology.extra_info.media_proxy[ + "sdk_port" + ] audio_format = audio_file_format_to_format_dict( str(audio_files_25_03[audio_type]["format"]) @@ -83,7 +85,9 @@ def test_local_ffmpeg_audio(hosts, test_config, audio_type: str, log_path, media yes_overwrite=False, ) logger.debug(f"Tx command: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor( + tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff + ) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMemifAudioIO( @@ -108,7 +112,9 @@ def test_local_ffmpeg_audio(hosts, test_config, audio_type: str, log_path, media ) logger.debug(f"Rx command: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor( + rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff + ) mcm_rx_executor.start() mcm_tx_executor.start() diff --git a/tests/validation/functional/local/blob/test_blob_25_03.py b/tests/validation/functional/local/blob/test_blob_25_03.py index a767351c1..385b40fe5 100644 --- a/tests/validation/functional/local/blob/test_blob_25_03.py +++ b/tests/validation/functional/local/blob/test_blob_25_03.py @@ -17,6 +17,7 @@ ) from Engine.media_files import blob_files_25_03 + @pytest.mark.smoke @pytest.mark.parametrize("file", [file for file in blob_files_25_03.keys()]) def test_blob_25_03( diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index 04b2bb2f7..16d967474 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -33,7 +33,9 @@ *[f for f in video_files_25_03.keys() if f != "FullHD_59.94"], ], ) -def test_local_ffmpeg_video(hosts, test_config, file: str, log_path, media_path) -> None: +def test_local_ffmpeg_video( + hosts, test_config, file: str, log_path, media_path +) -> None: host_list = list(hosts.values()) if len(host_list) < 1: pytest.skip("Local tests require at least 1 host") @@ -89,7 +91,9 @@ def test_local_ffmpeg_video(hosts, test_config, file: str, log_path, media_path) ) logger.debug(f"Tx command: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor( + tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff + ) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMemifVideoIO( @@ -116,7 +120,9 @@ def test_local_ffmpeg_video(hosts, test_config, file: str, log_path, media_path) ) logger.debug(f"Rx command: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor( + rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff + ) mcm_rx_executor.start() mcm_tx_executor.start() diff --git a/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py b/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py index c0d008398..9f4d96599 100644 --- a/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py +++ b/tests/validation/functional/st2110/st20/test_ffmpeg_mcm_to_mtl_video.py @@ -94,7 +94,9 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor( + tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff + ) # MTL Rx mtl_rx_inp = FFmpegMtlSt20pRx( @@ -136,7 +138,9 @@ def test_st2110_ffmpeg_mcm_to_mtl_video( yes_overwrite=True, ) logger.debug(f"Rx command executed on {rx_host.name}: {mtl_rx_ff.get_command()}") - mtl_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mtl_rx_ff) + mtl_rx_executor = FFmpegExecutor( + rx_host, log_path=log_path, ffmpeg_instance=mtl_rx_ff + ) time.sleep(2) # wait for media_proxy to start mtl_rx_executor.start() diff --git a/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py b/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py index b3f85111c..b923f16d1 100644 --- a/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py +++ b/tests/validation/functional/st2110/st20/test_ffmpeg_mtl_to_mcm_video.py @@ -32,7 +32,9 @@ @pytest.mark.parametrize("video_type", [k for k in yuv_files.keys()]) -def test_st2110_ffmpeg_video(media_proxy, hosts, test_config, video_type: str, log_path) -> None: +def test_st2110_ffmpeg_video( + media_proxy, hosts, test_config, video_type: str, log_path +) -> None: # media_proxy fixture used only to ensure that the media proxy is running # Get TX and RX hosts host_list = list(hosts.values()) @@ -97,7 +99,9 @@ def test_st2110_ffmpeg_video(media_proxy, hosts, test_config, video_type: str, l yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mtl_tx_ff.get_command()}") - mtl_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mtl_tx_ff) + mtl_tx_executor = FFmpegExecutor( + tx_host, log_path=log_path, ffmpeg_instance=mtl_tx_ff + ) # MCM Rx mcm_rx_inp = FFmpegMcmST2110VideoRx( @@ -129,7 +133,9 @@ def test_st2110_ffmpeg_video(media_proxy, hosts, test_config, video_type: str, l yes_overwrite=True, ) logger.debug(f"Rx command executed on {rx_host.name}: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor( + rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff + ) time.sleep(2) # wait for media_proxy to start mcm_rx_executor.start() diff --git a/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py b/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py index 232f109b7..b045d90e1 100644 --- a/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py +++ b/tests/validation/functional/st2110/st30/test_ffmpeg_mcm_to_mtl_audio.py @@ -109,7 +109,9 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor( + tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff + ) # MTL Rx mtl_rx_inp = FFmpegMtlSt30pRx( # TODO: Verify the variables @@ -148,7 +150,9 @@ def test_st2110_ffmpeg_mcm_to_mtl_audio( yes_overwrite=True, ) logger.debug(f"Rx command executed on {rx_host.name}: {mtl_rx_ff.get_command()}") - mtl_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mtl_rx_ff) + mtl_rx_executor = FFmpegExecutor( + rx_host, log_path=log_path, ffmpeg_instance=mtl_rx_ff + ) time.sleep(2) # wait for media_proxy to start mtl_rx_executor.start() diff --git a/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py b/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py index f793906f4..701f49ca8 100644 --- a/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py +++ b/tests/validation/functional/st2110/st30/test_ffmpeg_mtl_to_mcm_audio.py @@ -115,7 +115,9 @@ def test_st2110_ffmpeg_mtl_to_mcm_audio( yes_overwrite=False, ) logger.debug(f"Tx command executed on {tx_host.name}: {mtl_tx_ff.get_command()}") - mtl_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mtl_tx_ff) + mtl_tx_executor = FFmpegExecutor( + tx_host, log_path=log_path, ffmpeg_instance=mtl_tx_ff + ) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmST2110AudioRx( @@ -145,7 +147,9 @@ def test_st2110_ffmpeg_mtl_to_mcm_audio( yes_overwrite=True, ) logger.debug(f"Rx command executed on {rx_host.name}: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor( + rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff + ) time.sleep(2) # wait for media_proxy to start mcm_rx_executor.start() diff --git a/tests/validation/functional/test_demo.py b/tests/validation/functional/test_demo.py index 3c5f05f2e..320d70ca0 100644 --- a/tests/validation/functional/test_demo.py +++ b/tests/validation/functional/test_demo.py @@ -115,7 +115,9 @@ def test_sudo_command(hosts): logger.info("Sudo command execution test completed") -def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config, log_path) -> None: +def test_demo_local_ffmpeg_video_integrity( + media_proxy, hosts, test_config, log_path +) -> None: # media_proxy fixture used only to ensure that the media proxy is running tx_host = rx_host = list(hosts.values())[0] prefix_variables = test_config.get("prefix_variables", {}) @@ -167,7 +169,9 @@ def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config, log_ ) logger.debug(f"Tx command: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor( + tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff + ) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMemifVideoIO( @@ -196,7 +200,9 @@ def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config, log_ ) logger.debug(f"Rx command: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor( + rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff + ) integrator = FileVideoIntegrityRunner( host=rx_host, @@ -219,7 +225,9 @@ def test_demo_local_ffmpeg_video_integrity(media_proxy, hosts, test_config, log_ assert result, "Integrity check failed" -def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config, log_path) -> None: +def test_demo_local_ffmpeg_video_stream( + media_proxy, hosts, test_config, log_path +) -> None: # media_proxy fixture used only to ensure that the media proxy is running tx_host = rx_host = list(hosts.values())[0] prefix_variables = test_config.get("prefix_variables", {}) @@ -272,7 +280,9 @@ def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config, log_pat ) logger.debug(f"Tx command: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor( + tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff + ) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMemifVideoIO( @@ -302,7 +312,9 @@ def test_demo_local_ffmpeg_video_stream(media_proxy, hosts, test_config, log_pat ) logger.debug(f"Rx command: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor( + rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff + ) integrator = StreamVideoIntegrityRunner( host=rx_host, @@ -505,6 +517,7 @@ def test_build_mtl_ffmpeg(build_mtl_ffmpeg, hosts, test_config): logger.info("Testing MTL FFmpeg build process") assert build_mtl_ffmpeg, "MTL FFmpeg build failed" + def test_simple(log_path_dir, log_path, request): # For this test, log_path will be based on "test_simple" logging.info(f"Log path dir for test_simple: {log_path_dir}") From f79b5f64a793a094153a74da3d0e1857e356f085 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 17:54:14 +0000 Subject: [PATCH 082/123] refactor: improve code formatting and readability in various test files --- .../validation/Engine/rx_tx_app_engine_mcm.py | 16 +++- .../validation/common/log_validation_utils.py | 74 ++++++++++++++----- .../cluster/audio/test_ffmpeg_audio.py | 12 ++- tests/validation/functional/test_demo.py | 2 +- 4 files changed, 76 insertions(+), 28 deletions(-) diff --git a/tests/validation/Engine/rx_tx_app_engine_mcm.py b/tests/validation/Engine/rx_tx_app_engine_mcm.py index f68035261..0343b6664 100644 --- a/tests/validation/Engine/rx_tx_app_engine_mcm.py +++ b/tests/validation/Engine/rx_tx_app_engine_mcm.py @@ -42,7 +42,9 @@ def create_client_json( def create_connection_json( - build: str, rx_tx_app_connection: Engine.rx_tx_app_connection_json.ConnectionJson, log_path: str = "" + build: str, + rx_tx_app_connection: Engine.rx_tx_app_connection_json.ConnectionJson, + log_path: str = "", ) -> None: logger.debug("Connection JSON:") for line in rx_tx_app_connection.to_json().splitlines(): @@ -177,7 +179,9 @@ def start(self): # Use self.log_path for consistent logging across the application log_dir = self.log_path if self.log_path else LOG_FOLDER create_client_json(self.mcm_path, self.rx_tx_app_client_json, log_path=log_dir) - create_connection_json(self.mcm_path, self.rx_tx_app_connection_json, log_path=log_dir) + create_connection_json( + self.mcm_path, self.rx_tx_app_connection_json, log_path=log_dir + ) self._ensure_output_directory_exists() def stop(self): @@ -204,13 +208,17 @@ def stop(self): app_log_validation_status = False app_log_error_count = 0 - # Using common log validation utility from common.log_validation_utils import check_phrases_in_order if self.direction in ("Rx", "Tx"): from common.log_validation_utils import validate_log_file - required_phrases = RX_REQUIRED_LOG_PHRASES if self.direction == "Rx" else TX_REQUIRED_LOG_PHRASES + + required_phrases = ( + RX_REQUIRED_LOG_PHRASES + if self.direction == "Rx" + else TX_REQUIRED_LOG_PHRASES + ) validation_result = validate_log_file( log_file_path, required_phrases, self.direction diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py index b4d1cb9a1..f2cfb6f57 100644 --- a/tests/validation/common/log_validation_utils.py +++ b/tests/validation/common/log_validation_utils.py @@ -13,6 +13,7 @@ logger = logging.getLogger(__name__) + def check_phrases_in_order( log_path: str, phrases: List[str] ) -> Tuple[bool, List[str], Dict[str, List[str]]]: @@ -50,7 +51,10 @@ def check_phrases_in_order( return len(missing_phrases) == 0, missing_phrases, lines_around_missing -def check_for_errors(log_path: str, error_keywords: Optional[List[str]] = None) -> Tuple[bool, List[Dict[str, Any]]]: + +def check_for_errors( + log_path: str, error_keywords: Optional[List[str]] = None +) -> Tuple[bool, List[Dict[str, Any]]]: """ Check the log file for error keywords. @@ -68,30 +72,50 @@ def check_for_errors(log_path: str, error_keywords: Optional[List[str]] = None) errors = [] if not os.path.exists(log_path): logger.error(f"Log file not found: {log_path}") - return False, [{"line": "Log file not found", "line_number": 0, "keyword": "FILE_NOT_FOUND"}] + return False, [ + { + "line": "Log file not found", + "line_number": 0, + "keyword": "FILE_NOT_FOUND", + } + ] try: - with open(log_path, 'r', encoding='utf-8', errors='ignore') as f: + with open(log_path, "r", encoding="utf-8", errors="ignore") as f: for i, line in enumerate(f): for keyword in error_keywords: if keyword.lower() in line.lower(): # Ignore certain false positives - if "ERROR" in keyword and ("NO ERROR" in line.upper() or "NO_ERROR" in line.upper()): + if "ERROR" in keyword and ( + "NO ERROR" in line.upper() or "NO_ERROR" in line.upper() + ): continue - errors.append({ - "line": line.strip(), - "line_number": i + 1, - "keyword": keyword - }) + errors.append( + { + "line": line.strip(), + "line_number": i + 1, + "keyword": keyword, + } + ) break except Exception as e: logger.error(f"Error reading log file: {e}") - return False, [{"line": f"Error reading log file: {e}", "line_number": 0, "keyword": "FILE_READ_ERROR"}] + return False, [ + { + "line": f"Error reading log file: {e}", + "line_number": 0, + "keyword": "FILE_READ_ERROR", + } + ] return len(errors) == 0, errors -def validate_log_file(log_file_path: str, required_phrases: List[str], direction: str = "", - error_keywords: Optional[List[str]] = None) -> Dict: +def validate_log_file( + log_file_path: str, + required_phrases: List[str], + direction: str = "", + error_keywords: Optional[List[str]] = None, +) -> Dict: """ Validate log file for required phrases and return validation information. @@ -115,7 +139,9 @@ def validate_log_file(log_file_path: str, required_phrases: List[str], direction validation_info = [] # Phrase order validation - log_pass, missing, context_lines = check_phrases_in_order(log_file_path, required_phrases) + log_pass, missing, context_lines = check_phrases_in_order( + log_file_path, required_phrases + ) error_count = len(missing) # Error keyword validation (optional) @@ -139,7 +165,7 @@ def validate_log_file(log_file_path: str, required_phrases: List[str], direction if not log_pass: validation_info.append(f"Missing or out-of-order phrases analysis:") for phrase in missing: - validation_info.append(f"\n Expected phrase: \"{phrase}\"") + validation_info.append(f'\n Expected phrase: "{phrase}"') validation_info.append(f" Context in log file:") if phrase in context_lines: for line in context_lines[phrase]: @@ -147,13 +173,17 @@ def validate_log_file(log_file_path: str, required_phrases: List[str], direction else: validation_info.append(" ") if missing: - logger.warning(f"{dir_prefix}process did not pass. First missing phrase: {missing[0]}") + logger.warning( + f"{dir_prefix}process did not pass. First missing phrase: {missing[0]}" + ) # Error keywords info if errors: validation_info.append(f"\nError keywords found:") for error in errors: - validation_info.append(f" Line {error['line_number']} - {error['keyword']}: {error['line']}") + validation_info.append( + f" Line {error['line_number']} - {error['keyword']}: {error['line']}" + ) return { "is_pass": is_pass, @@ -161,10 +191,12 @@ def validate_log_file(log_file_path: str, required_phrases: List[str], direction "validation_info": validation_info, "missing_phrases": missing, "context_lines": context_lines, - "errors": errors + "errors": errors, } -def save_validation_report(report_path: str, validation_info: List[str], overall_status: bool) -> None: +def save_validation_report( + report_path: str, validation_info: List[str], overall_status: bool +) -> None: """ Save validation report to a file. @@ -175,7 +207,7 @@ def save_validation_report(report_path: str, validation_info: List[str], overall """ try: os.makedirs(os.path.dirname(report_path), exist_ok=True) - with open(report_path, 'w', encoding='utf-8') as f: + with open(report_path, "w", encoding="utf-8") as f: for line in validation_info: f.write(f"{line}\n") f.write(f"\n=== Overall Validation Summary ===\n") @@ -184,7 +216,9 @@ def save_validation_report(report_path: str, validation_info: List[str], overall except Exception as e: logger.error(f"Error saving validation report: {e}") -def output_validator(log_file_path: str, error_keywords: Optional[List[str]] = None) -> Dict[str, Any]: +def output_validator( + log_file_path: str, error_keywords: Optional[List[str]] = None +) -> Dict[str, Any]: """ Simple validator that checks for error keywords in a log file. diff --git a/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py b/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py index 3a5b25d00..346b6c6a4 100644 --- a/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/cluster/audio/test_ffmpeg_audio.py @@ -23,7 +23,9 @@ @pytest.mark.parametrize("audio_type", [file for file in audio_files.keys()]) -def test_cluster_ffmpeg_audio(hosts, media_proxy, test_config, audio_type: str, log_path) -> None: +def test_cluster_ffmpeg_audio( + hosts, media_proxy, test_config, audio_type: str, log_path +) -> None: # Get TX and RX hosts host_list = list(hosts.values()) if len(host_list) < 2: @@ -75,7 +77,9 @@ def test_cluster_ffmpeg_audio(hosts, media_proxy, test_config, audio_type: str, ) logger.debug(f"Tx command on {tx_host.name}: {mcm_tx_ff.get_command()}") - mcm_tx_executor = FFmpegExecutor(tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff) + mcm_tx_executor = FFmpegExecutor( + tx_host, log_path=log_path, ffmpeg_instance=mcm_tx_ff + ) # >>>>> MCM Rx mcm_rx_inp = FFmpegMcmMultipointGroupAudioIO( @@ -100,7 +104,9 @@ def test_cluster_ffmpeg_audio(hosts, media_proxy, test_config, audio_type: str, ) logger.debug(f"Rx command on {rx_host.name}: {mcm_rx_ff.get_command()}") - mcm_rx_executor = FFmpegExecutor(rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff) + mcm_rx_executor = FFmpegExecutor( + rx_host, log_path=log_path, ffmpeg_instance=mcm_rx_ff + ) mcm_rx_executor.start() mcm_tx_executor.start() diff --git a/tests/validation/functional/test_demo.py b/tests/validation/functional/test_demo.py index 320d70ca0..6d8933e12 100644 --- a/tests/validation/functional/test_demo.py +++ b/tests/validation/functional/test_demo.py @@ -522,4 +522,4 @@ def test_simple(log_path_dir, log_path, request): # For this test, log_path will be based on "test_simple" logging.info(f"Log path dir for test_simple: {log_path_dir}") logging.info(f"Log path for test_simple: {log_path}") - logging.info(f"Request: {request.node.name}") \ No newline at end of file + logging.info(f"Request: {request.node.name}") From c38c0017923a132bb6f437b031629a87c5d45b93 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 27 Aug 2025 17:57:41 +0000 Subject: [PATCH 083/123] linter fix --- tests/validation/common/log_validation_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py index f2cfb6f57..4412452e3 100644 --- a/tests/validation/common/log_validation_utils.py +++ b/tests/validation/common/log_validation_utils.py @@ -110,6 +110,7 @@ def check_for_errors( return len(errors) == 0, errors + def validate_log_file( log_file_path: str, required_phrases: List[str], @@ -194,6 +195,7 @@ def validate_log_file( "errors": errors, } + def save_validation_report( report_path: str, validation_info: List[str], overall_status: bool ) -> None: @@ -216,6 +218,7 @@ def save_validation_report( except Exception as e: logger.error(f"Error saving validation report: {e}") + def output_validator( log_file_path: str, error_keywords: Optional[List[str]] = None ) -> Dict[str, Any]: From 82e0dba962e896818c0ffd1bbca74f6edfe920d7 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 09:35:48 +0000 Subject: [PATCH 084/123] specify host to runner-4] --- .github/workflows/bare-metal-build.yml | 2 +- .github/workflows/smoke_tests.yml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 8a410f452..1189dc760 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -33,7 +33,7 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: [Linux, self-hosted] + runs-on: [Linux, self-hosted, X64, runner-4] timeout-minutes: 120 steps: - name: "Harden Runner" diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index e434c8421..de64d2e5f 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -64,7 +64,7 @@ jobs: tag: ${{ github.event.inputs.tag-to-checkout }} validation-prepare-setup-mcm: - runs-on: [Linux, self-hosted] + runs-on: [Linux, self-hosted, X64, runner-4] needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -98,6 +98,7 @@ jobs: - name: "build RxTxApp" working-directory: ${{ github.workspace }}/tests/tools/TestApp run: | + rm -rf build && \ mkdir build && cd build && \ cmake .. && \ make @@ -157,7 +158,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: [Linux, self-hosted] + runs-on: [Linux, self-hosted, X64, runner-4] timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From cee4b77f5fd5827a96604aeb2ff786ae6cb34192 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 09:47:51 +0000 Subject: [PATCH 085/123] remove x64 --- .github/actionlint.yaml | 2 ++ .github/workflows/bare-metal-build.yml | 2 +- .github/workflows/smoke_tests.yml | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 .github/actionlint.yaml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 000000000..3d2c2ce21 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,2 @@ +runner-labels: + - runner-4 diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 1189dc760..840fb4838 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -33,7 +33,7 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: [Linux, self-hosted, X64, runner-4] + runs-on: [linux, self-hosted, runner-4] timeout-minutes: 120 steps: - name: "Harden Runner" diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index de64d2e5f..cb2c1a45a 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -64,7 +64,7 @@ jobs: tag: ${{ github.event.inputs.tag-to-checkout }} validation-prepare-setup-mcm: - runs-on: [Linux, self-hosted, X64, runner-4] + runs-on: [linux, self-hosted, runner-4] needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -158,7 +158,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: [Linux, self-hosted, X64, runner-4] + runs-on: [linux, self-hosted, runner-4] timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From facff74ebf81879a363b6caed1d1cf09cdd4135a Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 09:49:28 +0000 Subject: [PATCH 086/123] keep only runs-on: [runner-4] --- .github/workflows/bare-metal-build.yml | 2 +- .github/workflows/smoke_tests.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 840fb4838..4d37392d6 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -33,7 +33,7 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: [linux, self-hosted, runner-4] + runs-on: [runner-4] timeout-minutes: 120 steps: - name: "Harden Runner" diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index cb2c1a45a..8b3998bcf 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -64,7 +64,7 @@ jobs: tag: ${{ github.event.inputs.tag-to-checkout }} validation-prepare-setup-mcm: - runs-on: [linux, self-hosted, runner-4] + runs-on: [runner-4] needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -158,7 +158,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: [linux, self-hosted, runner-4] + runs-on: [runner-4] timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From 0b9894fa2b6c9d7b2aeba7c49b51e3f00e746587 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 10:17:02 +0000 Subject: [PATCH 087/123] chore: add concurrency settings and ensure jobs run on runner-4 --- .github/workflows/smoke_tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 8b3998bcf..10a261bab 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -55,9 +55,14 @@ env: permissions: contents: read +concurrency: + group: smoke-tests-runner-4-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: call-bare-metal-build: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository + runs-on: [runner-4] uses: ./.github/workflows/bare-metal-build.yml with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} From a22869c59eda44a6f029de734882b7ee4b0b12cc Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 10:24:00 +0000 Subject: [PATCH 088/123] remove ssh key root setup --- .github/workflows/smoke_tests.yml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 10a261bab..0312ef6ce 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -124,26 +124,6 @@ jobs: source venv/bin/activate pip install -r requirements.txt echo "VIRTUAL_ENV=$PWD/venv/bin/activate" >> "$GITHUB_ENV" - - name: "ssh key root setup" - run: | - # Ensure root .ssh directory exists - sudo mkdir -p /root/.ssh - sudo chmod 700 /root/.ssh - - # Copy the public key to root's authorized_keys - if [ -f "/home/${USER}/.ssh/mcm_key.pub" ]; then - sudo cp "/home/${USER}/.ssh/mcm_key.pub" /root/.ssh/authorized_keys - elif [ -f "/home/${USER}/.ssh/mcm_key" ]; then - # Generate public key from private key if public key doesn't exist - ssh-keygen -y -f "/home/${USER}/.ssh/mcm_key" | sudo tee /root/.ssh/authorized_keys - fi - - # Set proper permissions - sudo chmod 600 /root/.ssh/authorized_keys - sudo chown root:root /root/.ssh/authorized_keys - - # Ensure the private key has correct permissions for gta user - chmod 600 "/home/${USER}/.ssh/mcm_key" - name: "add user name to environment and config" run: | From e06f9ec0da2d925e3222fc0422b0d4f8c262d196 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 10:32:12 +0000 Subject: [PATCH 089/123] chore: update runner input for bare-metal build and streamline smoke tests --- .github/workflows/bare-metal-build.yml | 26 +++++++++++++++++++++++++- .github/workflows/smoke_tests.yml | 21 ++------------------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 4d37392d6..806a42017 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -12,6 +12,11 @@ on: required: false type: string description: "Tag to checkout" + runner: + required: false + type: string + default: "runner-4" + description: "Runner to use for the build job" env: BUILD_TYPE: Release @@ -33,7 +38,7 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: [runner-4] + runs-on: [${{ inputs.runner }}] timeout-minutes: 120 steps: - name: "Harden Runner" @@ -153,6 +158,25 @@ jobs: ./configure-ffmpeg.sh "7.0" --prefix=${{ env.BUILD_DIR }}/ffmpeg-7-0 --disable-doc --disable-debug && \ ./build-ffmpeg.sh "7.0" + - name: Install RxTxApp dependencies + run: sudo apt-get update && sudo apt-get install -y libjansson-dev + - name: "build RxTxApp" + working-directory: ${{ github.workspace }}/tests/tools/TestApp + run: | + rm -rf build && \ + mkdir build && cd build && \ + cmake .. && \ + make + - name: "clone FFMPEG repository" + run: | + echo "Cloning FFMPEG repository" + - name: "clone MTL repository" + run: | + echo "Cloning MTL repository" + - name: "build MTL FFMPEG" + run: | + echo "Building MTL FFMPEG" + - name: "upload media-proxy and mcm binaries" uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 0312ef6ce..6616fcae1 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -62,11 +62,11 @@ concurrency: jobs: call-bare-metal-build: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository - runs-on: [runner-4] uses: ./.github/workflows/bare-metal-build.yml with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} tag: ${{ github.event.inputs.tag-to-checkout }} + runner: runner-4 validation-prepare-setup-mcm: runs-on: [runner-4] @@ -98,24 +98,7 @@ jobs: ls -la ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* || true ls -la ${{ env.BUILD_DIR }}/ffmpeg-6-1/ || true ls -la ${{ env.BUILD_DIR }}/ffmpeg-7-0/ || true - - name: Install RxTxApp dependencies - run: sudo apt-get update && sudo apt-get install -y libjansson-dev - - name: "build RxTxApp" - working-directory: ${{ github.workspace }}/tests/tools/TestApp - run: | - rm -rf build && \ - mkdir build && cd build && \ - cmake .. && \ - make - - name: "clone FFMPEG repository" - run: | - echo "Cloning FFMPEG repository" - - name: "clone MTL repository" - run: | - echo "Cloning MTL repository" - - name: "build MTL FFMPEG" - run: | - echo "Building MTL FFMPEG" + - name: "installation: Install pipenv environment" working-directory: tests/validation id: pipenv-install From 9268b2c92b1a291ca32f9323d53874c794b89275 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 10:34:10 +0000 Subject: [PATCH 090/123] fix build-baremetal-ubuntu --- .github/workflows/bare-metal-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bare-metal-build.yml b/.github/workflows/bare-metal-build.yml index 806a42017..f96ffce35 100644 --- a/.github/workflows/bare-metal-build.yml +++ b/.github/workflows/bare-metal-build.yml @@ -38,7 +38,7 @@ concurrency: jobs: build-baremetal-ubuntu: - runs-on: [${{ inputs.runner }}] + runs-on: ${{ inputs.runner }} timeout-minutes: 120 steps: - name: "Harden Runner" From 7d13ab4f9b2f190dcd8723e48a35875279378839 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 10:53:51 +0000 Subject: [PATCH 091/123] linter update --- .github/configs/.actionlint.yaml | 2 ++ .github/workflows/smoke_tests.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 .github/configs/.actionlint.yaml diff --git a/.github/configs/.actionlint.yaml b/.github/configs/.actionlint.yaml new file mode 100644 index 000000000..74ffc43a5 --- /dev/null +++ b/.github/configs/.actionlint.yaml @@ -0,0 +1,2 @@ +runner-labels: + - "runner-4" \ No newline at end of file diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 6616fcae1..bda4b57f9 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -112,7 +112,7 @@ jobs: run: | echo "USER=${USER}" >> "$GITHUB_ENV" sed -i "s/{{ USER }}/root/g" tests/validation/configs/topology_config_workflow.yaml - sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/mcm_key|g" tests/validation/configs/topology_config_workflow.yaml + sed -i "s|{{ KEY_PATH }}|/home/${USER}/.ssh/id_rsa|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ INTEGRITY_PATH }}|${{ env.INTEGRITY_PATH }}|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ OUTPUT_PATH }}|${{ env.OUTPUT_PATH }}|g" tests/validation/configs/topology_config_workflow.yaml sed -i "s|{{ INPUT_PATH }}|${{ env.INPUT_PATH }}|g" tests/validation/configs/topology_config_workflow.yaml From 6197855869d888d6fa96c394ddff5b2d23b58e2c Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 11:35:46 +0000 Subject: [PATCH 092/123] refactor: enhance log validation to support non-strict phrase checking --- .../validation/Engine/rx_tx_app_engine_mcm.py | 2 +- .../validation/common/log_validation_utils.py | 46 +++++++++++++++++-- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/tests/validation/Engine/rx_tx_app_engine_mcm.py b/tests/validation/Engine/rx_tx_app_engine_mcm.py index 0343b6664..5a13865c5 100644 --- a/tests/validation/Engine/rx_tx_app_engine_mcm.py +++ b/tests/validation/Engine/rx_tx_app_engine_mcm.py @@ -221,7 +221,7 @@ def stop(self): ) validation_result = validate_log_file( - log_file_path, required_phrases, self.direction + log_file_path, required_phrases, self.direction, strict_order=False ) self.is_pass = validation_result["is_pass"] diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py index 4412452e3..3540fe6fd 100644 --- a/tests/validation/common/log_validation_utils.py +++ b/tests/validation/common/log_validation_utils.py @@ -116,15 +116,17 @@ def validate_log_file( required_phrases: List[str], direction: str = "", error_keywords: Optional[List[str]] = None, + strict_order: bool = False, ) -> Dict: """ Validate log file for required phrases and return validation information. Args: log_file_path: Path to the log file - required_phrases: List of phrases to check for in order + required_phrases: List of phrases to check for direction: Optional string to identify the direction (e.g., 'Rx', 'Tx') error_keywords: Optional list of error keywords to check for + strict_order: Whether to check phrases in strict order (default: False) Returns: Dictionary containing validation results: @@ -139,10 +141,15 @@ def validate_log_file( """ validation_info = [] - # Phrase order validation - log_pass, missing, context_lines = check_phrases_in_order( - log_file_path, required_phrases - ) + # Phrase validation (either ordered or anywhere) + if strict_order: + log_pass, missing, context_lines = check_phrases_in_order( + log_file_path, required_phrases + ) + else: + log_pass, missing, context_lines = check_phrases_anywhere( + log_file_path, required_phrases + ) error_count = len(missing) # Error keyword validation (optional) @@ -219,6 +226,35 @@ def save_validation_report( logger.error(f"Error saving validation report: {e}") +def check_phrases_anywhere( + log_path: str, phrases: List[str] +) -> Tuple[bool, List[str], Dict[str, List[str]]]: + """ + Check that all required phrases appear anywhere in the log file, regardless of order. + Returns (all_found, missing_phrases, context_lines) + """ + missing_phrases = [] + lines_around_missing = {} + + try: + with open(log_path, "r", encoding="utf-8", errors="ignore") as f: + content = f.read() + lines = content.split('\n') + + for phrase in phrases: + if phrase not in content: + missing_phrases.append(phrase) + # Find where the phrase should have appeared + # Just give some context from the end of the log + context_end = len(lines) + context_start = max(0, context_end - 10) + lines_around_missing[phrase] = lines[context_start:context_end] + except Exception as e: + logger.error(f"Error reading log file {log_path}: {e}") + return False, phrases, {"error": [f"Error reading log file: {e}"]} + + return len(missing_phrases) == 0, missing_phrases, lines_around_missing + def output_validator( log_file_path: str, error_keywords: Optional[List[str]] = None ) -> Dict[str, Any]: From 5d39ae17e5f29586bd73e6629992f80e36e28b1a Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 11:41:17 +0000 Subject: [PATCH 093/123] chore: add actionlint configuration files and update linter workflow --- .actionlintrc.yaml | 9 +++++++++ .github/actionlint.yaml | 7 +++++++ .github/configs/actionlint.yaml | 9 +++++++++ .github/workflows/linter.yml | 1 + 4 files changed, 26 insertions(+) create mode 100644 .actionlintrc.yaml create mode 100644 .github/configs/actionlint.yaml diff --git a/.actionlintrc.yaml b/.actionlintrc.yaml new file mode 100644 index 000000000..6f8927018 --- /dev/null +++ b/.actionlintrc.yaml @@ -0,0 +1,9 @@ +runner-labels: + - runner-4 + - self-hosted + - x64 + - linux + - windows + - macos + - arm + - arm64 diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 3d2c2ce21..6f8927018 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -1,2 +1,9 @@ runner-labels: - runner-4 + - self-hosted + - x64 + - linux + - windows + - macos + - arm + - arm64 diff --git a/.github/configs/actionlint.yaml b/.github/configs/actionlint.yaml new file mode 100644 index 000000000..6f8927018 --- /dev/null +++ b/.github/configs/actionlint.yaml @@ -0,0 +1,9 @@ +runner-labels: + - runner-4 + - self-hosted + - x64 + - linux + - windows + - macos + - arm + - arm64 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 9ae32d3e2..41949cb7a 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -46,6 +46,7 @@ jobs: VALIDATE_CPP: true VALIDATE_GO: true VALIDATE_GITHUB_ACTIONS: true + ACTIONLINT_CONFIG_PATH: ".github/actionlint.yaml" VALIDATE_GROOVY: true VALIDATE_JSON_PRETTIER: true VALIDATE_JSONC_PRETTIER: true From c56e3610a69388ab927dc723739c084329bce22b Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 13:16:09 +0000 Subject: [PATCH 094/123] fix: update runner configuration to remove array syntax --- .github/workflows/smoke_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index bda4b57f9..084a85972 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -69,7 +69,7 @@ jobs: runner: runner-4 validation-prepare-setup-mcm: - runs-on: [runner-4] + runs-on: runner-4 needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -126,7 +126,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: [runner-4] + runs-on: runner-4 timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From 64f3c610c874c7e1e1568d3a9603efa165e80025 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 13:22:30 +0000 Subject: [PATCH 095/123] runner --- .github/workflows/smoke_tests.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 084a85972..d017265fc 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -34,6 +34,11 @@ on: default: "smoke" required: false description: "Markers to use for pytest" + runner: + required: false + type: string + default: "runner-4" + description: "Runner to use for the build job" env: BUILD_TYPE: "Release" @@ -69,7 +74,7 @@ jobs: runner: runner-4 validation-prepare-setup-mcm: - runs-on: runner-4 + runs-on: ${{ inputs.runner }} needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -126,7 +131,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: runner-4 + runs-on: ${{ inputs.runner }} timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From ec94956abbd274f075d81aef4b6405e0b1c0d2b2 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 13:36:46 +0000 Subject: [PATCH 096/123] fix: clean up whitespace and improve readability in log validation utility --- tests/validation/common/log_validation_utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py index 3540fe6fd..346c591fd 100644 --- a/tests/validation/common/log_validation_utils.py +++ b/tests/validation/common/log_validation_utils.py @@ -235,11 +235,11 @@ def check_phrases_anywhere( """ missing_phrases = [] lines_around_missing = {} - + try: with open(log_path, "r", encoding="utf-8", errors="ignore") as f: content = f.read() - lines = content.split('\n') + lines = content.split("\n") for phrase in phrases: if phrase not in content: @@ -252,9 +252,10 @@ def check_phrases_anywhere( except Exception as e: logger.error(f"Error reading log file {log_path}: {e}") return False, phrases, {"error": [f"Error reading log file: {e}"]} - + return len(missing_phrases) == 0, missing_phrases, lines_around_missing + def output_validator( log_file_path: str, error_keywords: Optional[List[str]] = None ) -> Dict[str, Any]: From 3d9d232f7f608a465abaec0c43af9c90c0f4812c Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 13:42:12 +0000 Subject: [PATCH 097/123] fix: update runner configuration to use input variable and clean up whitespace in log validation utility --- .github/workflows/smoke_tests.yml | 2 +- tests/validation/common/log_validation_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index d017265fc..9617e8f5b 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -71,7 +71,7 @@ jobs: with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} tag: ${{ github.event.inputs.tag-to-checkout }} - runner: runner-4 + runner: ${{ inputs.runner }} validation-prepare-setup-mcm: runs-on: ${{ inputs.runner }} diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py index 346c591fd..eed0c8e8a 100644 --- a/tests/validation/common/log_validation_utils.py +++ b/tests/validation/common/log_validation_utils.py @@ -240,7 +240,7 @@ def check_phrases_anywhere( with open(log_path, "r", encoding="utf-8", errors="ignore") as f: content = f.read() lines = content.split("\n") - + for phrase in phrases: if phrase not in content: missing_phrases.append(phrase) From d3f967f39664eef4388877e946e33240285099fc Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 13:45:34 +0000 Subject: [PATCH 098/123] fix --- .github/workflows/smoke_tests.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 9617e8f5b..fb3ff183b 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -34,11 +34,6 @@ on: default: "smoke" required: false description: "Markers to use for pytest" - runner: - required: false - type: string - default: "runner-4" - description: "Runner to use for the build job" env: BUILD_TYPE: "Release" @@ -71,10 +66,10 @@ jobs: with: branch: ${{ github.event_name == 'push' && github.ref_name || github.event.inputs.branch-to-checkout || 'main' }} tag: ${{ github.event.inputs.tag-to-checkout }} - runner: ${{ inputs.runner }} + runner: runner-4 validation-prepare-setup-mcm: - runs-on: ${{ inputs.runner }} + runs-on: [self-hosted, runner-4] needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -131,7 +126,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: ${{ inputs.runner }} + runs-on: [self-hosted, runner-4] timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From 797943de6330a73f4cc2fcd5c4e1626f66f6ed5f Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 13:51:57 +0000 Subject: [PATCH 099/123] fix: update self-hosted runner configuration to streamline labels --- .github/actionlint.yaml | 14 +++++--------- .github/configs/.actionlint.yaml | 7 +++++-- .github/configs/actionlint.yaml | 14 +++++--------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 6f8927018..fbc4f3bcf 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -1,9 +1,5 @@ -runner-labels: - - runner-4 - - self-hosted - - x64 - - linux - - windows - - macos - - arm - - arm64 +self-hosted-runner: + labels: + - DPDK + - runner-1 + - runner-4 \ No newline at end of file diff --git a/.github/configs/.actionlint.yaml b/.github/configs/.actionlint.yaml index 74ffc43a5..fbc4f3bcf 100644 --- a/.github/configs/.actionlint.yaml +++ b/.github/configs/.actionlint.yaml @@ -1,2 +1,5 @@ -runner-labels: - - "runner-4" \ No newline at end of file +self-hosted-runner: + labels: + - DPDK + - runner-1 + - runner-4 \ No newline at end of file diff --git a/.github/configs/actionlint.yaml b/.github/configs/actionlint.yaml index 6f8927018..fbc4f3bcf 100644 --- a/.github/configs/actionlint.yaml +++ b/.github/configs/actionlint.yaml @@ -1,9 +1,5 @@ -runner-labels: - - runner-4 - - self-hosted - - x64 - - linux - - windows - - macos - - arm - - arm64 +self-hosted-runner: + labels: + - DPDK + - runner-1 + - runner-4 \ No newline at end of file From a5ebb33cf48b1ea0f25910a5059d098ade8a41c7 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 14:14:17 +0000 Subject: [PATCH 100/123] runs-on: ["self-hosted", "runner-4"] --- .github/workflows/smoke_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index fb3ff183b..14d1028a4 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -69,7 +69,7 @@ jobs: runner: runner-4 validation-prepare-setup-mcm: - runs-on: [self-hosted, runner-4] + runs-on: ["self-hosted", "runner-4"] needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -126,7 +126,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: [self-hosted, runner-4] + runs-on: ["self-hosted", "runner-4"] timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From da9c654e997ab87de0fb90818a5b0b4b6b6fc7c7 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 14:34:26 +0000 Subject: [PATCH 101/123] runs-on: [Linux, self-hosted, DPDK] --- .github/workflows/smoke_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 14d1028a4..3ba1df36a 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -69,7 +69,7 @@ jobs: runner: runner-4 validation-prepare-setup-mcm: - runs-on: ["self-hosted", "runner-4"] + runs-on: [Linux, self-hosted, DPDK] needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -126,7 +126,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: ["self-hosted", "runner-4"] + runs-on: [Linux, self-hosted, DPDK] timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From a555bbea0541a9a2a8bd6eb32f7e837fcfa39919 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 14:43:45 +0000 Subject: [PATCH 102/123] fix --- .github/workflows/linter.yml | 2 +- .github/workflows/smoke_tests.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 41949cb7a..2f701a14f 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -46,7 +46,7 @@ jobs: VALIDATE_CPP: true VALIDATE_GO: true VALIDATE_GITHUB_ACTIONS: true - ACTIONLINT_CONFIG_PATH: ".github/actionlint.yaml" + ACTIONLINT_CONFIG_PATH: ".github/configs/actionlint.yaml" VALIDATE_GROOVY: true VALIDATE_JSON_PRETTIER: true VALIDATE_JSONC_PRETTIER: true diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 3ba1df36a..fb3ff183b 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -69,7 +69,7 @@ jobs: runner: runner-4 validation-prepare-setup-mcm: - runs-on: [Linux, self-hosted, DPDK] + runs-on: [self-hosted, runner-4] needs: call-bare-metal-build timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository @@ -126,7 +126,7 @@ jobs: validation-run-tests: needs: validation-prepare-setup-mcm - runs-on: [Linux, self-hosted, DPDK] + runs-on: [self-hosted, runner-4] timeout-minutes: 60 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository env: From 7f9f4a8d1d319186427a4533ae6aba8cf66ba5cf Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 14:52:33 +0000 Subject: [PATCH 103/123] fix --- .github/actionlint.yaml | 5 ++++- .github/configs/.actionlint.yaml | 5 ++++- .github/configs/actionlint.yaml | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index fbc4f3bcf..f1571a285 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -2,4 +2,7 @@ self-hosted-runner: labels: - DPDK - runner-1 - - runner-4 \ No newline at end of file + - runner-4 +runner-label: + - runner-4 + - runner-1 \ No newline at end of file diff --git a/.github/configs/.actionlint.yaml b/.github/configs/.actionlint.yaml index fbc4f3bcf..f1571a285 100644 --- a/.github/configs/.actionlint.yaml +++ b/.github/configs/.actionlint.yaml @@ -2,4 +2,7 @@ self-hosted-runner: labels: - DPDK - runner-1 - - runner-4 \ No newline at end of file + - runner-4 +runner-label: + - runner-4 + - runner-1 \ No newline at end of file diff --git a/.github/configs/actionlint.yaml b/.github/configs/actionlint.yaml index fbc4f3bcf..f1571a285 100644 --- a/.github/configs/actionlint.yaml +++ b/.github/configs/actionlint.yaml @@ -2,4 +2,7 @@ self-hosted-runner: labels: - DPDK - runner-1 - - runner-4 \ No newline at end of file + - runner-4 +runner-label: + - runner-4 + - runner-1 \ No newline at end of file From 35a63e55c0240db64c0663bd81aa0167563babea Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Thu, 28 Aug 2025 14:57:06 +0000 Subject: [PATCH 104/123] fix: remove unused actionlint configuration and update linter workflow --- .github/configs/actionlint.yaml | 8 -------- .github/workflows/linter.yml | 1 - 2 files changed, 9 deletions(-) delete mode 100644 .github/configs/actionlint.yaml diff --git a/.github/configs/actionlint.yaml b/.github/configs/actionlint.yaml deleted file mode 100644 index f1571a285..000000000 --- a/.github/configs/actionlint.yaml +++ /dev/null @@ -1,8 +0,0 @@ -self-hosted-runner: - labels: - - DPDK - - runner-1 - - runner-4 -runner-label: - - runner-4 - - runner-1 \ No newline at end of file diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 2f701a14f..9ae32d3e2 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -46,7 +46,6 @@ jobs: VALIDATE_CPP: true VALIDATE_GO: true VALIDATE_GITHUB_ACTIONS: true - ACTIONLINT_CONFIG_PATH: ".github/configs/actionlint.yaml" VALIDATE_GROOVY: true VALIDATE_JSON_PRETTIER: true VALIDATE_JSONC_PRETTIER: true From dabee083a30dce2548c90ac7f7c564cd5ed6fda2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Staszczuk?= <41273017+staszczuk@users.noreply.github.com> Date: Fri, 29 Aug 2025 11:07:18 +0200 Subject: [PATCH 105/123] Create actionlint.yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Staszczuk <41273017+staszczuk@users.noreply.github.com> --- .github/linters/actionlint.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/linters/actionlint.yaml diff --git a/.github/linters/actionlint.yaml b/.github/linters/actionlint.yaml new file mode 100644 index 000000000..345883997 --- /dev/null +++ b/.github/linters/actionlint.yaml @@ -0,0 +1,5 @@ +self-hosted-runner: + labels: + - DPDK + - runner-1 + - runner-4 From fc18f1ff44a774d5317db7c5caeee897ed8d2798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Staszczuk?= <41273017+staszczuk@users.noreply.github.com> Date: Fri, 29 Aug 2025 11:19:09 +0200 Subject: [PATCH 106/123] Rename actionlint.yaml to actionlint.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Staszczuk <41273017+staszczuk@users.noreply.github.com> --- .github/linters/{actionlint.yaml => actionlint.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/linters/{actionlint.yaml => actionlint.yml} (100%) diff --git a/.github/linters/actionlint.yaml b/.github/linters/actionlint.yml similarity index 100% rename from .github/linters/actionlint.yaml rename to .github/linters/actionlint.yml From 97e9812f43175764915a48bfcc6c25606ce1016a Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Fri, 29 Aug 2025 12:56:00 +0000 Subject: [PATCH 107/123] fix: update runner labels and configuration for actionlint --- .actionlintrc.yaml | 4 +++- .github/configs/actionlint.yml | 6 ++++++ .github/linters/actionlint.yml | 1 + .github/workflows/linter.yml | 1 + 4 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .github/configs/actionlint.yml diff --git a/.actionlintrc.yaml b/.actionlintrc.yaml index 6f8927018..a3ab092fb 100644 --- a/.actionlintrc.yaml +++ b/.actionlintrc.yaml @@ -1,5 +1,7 @@ -runner-labels: +runner-label: - runner-4 + - runner-1 + - DPDK - self-hosted - x64 - linux diff --git a/.github/configs/actionlint.yml b/.github/configs/actionlint.yml new file mode 100644 index 000000000..6f4bbed1c --- /dev/null +++ b/.github/configs/actionlint.yml @@ -0,0 +1,6 @@ +self-hosted-runner: + # Labels of self-hosted runner in array of strings. + labels: + - DPDK + - runner-1 + - runner-4 diff --git a/.github/linters/actionlint.yml b/.github/linters/actionlint.yml index 345883997..6f4bbed1c 100644 --- a/.github/linters/actionlint.yml +++ b/.github/linters/actionlint.yml @@ -1,4 +1,5 @@ self-hosted-runner: + # Labels of self-hosted runner in array of strings. labels: - DPDK - runner-1 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 9ae32d3e2..b225f6533 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -38,6 +38,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BASH_SEVERITY: "warning" LINTER_RULES_PATH: ".github/configs" + GITHUB_ACTIONS_CONFIG_FILE: "actionlint.yml" GITHUB_COMMENT_ON_PR: false GITHUB_STATUS_UPDATES: false VALIDATE_BASH_EXEC: true From a30457a91423b5217cf2c416ceac31370e51e45a Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Fri, 29 Aug 2025 13:03:00 +0000 Subject: [PATCH 108/123] remove unecessary actionlint --- .actionlintrc.yaml | 11 ----------- .github/actionlint.yaml | 8 -------- .github/configs/.actionlint.yaml | 8 -------- .github/linters/actionlint.yml | 6 ------ 4 files changed, 33 deletions(-) delete mode 100644 .actionlintrc.yaml delete mode 100644 .github/actionlint.yaml delete mode 100644 .github/configs/.actionlint.yaml delete mode 100644 .github/linters/actionlint.yml diff --git a/.actionlintrc.yaml b/.actionlintrc.yaml deleted file mode 100644 index a3ab092fb..000000000 --- a/.actionlintrc.yaml +++ /dev/null @@ -1,11 +0,0 @@ -runner-label: - - runner-4 - - runner-1 - - DPDK - - self-hosted - - x64 - - linux - - windows - - macos - - arm - - arm64 diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml deleted file mode 100644 index f1571a285..000000000 --- a/.github/actionlint.yaml +++ /dev/null @@ -1,8 +0,0 @@ -self-hosted-runner: - labels: - - DPDK - - runner-1 - - runner-4 -runner-label: - - runner-4 - - runner-1 \ No newline at end of file diff --git a/.github/configs/.actionlint.yaml b/.github/configs/.actionlint.yaml deleted file mode 100644 index f1571a285..000000000 --- a/.github/configs/.actionlint.yaml +++ /dev/null @@ -1,8 +0,0 @@ -self-hosted-runner: - labels: - - DPDK - - runner-1 - - runner-4 -runner-label: - - runner-4 - - runner-1 \ No newline at end of file diff --git a/.github/linters/actionlint.yml b/.github/linters/actionlint.yml deleted file mode 100644 index 6f4bbed1c..000000000 --- a/.github/linters/actionlint.yml +++ /dev/null @@ -1,6 +0,0 @@ -self-hosted-runner: - # Labels of self-hosted runner in array of strings. - labels: - - DPDK - - runner-1 - - runner-4 From f982b645d52158aa0d7e68eee16447f1b686b776 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Fri, 29 Aug 2025 14:25:21 +0000 Subject: [PATCH 109/123] feat: add report summary workflow and update smoke tests to upload JSON report --- .github/workflows/report-summary.yml | 114 ++++++++++++++++++ .github/workflows/smoke_tests.yml | 51 ++------ .../local/audio/test_ffmpeg_audio.py | 4 + 3 files changed, 130 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/report-summary.yml diff --git a/.github/workflows/report-summary.yml b/.github/workflows/report-summary.yml new file mode 100644 index 000000000..cc9a9cbd3 --- /dev/null +++ b/.github/workflows/report-summary.yml @@ -0,0 +1,114 @@ +name: report-summary + +on: + workflow_call: + inputs: + artifact-name: + required: true + type: string + artifact-path: + required: false + type: string + +jobs: + summarize: + runs-on: ubuntu-latest + steps: + - name: Install jq + run: | + sudo apt-get update -y + sudo apt-get install -y jq + + - name: Download report artifact + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: ./report_artifacts + + - name: Locate report.json + id: locate + run: | + if [ -n "${{ inputs.artifact-path }}" ]; then + REPORT_PATH="${{ inputs.artifact-path }}" + else + REPORT_PATH=$(find report_artifacts -type f -name 'report.json' | head -n1 || true) + fi + echo "REPORT_PATH=$REPORT_PATH" >> $GITHUB_ENV + if [ -z "$REPORT_PATH" ]; then + echo "No report.json found in downloaded artifacts" + exit 1 + fi + + - name: Add report to summary + if: always() + run: | + { + echo "## Smoke Tests Report" + echo "" + + REPORT_FILE="$REPORT_PATH" + if [ -f "$REPORT_FILE" ]; then + PASSED=$(jq -r '.summary.passed // empty' "$REPORT_FILE" 2>/dev/null || true) + FAILED=$(jq -r '.summary.failed // empty' "$REPORT_FILE" 2>/dev/null || true) + SKIPPED=$(jq -r '.summary.skipped // empty' "$REPORT_FILE" 2>/dev/null || true) + ERRORS=$(jq -r '.summary.errors // empty' "$REPORT_FILE" 2>/dev/null || true) + + if [ -z "${PASSED}" ] || [ -z "${FAILED}" ] || [ -z "${SKIPPED}" ] || [ -z "${ERRORS}" ]; then + if jq -e '.tests' "$REPORT_FILE" >/dev/null 2>&1; then + PASSED=$(jq '[.tests[] | select(.outcome=="passed")] | length' "$REPORT_FILE") + FAILED=$(jq '[.tests[] | select(.outcome=="failed" or .outcome=="error")] | length' "$REPORT_FILE") + SKIPPED=$(jq '[.tests[] | select(.outcome=="skipped")] | length' "$REPORT_FILE") + ERRORS=$(jq '[.tests[] | select(.outcome=="error")] | length' "$REPORT_FILE") + fi + fi + + PASSED=${PASSED:-0} + FAILED=${FAILED:-0} + SKIPPED=${SKIPPED:-0} + ERRORS=${ERRORS:-0} + + echo "| Status | Count |" + echo "| ------ | ----- |" + echo "| ✅ Passed | ${PASSED} |" + echo "| ❌ Failed | ${FAILED} |" + echo "| ⚠️ Error | ${ERRORS} |" + echo "| ⏭️ Skipped | ${SKIPPED} |" + echo "" + + TOTAL=$((PASSED + FAILED + ERRORS + SKIPPED)) + echo "**Total Tests:** $TOTAL" + echo "" + if [ "${FAILED}" -gt 0 ] || [ "${ERRORS}" -gt 0 ]; then + echo "❌ **Some tests failed!** Please check the detailed report." + else + echo "✅ **All tests passed!**" + fi + echo "" + + echo "" + echo "### Test results" + echo "" + echo "| Test | Status |" + echo "| ---- | ------ |" + if jq -e '.tests' "$REPORT_FILE" >/dev/null 2>&1; then + jq -r '.tests[] | "\(.nodeid)\t\(.outcome // \"passed\")"' "$REPORT_FILE" | \ + while IFS=$'\t' read -r NODEID OUTCOME; do + case "${OUTCOME}" in + passed) ICON="✅ Passed" ;; + failed) ICON="❌ Failed" ;; + error) ICON="⚠️ Error" ;; + skipped) ICON="⏭️ Skipped" ;; + xfailed) ICON="❗ XFailed" ;; + xpassed) ICON="❗ XPassed" ;; + *) ICON="${OUTCOME}" ;; + esac + ESC_NODEID=$(printf '%s' "${NODEID}" | sed 's/`/\\`/g' | sed 's/|/\\|/g') + echo "| \`${ESC_NODEID}\` | ${ICON} |" + done + else + echo "No per-test details available in report.json" + fi + else + echo "❌ No report.json file was generated" + fi + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index fb3ff183b..76d3be24e 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -170,44 +170,17 @@ jobs: name: smoke-tests-report path: | report.html - - name: "Add report to summary" + - name: "upload json report" if: always() - run: | - { - echo "## Smoke Tests Report" - echo "" - - # Check if JSON report exists - if [ -f "report.json" ]; then - # Parse JSON report - PASSED=$(jq '.summary.passed // 0' report.json) - FAILED=$(jq '.summary.failed // 0' report.json) - SKIPPED=$(jq '.summary.skipped // 0' report.json) - ERROR=$(jq '.summary.errors // 0' report.json) - - # Add summary stats - echo "| Status | Count |" - echo "| ------ | ----- |" - echo "| ✅ Passed | ${PASSED:-0} |" - echo "| ❌ Failed | ${FAILED:-0} |" - echo "| ⚠️ Error | ${ERROR:-0} |" - echo "| ⏭️ Skipped | ${SKIPPED:-0} |" - echo "" - - # Add test result details if available - TOTAL=$((${PASSED:-0} + ${FAILED:-0} + ${ERROR:-0} + ${SKIPPED:-0})) - echo "**Total Tests:** $TOTAL" - echo "" - if [ "${FAILED:-0}" -gt 0 ] || [ "${ERROR:-0}" -gt 0 ]; then - echo "❌ **Some tests failed!** Please check the detailed report." - else - echo "✅ **All tests passed!**" - fi - echo "" + id: upload-report-json + uses: actions/upload-artifact@v4 + with: + name: smoke-tests-report-json + path: | + report.json - # Add link to full report artifact - echo "📄 [Download Full HTML Report](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts/${{ steps.upload-report.outputs.artifact-id }})" - else - echo "❌ No report.json file was generated" - fi - } >> "$GITHUB_STEP_SUMMARY" + call-report-summary: + needs: validation-run-tests + uses: ./.github/workflows/report-summary.yml + with: + artifact-name: smoke-tests-report-json diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index 48dd64335..9d08e24ab 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -50,6 +50,10 @@ def test_local_ffmpeg_audio( "sdk_port" ] + # PCM 8 (pcm_s8) is not supported by the MCM FFmpeg plugin. Skip those cases. + if audio_files_25_03[audio_type]["format"] == "pcm_s8": + pytest.skip("PCM 8 is not supported by Media Communications Mesh FFmpeg plugin!") + audio_format = audio_file_format_to_format_dict( str(audio_files_25_03[audio_type]["format"]) ) # audio format From 2ad9ad92f93f491be78252827569a58724306bb0 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Fri, 29 Aug 2025 14:55:27 +0000 Subject: [PATCH 110/123] fix: update actions/upload-artifact version in workflows --- .github/workflows/report-summary.yml | 6 +++--- .github/workflows/smoke_tests.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/report-summary.yml b/.github/workflows/report-summary.yml index cc9a9cbd3..f28ae8b2d 100644 --- a/.github/workflows/report-summary.yml +++ b/.github/workflows/report-summary.yml @@ -20,7 +20,7 @@ jobs: sudo apt-get install -y jq - name: Download report artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: ${{ inputs.artifact-name }} path: ./report_artifacts @@ -33,7 +33,7 @@ jobs: else REPORT_PATH=$(find report_artifacts -type f -name 'report.json' | head -n1 || true) fi - echo "REPORT_PATH=$REPORT_PATH" >> $GITHUB_ENV + echo "REPORT_PATH=$REPORT_PATH" >> "$GITHUB_ENV" if [ -z "$REPORT_PATH" ]; then echo "No report.json found in downloaded artifacts" exit 1 @@ -91,7 +91,7 @@ jobs: echo "| Test | Status |" echo "| ---- | ------ |" if jq -e '.tests' "$REPORT_FILE" >/dev/null 2>&1; then - jq -r '.tests[] | "\(.nodeid)\t\(.outcome // \"passed\")"' "$REPORT_FILE" | \ + jq -r ".tests[] | \"\(.nodeid)\t\(.outcome // \\\"passed\\\")\"" "$REPORT_FILE" | \ while IFS=$'\t' read -r NODEID OUTCOME; do case "${OUTCOME}" in passed) ICON="✅ Passed" ;; diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index 76d3be24e..fb3e6ba16 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -156,7 +156,7 @@ jobs: -m "${{ env.MARKERS }}" - name: "upload logs" if: always() - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: smoke-tests-logs path: | @@ -165,7 +165,7 @@ jobs: - name: "upload report" if: always() id: upload-report - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: smoke-tests-report path: | @@ -173,7 +173,7 @@ jobs: - name: "upload json report" if: always() id: upload-report-json - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: smoke-tests-report-json path: | From 7bbdf972166beac3af9d51b16a6863ba0114ccb6 Mon Sep 17 00:00:00 2001 From: andremiszcz Date: Fri, 25 Jul 2025 16:49:30 +0200 Subject: [PATCH 111/123] Add results.csv preparation. Add new logger levels --- tests/validation/Engine/const.py | 2 + .../validation/Engine/rx_tx_app_engine_mcm.py | 4 +- .../common/ffmpeg_handler/ffmpeg.py | 3 +- tests/validation/conftest.py | 112 ++++++++++++++++++ tests/validation/pytest.ini | 4 - 5 files changed, 119 insertions(+), 6 deletions(-) diff --git a/tests/validation/Engine/const.py b/tests/validation/Engine/const.py index 836855cf4..c9f9eee21 100644 --- a/tests/validation/Engine/const.py +++ b/tests/validation/Engine/const.py @@ -7,6 +7,8 @@ DEFAULT_INPUT_PATH = "/opt/intel/input_path/" DEFAULT_OUTPUT_PATH = "/opt/intel/output_path/" +TESTCMD_LVL = 24 # Custom logging level for test commands + # time for establishing connection for example between TX and RX in st2110 MTL_ESTABLISH_TIMEOUT = 2 MCM_ESTABLISH_TIMEOUT = 5 diff --git a/tests/validation/Engine/rx_tx_app_engine_mcm.py b/tests/validation/Engine/rx_tx_app_engine_mcm.py index c15294702..ad41f478b 100644 --- a/tests/validation/Engine/rx_tx_app_engine_mcm.py +++ b/tests/validation/Engine/rx_tx_app_engine_mcm.py @@ -11,7 +11,7 @@ import Engine.rx_tx_app_client_json import Engine.rx_tx_app_connection_json -from Engine.const import LOG_FOLDER, RX_TX_APP_ERROR_KEYWORDS, DEFAULT_OUTPUT_PATH +from Engine.const import LOG_FOLDER, RX_TX_APP_ERROR_KEYWORDS, DEFAULT_OUTPUT_PATH, TESTCMD_LVL from Engine.mcm_apps import ( get_media_proxy_port, output_validator, @@ -279,6 +279,7 @@ def start(self): f"Starting Tx app with payload: {self.payload.payload_type} on {self.host}" ) cmd = self._get_app_cmd("Tx") + logger.log(TESTCMD_LVL, f"Tx command: {cmd}") self.process = self.host.connection.start_process( cmd, shell=True, stderr_to_stdout=True, cwd=self.app_path ) @@ -334,6 +335,7 @@ def start(self): f"Starting Rx app with payload: {self.payload.payload_type} on {self.host}" ) cmd = self._get_app_cmd("Rx") + logger.log(TESTCMD_LVL, f"Rx command: {cmd}") self.process = self.host.connection.start_process( cmd, shell=True, stderr_to_stdout=True, cwd=self.app_path ) diff --git a/tests/validation/common/ffmpeg_handler/ffmpeg.py b/tests/validation/common/ffmpeg_handler/ffmpeg.py index 33bc8b38f..ad1e12461 100644 --- a/tests/validation/common/ffmpeg_handler/ffmpeg.py +++ b/tests/validation/common/ffmpeg_handler/ffmpeg.py @@ -9,7 +9,7 @@ SSHRemoteProcessEndException, RemoteProcessInvalidState, ) -from Engine.const import LOG_FOLDER +from Engine.const import LOG_FOLDER, TESTCMD_LVL from Engine.mcm_apps import save_process_log from .ffmpeg_io import FFmpegIO @@ -77,6 +77,7 @@ def __init__(self, host, ffmpeg_instance: FFmpeg, log_path=None): def start(self): """Starts the FFmpeg process on the host, waits for the process to start.""" cmd = self.ff.get_command() + logger.log(TESTCMD_LVL, f"ffmpeg command: {cmd}") ffmpeg_process = self.host.connection.start_process( cmd, stderr_to_stdout=True, shell=True ) diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index a60c3031e..a431deeac 100644 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -7,6 +7,7 @@ import shutil from ipaddress import IPv4Interface from pathlib import Path +from typing import Dict from mfd_host import Host from mfd_connect.exceptions import ( @@ -16,6 +17,7 @@ import pytest from common.nicctl import Nicctl + from Engine.const import ( ALLOWED_FFMPEG_VERSIONS, DEFAULT_FFMPEG_PATH, @@ -32,11 +34,35 @@ MCM_BUILD_PATH, MTL_BUILD_PATH, OPENH264_VERSION_TAG, + TESTCMD_LVL +) +from Engine.csv_report import ( + csv_add_test, + csv_write_report, + update_compliance_result, + ) from Engine.mcm_apps import MediaProxy, MeshAgent, get_mcm_path, get_mtl_path from datetime import datetime +from mfd_common_libs.custom_logger import add_logging_level +from mfd_common_libs.log_levels import TEST_FAIL, TEST_INFO, TEST_PASS +from pytest_mfd_logging.amber_log_formatter import AmberLogFormatter + logger = logging.getLogger(__name__) +phase_report_key = pytest.StashKey[Dict[str, pytest.CollectReport]]() + + +@pytest.hookimpl(wrapper=True, tryfirst=True) +def pytest_runtest_makereport(item, call): + # execute all other hooks to obtain the report object + rep = yield + + # store test results for each phase of a call, which can + # be "setup", "call", "teardown" + item.stash.setdefault(phase_report_key, {})[rep.when] = rep + + return rep @pytest.fixture(scope="function") @@ -770,3 +796,89 @@ def log_interface_driver_info(hosts: dict[str, Host]) -> None: logger.info( f"Interface {interface.name} on host {host.name} uses driver: {driver_info.driver_name} ({driver_info.driver_version})" ) + + +@pytest.fixture(scope="session", autouse=True) +def log_session(): + add_logging_level("TESTCMD", TESTCMD_LVL) + + today = datetime.today() + folder = today.strftime("%Y-%m-%dT%H:%M:%S") + path = os.path.join(LOG_FOLDER, folder) + path_symlink = os.path.join(LOG_FOLDER, "latest") + try: + os.remove(path_symlink) + except FileNotFoundError: + pass + os.makedirs(path, exist_ok=True) + os.symlink(folder, path_symlink) + yield + shutil.copy("pytest.log", f"{LOG_FOLDER}/latest/pytest.log") + csv_write_report(f"{LOG_FOLDER}/latest/report.csv") + +@pytest.fixture(scope="function", autouse=True) +def log_case(request, caplog: pytest.LogCaptureFixture): + case_id = request.node.nodeid + case_folder = os.path.dirname(case_id) + os.makedirs(os.path.join(LOG_FOLDER, "latest", case_folder), exist_ok=True) + logfile = os.path.join(LOG_FOLDER, "latest", f"{case_id}.log") + fh = logging.FileHandler(logfile) + formatter = request.session.config.pluginmanager.get_plugin( + "logging-plugin" + ).formatter + format = AmberLogFormatter(formatter) + fh.setFormatter(format) + fh.setLevel(logging.DEBUG) + logger = logging.getLogger() + logger.addHandler(fh) + yield + report = request.node.stash[phase_report_key] + if report["setup"].failed: + logging.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}") + os.chmod(logfile, 0o4755) + result = "Fail" + elif report["call"].passed: + logging.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}") + result = "Skip" + + logger.removeHandler(fh) + + commands = [] + for record in caplog.get_records("call"): + if record.levelno == TESTCMD_LVL: + commands.append(record.message) + + csv_add_test( + test_case=case_id, + commands="\n".join(commands), + result=result, + issue="n/a", + result_note="n/a", + ) + + +@pytest.fixture(scope="session") +def compliance_report(request, log_session, test_config): + """ + This function is used for compliance check and report. + """ + # TODO: Implement compliance check logic. When tcpdump pcap is enabled, at the end of the test session all pcaps + # shall be send into EBU list. + # Pcaps shall be stored in the ramdisk, and then moved to the compliance + # folder or send into EBU list after each test finished and remove it from the ramdisk. + # Compliance report generation logic goes here after yield. Or in another class / function but triggered here. + # AFAIK names of pcaps contains test name so it can be matched with result of each test like in code below. + yield + if test_config.get("compliance", False): + logging.info("Compliance mode enabled, updating compliance results") + for item in request.session.items: + test_case = item.nodeid + update_compliance_result(test_case, "Fail") diff --git a/tests/validation/pytest.ini b/tests/validation/pytest.ini index 7d6a37fa3..59d014cc1 100644 --- a/tests/validation/pytest.ini +++ b/tests/validation/pytest.ini @@ -1,8 +1,4 @@ [pytest] pythonpath = . -log_cli = true log_cli_level = info log_file = pytest.log -log_file_level = debug -log_file_format = %(asctime)s,%(msecs)03d %(levelname)-8s %(filename)s:%(lineno)d %(message)s -log_file_date_format = %Y-%m-%d %H:%M:%S \ No newline at end of file From ae3f6a8ccf4753603f451bd076519c0d575a3347 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Fri, 29 Aug 2025 15:01:08 +0000 Subject: [PATCH 112/123] feat: add report summary workflow to process and display test results --- .github/workflows/{report-summary.yml => report_summary.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{report-summary.yml => report_summary.yml} (97%) diff --git a/.github/workflows/report-summary.yml b/.github/workflows/report_summary.yml similarity index 97% rename from .github/workflows/report-summary.yml rename to .github/workflows/report_summary.yml index f28ae8b2d..7f5e13d9c 100644 --- a/.github/workflows/report-summary.yml +++ b/.github/workflows/report_summary.yml @@ -91,7 +91,7 @@ jobs: echo "| Test | Status |" echo "| ---- | ------ |" if jq -e '.tests' "$REPORT_FILE" >/dev/null 2>&1; then - jq -r ".tests[] | \"\(.nodeid)\t\(.outcome // \\\"passed\\\")\"" "$REPORT_FILE" | \ + jq -r '.tests[] | "\(.nodeid)\t\(.outcome // "passed")"' "$REPORT_FILE" | \ while IFS=$'\t' read -r NODEID OUTCOME; do case "${OUTCOME}" in passed) ICON="✅ Passed" ;; From bd201fa6236fe9bcda52df27ef60ff02a3669bd7 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Fri, 29 Aug 2025 15:05:50 +0000 Subject: [PATCH 113/123] fix: correct report summary workflow filename in smoke tests --- .github/workflows/report_summary.yml | 2 +- .github/workflows/smoke_tests.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/report_summary.yml b/.github/workflows/report_summary.yml index 7f5e13d9c..30e4edf33 100644 --- a/.github/workflows/report_summary.yml +++ b/.github/workflows/report_summary.yml @@ -91,7 +91,7 @@ jobs: echo "| Test | Status |" echo "| ---- | ------ |" if jq -e '.tests' "$REPORT_FILE" >/dev/null 2>&1; then - jq -r '.tests[] | "\(.nodeid)\t\(.outcome // "passed")"' "$REPORT_FILE" | \ + jq -r '.tests[] | "\(.nodeid)\t\(.outcome // \"passed\")"' "$REPORT_FILE" | \ while IFS=$'\t' read -r NODEID OUTCOME; do case "${OUTCOME}" in passed) ICON="✅ Passed" ;; diff --git a/.github/workflows/smoke_tests.yml b/.github/workflows/smoke_tests.yml index fb3e6ba16..87ccf145e 100644 --- a/.github/workflows/smoke_tests.yml +++ b/.github/workflows/smoke_tests.yml @@ -181,6 +181,6 @@ jobs: call-report-summary: needs: validation-run-tests - uses: ./.github/workflows/report-summary.yml + uses: ./.github/workflows/report_summary.yml with: artifact-name: smoke-tests-report-json From 976e43a716009b17da30bd2db0a956b3b47548ae Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Fri, 29 Aug 2025 15:12:50 +0000 Subject: [PATCH 114/123] fix: simplify report summary parsing and improve error handling --- .github/workflows/report_summary.yml | 62 +++++++--------------------- 1 file changed, 14 insertions(+), 48 deletions(-) diff --git a/.github/workflows/report_summary.yml b/.github/workflows/report_summary.yml index 30e4edf33..1e9302c03 100644 --- a/.github/workflows/report_summary.yml +++ b/.github/workflows/report_summary.yml @@ -46,68 +46,34 @@ jobs: echo "## Smoke Tests Report" echo "" + # Check if JSON report exists REPORT_FILE="$REPORT_PATH" if [ -f "$REPORT_FILE" ]; then - PASSED=$(jq -r '.summary.passed // empty' "$REPORT_FILE" 2>/dev/null || true) - FAILED=$(jq -r '.summary.failed // empty' "$REPORT_FILE" 2>/dev/null || true) - SKIPPED=$(jq -r '.summary.skipped // empty' "$REPORT_FILE" 2>/dev/null || true) - ERRORS=$(jq -r '.summary.errors // empty' "$REPORT_FILE" 2>/dev/null || true) - - if [ -z "${PASSED}" ] || [ -z "${FAILED}" ] || [ -z "${SKIPPED}" ] || [ -z "${ERRORS}" ]; then - if jq -e '.tests' "$REPORT_FILE" >/dev/null 2>&1; then - PASSED=$(jq '[.tests[] | select(.outcome=="passed")] | length' "$REPORT_FILE") - FAILED=$(jq '[.tests[] | select(.outcome=="failed" or .outcome=="error")] | length' "$REPORT_FILE") - SKIPPED=$(jq '[.tests[] | select(.outcome=="skipped")] | length' "$REPORT_FILE") - ERRORS=$(jq '[.tests[] | select(.outcome=="error")] | length' "$REPORT_FILE") - fi - fi - - PASSED=${PASSED:-0} - FAILED=${FAILED:-0} - SKIPPED=${SKIPPED:-0} - ERRORS=${ERRORS:-0} + # Parse JSON report + PASSED=$(jq '.summary.passed // 0' "$REPORT_FILE") + FAILED=$(jq '.summary.failed // 0' "$REPORT_FILE") + SKIPPED=$(jq '.summary.skipped // 0' "$REPORT_FILE") + ERROR=$(jq '.summary.errors // 0' "$REPORT_FILE") + # Add summary stats echo "| Status | Count |" echo "| ------ | ----- |" - echo "| ✅ Passed | ${PASSED} |" - echo "| ❌ Failed | ${FAILED} |" - echo "| ⚠️ Error | ${ERRORS} |" - echo "| ⏭️ Skipped | ${SKIPPED} |" + echo "| ✅ Passed | ${PASSED:-0} |" + echo "| ❌ Failed | ${FAILED:-0} |" + echo "| ⚠️ Error | ${ERROR:-0} |" + echo "| ⏭️ Skipped | ${SKIPPED:-0} |" echo "" - TOTAL=$((PASSED + FAILED + ERRORS + SKIPPED)) + # Add test result details if available + TOTAL=$((${PASSED:-0} + ${FAILED:-0} + ${ERROR:-0} + ${SKIPPED:-0})) echo "**Total Tests:** $TOTAL" echo "" - if [ "${FAILED}" -gt 0 ] || [ "${ERRORS}" -gt 0 ]; then + if [ "${FAILED:-0}" -gt 0 ] || [ "${ERROR:-0}" -gt 0 ]; then echo "❌ **Some tests failed!** Please check the detailed report." else echo "✅ **All tests passed!**" fi echo "" - - echo "" - echo "### Test results" - echo "" - echo "| Test | Status |" - echo "| ---- | ------ |" - if jq -e '.tests' "$REPORT_FILE" >/dev/null 2>&1; then - jq -r '.tests[] | "\(.nodeid)\t\(.outcome // \"passed\")"' "$REPORT_FILE" | \ - while IFS=$'\t' read -r NODEID OUTCOME; do - case "${OUTCOME}" in - passed) ICON="✅ Passed" ;; - failed) ICON="❌ Failed" ;; - error) ICON="⚠️ Error" ;; - skipped) ICON="⏭️ Skipped" ;; - xfailed) ICON="❗ XFailed" ;; - xpassed) ICON="❗ XPassed" ;; - *) ICON="${OUTCOME}" ;; - esac - ESC_NODEID=$(printf '%s' "${NODEID}" | sed 's/`/\\`/g' | sed 's/|/\\|/g') - echo "| \`${ESC_NODEID}\` | ${ICON} |" - done - else - echo "No per-test details available in report.json" - fi else echo "❌ No report.json file was generated" fi From 14fb3196882e2d0187be1c36cc7b2195274a09dd Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Fri, 29 Aug 2025 15:17:18 +0000 Subject: [PATCH 115/123] fix: improve readability of skip message for unsupported PCM 8 format --- tests/validation/functional/local/audio/test_ffmpeg_audio.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index 9d08e24ab..e528efde9 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -52,7 +52,9 @@ def test_local_ffmpeg_audio( # PCM 8 (pcm_s8) is not supported by the MCM FFmpeg plugin. Skip those cases. if audio_files_25_03[audio_type]["format"] == "pcm_s8": - pytest.skip("PCM 8 is not supported by Media Communications Mesh FFmpeg plugin!") + pytest.skip( + "PCM 8 is not supported by Media Communications Mesh FFmpeg plugin!" + ) audio_format = audio_file_format_to_format_dict( str(audio_files_25_03[audio_type]["format"]) From 20fe34e269460afc944a25b20a36c7a74199f305 Mon Sep 17 00:00:00 2001 From: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> Date: Wed, 3 Sep 2025 10:26:32 +0200 Subject: [PATCH 116/123] Update .github/workflows/base_build.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> --- .github/workflows/base_build.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/base_build.yml b/.github/workflows/base_build.yml index 2a5f5f9a5..7cd75e120 100644 --- a/.github/workflows/base_build.yml +++ b/.github/workflows/base_build.yml @@ -118,7 +118,3 @@ jobs: ${{ env.BUILD_DIR }}/mcm/lib/libmcm_dp.so.* ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg - ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg - ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg - ${{ env.BUILD_DIR }}/ffmpeg-6-1/ffmpeg - ${{ env.BUILD_DIR }}/ffmpeg-7-0/ffmpeg From 1655ae93ff40e62c67d345c8589fb8fca9f7c2e5 Mon Sep 17 00:00:00 2001 From: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> Date: Wed, 3 Sep 2025 10:27:04 +0200 Subject: [PATCH 117/123] Update tests/validation/Engine/const.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> --- tests/validation/Engine/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/validation/Engine/const.py b/tests/validation/Engine/const.py index 58ec41f0b..19826d988 100644 --- a/tests/validation/Engine/const.py +++ b/tests/validation/Engine/const.py @@ -51,7 +51,7 @@ MESH_AGENT_ERROR_KEYWORDS = ["[ERRO]"] RX_TX_APP_ERROR_KEYWORDS = ["[ERRO]"] -DEFAULT_MPG_URN = "ipv4:224.0.0.1" +DEFAULT_MPG_URN = f"ipv4:224.0.0.1:{DEFAULT_REMOTE_PORT}" DEFAULT_REMOTE_IP_ADDR = "239.2.39.238" DEFAULT_REMOTE_PORT = 20000 DEFAULT_PACING = "narrow" From e320b09f6bc4656ffb790ec1632539c8f0e211d7 Mon Sep 17 00:00:00 2001 From: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> Date: Wed, 3 Sep 2025 10:27:25 +0200 Subject: [PATCH 118/123] Update tests/validation/common/log_validation_utils.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> --- tests/validation/common/log_validation_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py index eed0c8e8a..375eb0e52 100644 --- a/tests/validation/common/log_validation_utils.py +++ b/tests/validation/common/log_validation_utils.py @@ -81,7 +81,7 @@ def check_for_errors( ] try: - with open(log_path, "r", encoding="utf-8", errors="ignore") as f: + with open(log_path, "r", encoding="utf-8", errors="replace") as f: for i, line in enumerate(f): for keyword in error_keywords: if keyword.lower() in line.lower(): From 7569827d7b2f1aee9dd0b6dfa9b93958c7ca0fa6 Mon Sep 17 00:00:00 2001 From: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> Date: Wed, 3 Sep 2025 10:27:44 +0200 Subject: [PATCH 119/123] Update tests/validation/functional/local/audio/test_ffmpeg_audio.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> --- tests/validation/functional/local/audio/test_ffmpeg_audio.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/validation/functional/local/audio/test_ffmpeg_audio.py b/tests/validation/functional/local/audio/test_ffmpeg_audio.py index e528efde9..39423e303 100644 --- a/tests/validation/functional/local/audio/test_ffmpeg_audio.py +++ b/tests/validation/functional/local/audio/test_ffmpeg_audio.py @@ -125,8 +125,7 @@ def test_local_ffmpeg_audio( mcm_rx_executor.start() mcm_tx_executor.start() try: - if mcm_rx_executor._processes[0].running: - mcm_rx_executor._processes[0].wait(timeout=FFMPEG_RUN_TIMEOUT) + mcm_rx_executor.wait_with_timeout(timeout=FFMPEG_RUN_TIMEOUT) except Exception as e: logging.warning(f"RX executor did not finish in time or error occurred: {e}") From fb89f80d20364cd809ba86bc673a814d91a506f6 Mon Sep 17 00:00:00 2001 From: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> Date: Wed, 3 Sep 2025 10:27:51 +0200 Subject: [PATCH 120/123] Update tests/validation/functional/local/video/test_ffmpeg_video.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: KarolinaPomian <108665762+KarolinaPomian@users.noreply.github.com> --- tests/validation/functional/local/video/test_ffmpeg_video.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/validation/functional/local/video/test_ffmpeg_video.py b/tests/validation/functional/local/video/test_ffmpeg_video.py index 16d967474..203bedbc4 100644 --- a/tests/validation/functional/local/video/test_ffmpeg_video.py +++ b/tests/validation/functional/local/video/test_ffmpeg_video.py @@ -127,8 +127,7 @@ def test_local_ffmpeg_video( mcm_rx_executor.start() mcm_tx_executor.start() try: - if mcm_rx_executor._processes[0].running: - mcm_rx_executor._processes[0].wait(timeout=FFMPEG_RUN_TIMEOUT) + mcm_rx_executor.wait_with_timeout(timeout=FFMPEG_RUN_TIMEOUT) except Exception as e: logging.warning(f"RX executor did not finish in time or error occurred: {e}") From 57ded33bfdc9e48591b1fbd3f6c5c8ab9da4ae7f Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 3 Sep 2025 10:20:12 +0000 Subject: [PATCH 121/123] Update const.py and log_validation_utils.py for improved configuration and log phrase checking --- tests/validation/Engine/const.py | 2 +- tests/validation/common/log_validation_utils.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/validation/Engine/const.py b/tests/validation/Engine/const.py index 19826d988..c003636cb 100644 --- a/tests/validation/Engine/const.py +++ b/tests/validation/Engine/const.py @@ -51,9 +51,9 @@ MESH_AGENT_ERROR_KEYWORDS = ["[ERRO]"] RX_TX_APP_ERROR_KEYWORDS = ["[ERRO]"] +DEFAULT_REMOTE_PORT = 20000 DEFAULT_MPG_URN = f"ipv4:224.0.0.1:{DEFAULT_REMOTE_PORT}" DEFAULT_REMOTE_IP_ADDR = "239.2.39.238" -DEFAULT_REMOTE_PORT = 20000 DEFAULT_PACING = "narrow" DEFAULT_PAYLOAD_TYPE_ST2110_20 = 112 DEFAULT_PAYLOAD_TYPE_ST2110_30 = 111 diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py index 375eb0e52..894744887 100644 --- a/tests/validation/common/log_validation_utils.py +++ b/tests/validation/common/log_validation_utils.py @@ -242,7 +242,14 @@ def check_phrases_anywhere( lines = content.split("\n") for phrase in phrases: - if phrase not in content: + # Check if the phrase is contained in any line, not just the entire content + phrase_found = False + for line in lines: + if phrase in line: + phrase_found = True + break + + if not phrase_found: missing_phrases.append(phrase) # Find where the phrase should have appeared # Just give some context from the end of the log From 18bcc5c1973148e785b532497eb10bbd5f664e79 Mon Sep 17 00:00:00 2001 From: KarolinaPomian Date: Wed, 3 Sep 2025 10:51:10 +0000 Subject: [PATCH 122/123] Update const.py and log_validation_utils.py for timeout adjustments and code formatting --- tests/validation/Engine/const.py | 8 ++++---- tests/validation/common/log_validation_utils.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/validation/Engine/const.py b/tests/validation/Engine/const.py index c003636cb..fd5afd3c7 100644 --- a/tests/validation/Engine/const.py +++ b/tests/validation/Engine/const.py @@ -7,15 +7,15 @@ DEFAULT_INPUT_PATH = "/opt/intel/input_path/" DEFAULT_OUTPUT_PATH = "/opt/intel/output_path/" -FFMPEG_ESTABLISH_TIMEOUT = 5 # or use the same value as MCM_ESTABLISH_TIMEOUT -FFMPEG_RUN_TIMEOUT = 60 # or use the same value as MCM_RXTXAPP_RUN_TIMEOUT +FFMPEG_ESTABLISH_TIMEOUT = 6 # or use the same value as MCM_ESTABLISH_TIMEOUT +FFMPEG_RUN_TIMEOUT = 120 # or use the same value as MCM_RXTXAPP_RUN_TIMEOUT # time for establishing connection for example between TX and RX in st2110 MTL_ESTABLISH_TIMEOUT = 2 -MCM_ESTABLISH_TIMEOUT = 5 +MCM_ESTABLISH_TIMEOUT = 6 DEFAULT_LOOP_COUNT = 7 MCM_RXTXAPP_RUN_TIMEOUT = MCM_ESTABLISH_TIMEOUT * DEFAULT_LOOP_COUNT -MAX_TEST_TIME_DEFAULT = 60 +MAX_TEST_TIME_DEFAULT = 120 STOP_GRACEFULLY_PERIOD = 2 # seconds BUILD_DIR = "_build" diff --git a/tests/validation/common/log_validation_utils.py b/tests/validation/common/log_validation_utils.py index 894744887..b51c7f155 100644 --- a/tests/validation/common/log_validation_utils.py +++ b/tests/validation/common/log_validation_utils.py @@ -248,7 +248,7 @@ def check_phrases_anywhere( if phrase in line: phrase_found = True break - + if not phrase_found: missing_phrases.append(phrase) # Find where the phrase should have appeared From 96dd929fc196f6be1137f1f2b9e70904343f783f Mon Sep 17 00:00:00 2001 From: andremiszcz Date: Wed, 3 Sep 2025 15:20:34 +0200 Subject: [PATCH 123/123] Add csv report into test results. Copy pytest.log into test logs directory --- tests/validation/conftest.py | 64 ++++-------------------------------- tests/validation/pytest.ini | 2 -- 2 files changed, 6 insertions(+), 60 deletions(-) diff --git a/tests/validation/conftest.py b/tests/validation/conftest.py index 0704b6dd4..ce3cd911e 100644 --- a/tests/validation/conftest.py +++ b/tests/validation/conftest.py @@ -206,7 +206,7 @@ def media_path(test_config: dict) -> str: @pytest.fixture(scope="session") -def log_path_dir(test_config: dict) -> str: +def log_path_dir(test_config: dict, pytestconfig): """ Creates and returns the main log directory path for the test session. @@ -214,9 +214,8 @@ def log_path_dir(test_config: dict) -> str: If keep_logs is False, the existing log directory is removed before creating a new one. :param test_config: Dictionary containing test configuration. - :return: Path to the main log directory for the session. - :rtype: str """ + add_logging_level("TESTCMD", TESTCMD_LVL) keep_logs = test_config.get("keep_logs", True) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") log_dir_name = f"log_{timestamp}" @@ -226,7 +225,10 @@ def log_path_dir(test_config: dict) -> str: shutil.rmtree(log_dir) log_dir = Path(log_dir, log_dir_name) log_dir.mkdir(parents=True, exist_ok=True) - return str(log_dir) + yield str(log_dir) + pytest_log = Path(pytestconfig.inicfg["log_file"]) + shutil.copy(str(pytest_log), log_dir / pytest_log.name) + csv_write_report(str(log_dir / "report.csv")) def sanitize_name(test_name): @@ -822,59 +824,24 @@ def log_interface_driver_info(hosts: dict[str, Host]) -> None: ) -@pytest.fixture(scope="session", autouse=True) -def log_session(): - add_logging_level("TESTCMD", TESTCMD_LVL) - - today = datetime.today() - folder = today.strftime("%Y-%m-%dT%H:%M:%S") - path = os.path.join(LOG_FOLDER, folder) - path_symlink = os.path.join(LOG_FOLDER, "latest") - try: - os.remove(path_symlink) - except FileNotFoundError: - pass - os.makedirs(path, exist_ok=True) - os.symlink(folder, path_symlink) - yield - shutil.copy("pytest.log", f"{LOG_FOLDER}/latest/pytest.log") - csv_write_report(f"{LOG_FOLDER}/latest/report.csv") - @pytest.fixture(scope="function", autouse=True) def log_case(request, caplog: pytest.LogCaptureFixture): case_id = request.node.nodeid - case_folder = os.path.dirname(case_id) - os.makedirs(os.path.join(LOG_FOLDER, "latest", case_folder), exist_ok=True) - logfile = os.path.join(LOG_FOLDER, "latest", f"{case_id}.log") - fh = logging.FileHandler(logfile) - formatter = request.session.config.pluginmanager.get_plugin( - "logging-plugin" - ).formatter - format = AmberLogFormatter(formatter) - fh.setFormatter(format) - fh.setLevel(logging.DEBUG) - logger = logging.getLogger() - logger.addHandler(fh) yield report = request.node.stash[phase_report_key] if report["setup"].failed: logging.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}") - os.chmod(logfile, 0o4755) result = "Fail" elif report["call"].passed: logging.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}") result = "Skip" - logger.removeHandler(fh) - commands = [] for record in caplog.get_records("call"): if record.levelno == TESTCMD_LVL: @@ -887,22 +854,3 @@ def log_case(request, caplog: pytest.LogCaptureFixture): issue="n/a", result_note="n/a", ) - - -@pytest.fixture(scope="session") -def compliance_report(request, log_session, test_config): - """ - This function is used for compliance check and report. - """ - # TODO: Implement compliance check logic. When tcpdump pcap is enabled, at the end of the test session all pcaps - # shall be send into EBU list. - # Pcaps shall be stored in the ramdisk, and then moved to the compliance - # folder or send into EBU list after each test finished and remove it from the ramdisk. - # Compliance report generation logic goes here after yield. Or in another class / function but triggered here. - # AFAIK names of pcaps contains test name so it can be matched with result of each test like in code below. - yield - if test_config.get("compliance", False): - logging.info("Compliance mode enabled, updating compliance results") - for item in request.session.items: - test_case = item.nodeid - update_compliance_result(test_case, "Fail") diff --git a/tests/validation/pytest.ini b/tests/validation/pytest.ini index 1c81c2964..e61cd753c 100644 --- a/tests/validation/pytest.ini +++ b/tests/validation/pytest.ini @@ -3,7 +3,5 @@ pythonpath = . log_cli_level = info log_file = pytest.log log_file_level = debug -log_file_format = %(asctime)s,%(msecs)03d %(levelname)-8s %(filename)s:%(lineno)d %(message)s -log_file_date_format = %Y-%m-%d %H:%M:%S markers = smoke: marks tests as smoke tests (deselect with '-m "not smoke"') \ No newline at end of file