Testing
Overview
Holoscan provides a test harness interface for testing Holoscan operators with:
- Input/Output Port Management: Easy setup of test data and validation
- Condition Support: Add execution conditions like CountCondition, PeriodicCondition
- Validation Framework: Built-in validators for exact equality, floating-point comparison, and custom validation
- Fluent API: Chainable method calls for clean, readable test setup
Concepts
Test Harness
The OperatorTestHarness is a specialized Holoscan application designed to test individual operators in isolation. It automates the setup of a test pipeline by creating source operators to provide input data and sink operators to collect and validate output data.
Main Components:
-
OperatorTestHarness<OperatorType, Args...>: The main test harness class that orchestrates the test- Creates and manages source operators for each input port
- Creates and manages sink operators for each output port
- Connects all operators in the test pipeline
- Provides a fluent API for configuration
-
TestHarnessSourceOp<T>: A specialized operator that emits predetermined test data- Emits one value per compute cycle from a provided vector
- Automatically manages iteration through test data
- Used internally by the test harness for each input port
-
TestHarnessSinkOp<T>: A specialized operator that collects and validates outputs- Receives data from the operator under test
- Applies validation functions to each received value
- Tracks received data count for verification
- Used internally by the test harness for each output port
-
OperatorTestBase:- A base test fixture class designed for use with Google Test (
gtest). - Just a skeleton fixture, does not currently extend
SetUporTearDownmethods.
- A base test fixture class designed for use with Google Test (
How It Works:
When you create a test harness with create_operator_test<YourOp>():
- The harness creates a Holoscan application with your operator in the middle
- For each
add_input_port()call, it creates aTestHarnessSourceOpthat feeds test data - For each
add_output_port()call, it creates aTestHarnessSinkOpthat collects and validates output - When you call
run_test(), the entire pipeline executes and validations are performed automatically
The test harness ensures that:
- All input ports receive the same number of data elements
- Data flows correctly from sources through your operator to sinks
- Conditions (like
CountCondition) control execution properly - Validation failures are reported via Google Test assertions
Validator Functions
Validator functions are callable objects that verify operator outputs during test execution. They follow the signature void(const T&) where T is the output data type. Validators are called automatically by TestHarnessSinkOp for each value received from the operator under test.
Built-in Validators:
Holoscan provides three types of built-in validators:
-
create_exact_equality_validator<T>(expected_values)- Compares each output against expected values using
operator== - Best for: integers, strings, and other types with well-defined equality
- Automatically tracks which output index is being validated
- Compares each output against expected values using
-
create_float_equality_validator<T>(expected_values, tolerance = T{})- Compares floating-point values with approximate equality
- Uses Google Test’s
EXPECT_FLOAT_EQ(default) orEXPECT_NEAR(with tolerance) - Best for: float, double, and other floating-point types
-
create_transform_equality_validator<InputT, OutputT>(expected_values, transform_func)- Applies a transformation before comparing
- Best for: complex types where you want to validate a specific property
Using Multiple Validators:
You can apply multiple validators to a single output port using the validators<T>() helper:
Custom Validators:
Create custom validators by providing any callable that matches void(const T&):
Custom validators can perform any verification logic and use any Google Test assertion macros (EXPECT_*, ASSERT_*). Validation failures will cause the test to fail with descriptive error messages.
Basic Test Structure
Suppose we have an operator (DoublerOp) that has a single input and single output port, both int. The operator simply doubles the input value and emits that.
There are two ways to set up tests:
Approach 1: Fluent API
Fluent is a design pattern that enables code to be written in a way that flows naturally, often by chaining method calls together. This style improves readability and expressiveness, making it easier to set up complex objects or configurations in a concise and intuitive manner. The term “fluent API” refers to this general programming approach and is not specific to Holoscan.
Approach 2: Step-by-Step Setup
API Reference
Creating Test Harness
Adding Input Ports
The values that are passed to the operator’s input ports are defined and added via the add_input_port function. This function takes a std::vector<DataType> object, the elements of which will be passed to the operator one-by-one.
Adding Output Ports
The output ports of the operator are added to the test harness using the add_output_port function. This function can be used in two ways: without a validator, to simply collect the output data, or with a validator function to automatically check that the output matches expected results.
Adding Conditions
Running Tests
Testing Patterns
Source Operators
Operators with output ports, but no input ports.
Sink Operators
Operators with input ports, but no input ports.
Transform Operators
Operators with both input and output ports