How to Test#

Run tests#

Run all tests in a repository:

bazel test //...

Run a specific target:

bazel test //path/to:target

Force re-execution (skip cache):

bazel test //... --nocache_test_results

See full output (useful when a test fails):

bazel test //path/to:target --test_output=all

Filter by test name pattern:

bazel test //path/to:target --test_filter="MyTest.specific_case"

Write a C++ test#

GoogleTest is integrated via Bazel. Define a cc_test target:

load("@rules_cc//cc:defs.bzl", "cc_test")

cc_test(
    name = "my_test",
    srcs = ["my_test.cc"],
    deps = [
        "//src:my_library",
        "@googletest//:gtest_main",
    ],
)
// my_test.cc
#include <gtest/gtest.h>
#include "src/my_library.h"

TEST(MyLibraryTest, ReturnsExpectedValue) {
    EXPECT_EQ(compute(2, 3), 5);
}

If the test needs data files:

cc_test(
    name = "my_test",
    srcs = ["my_test.cc"],
    data = ["testdata/input.bin"],
    deps = ["@googletest//:gtest_main"],
)

Write a Rust test#

Native Rust tests work through rules_rust:

load("@rules_rust//rust:defs.bzl", "rust_test")

rust_test(
    name = "my_test",
    srcs = ["my_test.rs"],
    deps = ["//src:my_library"],
)
// my_test.rs
use my_library::compute;

#[test]
fn returns_expected_value() {
    assert_eq!(compute(2, 3), 5);
}

Write a Python test#

pytest-based tests are integrated through Bazel’s Python rules:

load("@rules_python//python:defs.bzl", "py_test")

py_test(
    name = "my_test",
    srcs = ["my_test.py"],
    deps = ["//src:my_library"],
)

Write an ITF integration test#

ITF runs tests against real environments — Docker containers, QEMU VMs, or hardware — through a unified Target interface. See Tutorial: Write Your First ITF Integration Test for a step-by-step introduction.

Add ITF to your workspace#

In MODULE.bazel:

bazel_dep(name = "score_itf", version = "0.2.0")

For Docker and QEMU test recipes, see the upstream how-to. For full plugin CLI args and capabilities, see the upstream plugin reference.

Capability guards#

Tests declare which target capabilities they need. The framework skips tests when the active target does not provide them:

from score.itf.plugins.core import requires_capabilities

@requires_capabilities("exec")
def test_docker_only(target):
    exit_code, output = target.execute("ls /tmp")
    assert exit_code == 0

@requires_capabilities("ssh", "sftp")
def test_network_features(target):
    with target.ssh() as ssh:
        ssh.execute_command("echo ok")

DLT log capture#

py_itf_test(
    name = "test_with_dlt",
    srcs = ["test_with_dlt.py"],
    args = [
        "--docker-image=my-app:latest",
        "--dlt-config=$(location dlt_config.json)",
    ],
    data = ["dlt_config.json"],
    plugins = [
        "@score_itf//score/itf/plugins:docker_plugin",
        "@score_itf//score/itf/plugins:dlt_plugin",
    ],
)
from score.itf.plugins.dlt.dlt_window import DltWindow
from score.itf.plugins.dlt.dlt_receive import Protocol
import re

def test_dlt_messages(target, dlt_config):
    with DltWindow(
        protocol=Protocol.UDP,
        host_ip="127.0.0.1",
        multicast_ips=["224.0.0.1"],
        binary_path=dlt_config.dlt_receive_path,
    ) as window:
        with target.ssh() as ssh:
            ssh.execute_command("my_application")

        record = window.record()
        results = record.find(query={
            "apid": re.compile(r"APP1"),
            "payload": re.compile(r".*Started successfully.*"),
        })
        assert len(results) > 0

Requirement traceability#

from attribute_plugin import add_test_properties

@add_test_properties(
    fully_verifies=["REQ-001", "REQ-002"],
    test_type="requirements-based",
    derivation_technique="requirements-analysis",
)
def test_hello(target):
    exit_code, output = target.execute("echo 'Hello from target!'")
    assert exit_code == 0
py_itf_test(
    name = "test_hello",
    srcs = ["test_hello.py"],
    args = ["--docker-image=ubuntu:24.04"],
    plugins = [
        "@score_itf//score/itf/plugins:docker_plugin",
        "@score_itf//score/itf/plugins:attribute_plugin",
    ],
)

Session-scoped targets#

Reuse the same target across all tests in a session (faster, but tests share state):

bazel test //path/to:test --test_arg=--keep-target

For available plugins and qemu_config.json format, see the Configuration Reference.

Advanced: QNX unit tests#

qnx_unit_tests provides cc_test_qnx and rust_test_qnx macros that wrap standard test targets for execution inside a QEMU microvm running QNX 8.

Prerequisites#

  • QEMU (qemu-system-x86_64 and/or qemu-system-aarch64)

  • KVM access (strongly recommended for performance)

  • QNX SDP 8.0 credentials (for toolchain download)

Add to your workspace#

In MODULE.bazel:

bazel_dep(name = "score_qnx_unit_tests", version = "...")

Wrap a test target#

Write a standard cc_test, then wrap it:

load("@rules_cc//cc:defs.bzl", "cc_test")
load("@score_qnx_unit_tests//:defs.bzl", "cc_test_qnx")

cc_test(
    name = "my_test",
    srcs = ["my_test.cpp"],
    deps = [
        "@googletest//:gtest",
        "@googletest//:gtest_main",
    ],
)

cc_test_qnx(
    name = "my_test_qnx",
    cc_test = ":my_test",
)

For Rust:

load("@rules_rust//rust:defs.bzl", "rust_test")
load("@score_qnx_unit_tests//:defs.bzl", "rust_test_qnx")

rust_test(
    name = "my_rust_test",
    srcs = ["my_test.rs"],
)

rust_test_qnx(
    name = "my_rust_test_qnx",
    rust_test = ":my_rust_test",
)

Configure .bazelrc#

Use the provided platform configs:

build:qnx-x86_64  # Cross-compile for QNX x86_64
build:qnx-aarch64 # Cross-compile for QNX aarch64

Run:

bazel test //path/to:my_test_qnx --config=qnx-x86_64

How it works#

The macro packages the test binary and runfiles into a tar archive, builds a QNX IFS boot image with the kernel and startup scripts, boots QEMU with the IFS image, mounts the test archive via virtio-9p, executes the test, and extracts results (XML, coverage) from the shared directory.

Collect coverage#

C++#

bazel coverage //... --combined_report

Produces LCOV output. Compiler instrumentation is configured by bazel_cpp_toolchains.

Rust#

bazel coverage //... --combined_report

Uses LLVM source-based coverage (llvm-cov). The same LCOV output format is used so downstream reporting does not need language-specific parsers.

ITF tests#

For Docker-based ITF tests, the Docker plugin supports extracting coverage data from the container:

py_itf_test(
    name = "test_with_coverage",
    srcs = ["test_example.py"],
    args = [
        "--docker-image=my-instrumented-image:latest",
        "--extract-coverage",
    ],
    plugins = ["@score_itf//score/itf/plugins:docker_plugin"],
)

Coverage files (.gcda) are extracted to $TEST_UNDECLARED_OUTPUTS_DIR/sysroot before container teardown.

Enable sanitizers#

score_cpp_policies provides selectable sanitizer features (ASan, UBSan, LSan, TSan) as Bazel cc_feature definitions. Enable them via select() expressions or feature flags in your build configuration.

For the full list of sanitizers and constraint targets like no_tsan and any_sanitizer, see the Configuration Reference.