FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
Testing

FhSim tests are based on GoogleTest. For simulation-oriented tests (especially for external SimObject/plugin projects), use FhSim::fhsim_test.

For full API and reference-mode behavior, see Test Tools Library.

SimObject test structure

Recommended layout:

tests/
├── CMakeLists.txt
├── RunTests.cpp
├── MyModel_Test.cpp
├── in/
│ └── MyModel/
│ └── MyModel_case_in.xml
└── out/ # generated

Minimal CMake setup

add_executable(my_model_tests RunTests.cpp MyModel_Test.cpp)
target_link_libraries(my_model_tests
PRIVATE
FhSim::fhsim
FhSim::fhsim_test
GTest::GTest)
add_test(
NAME my_model_tests
COMMAND my_model_tests "${CMAKE_CURRENT_SOURCE_DIR}" 0 3 n .)

Minimal runtime setup (RunTests.cpp)

Create one fhsim::test::TestRuntimeConfig and store it where tests can access it (project-local helper, fixture singleton, or equivalent).

Typical mapping:

  • testDir = argv[1]
  • inPath = <testDir>/in
  • resPath = <testDir>/out
  • logPath = <testDir>/out
  • runPath = current working directory
  • logLevelToScreen/logLevelToLog from CLI args

This is the same pattern used by FhSim core tests (tests/core/fhSim/Run_test.cpp).

Example test

#include "FhsimTest.h"
TEST(MyModel, CaseRunsWithoutRegression)
{
fhsim::test::TestSpec spec;
spec.ID = "MyModel_case";
spec.simTime = 5.0;
spec.maxRunTime = 30.0;
spec.runtimeConfig = GetTestRuntimeConfig(); // project-specific helper
spec.diagnosticsJsonPath = "out/MyModel_case_diag.json"; // optional
// Default is Auto: run Jacobian check only when Jacobians are available.
// spec.jacobianMode = fhsim::test::TestJacobianMode::Auto;
const auto run = fhsim::test::RunTest(spec);
ASSERT_TRUE(run.ok) << run.error;
const auto report = run.CompareRegression(spec);
EXPECT_TRUE(report.ok) << report.error;
}

Asserting on specific output values

When a test needs to check specific numeric values (not just regression comparison against a reference file), use CapturingObserver instead of reading result.output.

CapturingObserver collects snapshots in memory and provides named lookup via OutputAt (output port values from the XML output spec) and StateAt (raw ODE state values). For most tests, OutputAt and FinalOutput are the right choice: they access the same values that the CSV result file captures.

#include "FhsimTest.h"
#include "SimulationManagerFactory.h"
TEST(MyModel, FinalOutputMatchesExpected)
{
fhsim::test::TestSpec spec;
spec.ID = "MyModel_case";
spec.simTime = 5.0;
spec.runtimeConfig = GetTestRuntimeConfig();
spec.ResolvePaths();
fhsim::SimulationContext ctx;
ctx.logPath = spec.logFile.string();
ctx.logLevel = spec.runtimeConfig.logLevelToLog;
// Omit ctx.outputPath to skip writing a result file.
auto manager = fhsim::CreateSimulationManagerFromFile(
spec.inFile.string(), ctx);
manager->SetStopTime(spec.simTime);
auto capturing = std::make_unique<fhsim::test::CapturingObserver>();
auto* obs = capturing.get();
manager->RegisterObserver(std::move(capturing));
ASSERT_EQ(manager->Run(), fhsim::simRes_Completed);
// Assert on output port value (declared in the XML output spec).
EXPECT_NEAR(obs->FinalOutput("MyObj", "speed"), 3.14, 1e-4);
// Assert on a specific intermediate step.
EXPECT_NEAR(obs->OutputAt(0, "MyObj", "speed"), 0.0, 1e-9);
}

See the CapturingObserver section in Test Tools Library for the full API and the OutputAt vs StateAt decision table.

Jacobian modes in tests

TestSpec::jacobianMode controls Jacobian validation in RunTest:

  • No: do not run Jacobian check.
  • Auto (default): run Jacobian check when Jacobians are available, otherwise skip.
  • Yes: require Jacobian check; failures make the test fail.

Example:

fhsim::test::TestSpec spec;
spec.ID = "ImplicitIntegration_DIRK_VanDerPol_mu0";
spec.simTime = 10.0;
spec.runtimeConfig = GetTestRuntimeConfig();
spec.jacobianMode = fhsim::test::TestJacobianMode::Yes;
auto result = fhsim::test::RunTest(spec);
ASSERT_TRUE(result.ok) << result.error;

When Jacobian check runs, inspect result.jacobian for detailed diagnostics.

When spec.diagnosticsJsonPath is set, RunTest writes a diagnostics JSON artifact only when diagnostics are enabled in the model input (for example by adding <Diagnostics/> under <Integrator>).

Running tests

Build with tests enabled (for Conan setups: -c tools.build:skip_tests=False). Then run the produced test executable from its build output directory.

For external SimObject libraries, ensure SFH_LICENSE_FILE points to a valid license file before running tests.