diff --git a/.bazelrc b/.bazelrc index 4e27c368..6c80ac2e 100644 --- a/.bazelrc +++ b/.bazelrc @@ -38,6 +38,13 @@ common --//score/datarouter/build_configuration_flags:enable_dynamic_configurati common --//score/datarouter/build_configuration_flags:file_transfer=False common --//score/datarouter/build_configuration_flags:use_local_vlan=True +# TODO: Remove once score_baselibs fixes deprecated StringLiteral and +# ignored-attributes warnings (string_comparison_adaptor.h, text_format.h, etc.) +build --copt=-Wno-deprecated-declarations +build --copt=-Wno-ignored-attributes +# TODO: Remove once score_itf fixes strict-warning incompatibilities in dlt_daemon C code +build --per_file_copt=external.*score_itf.*dlt_daemon/.*@-Wno-missing-prototypes,-Wno-address,-Wno-stringop-truncation,-Wno-format,-Wno-format-nonliteral,-Wno-suggest-attribute=format + # Base QNX config (shared flags) common:qnx --credential_helper=*.qnx.com=%workspace%/scripts/internal/qnx_creds.py common:qnx --credential_helper_timeout="60s" diff --git a/.github/workflows/component_tests.yml b/.github/workflows/component_tests.yml new file mode 100644 index 00000000..cf3f340e --- /dev/null +++ b/.github/workflows/component_tests.yml @@ -0,0 +1,79 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +# ******************************************************************************** +# Copyright (c) 2025 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 +# ******************************************************************************** + +name: Component Tests + +on: + pull_request: + types: [opened, reopened, synchronize] + schedule: + # Run nightly at 02:00 UTC + - cron: "0 2 * * *" + workflow_dispatch: {} + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + component-tests: + name: Run Component Tests + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4.2.2 + + - name: Setup Bazel + uses: bazel-contrib/setup-bazel@0.15.0 + with: + bazelisk-version: 1.26.0 + disk-cache: true + repository-cache: true + bazelisk-cache: true + + - name: Bazel info (discover paths) + id: bazel-info + run: | + echo "BAZEL_OUTPUT_BASE=$(bazel info output_base)" >> $GITHUB_ENV + echo "BAZEL_REPO_CACHE=$(bazel info repository_cache)" >> $GITHUB_ENV + bazel info + + - name: Cache Bazel output base + uses: actions/cache@v4 + with: + path: | + ${{ env.BAZEL_OUTPUT_BASE }}/action_cache + ${{ env.BAZEL_OUTPUT_BASE }}/bazel-out + ${{ env.BAZEL_OUTPUT_BASE }}/external + ${{ env.BAZEL_OUTPUT_BASE }}/execroot + key: bazel-ob-component-v1-${{ runner.os }}-${{ hashFiles('.bazelversion', 'MODULE.bazel', 'MODULE.bazel.lock', '**/*.bzl') }} + restore-keys: | + bazel-ob-component-v1-${{ runner.os }}- + + - name: Run component tests + run: | + bazel test --lockfile_mode=error --config x86_64-linux \ + --test_tag_filters=integration \ + --test_output=errors \ + //score/test/component/... diff --git a/.github/workflows/coverage_report.yml b/.github/workflows/coverage_report.yml index 2b0f3435..14670750 100644 --- a/.github/workflows/coverage_report.yml +++ b/.github/workflows/coverage_report.yml @@ -27,4 +27,4 @@ jobs: uses: eclipse-score/cicd-workflows/.github/workflows/cpp-coverage.yml@main with: bazel-target: "//..." - extra-bazel-flags: "--lockfile_mode=error" + extra-bazel-flags: "--lockfile_mode=error --test_tag_filters=-integration" diff --git a/MODULE.bazel b/MODULE.bazel index 11e26dca..da9612f7 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -38,6 +38,21 @@ bazel_dep(name = "score_rust_policies", version = "0.0.3") bazel_dep(name = "score_process", version = "1.5.4", dev_dependency = True) bazel_dep(name = "score_platform", version = "0.5.5", dev_dependency = True) +bazel_dep(name = "score_itf", version = "0.4.0", dev_dependency = True) + +# OCI / Docker image rules for integration tests +bazel_dep(name = "rules_oci", version = "2.2.7", dev_dependency = True) +bazel_dep(name = "rules_pkg", version = "1.2.0", dev_dependency = True) + +oci = use_extension("@rules_oci//oci:extensions.bzl", "oci", dev_dependency = True) +oci.pull( + name = "ubuntu_24_04", + digest = "sha256:2e863c44b718727c860746568e1d54afd13b2fa71b160f5cd9058fc436217b30", + image = "ubuntu", + platforms = ["linux/amd64"], + tag = "24.04", +) +use_repo(oci, "ubuntu_24_04", "ubuntu_24_04_linux_amd64") # Toolchains and extensions bazel_dep(name = "score_bazel_cpp_toolchains", version = "0.5.1", dev_dependency = True) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index a802e7c8..04dc3a02 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -35,6 +35,7 @@ "https://bcr.bazel.build/modules/aspect_bazel_lib/2.20.0/MODULE.bazel": "c5565bac49e1973227225b441fad1c938d498d83df62dc5da95b2fab0f0626a2", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.0/MODULE.bazel": "7fe0191f047d4fe4a4a46c1107e2350cbb58a8fc2e10913aa4322d3190dec0bf", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.0/source.json": "369df5b7f2eae82f200fff95cf1425f90dee90a0d0948122060b48150ff0e224", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.2/MODULE.bazel": "780d1a6522b28f5edb7ea09630748720721dfe27690d65a2d33aa7509de77e07", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.9.3/MODULE.bazel": "66baf724dbae7aff4787bf2245cc188d50cb08e07789769730151c0943587c14", @@ -425,10 +426,13 @@ "https://bcr.bazel.build/modules/rules_nodejs/6.3.3/MODULE.bazel": "b66eadebd10f1f1b25f52f95ab5213a57e82c37c3f656fcd9a57ad04d2264ce7", "https://bcr.bazel.build/modules/rules_nodejs/6.5.2/MODULE.bazel": "7f9ea68a0ce6d82905ce9f74e76ab8a8b4531ed4c747018c9d76424ad0b3370d", "https://bcr.bazel.build/modules/rules_nodejs/6.5.2/source.json": "6a6ca0940914d55c550d1417cad13a56c9900e23f651a762d8ccc5a64adcf661", + "https://bcr.bazel.build/modules/rules_oci/2.2.7/MODULE.bazel": "f6150e4b224d459f7f6523ef65967464ca4efdd266c7fbf2f5a2a51011957e0c", + "https://bcr.bazel.build/modules/rules_oci/2.2.7/source.json": "b099f02af330f47f19dc67fc9300ef6e1937a8c86882690db0e7a2fcea8c7f6b", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", "https://bcr.bazel.build/modules/rules_pkg/1.1.0/MODULE.bazel": "9db8031e71b6ef32d1846106e10dd0ee2deac042bd9a2de22b4761b0c3036453", - "https://bcr.bazel.build/modules/rules_pkg/1.1.0/source.json": "fef768df13a92ce6067e1cd0cdc47560dace01354f1d921cfb1d632511f7d608", + "https://bcr.bazel.build/modules/rules_pkg/1.2.0/MODULE.bazel": "c7db3c2b407e673c7a39e3625dc05dc9f12d6682cbd82a3a5924a13b491eda7e", + "https://bcr.bazel.build/modules/rules_pkg/1.2.0/source.json": "9062e00845bf91a4247465d371baa837adf9b6ff44c542f73ba084f07667e1dc", "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", @@ -458,7 +462,8 @@ "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", "https://bcr.bazel.build/modules/rules_python/1.5.1/MODULE.bazel": "acfe65880942d44a69129d4c5c3122d57baaf3edf58ae5a6bd4edea114906bf5", "https://bcr.bazel.build/modules/rules_python/1.8.3/MODULE.bazel": "f343e159b59701334be3914416b9f1b72845801ba47920fcb288af4ce8c5cce3", - "https://bcr.bazel.build/modules/rules_python/1.8.3/source.json": "e5439f308e3c6f79f318a0f87108db46fc575be89370c3dfb3f7e0eaa571a3f8", + "https://bcr.bazel.build/modules/rules_python/1.8.5/MODULE.bazel": "28b2d79ed8368d7d45b34bacc220e3c0b99cbcd9392641961b849e4c3f55dd30", + "https://bcr.bazel.build/modules/rules_python/1.8.5/source.json": "e261b03c8804f2582c9536013f987e1ea105a2b38c238aa2ac8f98fc34c8b18a", "https://bcr.bazel.build/modules/rules_rust/0.56.0/MODULE.bazel": "3295b00757db397122092322fe1e920be7f5c9fbfb8619138977e820f2cbbbae", "https://bcr.bazel.build/modules/rules_rust/0.61.0/MODULE.bazel": "0318a95777b9114c8740f34b60d6d68f9cfef61e2f4b52424ca626213d33787b", "https://bcr.bazel.build/modules/rules_rust/0.67.0/MODULE.bazel": "87c3816c4321352dcfd9e9e26b58e84efc5b21351ae3ef8fb5d0d57bde7237f5", @@ -537,6 +542,7 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.20.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.22.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.7.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/aspect_bazel_lib/2.9.3/MODULE.bazel": "not found", @@ -823,9 +829,11 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/6.3.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/6.3.3/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_nodejs/6.5.2/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_oci/2.2.7/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/0.7.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/1.0.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/1.1.0/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_pkg/1.2.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/4.0.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "not found", @@ -854,6 +862,7 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/1.4.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/1.5.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/1.8.3/MODULE.bazel": "not found", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_python/1.8.5/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.56.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.61.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/rules_rust/0.67.0/MODULE.bazel": "not found", @@ -886,6 +895,8 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_crates/0.0.9/source.json": "58f24336e5e21d53551c0f0d304b0efa318eca031d296f33b998996c73573ce3", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/4.0.1/MODULE.bazel": "5955f4cf37228a9cdda7f6009b81db0446f005c618f4bc43665bfa45f2673ebc", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_docs_as_code/4.0.1/source.json": "636a0e9413885bc483d98cd32ba14709fb694f8b07bd8a112641092dc699db90", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_itf/0.4.0/MODULE.bazel": "dd3e225e6fea37ab0c950888f4785faa4485215c15dfc5082ea8c577709db4e9", + "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_itf/0.4.0/source.json": "95fc7a5446339690dd40700ae3cab0492257cfd0269d402f42531af1e3278734", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_platform/0.5.5/MODULE.bazel": "070fa5fb10456e50675bcad2958fec0ed834f0aadbd0b71f25917f9e7edae2e2", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_platform/0.5.5/source.json": "1ce2e94f0e366eaae131091aaee2c16199c974e82959eec9d863c9b0c93f2fdc", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/score_process/1.3.2/MODULE.bazel": "a32390ef217cef9a811408b0a1c5aeed1398c377aa846f5d5416d7b95b4e4366", @@ -2334,10 +2345,199 @@ "recordedRepoMappingEntries": [] } }, + "@@rules_oci+//oci:extensions.bzl%oci": { + "general": { + "bzlTransitiveDigest": "kH8yGTr0UMGNfLvOd8gBxVC/iY5HHeVuhIZfOyZpCSU=", + "usagesDigest": "sRVlTEOffP1bFxYjWmpA8p+LdvK9HarT6DEnX61AroE=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "ubuntu_24_04_linux_amd64": { + "repoRuleId": "@@rules_oci+//oci/private:pull.bzl%oci_pull", + "attributes": { + "www_authenticate_challenges": {}, + "scheme": "https", + "registry": "index.docker.io", + "repository": "library/ubuntu", + "identifier": "sha256:2e863c44b718727c860746568e1d54afd13b2fa71b160f5cd9058fc436217b30", + "platform": "linux/amd64", + "target_name": "ubuntu_24_04_linux_amd64", + "bazel_tags": [] + } + }, + "ubuntu_24_04": { + "repoRuleId": "@@rules_oci+//oci/private:pull.bzl%oci_alias", + "attributes": { + "target_name": "ubuntu_24_04", + "www_authenticate_challenges": {}, + "scheme": "https", + "registry": "index.docker.io", + "repository": "library/ubuntu", + "identifier": "sha256:2e863c44b718727c860746568e1d54afd13b2fa71b160f5cd9058fc436217b30", + "platforms": { + "@@platforms//cpu:x86_64": "@ubuntu_24_04_linux_amd64" + }, + "bzlmod_repository": "ubuntu_24_04", + "reproducible": true + } + }, + "oci_crane_darwin_amd64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "darwin_amd64", + "crane_version": "v0.18.0" + } + }, + "oci_crane_darwin_arm64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "darwin_arm64", + "crane_version": "v0.18.0" + } + }, + "oci_crane_linux_arm64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "linux_arm64", + "crane_version": "v0.18.0" + } + }, + "oci_crane_linux_armv6": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "linux_armv6", + "crane_version": "v0.18.0" + } + }, + "oci_crane_linux_i386": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "linux_i386", + "crane_version": "v0.18.0" + } + }, + "oci_crane_linux_s390x": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "linux_s390x", + "crane_version": "v0.18.0" + } + }, + "oci_crane_linux_amd64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "linux_amd64", + "crane_version": "v0.18.0" + } + }, + "oci_crane_windows_armv6": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "windows_armv6", + "crane_version": "v0.18.0" + } + }, + "oci_crane_windows_amd64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%crane_repositories", + "attributes": { + "platform": "windows_amd64", + "crane_version": "v0.18.0" + } + }, + "oci_crane_toolchains": { + "repoRuleId": "@@rules_oci+//oci/private:toolchains_repo.bzl%toolchains_repo", + "attributes": { + "toolchain_type": "@rules_oci//oci:crane_toolchain_type", + "toolchain": "@oci_crane_{platform}//:crane_toolchain" + } + }, + "oci_regctl_darwin_amd64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", + "attributes": { + "platform": "darwin_amd64" + } + }, + "oci_regctl_darwin_arm64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", + "attributes": { + "platform": "darwin_arm64" + } + }, + "oci_regctl_linux_arm64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", + "attributes": { + "platform": "linux_arm64" + } + }, + "oci_regctl_linux_s390x": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", + "attributes": { + "platform": "linux_s390x" + } + }, + "oci_regctl_linux_amd64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", + "attributes": { + "platform": "linux_amd64" + } + }, + "oci_regctl_windows_amd64": { + "repoRuleId": "@@rules_oci+//oci:repositories.bzl%regctl_repositories", + "attributes": { + "platform": "windows_amd64" + } + }, + "oci_regctl_toolchains": { + "repoRuleId": "@@rules_oci+//oci/private:toolchains_repo.bzl%toolchains_repo", + "attributes": { + "toolchain_type": "@rules_oci//oci:regctl_toolchain_type", + "toolchain": "@oci_regctl_{platform}//:regctl_toolchain" + } + } + }, + "moduleExtensionMetadata": { + "explicitRootModuleDirectDeps": [], + "explicitRootModuleDirectDevDeps": [ + "ubuntu_24_04", + "ubuntu_24_04_linux_amd64" + ], + "useAllRepos": "NO", + "reproducible": false + }, + "recordedRepoMappingEntries": [ + [ + "aspect_bazel_lib+", + "bazel_tools", + "bazel_tools" + ], + [ + "bazel_features+", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_oci+", + "aspect_bazel_lib", + "aspect_bazel_lib+" + ], + [ + "rules_oci+", + "bazel_features", + "bazel_features+" + ], + [ + "rules_oci+", + "bazel_skylib", + "bazel_skylib+" + ] + ] + } + }, "@@rules_python+//python/extensions:config.bzl%config": { "general": { "bzlTransitiveDigest": "V+wrDxg73HRC54yN8US3eQCsE4GnYCcebkee7voYa1E=", - "usagesDigest": "HZ99ezJBkgjKcxXQ3OBxGgesUIMF6eg7E0TUNOait2I=", + "usagesDigest": "lDbpRfhoWmZCHSaNxwZv/8fF2y0wu2th0G0f/uqX7VM=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -2572,7 +2772,7 @@ "@@rules_python+//python/uv:uv.bzl%uv": { "general": { "bzlTransitiveDigest": "85utVTiuGY4Ut0FgpNLZvwD42nCeVn+J4Cit6fSR5OM=", - "usagesDigest": "XRYYokHTb3p1WwpZj2sncZ1bh8zJfO4YrKJ2PkapnO8=", + "usagesDigest": "c4BCoL7WnccEomzDYulDuOys9pd6N93KaNI4mTVbqi0=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, diff --git a/score/datarouter/BUILD b/score/datarouter/BUILD index c28de93b..1dd58f29 100644 --- a/score/datarouter/BUILD +++ b/score/datarouter/BUILD @@ -367,6 +367,19 @@ filegroup( "//platform/aas/tools/sctf:__subpackages__", "//pqr/abc/abc-lmn/config/project/cpu/pas/datarouter:__pkg__", "//score/datarouter/test:__subpackages__", + "//score/test/component:__subpackages__", + ], +) + +filegroup( + name = "integration_test_config_files", + srcs = [ + "etc/log-channels.json", + "etc/logging.json", + "etc/raw-channels.json", + ], + visibility = [ + "//score/test/component:__subpackages__", ], ) diff --git a/score/datarouter/build_configuration_flags/BUILD b/score/datarouter/build_configuration_flags/BUILD index 4fb2ee30..34a80766 100644 --- a/score/datarouter/build_configuration_flags/BUILD +++ b/score/datarouter/build_configuration_flags/BUILD @@ -51,7 +51,7 @@ config_setting( ":enable_nonverbose_dlt": "True", }, visibility = [ - "@score_logging//score/datarouter:__subpackages__", + "//visibility:public", ], ) diff --git a/score/datarouter/dlt_filetransfer_trigger_lib/BUILD b/score/datarouter/dlt_filetransfer_trigger_lib/BUILD index 5adca908..928d4101 100644 --- a/score/datarouter/dlt_filetransfer_trigger_lib/BUILD +++ b/score/datarouter/dlt_filetransfer_trigger_lib/BUILD @@ -43,6 +43,7 @@ cc_library( "//platform/aas/pas/datarouterconf/src/services:__pkg__", "//platform/aas/test/pas:__subpackages__", "//score/datarouter/test:__subpackages__", + "//score/test/component:__subpackages__", "@score_baselibs//score/logging:__pkg__", ], deps = [ diff --git a/score/mw/log/legacy_non_verbose_api/BUILD b/score/mw/log/legacy_non_verbose_api/BUILD index cc573333..dca3d4ca 100644 --- a/score/mw/log/legacy_non_verbose_api/BUILD +++ b/score/mw/log/legacy_non_verbose_api/BUILD @@ -36,6 +36,7 @@ cc_library( visibility = [ "//platform/aas/ara/log:__subpackages__", "//score/mw/log:__subpackages__", + "//score/test/component:__subpackages__", "@score_baselibs//score/mw/log:__subpackages__", "@score_logging//score/datarouter:__subpackages__", ], diff --git a/score/test/component/BUILD b/score/test/component/BUILD new file mode 100644 index 00000000..0241f230 --- /dev/null +++ b/score/test/component/BUILD @@ -0,0 +1,69 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") +load("@rules_python//python:defs.bzl", "py_library") +load("@score_itf//bazel:py_itf_plugin.bzl", "py_itf_plugin") + +# ============================================================================= +# Shared base layers (included in every test image by the macro) +# ============================================================================= +pkg_tar( + name = "dlt_pkg", + srcs = [ + "@score_itf//third_party/dlt:dlt-receive", + ], + visibility = ["//score/test/component:__subpackages__"], +) + +pkg_tar( + name = "datarouter_bin_pkg", + srcs = ["//score/datarouter"], + package_dir = "opt/datarouter/bin", +) + +pkg_tar( + name = "datarouter_conf_pkg", + srcs = ["//score/datarouter:integration_test_config_files"], + package_dir = "opt/datarouter", + strip_prefix = "/score/datarouter", +) + +pkg_tar( + name = "datarouter_pkg", + visibility = ["//score/test/component:__subpackages__"], + deps = [ + ":datarouter_bin_pkg", + ":datarouter_conf_pkg", + ], +) + +# ============================================================================= +# Shared Python libraries and plugin +# ============================================================================= + +py_library( + name = "logging_plugin_lib", + srcs = ["logging_plugin.py"], + imports = ["."], + deps = ["@score_itf//score/itf/plugins:docker"], +) + +py_itf_plugin( + name = "logging_plugin", + enabled_plugins = [ + "logging_plugin", + ], + py_library = ":logging_plugin_lib", + visibility = ["//score/test/component:__subpackages__"], +) diff --git a/score/test/component/datarouter/BUILD b/score/test/component/datarouter/BUILD new file mode 100644 index 00000000..025cfa7b --- /dev/null +++ b/score/test/component/datarouter/BUILD @@ -0,0 +1,37 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +# ******************************************************************************** +# Copyright (c) 2025 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 +# ******************************************************************************** + +load("//score/test/component:defs.bzl", "py_logging_itf_test") + +py_logging_itf_test( + name = "test_datarouter_filters", + srcs = ["test_datarouter_filters.py"], + filesystem = "//score/test/component/filters_app:filtertest_filesystem", + target_compatible_with = select({ + "//score/datarouter/build_configuration_flags:config_datarouter_nonverbose_dlt": [], + "//conditions:default": ["@platforms//:incompatible"], + }), + deps = ["@score_itf//score/itf/plugins/dlt"], +) diff --git a/score/test/component/datarouter/test_datarouter_filters.py b/score/test/component/datarouter/test_datarouter_filters.py new file mode 100644 index 00000000..1f21116e --- /dev/null +++ b/score/test/component/datarouter/test_datarouter_filters.py @@ -0,0 +1,86 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 datarouter non-verbose (TRACE) log-level filtering. + +Verifies that per-context log-level filtering works for non-verbose (TRACE) +messages routed through the datarouter. The filtertest binary emits 18 +TRACE'd structs (6 per context x 3 contexts: AAAA, BBBB, CCCC) plus one +UndefinedStruct. Each context is configured at a different log level: + + - AAAA (kVerbose): all 6 messages pass the filter + - BBBB (kInfo): 4 messages pass (Fatal, Error, Warn, Info) + - CCCC (kFatal): 1 message passes (Fatal only) + +Total expected non-verbose messages: 11 + +NOTE: This test requires building with the nonverbose DLT flag: + --//score/datarouter/build_configuration_flags:enable_nonverbose_dlt=True +""" + +import ctypes +import logging + +from logging_plugin import download_dlt + +LOGGER = logging.getLogger(__name__) + +TOTAL_NONVERBOSE_MESSAGES = 11 + + +def test_datarouter_filters(target, datarouter_on_target, dlt_capture): + """Verify per-context non-verbose log-level filtering via TRACE(). + + Expected message counts per context group (type_id // 100): + - group 1 (AAAA, kVerbose): 6 messages + - group 2 (BBBB, kInfo): 4 messages + - group 3 (CCCC, kFatal): 1 message + Total non-verbose messages: 11 + """ + with dlt_capture() as receiver: + target.execute("cd /opt/test_apps/filtertest && ./bin/filtertest") + + # Download the captured .dlt file + record = download_dlt(target, receiver.dlt_file) + + # Find only non-extended (non-verbose / TRACE) messages + messages = record.find(include_ext=False, include_non_ext=True) + LOGGER.info(f"Non-verbose messages found: {len(messages)}") + + # Extract type IDs from the raw DLT payload + received_ids = {} + for msg in messages: + id_t = ctypes.c_uint32 + message_address = ctypes.addressof(msg.raw_msg.databuffer.contents) + typeid = id_t.from_address(message_address).value + + ctxid = typeid // 100 + if ctxid not in received_ids: + received_ids[ctxid] = [] + received_ids[ctxid].append(typeid) + + LOGGER.info(f"Received IDs by context group: {received_ids}") + + assert len(received_ids.get(1, [])) == 6, ( + f"AAAA (kVerbose): expected 6, got {len(received_ids.get(1, []))}" + ) + assert len(received_ids.get(2, [])) == 4, ( + f"BBBB (kInfo): expected 4, got {len(received_ids.get(2, []))}" + ) + assert len(received_ids.get(3, [])) == 1, ( + f"CCCC (kFatal): expected 1, got {len(received_ids.get(3, []))}" + ) + assert len(messages) == TOTAL_NONVERBOSE_MESSAGES, ( + f"Total: expected {TOTAL_NONVERBOSE_MESSAGES}, got {len(messages)}" + ) + LOGGER.info("Datarouter non-verbose filter validation succeeded") diff --git a/score/test/component/defs.bzl b/score/test/component/defs.bzl new file mode 100644 index 00000000..f4bc3cb5 --- /dev/null +++ b/score/test/component/defs.bzl @@ -0,0 +1,82 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load") +load("@score_itf//:defs.bzl", "py_itf_test") + +def _extend_list_in_kwargs(kwargs, key, values): + kwargs[key] = kwargs.get(key, []) + values + return kwargs + +def py_logging_itf_test(name, srcs, filesystem, **kwargs): + """Integration test macro for score_logging using Docker + DLT plugins. + + Builds a per-test Docker image from the given filesystem tar layers + (which must include the test app binaries/configs) combined with the + shared base layers (DLT daemon, datarouter). + + Args: + name: Test target name. + srcs: Python test source files. + filesystem: A pkg_tar target containing the test-specific binaries + and configuration files. + **kwargs: Forwarded to py_itf_test (e.g. deps, data, tags, timeout). + """ + image_name = "_image_{}".format(name) + image_loader = "_image_{}_loader".format(name) + repo_tag = "{}:latest".format(name) + + oci_image( + name = image_name, + base = "@ubuntu_24_04", + tars = [ + "//score/test/component:dlt_pkg", + "//score/test/component:datarouter_pkg", + filesystem, + ], + ) + + oci_load( + name = image_loader, + image = image_name, + repo_tags = [repo_tag], + ) + + _extend_list_in_kwargs(kwargs, "data", [image_loader]) + _extend_list_in_kwargs(kwargs, "args", [ + "--docker-image-bootstrap=$(location {})".format(image_loader), + "--docker-image={}".format(repo_tag), + ]) + + if "tags" not in kwargs: + kwargs["tags"] = [] + if "integration" not in kwargs["tags"]: + kwargs["tags"] = kwargs["tags"] + ["integration"] + + if "size" not in kwargs: + kwargs["size"] = "enormous" + + if "timeout" not in kwargs: + kwargs["timeout"] = "short" + + py_itf_test( + name = name, + srcs = srcs, + plugins = [ + "@score_itf//score/itf/plugins:docker_plugin", + "@score_itf//score/itf/plugins:dlt_plugin", + "//score/test/component:logging_plugin", + ], + env = {"DOCKER_HOST": ""}, + **kwargs + ) diff --git a/score/test/component/dlt_generator_app/BUILD b/score/test/component/dlt_generator_app/BUILD new file mode 100644 index 00000000..9bde70fc --- /dev/null +++ b/score/test/component/dlt_generator_app/BUILD @@ -0,0 +1,54 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +# ******************************************************************************** +# Copyright (c) 2025 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 +# ******************************************************************************** + +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") + +cc_binary( + name = "dlt_generator", + srcs = ["dlt_generator.cpp"], + deps = ["//score/mw/log"], +) + +pkg_tar( + name = "dlt_generator_bin_pkg", + srcs = [":dlt_generator"], + package_dir = "opt/test_apps/dlt_generator/bin", +) + +pkg_tar( + name = "dlt_generator_config_pkg", + srcs = ["etc/logging.json"], + package_dir = "opt/test_apps/dlt_generator/etc", +) + +pkg_tar( + name = "dlt_generator_filesystem", + visibility = ["//score/test/component:__subpackages__"], + deps = [ + ":dlt_generator_bin_pkg", + ":dlt_generator_config_pkg", + ], +) diff --git a/score/test/component/dlt_generator_app/dlt_generator.cpp b/score/test/component/dlt_generator_app/dlt_generator.cpp new file mode 100644 index 00000000..3e93aa88 --- /dev/null +++ b/score/test/component/dlt_generator_app/dlt_generator.cpp @@ -0,0 +1,125 @@ +/******************************************************************************** + * Copyright (c) 2025 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 + ********************************************************************************/ +#include "score/mw/log/logger.h" +#include "score/mw/log/logging.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace log = score::mw::log; + +namespace { + +const std::string kDefaultMessage = "default message text for example log generating application"; + +void DltLogJob(log::Logger& logger, + uint32_t iterations, + uint32_t sleep_ms, + bool counter, + bool use_full_output, + std::optional thread_id) { + for (uint32_t i = 0; i < iterations; ++i) { + std::string prefix; + if (thread_id.has_value()) { + prefix = "t" + std::to_string(thread_id.value()) + ":"; + } + if (counter) { + prefix += std::to_string(i + 1) + ": "; + } + + if (use_full_output) { + logger.LogFatal() << prefix << kDefaultMessage; + logger.LogError() << prefix << kDefaultMessage; + logger.LogWarn() << prefix << kDefaultMessage; + } + logger.LogInfo() << prefix << kDefaultMessage; + logger.LogVerbose() << prefix << kDefaultMessage; + logger.LogDebug() << prefix << kDefaultMessage; + + if (sleep_ms > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); + } + } +} + +} // namespace + +int main(int argc, char* argv[]) { + uint32_t threads_num = 1; + uint32_t iterations = 1; + uint32_t sleep_ms = 100; + bool counter = false; + bool use_full_output = true; + uint32_t sleep_before_shutdown_ms = 500; + + int opt; + while ((opt = getopt(argc, argv, "t:i:s:c:f:w:")) != -1) { + switch (opt) { + case 't': + threads_num = static_cast(std::atoi(optarg)); + break; + case 'i': + iterations = static_cast(std::atoi(optarg)); + break; + case 's': + sleep_ms = static_cast(std::atoi(optarg)); + break; + case 'c': + counter = (std::string(optarg) == "true"); + break; + case 'f': + use_full_output = (std::string(optarg) == "true"); + break; + case 'w': + sleep_before_shutdown_ms = static_cast(std::atoi(optarg)); + break; + default: + std::cerr << "Usage: " << argv[0] + << " [-t threads] [-i iterations] [-s sleep_ms]" + << " [-c true|false] [-f true|false] [-w shutdown_wait_ms]" + << std::endl; + return 1; + } + } + + log::Logger& logger = log::CreateLogger("LOGG", "dlt generator context"); + logger.LogInfo() << "Welcome warm-up message to initialize logging context."; + + std::vector threads; + for (uint32_t tid = 0; tid < threads_num; ++tid) { + std::optional pass_tid = + (threads_num == 1) ? std::nullopt : std::optional(static_cast(tid)); + threads.emplace_back(DltLogJob, std::ref(logger), iterations, sleep_ms, + counter, use_full_output, pass_tid); + } + + for (auto& t : threads) { + t.join(); + } + + logger.LogInfo() << "DLT generator finished."; + + if (sleep_before_shutdown_ms > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_before_shutdown_ms)); + } + + return 0; +} diff --git a/score/test/component/dlt_generator_app/etc/logging.json b/score/test/component/dlt_generator_app/etc/logging.json new file mode 100644 index 00000000..ccf0f30c --- /dev/null +++ b/score/test/component/dlt_generator_app/etc/logging.json @@ -0,0 +1,7 @@ +{ + "appId": "LGGG", + "appDesc": "DLT Generator App", + "logMode": "kFile|kRemote", + "logLevel": "kVerbose", + "logLevelThresholdConsole": "kDebug" +} diff --git a/score/test/component/filetransfer/BUILD b/score/test/component/filetransfer/BUILD new file mode 100644 index 00000000..1b7e6fe5 --- /dev/null +++ b/score/test/component/filetransfer/BUILD @@ -0,0 +1,51 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") +load("//score/test/component:defs.bzl", "py_logging_itf_test") + +cc_binary( + name = "filetransfer_app", + srcs = ["filetransfer_app.cpp"], + deps = [ + "//score/datarouter/dlt_filetransfer_trigger_lib", + "//score/mw/log", + ], +) + +pkg_tar( + name = "filetransfer_bin_pkg", + srcs = [":filetransfer_app"], + package_dir = "opt/test_apps/filetransfer/bin", +) + +pkg_tar( + name = "filetransfer_config_pkg", + srcs = ["etc/logging.json"], + package_dir = "opt/test_apps/filetransfer/etc", +) + +pkg_tar( + name = "filetransfer_filesystem", + deps = [ + ":filetransfer_bin_pkg", + ":filetransfer_config_pkg", + ], +) + +py_logging_itf_test( + name = "test_filetransfer", + srcs = ["test_filetransfer.py"], + filesystem = ":filetransfer_filesystem", +) diff --git a/score/test/component/filetransfer/etc/logging.json b/score/test/component/filetransfer/etc/logging.json new file mode 100644 index 00000000..067456b4 --- /dev/null +++ b/score/test/component/filetransfer/etc/logging.json @@ -0,0 +1,6 @@ +{ + "appId": "FTEA", + "appDesc": "File Transfer Example App", + "logMode": "kRemote", + "logLevel": "kVerbose" +} diff --git a/score/test/component/filetransfer/filetransfer_app.cpp b/score/test/component/filetransfer/filetransfer_app.cpp new file mode 100644 index 00000000..eaad1b1c --- /dev/null +++ b/score/test/component/filetransfer/filetransfer_app.cpp @@ -0,0 +1,77 @@ +/******************************************************************************** + * Copyright (c) 2025 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 + ********************************************************************************/ +#include "score/datarouter/dlt_filetransfer_trigger_lib/include/file_transfer.h" +#include "score/mw/log/logger.h" +#include "score/mw/log/logging.h" + +#include +#include +#include +#include +#include + +namespace log = score::mw::log; + +namespace { + +const std::string kTestFileContent = + "This is test content for file transfer integration test.\n" + "Line 2 of test data.\n" + "Line 3 of test data.\n"; + +void CreateTestFile(const std::string& path) { + std::ofstream out(path); + out << kTestFileContent; +} + +} // namespace + +int main() { + log::Logger& logger = log::CreateLogger("FTEA", "File Transfer Example App"); + + logger.LogInfo() << "File Transfer Example Application" << "initialize"; + + // Create a test file to transfer + const std::string original_file = "/tmp/filetransfer_test_file.txt"; + CreateTestFile(original_file); + logger.LogInfo() << "Created test file:" << original_file; + + // Initialize file transfer + score::logging::FileTransfer file_transfer("FTEA", "FMSG"); + + logger.LogInfo() << "File Transfer Example Application" << "DoFileTransfer"; + + // Transfer multiple copies of the file + constexpr int kNumTransfers = 50; + for (int i = 0; i < kNumTransfers; ++i) { + 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; + } + + file_transfer.TransferFile(filename.str(), false); + logger.LogInfo() << "Transferred file:" << filename.str(); + } + + logger.LogInfo() << "File Transfer Example Application" << "done"; + + // Wait for logs to be processed + sleep(2); + + return 0; +} diff --git a/score/test/component/filetransfer/test_filetransfer.py b/score/test/component/filetransfer/test_filetransfer.py new file mode 100644 index 00000000..9e27a09b --- /dev/null +++ b/score/test/component/filetransfer/test_filetransfer.py @@ -0,0 +1,76 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 file transfer. + +Verifies that the filetransfer_app correctly creates test files and invokes +the FileTransfer API via the datarouter. The verbose log messages confirming +each transfer are received via dlt-receive. + +Note: The datarouter's file transfer feature flag may be disabled in the +default configuration, in which case the FLST/FLDA/FLFI protocol messages +are not emitted. This test verifies the app-side transfer invocations and +that the test files are created on the target filesystem. +""" + +import logging + +import pytest +from logging_plugin import download_dlt + + +LOGGER = logging.getLogger(__name__) + +APP_ID = "FTEA" +NUM_TRANSFERS = 50 + + +def test_filetransfer(target, datarouter_on_target, dlt_capture): + """Verify that file transfer DLT messages are sent and received.""" + with dlt_capture() as receiver: + target.execute("cd /opt/test_apps/filetransfer && ./bin/filetransfer_app") + + record = download_dlt(target, receiver.dlt_file) + messages = record.find(query=dict(apid=APP_ID)) + LOGGER.info(f"App message count: {len(messages)}") + + assert len(messages) > 0, "No DLT messages received from filetransfer_app" + + app_payloads = "\n".join(str(m.payload) for m in messages) + + # Verify lifecycle messages + assert "initialize" in app_payloads, "Missing 'initialize' message" + assert "DoFileTransfer" in app_payloads, "Missing 'DoFileTransfer' message" + assert "done" in app_payloads, "Missing 'done' message" + + # Verify transfer confirmations from the app + transfer_msgs = [m for m in messages if "Transferred file" in str(m.payload)] + assert len(transfer_msgs) == NUM_TRANSFERS, ( + f"Expected {NUM_TRANSFERS} transfer confirmations, got {len(transfer_msgs)}" + ) + + # Verify the test files were created on the target filesystem + exit_code, ls_output = target.execute( + "ls /tmp/filetransfer_test_[0-9]*.txt | wc -l" + ) + file_count = int(ls_output.strip()) + assert file_count == NUM_TRANSFERS, ( + f"Expected {NUM_TRANSFERS} test files on target, found {file_count}" + ) + + +@pytest.mark.skip( + reason="file_transfer_impl not available in this repo — FLST/FLDA/FLFI protocol messages are not emitted" +) +def test_filetransfer_protocol_verification(target, datarouter_on_target, dlt_capture): + """Verify DLT file transfer protocol messages (FLST/FLDA/FLFI) and hash integrity.""" diff --git a/score/test/component/filters_app/BUILD b/score/test/component/filters_app/BUILD new file mode 100644 index 00000000..09ba647f --- /dev/null +++ b/score/test/component/filters_app/BUILD @@ -0,0 +1,109 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +# ******************************************************************************** +# Copyright (c) 2025 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 +# ******************************************************************************** + +load("@rules_cc//cc:defs.bzl", "cc_binary") +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") + +# --------------------------------------------------------------------------- +# mwfiltertest — used by mw_log verbose filter tests +# --------------------------------------------------------------------------- +cc_binary( + name = "mwfiltertest", + srcs = ["mwfiltertest.cpp"], + deps = ["//score/mw/log"], +) + +pkg_tar( + name = "mwfiltertest_bin_pkg", + srcs = [":mwfiltertest"], + package_dir = "opt/test_apps/mwfiltertest/bin", +) + +pkg_tar( + name = "mwfiltertest_config_pkg", + srcs = ["etc/logging.json"], + package_dir = "opt/test_apps/mwfiltertest/etc", +) + +pkg_tar( + name = "mwfiltertest_filesystem", + visibility = ["//score/test/component:__subpackages__"], + deps = [ + ":mwfiltertest_bin_pkg", + ":mwfiltertest_config_pkg", + ], +) + +# --------------------------------------------------------------------------- +# filtertest — used by datarouter non-verbose filter tests +# --------------------------------------------------------------------------- +cc_binary( + name = "filtertest", + srcs = [ + "filtertest.cpp", + "filtertest.h", + ], + deps = [ + "//score/mw/log", + "//score/mw/log/legacy_non_verbose_api", + "@score_baselibs//score/static_reflection_with_serialization/visitor", + ], +) + +pkg_tar( + name = "filtertest_bin_pkg", + srcs = [":filtertest"], + package_dir = "opt/test_apps/filtertest/bin", +) + +genrule( + name = "filtertest_logging_json", + srcs = ["etc/filtertest_logging.json"], + outs = ["filtertest_etc/logging.json"], + cmd = "cp $< $@", +) + +pkg_tar( + name = "filtertest_config_pkg", + srcs = [":filtertest_logging_json"], + package_dir = "opt/test_apps/filtertest/etc", + strip_prefix = "filtertest_etc", +) + +pkg_tar( + name = "filtertest_classid_pkg", + srcs = ["etc/class-id.json"], + package_dir = "bmw/platform/opt/datarouter/etc", +) + +pkg_tar( + name = "filtertest_filesystem", + visibility = ["//score/test/component:__subpackages__"], + deps = [ + ":filtertest_bin_pkg", + ":filtertest_classid_pkg", + ":filtertest_config_pkg", + ], +) diff --git a/score/test/component/filters_app/etc/class-id.json b/score/test/component/filters_app/etc/class-id.json new file mode 100644 index 00000000..a8ab259f --- /dev/null +++ b/score/test/component/filters_app/etc/class-id.json @@ -0,0 +1,3 @@ +{"AAAA_kFatal": {"id": 111, "ctxid": "AAAA", "appid": "APP", "loglevel": 1}, "AAAA_kError": {"id": 112, "ctxid": "AAAA", "appid": "APP", "loglevel": 2}, "AAAA_kWarn": {"id": 113, "ctxid": "AAAA", "appid": "APP", "loglevel": 3}, "AAAA_kInfo": {"id": 114, "ctxid": "AAAA", "appid": "APP", "loglevel": 4}, "AAAA_kDebug": {"id": 115, "ctxid": "AAAA", "appid": "APP", "loglevel": 5}, "AAAA_kVerbose": {"id": 116, "ctxid": "AAAA", "appid": "APP", "loglevel": 6}, + "BBBB_kFatal": {"id": 211, "ctxid": "BBBB", "appid": "APP", "loglevel": 1}, "BBBB_kError": {"id": 212, "ctxid": "BBBB", "appid": "APP", "loglevel": 2}, "BBBB_kWarn": {"id": 213, "ctxid": "BBBB", "appid": "APP", "loglevel": 3}, "BBBB_kInfo": {"id": 214, "ctxid": "BBBB", "appid": "APP", "loglevel": 4}, "BBBB_kDebug": {"id": 215, "ctxid": "BBBB", "appid": "APP", "loglevel": 5}, "BBBB_kVerbose": {"id": 216, "ctxid": "BBBB", "appid": "APP", "loglevel": 6}, + "CCCC_kFatal": {"id": 311, "ctxid": "CCCC", "appid": "APP", "loglevel": 1}, "CCCC_kError": {"id": 312, "ctxid": "CCCC", "appid": "APP", "loglevel": 2}, "CCCC_kWarn": {"id": 313, "ctxid": "CCCC", "appid": "APP", "loglevel": 3}, "CCCC_kInfo": {"id": 314, "ctxid": "CCCC", "appid": "APP", "loglevel": 4}, "CCCC_kDebug": {"id": 315, "ctxid": "CCCC", "appid": "APP", "loglevel": 5}, "CCCC_kVerbose": {"id": 316, "ctxid": "CCCC", "appid": "APP", "loglevel": 6}} diff --git a/score/test/component/filters_app/etc/filtertest_logging.json b/score/test/component/filters_app/etc/filtertest_logging.json new file mode 100644 index 00000000..2dcc839d --- /dev/null +++ b/score/test/component/filters_app/etc/filtertest_logging.json @@ -0,0 +1,18 @@ +{ + "appId": "FTST", + "appDesc": "Filter Test Application", + "contextConfigs": [ + { + "name": "AAAA", + "logLevel": "kVerbose" + }, + { + "name": "BBBB", + "logLevel": "kInfo" + }, + { + "name": "CCCC", + "logLevel": "kFatal" + } + ] + } diff --git a/score/test/component/filters_app/etc/logging.json b/score/test/component/filters_app/etc/logging.json new file mode 100644 index 00000000..a1953e81 --- /dev/null +++ b/score/test/component/filters_app/etc/logging.json @@ -0,0 +1,32 @@ +{ + "appId": "TAP", + "appDesc": "Test Application", + "logLevel": "kFatal", + "logMode": "kRemote", + "contextConfigs": [ + { + "name": "FAT", + "logLevel": "kFatal" + }, + { + "name": "ERR", + "logLevel": "kError" + }, + { + "name": "WRN", + "logLevel": "kWarn" + }, + { + "name": "INF", + "logLevel": "kInfo" + }, + { + "name": "DBG", + "logLevel": "kDebug" + }, + { + "name": "VBS", + "logLevel": "kVerbose" + } + ] +} diff --git a/score/test/component/filters_app/filtertest.cpp b/score/test/component/filters_app/filtertest.cpp new file mode 100644 index 00000000..98c81a1d --- /dev/null +++ b/score/test/component/filters_app/filtertest.cpp @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2025 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 + ********************************************************************************/ +#include "filtertest.h" + +#include +#include + +#include "score/mw/log/logging.h" + +struct UndefinedStruct +{ + int a; + double b; +}; + +STRUCT_VISITABLE(UndefinedStruct, a, b) + +int main(int, char**) +{ + score::mw::log::LogInfo() << "Filtertest starting"; + + AAAA_kFatal a11{{1, 1}}; + TRACE(a11); + AAAA_kError a12{{1, 2}}; + TRACE(a12); + AAAA_kWarn a13{{1, 3}}; + TRACE(a13); + AAAA_kInfo a14{{1, 4}}; + TRACE(a14); + AAAA_kDebug a15{{1, 5}}; + TRACE(a15); + AAAA_kVerbose a16{{1, 6}}; + TRACE(a16); + + BBBB_kFatal b11{{2, 1}}; + TRACE(b11); + BBBB_kError b12{{2, 2}}; + TRACE(b12); + BBBB_kWarn b13{{2, 3}}; + TRACE(b13); + BBBB_kInfo b14{{2, 4}}; + TRACE(b14); + BBBB_kDebug b15{{2, 5}}; + TRACE(b15); + BBBB_kVerbose b16{{2, 6}}; + TRACE(b16); + + CCCC_kFatal c11{{3, 1}}; + TRACE(c11); + CCCC_kError c12{{3, 2}}; + TRACE(c12); + CCCC_kWarn c13{{3, 3}}; + TRACE(c13); + CCCC_kInfo c14{{3, 4}}; + TRACE(c14); + CCCC_kDebug c15{{3, 5}}; + TRACE(c15); + CCCC_kVerbose c16{{3, 6}}; + TRACE(c16); + + UndefinedStruct us{1, 1.0}; + TRACE(us); + + sleep(1); + + score::mw::log::LogInfo() << "Filtertest done"; + + return 0; +} diff --git a/score/test/component/filters_app/filtertest.h b/score/test/component/filters_app/filtertest.h new file mode 100644 index 00000000..e860cb9a --- /dev/null +++ b/score/test/component/filters_app/filtertest.h @@ -0,0 +1,57 @@ +/******************************************************************************** + * Copyright (c) 2025 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 + ********************************************************************************/ +#ifndef FILTERTEST_H +#define FILTERTEST_H + +#include "static_reflection_with_serialization/visitor/visit_as_struct.h" + +struct default_struct +{ + int a; + char b; +}; + +STRUCT_VISITABLE(default_struct, a, b) + +#define dummy_struct_name(ctx, level) dummy_struct_name2(ctx, level) +#define dummy_struct_name2(ctx, level) ctx##_##level + +#define dummy_struct(name) \ + struct name \ + { \ + default_struct val; \ + }; \ + STRUCT_VISITABLE(name, val) + +dummy_struct(dummy_struct_name(AAAA, kFatal)); +dummy_struct(dummy_struct_name(AAAA, kError)); +dummy_struct(dummy_struct_name(AAAA, kWarn)); +dummy_struct(dummy_struct_name(AAAA, kInfo)); +dummy_struct(dummy_struct_name(AAAA, kDebug)); +dummy_struct(dummy_struct_name(AAAA, kVerbose)); + +dummy_struct(dummy_struct_name(BBBB, kFatal)); +dummy_struct(dummy_struct_name(BBBB, kError)); +dummy_struct(dummy_struct_name(BBBB, kWarn)); +dummy_struct(dummy_struct_name(BBBB, kInfo)); +dummy_struct(dummy_struct_name(BBBB, kDebug)); +dummy_struct(dummy_struct_name(BBBB, kVerbose)); + +dummy_struct(dummy_struct_name(CCCC, kFatal)); +dummy_struct(dummy_struct_name(CCCC, kError)); +dummy_struct(dummy_struct_name(CCCC, kWarn)); +dummy_struct(dummy_struct_name(CCCC, kInfo)); +dummy_struct(dummy_struct_name(CCCC, kDebug)); +dummy_struct(dummy_struct_name(CCCC, kVerbose)); + +#endif // FILTERTEST_H diff --git a/score/test/component/filters_app/mwfiltertest.cpp b/score/test/component/filters_app/mwfiltertest.cpp new file mode 100644 index 00000000..9ce8256a --- /dev/null +++ b/score/test/component/filters_app/mwfiltertest.cpp @@ -0,0 +1,67 @@ +/******************************************************************************** + * Copyright (c) 2025 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 + ********************************************************************************/ +#include "score/mw/log/logging.h" + +#include + +// With filtering configured for this app in the attached logging.json file, +// totally 22 ( 1 + 2 + 3 + 4 + 5 + 6 + 1 = 22) messages should be visible in DLT +int main() +{ + score::mw::log::LogFatal("FAT") << "Fatal message"; + score::mw::log::LogError("FAT") << "Error message"; + score::mw::log::LogWarn("FAT") << "Warning message"; + score::mw::log::LogInfo("FAT") << "Info message"; + score::mw::log::LogDebug("FAT") << "Debug message"; + score::mw::log::LogVerbose("FAT") << "Verbose message"; + + score::mw::log::LogFatal("ERR") << "Fatal message"; + score::mw::log::LogError("ERR") << "Error message"; + score::mw::log::LogWarn("ERR") << "Warning message"; + score::mw::log::LogInfo("ERR") << "Info message"; + score::mw::log::LogDebug("ERR") << "Debug message"; + score::mw::log::LogVerbose("ERR") << "Verbose message"; + + score::mw::log::LogFatal("WRN") << "Fatal message"; + score::mw::log::LogError("WRN") << "Error message"; + score::mw::log::LogWarn("WRN") << "Warning message"; + score::mw::log::LogInfo("WRN") << "Info message"; + score::mw::log::LogDebug("WRN") << "Debug message"; + score::mw::log::LogVerbose("WRN") << "Verbose message"; + + score::mw::log::LogFatal("INF") << "Fatal message"; + score::mw::log::LogError("INF") << "Error message"; + score::mw::log::LogWarn("INF") << "Warning message"; + score::mw::log::LogInfo("INF") << "Info message"; + score::mw::log::LogDebug("INF") << "Debug message"; + score::mw::log::LogVerbose("INF") << "Verbose message"; + + score::mw::log::LogFatal("DBG") << "Fatal message"; + score::mw::log::LogError("DBG") << "Error message"; + score::mw::log::LogWarn("DBG") << "Warning message"; + score::mw::log::LogInfo("DBG") << "Info message"; + score::mw::log::LogDebug("DBG") << "Debug message"; + score::mw::log::LogVerbose("DBG") << "Verbose message"; + + score::mw::log::LogFatal("VBS") << "Fatal message"; + score::mw::log::LogError("VBS") << "Error message"; + score::mw::log::LogWarn("VBS") << "Warning message"; + score::mw::log::LogInfo("VBS") << "Info message"; + score::mw::log::LogDebug("VBS") << "Debug message"; + score::mw::log::LogVerbose("VBS") << "Verbose message"; + + score::mw::log::LogFatal() << "LogFatal"; + + sleep(1); + return 0; +} diff --git a/score/test/component/logging_app/BUILD b/score/test/component/logging_app/BUILD new file mode 100644 index 00000000..de63c65c --- /dev/null +++ b/score/test/component/logging_app/BUILD @@ -0,0 +1,54 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +# ******************************************************************************** +# Copyright (c) 2025 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 +# ******************************************************************************** + +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_pkg//pkg:tar.bzl", "pkg_tar") + +cc_binary( + name = "logging_app", + srcs = ["logging_app.cpp"], + deps = ["//score/mw/log"], +) + +pkg_tar( + name = "logging_app_bin_pkg", + srcs = [":logging_app"], + package_dir = "opt/test_apps/logging_app/bin", +) + +pkg_tar( + name = "logging_app_config_pkg", + srcs = ["etc/logging.json"], + package_dir = "opt/test_apps/logging_app/etc", +) + +pkg_tar( + name = "logging_app_filesystem", + visibility = ["//score/test/component:__subpackages__"], + deps = [ + ":logging_app_bin_pkg", + ":logging_app_config_pkg", + ], +) diff --git a/score/test/component/logging_app/etc/logging.json b/score/test/component/logging_app/etc/logging.json new file mode 100644 index 00000000..66d1e45a --- /dev/null +++ b/score/test/component/logging_app/etc/logging.json @@ -0,0 +1,7 @@ +{ + "appId": "EXA", + "appDesc": "Example App", + "logMode": "kConsole|kRemote|kFile", + "logLevel": "kVerbose", + "logLevelThresholdConsole": "kVerbose" +} diff --git a/score/test/component/logging_app/logging_app.cpp b/score/test/component/logging_app/logging_app.cpp new file mode 100644 index 00000000..b036a17f --- /dev/null +++ b/score/test/component/logging_app/logging_app.cpp @@ -0,0 +1,95 @@ +/******************************************************************************** + * Copyright (c) 2025 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 + ********************************************************************************/ +#include "score/mw/log/logging.h" + +#include +#include +#include +#include + +namespace log = score::mw::log; + +namespace { + +void DoLogging() { + const std::string SWID{"Logging Application"}; + log::LogInfo() << SWID << "DoLogging"; + log::LogInfo() << "val_bool" << true; + + // Unsigned integers + const uint8_t val_uint8t = 123; + const uint16_t val_uint16t = 1234; + const uint32_t val_uint32t = 12345; + const uint64_t val_uint64t = 123456; + log::LogDebug() << "val_uint8t" << val_uint8t << "val_uint16t" << val_uint16t + << "val_uint32t" << val_uint32t << "val_uint64t" << val_uint64t; + + log::LogWarn() << "val_uint8tmax" << UINT8_MAX << "val_uint16tmax" << UINT16_MAX + << "val_uint32tmax" << UINT32_MAX << "val_uint64tmax" << UINT64_MAX; + + // Signed integers + const int8_t val_int8t = -34; + const int16_t val_int16t = -14576; + const int32_t val_int32t = -2147483640; + const int64_t val_int64t = -9223372036854775700LL; + log::LogFatal() << "val_int8t" << val_int8t << "val_int16t" << val_int16t + << "val_int32t" << val_int32t << "val_int64t" << val_int64t; + + log::LogWarn() << "val_int8tmax" << INT8_MAX << "val_int16tmax" << INT16_MAX + << "val_int32tmax" << INT32_MAX << "val_int64tmax" << INT64_MAX; + + log::LogDebug() << "val_uint8t" << val_uint8t << "val_uint16t" << val_uint16t + << "val_uint32t" << val_uint32t << "val_uint64t" << val_uint64t; + + log::LogWarn() << "val_int8tmin" << INT8_MIN << "val_int16tmin" << INT16_MIN + << "val_int32tmin" << INT32_MIN << "val_int64tmin" << INT64_MIN; + + const int32_t val_int8tminplusint8t = static_cast(INT8_MIN) - val_int8t; + const int32_t val_int16tminplusint16t = static_cast(INT16_MIN) - val_int16t; + const int32_t val_int32tminplusint32t = INT32_MIN - val_int32t; + const int64_t val_int64tminplusint64t = INT64_MIN - val_int64t; + log::LogError() << "val_int8tminplusint8t" << val_int8tminplusint8t + << "val_int16tminplusint16t" << val_int16tminplusint16t + << "val_int32tminplusint32t" << val_int32tminplusint32t + << "val_int64tminplusint64t" << val_int64tminplusint64t; + + // String and double + log::LogFatal() << "val_string" << "Logging"; + log::LogFatal() << "val_double" << 93454.6; + + // Hex values + log::LogInfo() << "log_hex_8" << log::LogHex8{10} + << "log_hex_16" << log::LogHex16{9876} + << "log_hex_32" << log::LogHex32{543210987} + << "log_hex_64" << log::LogHex64{654321098765432109ULL}; + + // Bin values + log::LogWarn() << "log_bin_8" << log::LogBin8{8} + << "log_bin_16" << log::LogBin16{9012} + << "log_bin_32" << log::LogBin32{3456789012UL} + << "log_bin_64" << log::LogBin64{3456789012345678901ULL}; + + // Raw buffer + log::LogFatal() << "log_raw_buffer" << log::LogRawBuffer{"raw", 3}; + + // Slog2 message + log::LogFatal() << "log_slog2_message" << log::LogSlog2Message{11, "slog2_message"}; +} + +} // namespace + +int main() { + DoLogging(); + sleep(1); + return 0; +} diff --git a/score/test/component/logging_plugin.py b/score/test/component/logging_plugin.py new file mode 100644 index 00000000..8a8cb422 --- /dev/null +++ b/score/test/component/logging_plugin.py @@ -0,0 +1,125 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 +# ******************************************************************************* + +"""Shared pytest plugin for score_logging integration tests. + +Provides Docker container configuration, a datarouter lifecycle fixture, +and DLT utility functions used by all logging ITF tests. +""" + +import logging +import os +import tempfile +import time +from contextlib import contextmanager + +import pytest +from score.itf.plugins.dlt.dlt_receive import Protocol +from score.itf.plugins.dlt.dlt_window import DltLogRecord + +LOGGER = logging.getLogger(__name__) + +# UDP multicast group addresses for DLT — dlt-receive joins these groups +# to capture messages sent by the datarouter. +DLT_MULTICAST_IPS = ["239.255.42.99"] + +# Settle time (seconds) after dlt-receive starts, to allow the multicast group +# join to propagate before the application under test begins sending. +_DLT_RECEIVER_SETTLE_DELAY = 0.2 + +# Datarouter readiness polling parameters. +_DATAROUTER_READY_TIMEOUT = 5.0 +_DATAROUTER_READY_INTERVAL = 0.1 + + +def _wait_for_datarouter(target, timeout=_DATAROUTER_READY_TIMEOUT): + """Poll /proc/net/unix for the datarouter abstract socket to appear.""" + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + exit_code, _ = target.execute("grep -q datarouter /proc/net/unix") + if exit_code == 0: + LOGGER.info("Datarouter socket detected") + return + time.sleep(_DATAROUTER_READY_INTERVAL) + raise TimeoutError(f"Datarouter socket not found within {timeout}s") + + +def download_dlt(target, remote_path): + """Download a .dlt file from the target and return a DltLogRecord for querying. + + The file is stored in a temporary directory that persists for the process + lifetime. Use ``record.find()`` to query verbose/non-verbose messages. + """ + local_dir = tempfile.mkdtemp(prefix="dlt_") + local_path = os.path.join(local_dir, os.path.basename(remote_path)) + target.download(remote_path, local_path) + LOGGER.info(f"Downloaded {remote_path}: {os.path.getsize(local_path)} bytes") + return DltLogRecord(local_path) + + +@pytest.fixture(scope="session") +def docker_configuration(): + """Configure Docker container for DLT integration tests.""" + return { + "init": True, + "shm_size": "2G", + } + + +@pytest.fixture(scope="function") +def datarouter_on_target(target): + """Start the datarouter (DLT daemon) on the Docker target. + + Launches the datarouter in non-adaptive mode as a background process, + polls for socket readiness, and stops it on teardown. + """ + proc = target.execute_async( + "/opt/datarouter/bin/datarouter", + args=["--no_adaptive_runtime"], + cwd="/opt/datarouter", + ) + _wait_for_datarouter(target) + yield target + if proc.is_running(): + proc.stop() + + +@pytest.fixture(scope="function") +def dlt_capture(target, dlt_on_target): + """Start a DLT receiver with deterministic multicast reception. + + Wraps the framework's ``dlt_on_target`` with default multicast parameters + and a settle delay to ensure the multicast group join propagates before the + application under test begins sending. + + Usage in tests:: + + with dlt_capture() as receiver: + target.execute(app_cmd) + + output = receiver.get_output() + """ + + @contextmanager + def _start(protocol=Protocol.UDP, host_ip=None, multicast_ips=None): + if host_ip is None: + 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 + + return _start diff --git a/score/test/component/mw_log/BUILD b/score/test/component/mw_log/BUILD new file mode 100644 index 00000000..1f780db2 --- /dev/null +++ b/score/test/component/mw_log/BUILD @@ -0,0 +1,50 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* +# ******************************************************************************** +# Copyright (c) 2025 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 +# ******************************************************************************** + +load("//score/test/component:defs.bzl", "py_logging_itf_test") + +py_logging_itf_test( + name = "test_mw_log", + srcs = ["test_mw_log.py"], + filesystem = "//score/test/component/logging_app:logging_app_filesystem", + deps = [ + "@score_itf//third_party/python_dlt", + ], +) + +py_logging_itf_test( + name = "test_mw_log_kfile", + srcs = ["test_mw_log_kfile.py"], + filesystem = "//score/test/component/dlt_generator_app:dlt_generator_filesystem", + deps = [ + "@score_itf//third_party/python_dlt", + ], +) + +py_logging_itf_test( + name = "test_mw_log_filters", + srcs = ["test_mw_log_filters.py"], + filesystem = "//score/test/component/filters_app:mwfiltertest_filesystem", +) diff --git a/score/test/component/mw_log/test_mw_log.py b/score/test/component/mw_log/test_mw_log.py new file mode 100644 index 00000000..7719fa89 --- /dev/null +++ b/score/test/component/mw_log/test_mw_log.py @@ -0,0 +1,135 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 mw::log with all data types. + +Verifies that the logging_app binary correctly sends DLT messages containing +all supported data types (bool, integers, hex, bin, raw buffer, slog2, etc.) +via the datarouter, and that they are received by dlt-receive with correct +content. Also verifies console logging output and file (.dlt) logging. +""" + +import logging +import os +import tempfile + +import dlt.dlt as python_dlt +from logging_plugin import download_dlt + + +LOGGER = logging.getLogger(__name__) + +LOGGING_APP_ID = "EXA" +LOGGING_APP_CMD = "cd /opt/test_apps/logging_app && ./bin/logging_app" + +# Expected substrings for DLT binary file output parsed by python-dlt. +# Differs from dlt-receive -a (remote) in: +# - UINT64_MAX renders as 18446744073709551615 (unsigned, not -1) +# - log_raw_buffer renders as 72'61'77 (apostrophe-separated hex) +VALUES_TO_CHECK_FILE = [ + "Logging Application DoLogging", + "val_bool 1", + "val_uint8t 123 val_uint16t 1234 val_uint32t 12345 val_uint64t 123456", + "val_uint8tmax 255 val_uint16tmax 65535 val_uint32tmax 4294967295 val_uint64tmax 18446744073709551615", + "val_int8t -34 val_int16t -14576 val_int32t -2147483640 val_int64t -9223372036854775700", + "val_int8tmax 127 val_int16tmax 32767 val_int32tmax 2147483647 val_int64tmax 9223372036854775807", + "val_int8tmin -128 val_int16tmin -32768 val_int32tmin -2147483648 val_int64tmin -9223372036854775808", + "val_int8tminplusint8t -94 val_int16tminplusint16t -18192 val_int32tminplusint32t -8 val_int64tminplusint64t -108", + "val_string Logging", + "val_double 93454.6", + "log_hex_8 10 log_hex_16 9876 log_hex_32 543210987 log_hex_64 654321098765432109", + "log_bin_8 8 log_bin_16 9012 log_bin_32 3456789012 log_bin_64 3456789012345678901", + "log_raw_buffer", + "log_slog2_message slog2_message", +] + +# Expected substrings for console output (captured from the app's stdout). +VALUES_TO_CHECK_CONSOLE = [ + "Logging Application DoLogging", + "val_bool True", + "val_uint8t 123 val_uint16t 1234 val_uint32t 12345 val_uint64t 123456", + "val_uint8tmax 255 val_uint16tmax 65535 val_uint32tmax 4294967295 val_uint64tmax 18446744073709551615", + "val_int8t -34 val_int16t -14576 val_int32t -2147483640 val_int64t -9223372036854775700", + "val_int8tmax 127 val_int16tmax 32767 val_int32tmax 2147483647 val_int64tmax 9223372036854775807", + "val_int8tmin -128 val_int16tmin -32768 val_int32tmin -2147483648 val_int64tmin -9223372036854775808", + "val_int8tminplusint8t -94 val_int16tminplusint16t -18192 val_int32tminplusint32t -8 val_int64tminplusint64t -108", + "val_string Logging", + "val_double 93454.600000", + "log_hex_8 10 log_hex_16 9876 log_hex_32 543210987 log_hex_64 654321098765432109", + "log_bin_8 8 log_bin_16 9012 log_bin_32 3456789012 log_bin_64 3456789012345678901", + "log_raw_buffer 726177", + "log_slog2_message slog2_message", +] + + +def test_mw_log_remote_logging(target, datarouter_on_target, dlt_capture): + """Verify all data types are correctly logged and received via DLT.""" + with dlt_capture() as receiver: + target.execute(LOGGING_APP_CMD) + + record = download_dlt(target, receiver.dlt_file) + messages = record.find(query=dict(apid=LOGGING_APP_ID)) + LOGGER.info(f"Received {len(messages)} messages from {LOGGING_APP_ID}") + for msg in messages: + LOGGER.info(f" [{msg.ctid}] {msg.payload}") + + assert len(messages) > 0, "No DLT messages received from logging_app" + + app_output = "\n".join(str(msg.payload) for msg in messages) + missing = [v for v in VALUES_TO_CHECK_FILE if v not in app_output] + assert not missing, f"Missing expected values in DLT output: {missing}" + + +def test_mw_log_console_logging(target, datarouter_on_target): + """Verify all data types are correctly logged to console (stdout).""" + proc = target.execute_async( + "/opt/test_apps/logging_app/bin/logging_app", + cwd="/opt/test_apps/logging_app", + ) + proc.wait(timeout_s=10) + console_output = proc.get_output() + LOGGER.info(f"Console output length: {len(console_output)} chars") + LOGGER.info(f"Console output:\n{console_output}") + + assert len(console_output) > 0, "No console output captured from logging_app" + + missing = [v for v in VALUES_TO_CHECK_CONSOLE if v not in console_output] + assert not missing, f"Missing expected values in console output: {missing}" + + +def test_mw_log_file_logging(target, datarouter_on_target): + """Verify all data types are correctly logged to a .dlt file on disk.""" + target.execute(LOGGING_APP_CMD) + + with tempfile.TemporaryDirectory() as tmpdir: + local_dlt = os.path.join(tmpdir, "EXA.dlt") + target.download("/tmp/EXA.dlt", local_dlt) + LOGGER.info(f"Downloaded DLT file: {os.path.getsize(local_dlt)} bytes") + + messages = python_dlt.load(local_dlt, None) + payloads = [] + for msg in messages: + if getattr(msg, "apid", None) == LOGGING_APP_ID: + payload = msg.payload_decoded + if isinstance(payload, bytes): + payload = payload.decode(errors="ignore") + payloads.append(str(payload)) + + LOGGER.info(f"Parsed {len(payloads)} messages from DLT file") + for p in payloads: + LOGGER.info(f" FILE payload: {p}") + assert len(payloads) > 0, "No messages from logging_app in DLT file" + + merged = "\n".join(payloads) + missing = [v for v in VALUES_TO_CHECK_FILE if v not in merged] + assert not missing, f"Missing expected values in DLT file: {missing}" diff --git a/score/test/component/mw_log/test_mw_log_filters.py b/score/test/component/mw_log/test_mw_log_filters.py new file mode 100644 index 00000000..f7e39acf --- /dev/null +++ b/score/test/component/mw_log/test_mw_log_filters.py @@ -0,0 +1,66 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 mw::log verbose log-level filtering (free-function API). + +Verifies that per-context log-level filtering works correctly when using the +score::mw::log free-function API (LogFatal("CTX"), LogError("CTX"), etc.). + +The mwfiltertest binary emits 6 messages (Fatal through Verbose) to each of +6 contexts (FAT, ERR, WRN, INF, DBG, VBS), each configured at a different +log level, plus 1 default-context LogFatal. With the configured filters, +exactly 22 messages should be visible. +""" + +import logging + +from logging_plugin import download_dlt + +LOGGER = logging.getLogger(__name__) + +TOTAL_VERBOSE_MESSAGES = 22 +TEST_APP_ID = "TAP" +CONTEXT_IDS = ["FAT", "ERR", "WRN", "INF", "DBG", "VBS"] + + +def test_mw_verbose_filters(target, datarouter_on_target, dlt_capture): + """Verify per-context verbose log-level filtering with free-function API. + + Expected message counts per context (based on configured log levels): + - FAT (kFatal): 1 message (only Fatal passes) + - ERR (kError): 2 messages (Fatal + Error pass) + - WRN (kWarn): 3 messages (Fatal + Error + Warn pass) + - INF (kInfo): 4 messages (Fatal + Error + Warn + Info pass) + - DBG (kDebug): 5 messages (Fatal + Error + Warn + Info + Debug pass) + - VBS (kVerbose): 6 messages (all pass) + Plus 1 default-context LogFatal = 22 total. + """ + with dlt_capture() as receiver: + target.execute("cd /opt/test_apps/mwfiltertest && ./bin/mwfiltertest") + + record = download_dlt(target, receiver.dlt_file) + + def count(ctx): + return len(record.find(query=dict(apid=TEST_APP_ID, ctid=ctx))) + + assert count("FAT") == 1, f"FAT: expected 1, got {count('FAT')}" + assert count("ERR") == 2, f"ERR: expected 2, got {count('ERR')}" + assert count("WRN") == 3, f"WRN: expected 3, got {count('WRN')}" + assert count("INF") == 4, f"INF: expected 4, got {count('INF')}" + assert count("DBG") == 5, f"DBG: expected 5, got {count('DBG')}" + assert count("VBS") == 6, f"VBS: expected 6, got {count('VBS')}" + + total = sum(count(ctx) for ctx in CONTEXT_IDS) + count("DFLT") + assert total == TOTAL_VERBOSE_MESSAGES, ( + f"Total: expected {TOTAL_VERBOSE_MESSAGES}, got {total}" + ) diff --git a/score/test/component/mw_log/test_mw_log_kfile.py b/score/test/component/mw_log/test_mw_log_kfile.py new file mode 100644 index 00000000..fe108860 --- /dev/null +++ b/score/test/component/mw_log/test_mw_log_kfile.py @@ -0,0 +1,100 @@ +# ******************************************************************************* +# Copyright (c) 2025 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 mw::log kFile logging mode. + +Verifies that dlt_generator binary correctly writes DLT messages to a local +file when configured with kFile|kRemote log mode. Message counts are asserted +against the on-disk .dlt file (written directly by the datarouter from shared +memory). Remote DLT output via UDP is logged for diagnostics but not asserted +exactly, because UDP multicast can truncate messages under load. +""" + +import logging +import os +import tempfile + +import dlt.dlt as python_dlt +from logging_plugin import download_dlt + + +LOGGER = logging.getLogger(__name__) + +APP_ID = "LGGG" +ITERATIONS = 100 +DEFAULT_MESSAGE = "default message text for example log generating application" +# With use_full_output=true and kVerbose log level via remote: +# Fatal + Error + Warn + Info + Verbose + Debug = 6 messages per iteration +MESSAGES_PER_ITERATION = 6 + + +def _verify_kfile_messages(target, expected): + """Download /tmp/LGGG.dlt from the target and assert the message count.""" + 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) + file_count = 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"DLT file message count: {file_count}") + assert file_count == expected, ( + f"DLT file: expected {expected} messages, got {file_count}" + ) + + +def test_mw_log_kfile(target, datarouter_on_target, dlt_capture): + """Verify kFile logging produces the expected number of messages.""" + expected = ITERATIONS * MESSAGES_PER_ITERATION + + with dlt_capture() as receiver: + target.execute( + "cd /opt/test_apps/dlt_generator && ./bin/dlt_generator" + f" -i {ITERATIONS} -s 0 -c true -w 500" + ) + + # Log remote DLT count for diagnostics (UDP is inherently lossy) + record = download_dlt(target, receiver.dlt_file) + messages = record.find(query=dict(apid=APP_ID)) + remote_count = sum(1 for m in messages if DEFAULT_MESSAGE in str(m.payload)) + LOGGER.info(f"Remote DLT message count: {remote_count} / {expected} expected") + + _verify_kfile_messages(target, expected) + + +def test_mw_log_kfile_multiple_threads(target, datarouter_on_target, dlt_capture): + """Verify kFile logging with multiple threads.""" + threads = 4 + expected = threads * ITERATIONS * MESSAGES_PER_ITERATION + + with dlt_capture() as receiver: + target.execute( + "cd /opt/test_apps/dlt_generator && ./bin/dlt_generator" + f" -t {threads} -i {ITERATIONS} -s 0 -c true -w 500" + ) + + # Log remote DLT count for diagnostics (UDP is inherently lossy) + record = download_dlt(target, receiver.dlt_file) + messages = record.find(query=dict(apid=APP_ID)) + remote_count = sum(1 for m in messages if DEFAULT_MESSAGE in str(m.payload)) + LOGGER.info(f"Remote DLT message count: {remote_count} / {expected} expected") + + _verify_kfile_messages(target, expected)