Skip to content
Draft
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
97 changes: 97 additions & 0 deletions score/test/component/datarouter/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,100 @@ py_logging_itf_test(
filesystem_pkg = "//score/test/component/filters_app:filtertest_filesystem_pkg",
deps = ["@score_itf//score/itf/plugins/dlt"],
)

# =============================================================================
# dr_restart test
# =============================================================================

# No app-under-test needed: datarouter itself is already baked into every
# test image via //score/test/component:datarouter_pkg. Reused below by any
# other test that only needs datarouter itself, with no additional app.
pkg_filegroup(
name = "empty_filesystem_pkg",
srcs = [],
)

pkg_tar(
name = "empty_filesystem",
srcs = [":empty_filesystem_pkg"],
)

py_logging_itf_test(
name = "test_dr_restart",
srcs = ["test_dr_restart.py"],
filesystem = ":empty_filesystem",
filesystem_pkg = ":empty_filesystem_pkg",
)

# =============================================================================
# bandwidth quotas test
# =============================================================================

py_logging_itf_test(
name = "test_bandwidth_quotes_exists",
srcs = ["test_bandwidth_quotes_exists.py"],
filesystem = ":empty_filesystem",
filesystem_pkg = ":empty_filesystem_pkg",
)

# =============================================================================
# file_logging_test (kFile logging)
# =============================================================================

py_logging_itf_test(
name = "test_file_logging",
srcs = ["test_file_logging.py"],
filesystem = "//score/test/component/dlt_generator_app:dlt_generator_filesystem",
filesystem_pkg = "//score/test/component/dlt_generator_app:dlt_generator_filesystem_pkg",
deps = [
"@score_itf//third_party/python_dlt",
],
)

# =============================================================================
# quota_exceed test
# =============================================================================

py_logging_itf_test(
name = "test_quota_exceed",
timeout = "long",
srcs = ["test_quota_exceed.py"],
filesystem = "//score/test/component/dlt_generator_app:dlt_generator_filesystem",
filesystem_pkg = "//score/test/component/dlt_generator_app:dlt_generator_filesystem_pkg",
deps = ["@score_itf//score/itf/plugins/dlt"],
)

# =============================================================================
# safe_logging_ipc test
# =============================================================================

py_logging_itf_test(
name = "test_safe_logging_ipc",
srcs = ["test_safe_logging_ipc.py"],
filesystem = ":empty_filesystem",
filesystem_pkg = ":empty_filesystem_pkg",
)

# =============================================================================
# logging_after_delayed_dr_start test
# =============================================================================

py_logging_itf_test(
name = "test_logging_after_delayed_dr_start",
srcs = ["test_logging_after_delayed_dr_start.py"],
filesystem = "//score/test/component/dlt_generator_app:dlt_generator_filesystem",
filesystem_pkg = "//score/test/component/dlt_generator_app:dlt_generator_filesystem_pkg",
deps = ["@score_itf//score/itf/plugins/dlt"],
)

# =============================================================================
# logging_detached_logs test
# =============================================================================

py_logging_itf_test(
name = "test_logging_detached_logs",
srcs = ["test_logging_detached_logs.py"],
filesystem = "//score/test/component/dlt_generator_app:dlt_generator_filesystem",
filesystem_pkg = "//score/test/component/dlt_generator_app:dlt_generator_filesystem_pkg",
deps = ["@score_itf//score/itf/plugins/dlt"],
)
7 changes: 7 additions & 0 deletions score/test/component/datarouter/etc/log-channels.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@
]
}
},
"quotas": {
"quotaEnforcementEnabled": false,
"throughput": {
"overallMbps": 100,
"applicationsKbps": {}
}
},
"defaultChannel": "3493",
"defaultThresold": "kVerbose",
"messageThresholds": {
Expand Down
49 changes: 49 additions & 0 deletions score/test/component/datarouter/test_bandwidth_quotes_exists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

"""Integration test verifying datarouter's log-channels.json declares DLT
bandwidth quotas. Ported from SPP's test_bandwidth_quotes_exists.py.
"""

import json
import logging
import os
import tempfile

LOGGER = logging.getLogger(__name__)

_LINUX_LOG_CHANNELS_PATH = "/opt/datarouter/etc/log-channels.json"
_QNX_LOG_CHANNELS_PATH = "/usr/bin/datarouter/etc/log-channels.json"


def _is_qnx(target) -> bool:
exit_code, out = target.execute("uname -s")
output = out.decode() if isinstance(out, bytes) else out
return "QNX" in output


def test_bandwidth_quotes_exists(target):
"""Verify datarouter's log-channels.json declares a 'quotas' section."""
remote_path = (
_QNX_LOG_CHANNELS_PATH if _is_qnx(target) else _LINUX_LOG_CHANNELS_PATH
)

with tempfile.TemporaryDirectory() as tmpdir:
local_path = os.path.join(tmpdir, "log-channels.json")
target.download(remote_path, local_path)

with open(local_path, "r") as f:
log_channel_content = json.load(f)

LOGGER.info(f"log-channels.json keys: {list(log_channel_content.keys())}")
assert "quotas" in log_channel_content
62 changes: 62 additions & 0 deletions score/test/component/datarouter/test_dr_restart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

"""Migration status for SPP's datarouter auto-restart-on-crash test.

SPP's version (test_logging_dr_restart.py::test_datarouter_restart) does:
execute_command(ssh, "killall -9 datarouter")
time.sleep(1)
assert execute_command(ssh, "pidof datarouter", timeout=60) == 0

That assertion only holds because SPP's target runs datarouter as a systemd
service with a Restart=always policy -- systemd, not the test, is what
relaunches the process after the kill. The test itself contains no restart
logic at all.

SCORE's test infrastructure has no equivalent service manager or process
supervisor. Evidence checked directly in this repo before concluding that:
- quality/integration_testing/environments/qnx8_qemu/system.build has no
service-manager entry for datarouter, only a direct IFS binary mapping
("[perms=777] /usr/bin/datarouter/datarouter = ${DATAROUTER}").
- score/test/component/logging_plugin.py's datarouter_on_target fixture
launches datarouter with a single direct execute_async/exec call
(Docker) or a single "on ... ./datarouter" invocation (QNX) -- in both
cases a one-shot launch with no restart wrapper.
- No systemd unit, supervisor process, or Restart=/respawn configuration
exists anywhere under score/test/component/ or
quality/integration_testing/ (grepped for "systemd", "supervisor",
"Restart=", "respawn" -- zero matches).

This is infrastructure the SUT's test deployment doesn't have, not a test
authoring gap -- per the ticket, migrating means porting the test against
what SCORE already provides, not building new supervisory infrastructure to
make an unsupported scenario pass. Skipped, not deleted, so the gap stays
visible in test output/reports.
"""

import pytest


@pytest.mark.skip(
reason=(
"SPP's test relies on systemd's Restart=always policy to relaunch "
"datarouter after a kill -- SCORE's Docker/QNX test images run no "
"service manager or process supervisor at all (verified: no "
"systemd/supervisor/Restart=/respawn configuration anywhere in "
"quality/integration_testing/ or score/test/component/). Not "
"portable without adding new supervisory infrastructure, which is "
"out of scope for this migration."
)
)
def test_dr_restart():
pass
60 changes: 60 additions & 0 deletions score/test/component/datarouter/test_file_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

"""Integration test for DLT kFile logging via dlt_generator.

Verifies that dlt_generator writes the expected number of DLT messages to a
local .dlt file when configured with kFile logging mode. Ported from SPP's
test_file_logging.py::test_dlt_kfile_logging.
"""

import logging
import os
import tempfile

import dlt.dlt as python_dlt

LOGGER = logging.getLogger(__name__)

APP_ID = "LGGG"
ITERATIONS = 12
DEFAULT_MESSAGE = "default message text for example log generating application"
# use_full_output defaults to true in dlt_generator: Fatal + Error + Warn + Info + Verbose + Debug
MESSAGES_PER_ITERATION = 6


def test_dlt_kfile_logging(target, datarouter_on_target):
"""Verify DLT kFile logging produces the expected number of messages."""
expected = ITERATIONS * MESSAGES_PER_ITERATION

target.execute(
f"cd /opt/test_apps/dlt_generator && ./bin/dlt_generator -i {ITERATIONS} -s 0"
)

exit_code, _ = target.execute("test -f /tmp/LGGG.dlt && echo EXISTS")
assert exit_code == 0, "DLT file /tmp/LGGG.dlt was not created on the target"

with tempfile.TemporaryDirectory() as tmpdir:
local_dlt = os.path.join(tmpdir, "LGGG.dlt")
target.download("/tmp/LGGG.dlt", local_dlt)
LOGGER.info(f"Downloaded DLT file: {os.path.getsize(local_dlt)} bytes")

dlt_messages = python_dlt.load(local_dlt, None)
occurrences = sum(
1
for m in dlt_messages
if getattr(m, "apid", None) == APP_ID
and DEFAULT_MESSAGE in str(getattr(m, "payload_decoded", ""))
)
LOGGER.info(f"Expected {expected} occurrences, got {occurrences}")
assert occurrences == expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# *******************************************************************************
# Copyright (c) 2026 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# This program and the accompanying materials are made available under the
# terms of the Apache License Version 2.0 which is available at
# https://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

"""Integration test verifying DataRouter can pick up an already-running
logging app's queued messages after a delayed start. Ported from SPP's
test_logging_after_delayed_dr_start.py.

SPP's assertion looks for a specific log line ("Accumulated frontend
exectution time:") that SPP's own dlt_generator prints at shutdown; SCORE's
dlt_generator (score/test/component/dlt_generator_app/dlt_generator.cpp) has
no such message. The actual intent (verify DataRouter can connect to and
drain a client's already-buffered messages once it finally starts) doesn't
depend on that specific string, so this checks for the app's regular
messages having been received instead.

Uses the datarouter_manager fixture (manual start/stop) instead of
datarouter_on_target so the app can be started BEFORE DataRouter, matching
SPP's delayed-start scenario. This needs kRemote + dlt_capture (not kFile):
the client's own local file writer doesn't care whether DataRouter is
running, so kFile-mode counting would trivially pass regardless of the
delayed-start behavior being tested -- see test_quota_exceed.py's docstring
for the same client-vs-DataRouter-side distinction. As with any
dlt_capture()-based test in this suite, this is subject to the local
Docker/multicast limitation and is build-verified locally, relying on CI for
the actual pass/fail.
"""

import logging
import time

from logging_plugin import download_dlt

LOGGER = logging.getLogger(__name__)

APP_ID = "LGGG"
DEFAULT_MESSAGE = "default message text for example log generating application"

_DR_START_DELAY_SEC = 2
_GENERATION_ITERATIONS = 20
_GENERATION_SLEEP_MS = 500
_SHUTDOWN_WAIT_MS = 2000


def test_logging_after_delayed_dr_start(target, datarouter_manager, dlt_capture):
"""Verify DataRouter connects to and drains a client started before it."""
with dlt_capture() as receiver:
proc = target.execute_async(
"/opt/test_apps/dlt_generator/bin/dlt_generator",
args=[
"-i",
str(_GENERATION_ITERATIONS),
"-s",
str(_GENERATION_SLEEP_MS),
"-w",
str(_SHUTDOWN_WAIT_MS),
],
cwd="/opt/test_apps/dlt_generator",
)
LOGGER.info(
f"dlt_generator started; delaying DataRouter start by {_DR_START_DELAY_SEC}s"
)
time.sleep(_DR_START_DELAY_SEC)
datarouter_manager.start()
proc.wait(timeout_s=30)

record = download_dlt(target, receiver.dlt_file)
messages = record.find(query=dict(apid=APP_ID))
count = sum(1 for m in messages if DEFAULT_MESSAGE in str(m.payload))
LOGGER.info(f"Received {count} messages after delayed DataRouter start")
assert count > 0, "No messages received after delayed DataRouter start"
Loading
Loading