All checks were successful
Build and Test / build (pull_request) Successful in 29m11s
Phase 1e: Add C++ gtest and Python unittest coverage for the pluggable solver system introduced in Phases 1a-1d. C++ tests (KCSolve_tests_run): - SolverRegistryTest (8 tests): register/get, duplicates, defaults, available list, joints_for capability queries - OndselAdapterTest (10 tests): identity checks, supported/unsupported joints, Fixed/Revolute solve round-trips, no-grounded-parts handling, exception safety, drag protocol (pre_drag/drag_step/post_drag), redundant constraint diagnostics Python tests (TestSolverIntegration): - Full-stack solve via AssemblyObject → IKCSolver → OndselAdapter - Fixed joint placement matching, revolute joint success - No-ground error code (-6), redundancy detection (-2) - ASMT export produces non-empty file - Deterministic solve stability (solve twice → same result)
132 lines
3.9 KiB
C++
132 lines
3.9 KiB
C++
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <Mod/Assembly/Solver/IKCSolver.h>
|
|
#include <Mod/Assembly/Solver/SolverRegistry.h>
|
|
#include <Mod/Assembly/Solver/Types.h>
|
|
|
|
#include <algorithm>
|
|
|
|
using namespace KCSolve;
|
|
|
|
// ── Minimal mock solver for registry tests ─────────────────────────
|
|
|
|
namespace
|
|
{
|
|
|
|
class MockSolver : public IKCSolver
|
|
{
|
|
public:
|
|
std::string name() const override
|
|
{
|
|
return "MockSolver";
|
|
}
|
|
|
|
std::vector<BaseJointKind> supported_joints() const override
|
|
{
|
|
return {BaseJointKind::Fixed, BaseJointKind::Revolute};
|
|
}
|
|
|
|
SolveResult solve(const SolveContext& /*ctx*/) override
|
|
{
|
|
return SolveResult {SolveStatus::Success, {}, 0, {}, 0};
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
// ── Tests ──────────────────────────────────────────────────────────
|
|
//
|
|
// SolverRegistry is a singleton — tests use unique names to avoid
|
|
// interference across test cases.
|
|
|
|
TEST(SolverRegistryTest, GetUnknownReturnsNull) // NOLINT
|
|
{
|
|
auto solver = SolverRegistry::instance().get("nonexistent_solver_xyz");
|
|
EXPECT_EQ(solver, nullptr);
|
|
}
|
|
|
|
TEST(SolverRegistryTest, RegisterAndGet) // NOLINT
|
|
{
|
|
auto& reg = SolverRegistry::instance();
|
|
|
|
bool ok = reg.register_solver("test_reg_get",
|
|
[]() { return std::make_unique<MockSolver>(); });
|
|
EXPECT_TRUE(ok);
|
|
|
|
auto solver = reg.get("test_reg_get");
|
|
ASSERT_NE(solver, nullptr);
|
|
EXPECT_EQ(solver->name(), "MockSolver");
|
|
}
|
|
|
|
TEST(SolverRegistryTest, DuplicateRegistrationFails) // NOLINT
|
|
{
|
|
auto& reg = SolverRegistry::instance();
|
|
|
|
bool first = reg.register_solver("test_dup",
|
|
[]() { return std::make_unique<MockSolver>(); });
|
|
EXPECT_TRUE(first);
|
|
|
|
bool second = reg.register_solver("test_dup",
|
|
[]() { return std::make_unique<MockSolver>(); });
|
|
EXPECT_FALSE(second);
|
|
}
|
|
|
|
TEST(SolverRegistryTest, AvailableListsSolvers) // NOLINT
|
|
{
|
|
auto& reg = SolverRegistry::instance();
|
|
|
|
reg.register_solver("test_avail_1",
|
|
[]() { return std::make_unique<MockSolver>(); });
|
|
reg.register_solver("test_avail_2",
|
|
[]() { return std::make_unique<MockSolver>(); });
|
|
|
|
auto names = reg.available();
|
|
EXPECT_NE(std::find(names.begin(), names.end(), "test_avail_1"), names.end());
|
|
EXPECT_NE(std::find(names.begin(), names.end(), "test_avail_2"), names.end());
|
|
}
|
|
|
|
TEST(SolverRegistryTest, SetDefaultAndGet) // NOLINT
|
|
{
|
|
auto& reg = SolverRegistry::instance();
|
|
|
|
reg.register_solver("test_default",
|
|
[]() { return std::make_unique<MockSolver>(); });
|
|
|
|
bool ok = reg.set_default("test_default");
|
|
EXPECT_TRUE(ok);
|
|
|
|
// get() with no arg should return the default.
|
|
auto solver = reg.get();
|
|
ASSERT_NE(solver, nullptr);
|
|
EXPECT_EQ(solver->name(), "MockSolver");
|
|
}
|
|
|
|
TEST(SolverRegistryTest, SetDefaultUnknownFails) // NOLINT
|
|
{
|
|
auto& reg = SolverRegistry::instance();
|
|
bool ok = reg.set_default("totally_unknown_solver");
|
|
EXPECT_FALSE(ok);
|
|
}
|
|
|
|
TEST(SolverRegistryTest, JointsForReturnsCapabilities) // NOLINT
|
|
{
|
|
auto& reg = SolverRegistry::instance();
|
|
|
|
reg.register_solver("test_joints",
|
|
[]() { return std::make_unique<MockSolver>(); });
|
|
|
|
auto joints = reg.joints_for("test_joints");
|
|
EXPECT_EQ(joints.size(), 2u);
|
|
EXPECT_NE(std::find(joints.begin(), joints.end(), BaseJointKind::Fixed), joints.end());
|
|
EXPECT_NE(std::find(joints.begin(), joints.end(), BaseJointKind::Revolute), joints.end());
|
|
}
|
|
|
|
TEST(SolverRegistryTest, JointsForUnknownReturnsEmpty) // NOLINT
|
|
{
|
|
auto joints = SolverRegistry::instance().joints_for("totally_unknown_solver_2");
|
|
EXPECT_TRUE(joints.empty());
|
|
}
|