Skip to content
Open
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
122 changes: 122 additions & 0 deletions quality/qnx_unit_testing/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# *******************************************************************************
# 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("@bazel_skylib//rules:expand_template.bzl", "expand_template")
load("@score_communication_pip//:requirements.bzl", "requirement")
load("@score_toolchains_qnx//rules/fs:ifs.bzl", "qnx_ifs")
load("//quality/qnx_unit_testing/environments:run_as_exec.bzl", "run_as_exec")

# Export Bazel macros and shell scripts for QNX unit testing
exports_files(
[
"cc_test_qnx.bzl",
"py_unittest_qnx_test.bzl",
"run_qemu_shell.sh",
"run_qemu.sh",
"configure_persistency.sh",
"run_test.sh",
"startup.sh",
"tools.build",
"init_x86_64_cc_test.build.template",
],
visibility = ["//visibility:public"],
)

# Create empty FAT32 filesystem image for test results (10MB)
genrule(
name = "test_results",
outs = ["test_results.img"],
cmd = """
# Create 10MB empty file for FAT32 test result exchange
dd if=/dev/zero of=$@ bs=1M count=10 2>/dev/null
""",
tags = ["manual"],
visibility = ["//visibility:public"],
)

# Create empty QNX6 filesystem image for persistent storage (10MB)
genrule(
name = "persistent",
outs = ["persistent.img"],
cmd = """
# Create 10MB empty file for QNX6 persistent storage
dd if=/dev/zero of=$@ bs=1M count=10 2>/dev/null
""",
tags = ["manual"],
visibility = ["//visibility:public"],
)

# Generate IFS build file for C++ unit tests (runs test binary)
expand_template(
name = "init_build_cc_test",
out = "init_cc_test.build",
substitutions = {
"{RUN_BINARY}": "run_test.sh",
},
template = ":init_x86_64_cc_test.build.template",
visibility = ["//visibility:public"],
)

# Generate IFS build file for interactive shell (debugging)
expand_template(
name = "init_build_shell",
out = "init_shell.build",
substitutions = {
"{RUN_BINARY}": "[+session] /bin/sh &",
},
template = ":init_x86_64_cc_test.build.template",
visibility = ["//visibility:public"],
)

# QNX bootable image for interactive shell (debugging)
qnx_ifs(
name = "init_shell",
testonly = True,
srcs = [
":configure_persistency.sh",
":run_test.sh",
":startup.sh",
":tools.build",
],
build_file = ":init_build_shell",
ext_repo_maping = {
"CONFIGURE_PERSISTENCY_SCRIPT": "$(location :configure_persistency.sh)",
"RUN_TEST_SCRIPT": "$(location :run_test.sh)",
"STARTUP_SCRIPT": "$(location :startup.sh)",
"TOOLS_BUILDFILE": "$(location :tools.build)",
},
tags = ["manual"],
target_compatible_with = ["@platforms//os:qnx"],
visibility = ["//visibility:public"],
)

# Python script to parse test results from FAT32 image
py_binary(
name = "process_test_results_bin",
srcs = ["process_test_results.py"],
main = "process_test_results.py",
tags = ["manual"],
visibility = ["//visibility:public"],
deps = [
requirement("pyfatfs"),
requirement("fs"),
],
)

# Wrap py_binary with run_as_exec to force execution platform (Linux)
# even when building with --config=qnx_x86_64 (which sets target platform to QNX)
run_as_exec(
name = "process_test_results",
executable = ":process_test_results_bin",
visibility = ["//visibility:public"],
)
195 changes: 195 additions & 0 deletions quality/qnx_unit_testing/cc_test_qnx.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# *******************************************************************************
# 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
# *******************************************************************************

"""
QNX C++ Unit Test Macro for SCORE Integration Testing Framework

This macro wraps cc_test targets to run them on QNX 8.0 in QEMU using the
SCORE integration testing framework's QEMURunner infrastructure.

It creates a bootable QNX image containing the test binary and executes it
using Python-based QEMU management (replacing the old shell-based approach).

## Architecture:

1. **qnx_ifs target**: Creates QNX bootable .ifs image
- Marked with target_compatible_with = QNX
- Built with QNX cross-compilation toolchain (--config=qnx_x86_64)

2. **py_test wrapper**: Runs on Linux host
- Uses QEMURunner from quality/integration_testing/environments/qnx8_qemu/
- Launches QEMU, executes tests, parses results from FAT32 image
- No target_compatible_with (runs on execution platform)

This consolidates QEMU logic into a single Python-based implementation,
eliminating the previous duplication between shell scripts and Python.
"""

load("@rules_pkg//pkg:mappings.bzl", "pkg_attributes", "pkg_files")
load("@rules_pkg//pkg:tar.bzl", "pkg_tar")
load("@score_toolchains_qnx//rules/fs:ifs.bzl", "qnx_ifs")

def _get_test_and_data_impl(ctx):
"""Extract test binary and data files (excluding .so libraries)"""
data_files = []
for file in ctx.attr.src[DefaultInfo].data_runfiles.files.to_list():
if not file.basename.endswith(".so"):
data_files.append(file)
return [DefaultInfo(files = ctx.attr.src[DefaultInfo].files, runfiles = ctx.runfiles(files = data_files))]

_get_test_and_data = rule(
implementation = _get_test_and_data_impl,
attrs = {
"src": attr.label(providers = [CcInfo]),
},
)

def cc_test_qnx(name, cc_test, excluded_tests_filter = None, app_tar = None):
"""Compile and run C++ unit tests on QNX 8.0 in QEMU

This macro uses the SCORE integration testing framework's QEMURunner
infrastructure to execute C++ tests in a QNX virtual machine.

Args:
name: Test name
cc_test: cc_test target to execute in QNX
excluded_tests_filter: list of GTest filters to exclude from execution.
Examples:
["FooTest.Test1"] - do not run Test1 from test suite FooTest
["FooTest.*"] - do not run any test from test suite FooTest
["*FooTest.*"] - runs all non-FooTest tests
app_tar: optional pkg_tar target containing application binaries to include
in the QNX image. Similar to integration_test's filesystem parameter.
The tar will be extracted to /app directory in the QNX filesystem.
"""
excluded_tests_filter = excluded_tests_filter if excluded_tests_filter else []

# Convert filter list to GTest format: "-FooTest.Test1:BarTest.*"
excluded_tests_filter_str = "-"
for test_filter in excluded_tests_filter:
excluded_tests_filter_str = excluded_tests_filter_str + (test_filter + ":\\")

native.genrule(
name = "{}_excluded_tests_filter".format(name),
cmd_bash = """
echo {} > $(@)
""".format(excluded_tests_filter_str),
testonly = True,
tags = ["manual"],
outs = ["{}_excluded_tests_filter.txt".format(name)],
)

_get_test_and_data(
name = "%s_test_and_data" % name,
src = cc_test,
testonly = True,
tags = ["manual"],
)

pkg_files(
name = "%s_test_and_runfiles" % name,
srcs = [
":{}_excluded_tests_filter".format(name),
":{}_test_and_data".format(name),
],
include_runfiles = True,
prefix = "/tests",
testonly = True,
tags = ["manual"],
attributes = pkg_attributes(mode = "0755"),
)

pkg_tar(
name = "%s_pkgtar" % name,
srcs = [
"%s_test_and_runfiles" % name,
],
testonly = True,
tags = ["manual"],
symlinks = {
"/tests/cc_test_qnx": native.package_relative_label(cc_test).name,
"/tests/cc_test_qnx_filters.txt": "{}_excluded_tests_filter.txt".format(name),
},
modes = {
"/tests/cc_test_qnx": "0777",
"/tests/cc_test_qnx_filters.txt": "0777",
},
)

# Create QNX bootable image with test package
# Uses tars parameter (like integration_test) instead of DUI
# Optionally includes app_tar if provided (similar to integration_test's filesystem)
qnx_ifs(
name = "%s_test_img" % name,
out = "{}_test.ifs".format(name),
build_file = "//quality/qnx_unit_testing:init_build_cc_test",
tars = {"TESTS": ":%s_pkgtar" % name} | ({"APP": app_tar} if app_tar else {}),
testonly = True,
target_compatible_with = [
"@platforms//os:qnx",
],
tags = [
"manual",
],
)

# Shell test wrapper (temporary - keeps old sh_test approach for compatibility)
# TODO: Migrate to py_test + QEMURunner once platform constraints are resolved
native.sh_test(
name = name,
srcs = ["//quality/qnx_unit_testing:run_qemu.sh"],
args = [
"$(location //quality/qnx_unit_testing:init_shell)",
"$(location //quality/qnx_unit_testing:persistent)",
"$(location //quality/qnx_unit_testing:process_test_results)",
"$(location //quality/qnx_unit_testing:test_results)",
"$(location :%s_test_img)" % name,
],
data = [
":%s_test_img" % name,
"//quality/qnx_unit_testing:init_shell",
"//quality/qnx_unit_testing:persistent",
"//quality/qnx_unit_testing:process_test_results",
"//quality/qnx_unit_testing:test_results",
],
timeout = "short",
size = "medium",
# Note: sh_test runs on Linux host (launches QEMU), so no target_compatible_with
# The QNX IFS image (qnx_ifs) has proper platform constraints
tags = [
"cpu:2",
"manual",
"qnx_unit_test",
],
)

# Shell access for debugging (kept for compatibility)
native.sh_binary(
name = "%s_shell" % name,
srcs = ["//quality/qnx_unit_testing:run_qemu_shell.sh"],
args = [
"$(location //quality/qnx_unit_testing:init_shell)",
"$(location //quality/qnx_unit_testing:persistent)",
"$(location //quality/qnx_unit_testing:test_results)",
"$(location :%s_test_img)" % name,
],
data = [
":%s_test_img" % name,
"//quality/qnx_unit_testing:init_shell",
"//quality/qnx_unit_testing:persistent",
"//quality/qnx_unit_testing:test_results",
],
testonly = True,
# Note: sh_binary runs on Linux host (launches QEMU), so no target_compatible_with
tags = ["manual"],
)
Loading
Loading