From 4d6666b65173bd9bdd30490e3c743cd45b9177f8 Mon Sep 17 00:00:00 2001 From: rahulsasingh Date: Wed, 17 Jun 2026 12:57:30 +0200 Subject: [PATCH 1/5] Fixing the integration test failure issu --- .bazelrc | 4 ++ .github/workflows/integration_tests.yml | 5 ++ .../environments/qnx8_qemu/init.build | 1 + .../environments/qnx8_qemu/startup.sh | 1 - score/test/component/datarouter/BUILD | 1 - .../component/datarouter/filetransfer_app.cpp | 13 ++-- score/test/component/filters_app/BUILD | 1 + score/test/component/logging_plugin.py | 60 ++++++++++++------- 8 files changed, 58 insertions(+), 28 deletions(-) diff --git a/.bazelrc b/.bazelrc index b90e1ef5..05f7f9c8 100644 --- a/.bazelrc +++ b/.bazelrc @@ -72,6 +72,10 @@ build:x86_64-qnx --platforms=@score_bazel_platforms//:x86_64-qnx-sdp_8.0.0-posix build:x86_64-qnx --extra_toolchains=@score_qcc_x86_64_toolchain//:x86_64-qnx-sdp_8.0.0 build:x86_64-qnx --extra_toolchains=@score_qnx_x86_64_ifs_toolchain//:ifs-x86_64-qnx-sdp_8.0.0 build:x86_64-qnx --extra_toolchains=@score_toolchains_rust//toolchains/ferrocene:ferrocene_x86_64_pc_nto_qnx800 +# Integration tests require direct device access (KVM, TAP) — bypass linux-sandbox +test:x86_64-qnx --strategy=TestRunner=local +# All QEMU instances share one tap0/IP — tests must run one at a time +test:x86_64-qnx --local_test_jobs=1 # TODO arm64 when rust support is there diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 75c6661e..321b58c9 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -117,6 +117,11 @@ jobs: if-no-files-found: warn retention-days: 7 + - name: Cleanup tap0 network interface + if: always() + run: | + sudo ip link delete tap0 2>/dev/null || true + - name: Cleanup QNX license if: always() run: sudo rm -rf /opt/score_qnx diff --git a/quality/integration_testing/environments/qnx8_qemu/init.build b/quality/integration_testing/environments/qnx8_qemu/init.build index 44428bc6..2fbb8fec 100644 --- a/quality/integration_testing/environments/qnx8_qemu/init.build +++ b/quality/integration_testing/environments/qnx8_qemu/init.build @@ -21,6 +21,7 @@ [+script] startup-script = { procmgr_symlink /dev/shmem /tmp + procmgr_symlink /tmp_ram/tmp_discovery /tmp_discovery display_msg Welcome to QNX OS 8.0 on x86_64 - score_logging component tests diff --git a/quality/integration_testing/environments/qnx8_qemu/startup.sh b/quality/integration_testing/environments/qnx8_qemu/startup.sh index 03343945..504133e1 100644 --- a/quality/integration_testing/environments/qnx8_qemu/startup.sh +++ b/quality/integration_testing/environments/qnx8_qemu/startup.sh @@ -58,6 +58,5 @@ fi echo "---> Adding /tmp_discovery folder" mkdir -p /tmp_ram/tmp_discovery -ln -s /tmp_ram/tmp_discovery /tmp_discovery /proc/boot/sshd -f /var/ssh/sshd_config diff --git a/score/test/component/datarouter/BUILD b/score/test/component/datarouter/BUILD index fe6c0212..5fe797f8 100644 --- a/score/test/component/datarouter/BUILD +++ b/score/test/component/datarouter/BUILD @@ -72,7 +72,6 @@ filegroup( py_logging_itf_test( name = "test_datarouter_filters", srcs = ["test_datarouter_filters.py"], - extra_oci_tars = ["//score/test/component/filters_app:filtertest_classid_pkg"], filesystem = "//score/test/component/filters_app:filtertest_filesystem", deps = ["@score_itf//score/itf/plugins/dlt"], ) diff --git a/score/test/component/datarouter/filetransfer_app.cpp b/score/test/component/datarouter/filetransfer_app.cpp index ab9c5732..870ebf68 100644 --- a/score/test/component/datarouter/filetransfer_app.cpp +++ b/score/test/component/datarouter/filetransfer_app.cpp @@ -15,7 +15,6 @@ #include "score/mw/log/logging.h" #include -#include #include #include #include @@ -57,11 +56,13 @@ int main() { std::stringstream filename; filename << "/tmp/filetransfer_test_" << i << ".txt"; - std::error_code ec; - std::filesystem::copy(original_file, filename.str(), ec); - if (ec) { - logger.LogError() << "Failed to copy file:" << ec.message(); - continue; + { + std::ifstream src(original_file, std::ios::binary); + std::ofstream dst(filename.str(), std::ios::binary | std::ios::trunc); + if (!src || !dst || !(dst << src.rdbuf())) { + logger.LogError() << "Failed to copy file:" << filename.str(); + continue; + } } file_transfer.TransferFile(filename.str(), false); diff --git a/score/test/component/filters_app/BUILD b/score/test/component/filters_app/BUILD index 65cc1fae..746bcb46 100644 --- a/score/test/component/filters_app/BUILD +++ b/score/test/component/filters_app/BUILD @@ -110,6 +110,7 @@ pkg_tar( visibility = ["//score/test/component:__subpackages__"], deps = [ ":filtertest_bin_pkg", + ":filtertest_classid_pkg", ":filtertest_config_pkg", ], ) diff --git a/score/test/component/logging_plugin.py b/score/test/component/logging_plugin.py index 8bc06356..8d94dc44 100644 --- a/score/test/component/logging_plugin.py +++ b/score/test/component/logging_plugin.py @@ -20,7 +20,7 @@ from contextlib import contextmanager import pytest -from score.itf.plugins.dlt.dlt_receive import Protocol +from score.itf.plugins.dlt.dlt_receive import DltReceive, Protocol from score.itf.plugins.dlt.dlt_window import DltLogRecord LOGGER = logging.getLogger(__name__) @@ -31,8 +31,9 @@ _DATAROUTER_READY_INTERVAL = 0.2 _QNX_DR_CMD = ( - "on -A nonroot,allow,pathspace -u 1000:1000 " - "/usr/bin/datarouter/datarouter --no_adaptive_runtime " + "cd /usr/bin/datarouter && " + "nohup on -A nonroot,allow,pathspace -u 1000:1000 " + "./datarouter --no_adaptive_runtime " "> /dev/null 2>&1 &" ) _QNX_DR_STOP_CMD = ( @@ -75,8 +76,21 @@ def _wait_for_datarouter(target, timeout=_DATAROUTER_READY_TIMEOUT): raise TimeoutError(f"Datarouter not ready within {timeout}s") +class _LocalDltReceiver: + """Exposes a local DLT file path via the same .dlt_file interface as DltReceiver.""" + + def __init__(self, local_path): + self.dlt_file = local_path + + def download_dlt(target, remote_path): - """Download a .dlt file from the target and return a DltLogRecord.""" + """Download a .dlt file from the target and return a DltLogRecord. + + If remote_path is already a local file (QNX HOST-side capture), returns it directly. + """ + if os.path.exists(remote_path): + LOGGER.info(f"Using local DLT file: {os.path.getsize(remote_path)} bytes") + return DltLogRecord(remote_path) local_dir = tempfile.mkdtemp(prefix="dlt_") local_path = os.path.join(local_dir, os.path.basename(remote_path)) target.download(remote_path, local_path) @@ -97,8 +111,8 @@ def docker_configuration(): def datarouter_on_target(target): """Start the datarouter on the target (Docker or QNX), yield, then stop it.""" if _is_qnx(target): - target.execute(_QNX_DR_CMD) try: + target.execute(_QNX_DR_CMD) _wait_for_datarouter(target) yield target finally: @@ -119,25 +133,31 @@ def datarouter_on_target(target): @pytest.fixture(scope="function") def dlt_capture(target, dlt_on_target, request): - """Start a DLT receiver. On QNX, binds to the host tap0 IP from dlt_config.""" + """Start a DLT receiver. On QNX, runs dlt-receive on the host; on Docker, on the target.""" @contextmanager def _start(protocol=Protocol.UDP, host_ip=None, multicast_ips=None): - if host_ip is None: - if _is_qnx(target): - try: - dlt_cfg = request.getfixturevalue("dlt_config") - host_ip = dlt_cfg.host_ip - except pytest.FixtureLookupError: - host_ip = target.get_ip() - else: - host_ip = target.get_ip() if multicast_ips is None: multicast_ips = DLT_MULTICAST_IPS - with dlt_on_target( - protocol, host_ip=host_ip, multicast_ips=multicast_ips - ) as receiver: - time.sleep(_DLT_RECEIVER_SETTLE_DELAY) - yield receiver + + if _is_qnx(target): + dlt_cfg = request.getfixturevalue("dlt_config") + effective_host_ip = host_ip or dlt_cfg.host_ip + with DltReceive( + protocol=protocol, + host_ip=effective_host_ip, + multicast_ips=multicast_ips, + binary_path=dlt_cfg.dlt_receive_path, + ) as receiver: + time.sleep(_DLT_RECEIVER_SETTLE_DELAY) + yield _LocalDltReceiver(receiver.file_name()) + else: + if host_ip is None: + host_ip = target.get_ip() + with dlt_on_target( + protocol, host_ip=host_ip, multicast_ips=multicast_ips + ) as receiver: + time.sleep(_DLT_RECEIVER_SETTLE_DELAY) + yield receiver return _start From 47894a7a64e2c4248afc9a4570d503d6c1fcf79b Mon Sep 17 00:00:00 2001 From: rmaddikery Date: Mon, 22 Jun 2026 14:36:29 +0200 Subject: [PATCH 2/5] workflow: Fix QNX TAP interface and hardnen integration workflow - ensure QEMU reliably attaches and the guest is pingable - gate the job on the integration_testing label - make failures explicit - minor opt --- .github/workflows/integration_tests.yml | 53 ++++++++++++++++++++----- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 321b58c9..24b70316 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -22,7 +22,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request_target' }} + cancel-in-progress: true jobs: approval: @@ -39,7 +39,14 @@ jobs: integration-tests-qnx: name: QNX Integration Tests (x86_64-qnx) needs: approval - if: ${{ !cancelled() }} + # Run for all events except 'labeled', which only triggers on the opt-in label + # to avoid rerunning this expensive job on unrelated label changes. + if: >- + ${{ !cancelled() + && (github.event_name != 'pull_request_target' + || github.event.action != 'labeled' + || github.event.label.name == 'integration_testing') }} + timeout-minutes: 45 runs-on: ${{ vars.runner_labels_ghub_standard_x64 && fromJSON(vars.runner_labels_ghub_standard_x64) || vars.REPO_RUNNER_LABELS && fromJSON(vars.REPO_RUNNER_LABELS) || 'ubuntu-latest' }} permissions: contents: read @@ -51,13 +58,13 @@ jobs: level: 4 - name: Checkout repository (Handle all events) - uses: actions/checkout@v4.2.2 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.head_ref || github.event.pull_request.head.ref || github.ref }} repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - name: Setup Bazel with shared caching - uses: bazel-contrib/setup-bazel@0.18.0 + uses: bazel-contrib/setup-bazel@77a1d3d18379c7cb0a7e3b9fcaaa4d94f1029763 # 0.18.0 with: bazelisk-version: 1.26.0 disk-cache: true @@ -79,7 +86,7 @@ jobs: - name: Install qemu run: | sudo apt-get update - sudo apt-get install -y qemu-system + sudo apt-get install -y --no-install-recommends qemu-system-x86 - name: Enable KVM group permissons run: | @@ -89,9 +96,32 @@ jobs: - name: Setup tap0 network interface for QEMU run: | - sudo ip tuntap add dev tap0 mode tap user runner - sudo ip link set dev tap0 up + set -euo pipefail + + RUNNER_USER="${SUDO_USER:-$USER}" + if ! getent passwd "$RUNNER_USER" > /dev/null; then + RUNNER_USER="$(id -un)" + fi + + if [ ! -c /dev/net/tun ]; then + echo "Missing /dev/net/tun; TAP devices are unavailable on this runner." >&2 + exit 1 + fi + + if ! ip link show tap0 > /dev/null 2>&1; then + sudo ip tuntap add dev tap0 mode tap user "$RUNNER_USER" + fi + + sudo ip addr flush dev tap0 sudo ip addr add 169.254.21.88/16 dev tap0 + sudo ip link set dev tap0 up + + - name: Verify QEMU tap0 network preflight + run: | + set -euo pipefail + test -c /dev/net/tun + ip link show tap0 + ip -4 addr show dev tap0 | grep -q "169.254.21.88/16" - name: Allow unprivileged user namespaces run: | @@ -100,22 +130,23 @@ jobs: - name: Run integration tests run: | set -euo pipefail + : "${QNX_CREDENTIAL_HELPER:?QNX_CREDENTIAL_HELPER is not set (expected from setup-qnx-sdp)}" bazel test --config x86_64-qnx \ --credential_helper=*.qnx.com="${QNX_CREDENTIAL_HELPER}" \ - --lockfile_mode=error --test_tag_filters=integration --test_output=all -- \ + --lockfile_mode=error --test_tag_filters=integration --test_output=errors -- \ //score/test/component/... - name: Upload QNX ITF test logs - if: always() - uses: actions/upload-artifact@v6 + if: ${{ failure() || cancelled() }} + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: qnx-itf-test-logs path: | bazel-testlogs/**/test.log bazel-testlogs/**/test.outputs/** if-no-files-found: warn - retention-days: 7 + retention-days: 3 - name: Cleanup tap0 network interface if: always() From 01ec6703e68cd35572503a7c7407d9bd21119d1a Mon Sep 17 00:00:00 2001 From: rmaddikery Date: Mon, 22 Jun 2026 15:25:32 +0200 Subject: [PATCH 3/5] Removes redundant step for unblokcing user namespace --- .github/workflows/integration_tests.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 24b70316..7d7789fd 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -123,10 +123,6 @@ jobs: ip link show tap0 ip -4 addr show dev tap0 | grep -q "169.254.21.88/16" - - name: Allow unprivileged user namespaces - run: | - sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0 - - name: Run integration tests run: | set -euo pipefail From 99c119938bb6838b1a4ed2b5b6fff91893263a35 Mon Sep 17 00:00:00 2001 From: rahulsasingh Date: Tue, 23 Jun 2026 05:57:23 +0200 Subject: [PATCH 4/5] bypass lcov fatal errors on negative branch counts --- .bazelrc | 2 ++ .github/workflows/coverage_report.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.bazelrc b/.bazelrc index 05f7f9c8..85329f8c 100644 --- a/.bazelrc +++ b/.bazelrc @@ -102,6 +102,8 @@ coverage --combined_report=lcov coverage --test_env=COVERAGE_GCOV_OPTIONS=-bcu coverage --features=coverage coverage --cache_test_results=no +# fixes negative gcov counts under concurrent code, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68080 +coverage --cxxopt=-fprofile-update=atomic # ============================================================================== # Dynamic analysis (sanitizers) for Linux host builds/tests diff --git a/.github/workflows/coverage_report.yml b/.github/workflows/coverage_report.yml index cfa48fca..a4326083 100644 --- a/.github/workflows/coverage_report.yml +++ b/.github/workflows/coverage_report.yml @@ -38,3 +38,5 @@ jobs: # Please execute "bazel query 'kind(rule, //:*)'" to find the list of all excluded targets by this option. bazel-target: "//... -//:*" extra-bazel-flags: "--lockfile_mode=error --test_tag_filters=-integration" + # FIXME (tech-debt): lcov merge-consistency workaround, see https://github.com/eclipse-score/logging/issues/156 + genhtml-extra-flags: "--ignore-errors inconsistent,corrupt" From d6c13d1ed4ef84eac28d5d8f289b3afdb4ac7a9e Mon Sep 17 00:00:00 2001 From: rmaddikery Date: Tue, 23 Jun 2026 12:51:39 +0200 Subject: [PATCH 5/5] Adds reasoning for -fprofile-update as per gcc manual - Remove stale issue and link recommended approach for tackling gcc profile counter couter updates in multithreaded applications --- .bazelrc | 6 ++++-- .github/workflows/coverage_report.yml | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bazelrc b/.bazelrc index 85329f8c..a97c3019 100644 --- a/.bazelrc +++ b/.bazelrc @@ -102,14 +102,16 @@ coverage --combined_report=lcov coverage --test_env=COVERAGE_GCOV_OPTIONS=-bcu coverage --features=coverage coverage --cache_test_results=no -# fixes negative gcov counts under concurrent code, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68080 +# See -fprofile-update in the GCC manual (recommended for multithreaded applications): +# https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html +# Reasoning: https://github.com/eclipse-score/logging/issues/156 coverage --cxxopt=-fprofile-update=atomic # ============================================================================== # Dynamic analysis (sanitizers) for Linux host builds/tests # ============================================================================== -# Debug symbols for sanitizer stack traces +# Debug symbols for sanitizer stack traces test:with_debug_symbols --cxxopt=-g1 test:with_debug_symbols --strip=never diff --git a/.github/workflows/coverage_report.yml b/.github/workflows/coverage_report.yml index a4326083..cfa48fca 100644 --- a/.github/workflows/coverage_report.yml +++ b/.github/workflows/coverage_report.yml @@ -38,5 +38,3 @@ jobs: # Please execute "bazel query 'kind(rule, //:*)'" to find the list of all excluded targets by this option. bazel-target: "//... -//:*" extra-bazel-flags: "--lockfile_mode=error --test_tag_filters=-integration" - # FIXME (tech-debt): lcov merge-consistency workaround, see https://github.com/eclipse-score/logging/issues/156 - genhtml-extra-flags: "--ignore-errors inconsistent,corrupt"