Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -98,12 +102,16 @@ coverage --combined_report=lcov
coverage --test_env=COVERAGE_GCOV_OPTIONS=-bcu
coverage --features=coverage
coverage --cache_test_results=no
# 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

Expand Down
58 changes: 45 additions & 13 deletions .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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: |
Expand All @@ -89,33 +96,58 @@ 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: Allow unprivileged user namespaces
- name: Verify QEMU tap0 network preflight
run: |
sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
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: 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()
run: |
sudo ip link delete tap0 2>/dev/null || true

- name: Cleanup QNX license
if: always()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 0 additions & 1 deletion score/test/component/datarouter/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
)
13 changes: 7 additions & 6 deletions score/test/component/datarouter/filetransfer_app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
#include "score/mw/log/logging.h"

#include <unistd.h>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions score/test/component/filters_app/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pkg_tar(
visibility = ["//score/test/component:__subpackages__"],
deps = [
":filtertest_bin_pkg",
":filtertest_classid_pkg",
":filtertest_config_pkg",
],
)
60 changes: 40 additions & 20 deletions score/test/component/logging_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand All @@ -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 = (
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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
Loading