Change LoadModelFromString to return a smart pointer, add MakeData, and update tests to have C++ RAII clean up model and data.

PiperOrigin-RevId: 935980153
Change-Id: I41d25bfab4935494dc984168820cb7cad123cadf
This commit is contained in:
Kyle Bayes
2026-06-22 04:18:29 -07:00
committed by Copybara-Service
parent 34d142ee50
commit 1490336955
39 changed files with 4616 additions and 5908 deletions
+77 -116
View File
@@ -95,15 +95,13 @@ TEST_F(UserModelTest, WeldRootID) {
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, NotNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), NotNull());
EXPECT_THAT(AsVector(model->body_rootid, model->nbody),
ElementsAre(0, 1, 1, 3, 3, 5, 5, 7, 7, 9, 9));
EXPECT_THAT(AsVector(model->body_weldid, model->nbody),
ElementsAre(0, 1, 2, 3, 3, 0, 0, 0, 0, 0, 10));
mj_deleteModel(model);
}
TEST_F(UserModelTest, RepeatedNames) {
@@ -119,8 +117,8 @@ TEST_F(UserModelTest, RepeatedNames) {
</mujoco>)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), IsNull());
EXPECT_THAT(error.data(), HasSubstr("repeated name 'geom1' in geom"));
}
@@ -143,8 +141,8 @@ TEST_F(UserModelTest, SameFrame) {
</mujoco>)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model.get(), NotNull()) << error.data();
EXPECT_EQ(model->geom_sameframe[0], mjSAMEFRAME_NONE);
EXPECT_EQ(model->geom_sameframe[1], mjSAMEFRAME_BODY);
EXPECT_EQ(model->geom_sameframe[2], mjSAMEFRAME_INERTIA);
@@ -152,27 +150,24 @@ TEST_F(UserModelTest, SameFrame) {
EXPECT_EQ(model->geom_sameframe[4], mjSAMEFRAME_INERTIAROT);
// make data, get geom_xpos
mjData* data = mj_makeData(model);
mj_kinematics(model, data);
auto geom_xpos = AsVector(data->geom_xpos, model->ngeom*3);
auto geom_xmat = AsVector(data->geom_xmat, model->ngeom*9);
MjDataPtr data = MakeData(model);
mj_kinematics(model.get(), data.get());
auto geom_xpos = AsVector(data->geom_xpos, model->ngeom * 3);
auto geom_xmat = AsVector(data->geom_xmat, model->ngeom * 9);
// set all geom_sameframe to 0, call kinematics again
for (int i = 0; i < model->ngeom; i++) {
model->geom_sameframe[i] = mjSAMEFRAME_NONE;
}
mj_resetData(model, data);
mj_kinematics(model, data);
auto geom_xpos2 = AsVector(data->geom_xpos, model->ngeom*3);
auto geom_xmat2 = AsVector(data->geom_xmat, model->ngeom*9);
mj_resetData(model.get(), data.get());
mj_kinematics(model.get(), data.get());
auto geom_xpos2 = AsVector(data->geom_xpos, model->ngeom * 3);
auto geom_xmat2 = AsVector(data->geom_xmat, model->ngeom * 9);
// expect them to be equal
constexpr double eps = 1e-6;
EXPECT_THAT(geom_xpos, Pointwise(MjNear(eps, eps), geom_xpos2));
EXPECT_THAT(geom_xmat, Pointwise(MjNear(eps, eps), geom_xmat2));
mj_deleteData(data);
mj_deleteModel(model);
}
TEST_F(UserModelTest, ActuatorSparsity) {
@@ -194,9 +189,8 @@ TEST_F(UserModelTest, ActuatorSparsity) {
</actuator>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr m = LoadModelFromString(xml);
ASSERT_EQ(m->nJmom, 2);
mj_deleteModel(m);
}
TEST_F(UserModelTest, FixedTendonSparsity) {
@@ -226,8 +220,8 @@ TEST_F(UserModelTest, FixedTendonSparsity) {
</tendon>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
ASSERT_THAT(m, NotNull());
MjModelPtr m = LoadModelFromString(xml);
ASSERT_THAT(m.get(), NotNull());
EXPECT_EQ(m->nJten, 3);
EXPECT_EQ(m->ten_J_rownnz[0], 3);
@@ -237,8 +231,6 @@ TEST_F(UserModelTest, FixedTendonSparsity) {
int rowadr = m->ten_J_rowadr[0];
int* colind = m->ten_J_colind + rowadr;
EXPECT_THAT(std::vector<int>(colind, colind + 3), ElementsAre(0, 1, 2));
mj_deleteModel(m);
}
TEST_F(UserModelTest, NestedZeroMassBodiesOK) {
@@ -259,9 +251,8 @@ TEST_F(UserModelTest, NestedZeroMassBodiesOK) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
}
TEST_F(UserModelTest, NestedZeroMassBodiesWithJointOK) {
@@ -286,9 +277,8 @@ TEST_F(UserModelTest, NestedZeroMassBodiesWithJointOK) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, NotNull()) << error;
mj_deleteModel(model);
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), NotNull()) << error;
}
TEST_F(UserModelTest, NestedZeroMassBodiesFail) {
@@ -309,14 +299,13 @@ TEST_F(UserModelTest, NestedZeroMassBodiesFail) {
</mujoco>
)";
char error[1024];
mjModel* model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model, IsNull());
MjModelPtr model = LoadModelFromString(xml, error, sizeof(error));
ASSERT_THAT(model.get(), IsNull());
EXPECT_THAT(
error,
HasSubstr(
"mass and inertia of moving bodies must be larger than mjMINVAL"));
EXPECT_THAT(error, HasSubstr("Element name 'bad'"));
mj_deleteModel(model);
}
TEST_F(UserModelTest, ConvexHullForCollisionMeshes) {
@@ -338,14 +327,14 @@ TEST_F(UserModelTest, ConvexHullForCollisionMeshes) {
</mujoco>)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model.get(), NotNull()) << error.data();
int no_hull_id = mj_name2id(model, mjOBJ_MESH, "mesh_no_hull");
int no_hull_id = mj_name2id(model.get(), mjOBJ_MESH, "mesh_no_hull");
int with_hull_contype_id =
mj_name2id(model, mjOBJ_MESH, "mesh_with_hull_contype");
mj_name2id(model.get(), mjOBJ_MESH, "mesh_with_hull_contype");
int with_hull_conaffinity_id =
mj_name2id(model, mjOBJ_MESH, "mesh_with_hull_conaffinity");
mj_name2id(model.get(), mjOBJ_MESH, "mesh_with_hull_conaffinity");
EXPECT_NE(no_hull_id, -1);
EXPECT_NE(with_hull_contype_id, -1);
@@ -359,8 +348,6 @@ TEST_F(UserModelTest, ConvexHullForCollisionMeshes) {
// mesh_with_hull_conaffinity should have a convex hull.
EXPECT_NE(model->mesh_graphadr[with_hull_conaffinity_id], -1);
mj_deleteModel(model);
}
TEST_F(UserModelTest, ConvexHullForPairCollisionMeshes) {
@@ -380,17 +367,15 @@ TEST_F(UserModelTest, ConvexHullForPairCollisionMeshes) {
</mujoco>)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model.get(), NotNull()) << error.data();
int mesh_in_pair_id = mj_name2id(model, mjOBJ_MESH, "mesh");
int mesh_in_pair_id = mj_name2id(model.get(), mjOBJ_MESH, "mesh");
EXPECT_NE(mesh_in_pair_id, -1);
// mesh_in_pair should have a convex hull because it is in a collision pair.
EXPECT_NE(model->mesh_graphadr[mesh_in_pair_id], -1);
mj_deleteModel(model);
}
// ------------- test automatic inference of nuser_xxx -------------------------
@@ -406,11 +391,10 @@ TEST_F(UserDataTest, AutoNUserBody) {
</worldbody>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr m = LoadModelFromString(xml);
ASSERT_EQ(m->nuser_body, 3);
EXPECT_THAT(GetRow(m->body_user, m->nuser_body, 1), ElementsAre(1, 2, 3));
EXPECT_THAT(GetRow(m->body_user, m->nuser_body, 2), ElementsAre(2, 3, 0));
mj_deleteModel(m);
}
TEST_F(UserDataTest, AutoNUserJoint) {
@@ -425,11 +409,10 @@ TEST_F(UserDataTest, AutoNUserJoint) {
</worldbody>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr m = LoadModelFromString(xml);
ASSERT_EQ(m->nuser_jnt, 3);
EXPECT_THAT(GetRow(m->jnt_user, m->nuser_jnt, 0), ElementsAre(1, 2, 3));
EXPECT_THAT(GetRow(m->jnt_user, m->nuser_jnt, 1), ElementsAre(2, 3, 0));
mj_deleteModel(m);
}
TEST_F(UserDataTest, AutoNUserGeom) {
@@ -441,11 +424,10 @@ TEST_F(UserDataTest, AutoNUserGeom) {
</worldbody>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr m = LoadModelFromString(xml);
ASSERT_EQ(m->nuser_geom, 3);
EXPECT_THAT(GetRow(m->geom_user, m->nuser_geom, 0), ElementsAre(1, 2, 3));
EXPECT_THAT(GetRow(m->geom_user, m->nuser_geom, 1), ElementsAre(2, 3, 0));
mj_deleteModel(m);
}
TEST_F(UserDataTest, AutoNUserSite) {
@@ -457,11 +439,10 @@ TEST_F(UserDataTest, AutoNUserSite) {
</worldbody>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr m = LoadModelFromString(xml);
ASSERT_EQ(m->nuser_site, 3);
EXPECT_THAT(GetRow(m->site_user, m->nuser_site, 0), ElementsAre(1, 2, 3));
EXPECT_THAT(GetRow(m->site_user, m->nuser_site, 1), ElementsAre(2, 3, 0));
mj_deleteModel(m);
}
TEST_F(UserDataTest, AutoNUserCamera) {
@@ -473,11 +454,10 @@ TEST_F(UserDataTest, AutoNUserCamera) {
</worldbody>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr m = LoadModelFromString(xml);
ASSERT_EQ(m->nuser_cam, 3);
EXPECT_THAT(GetRow(m->cam_user, m->nuser_cam, 0), ElementsAre(1, 2, 3));
EXPECT_THAT(GetRow(m->cam_user, m->nuser_cam, 1), ElementsAre(2, 3, 0));
mj_deleteModel(m);
}
TEST_F(UserDataTest, AutoNUserTendon) {
@@ -499,11 +479,10 @@ TEST_F(UserDataTest, AutoNUserTendon) {
</tendon>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr m = LoadModelFromString(xml);
ASSERT_EQ(m->nuser_tendon, 3);
EXPECT_THAT(GetRow(m->tendon_user, m->nuser_tendon, 0), ElementsAre(1, 2, 3));
EXPECT_THAT(GetRow(m->tendon_user, m->nuser_tendon, 1), ElementsAre(2, 3, 0));
mj_deleteModel(m);
}
TEST_F(UserDataTest, AutoNUserActuator) {
@@ -521,13 +500,12 @@ TEST_F(UserDataTest, AutoNUserActuator) {
</actuator>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr m = LoadModelFromString(xml);
ASSERT_EQ(m->nuser_actuator, 3);
EXPECT_THAT(GetRow(m->actuator_user, m->nuser_actuator, 0),
ElementsAre(1, 2, 3));
EXPECT_THAT(GetRow(m->actuator_user, m->nuser_actuator, 1),
ElementsAre(2, 3, 0));
mj_deleteModel(m);
}
TEST_F(UserDataTest, AutoNUserSensor) {
@@ -542,11 +520,10 @@ TEST_F(UserDataTest, AutoNUserSensor) {
</sensor>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
MjModelPtr m = LoadModelFromString(xml);
ASSERT_EQ(m->nuser_sensor, 3);
EXPECT_THAT(GetRow(m->sensor_user, m->nuser_sensor, 0), ElementsAre(1, 2, 3));
EXPECT_THAT(GetRow(m->sensor_user, m->nuser_sensor, 1), ElementsAre(2, 3, 0));
mj_deleteModel(m);
}
// ------------- test duplicate names ------------------------------------------
@@ -584,10 +561,10 @@ TEST_F(FuseStaticTest, FuseStaticEquivalent) {
std::string fuse = absl::StrFormat(xml_template, "true");
std::string no_fuse = absl::StrFormat(xml_template, "false");
mjModel* m_fuse = LoadModelFromString(fuse.c_str());
mjModel* m_no_fuse = LoadModelFromString(no_fuse.c_str());
ASSERT_THAT(m_fuse, NotNull());
ASSERT_THAT(m_no_fuse, NotNull());
MjModelPtr m_fuse = LoadModelFromString(fuse.c_str());
MjModelPtr m_no_fuse = LoadModelFromString(no_fuse.c_str());
ASSERT_THAT(m_fuse.get(), NotNull());
ASSERT_THAT(m_no_fuse.get(), NotNull());
EXPECT_EQ(m_fuse->nbody, 2) << "Expecting a world body and one other body";
EXPECT_EQ(m_no_fuse->nbody, 3) << "Expecting a world body and two others";
@@ -600,20 +577,16 @@ TEST_F(FuseStaticTest, FuseStaticEquivalent) {
EXPECT_EQ(m_no_fuse->body_bvhnum[2], 3);
EXPECT_EQ(m_fuse->body_bvhnum[1], 3);
mjData* d_fuse = mj_makeData(m_fuse);
mjData* d_no_fuse = mj_makeData(m_no_fuse);
MjDataPtr d_fuse = MakeData(m_fuse);
MjDataPtr d_no_fuse = MakeData(m_no_fuse);
mj_step(m_fuse, d_fuse);
mj_step(m_no_fuse, d_no_fuse);
mj_step(m_fuse.get(), d_fuse.get());
mj_step(m_no_fuse.get(), d_no_fuse.get());
EXPECT_NEAR(d_fuse->qvel[0], d_no_fuse->qvel[0], MjTol(2e-17, 1e-8))
EXPECT_NEAR(d_fuse.get()->qvel[0], d_no_fuse.get()->qvel[0],
MjTol(2e-17, 1e-8))
<< "Velocity should be the same after 1 step";
EXPECT_NE(d_fuse->qvel[0], 0);
mj_deleteData(d_fuse);
mj_deleteData(d_no_fuse);
mj_deleteModel(m_fuse);
mj_deleteModel(m_no_fuse);
EXPECT_NE(d_fuse.get()->qvel[0], 0);
}
TEST_F(FuseStaticTest, FuseStaticActuatorReferencedBody) {
@@ -642,10 +615,9 @@ TEST_F(FuseStaticTest, FuseStaticActuatorReferencedBody) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml_template, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml_template, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_EQ(m->nbody, 3) << "Expecting a world body and two others";
mj_deleteModel(m);
}
TEST_F(FuseStaticTest, FuseStaticLightReferencedBody) {
@@ -671,10 +643,9 @@ TEST_F(FuseStaticTest, FuseStaticLightReferencedBody) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml_template, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml_template, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_EQ(m->nbody, 3) << "Expecting a world body and two others";
mj_deleteModel(m);
}
TEST_F(FuseStaticTest, FuseStaticForceSensorReferencedBody) {
@@ -704,10 +675,9 @@ TEST_F(FuseStaticTest, FuseStaticForceSensorReferencedBody) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml_template, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml_template, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_EQ(m->nbody, 3) << "Expecting a world body and two others";
mj_deleteModel(m);
}
TEST_F(FuseStaticTest, FuseStaticCameraInBody) {
@@ -727,11 +697,10 @@ TEST_F(FuseStaticTest, FuseStaticCameraInBody) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_EQ(m->nbody, 2) << "Static body should be fused";
EXPECT_EQ(m->ncam, 1);
mj_deleteModel(m);
}
TEST_F(FuseStaticTest, FuseStaticLightInBody) {
@@ -750,11 +719,10 @@ TEST_F(FuseStaticTest, FuseStaticLightInBody) {
</mujoco>
)";
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m, NotNull()) << error.data();
MjModelPtr m = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(m.get(), NotNull()) << error.data();
EXPECT_EQ(m->nbody, 2) << "Static body should be fused";
EXPECT_EQ(m->nlight, 1);
mj_deleteModel(m);
}
// ------------- test discardvisual --------------------------------------------
@@ -789,13 +757,12 @@ TEST_F(DiscardVisualTest, DiscardVisualKeepsInertia) {
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, NotNull()) << error.data();
MjModelPtr model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model.get(), NotNull()) << error.data();
EXPECT_THAT(model->nmesh, 1);
EXPECT_THAT(model->body_inertia[3], model->body_inertia[6]);
EXPECT_THAT(model->body_inertia[4], model->body_inertia[7]);
EXPECT_THAT(model->body_inertia[5], model->body_inertia[8]);
mj_deleteModel(model);
}
TEST_F(DiscardVisualTest, DiscardVisualEquivalent) {
@@ -818,8 +785,8 @@ TEST_F(DiscardVisualTest, DiscardVisualEquivalent) {
EXPECT_THAT(model1->nq, model2->nq);
EXPECT_THAT(model1->nmat, 0);
EXPECT_THAT(model1->ntex, 0);
EXPECT_THAT(model2->ngeom-model1->ngeom, 3);
EXPECT_THAT(model2->nmesh-model1->nmesh, 2);
EXPECT_THAT(model2->ngeom - model1->ngeom, 3);
EXPECT_THAT(model2->nmesh - model1->nmesh, 2);
EXPECT_THAT(model1->npair, model2->npair);
EXPECT_THAT(model1->nsensor, model2->nsensor);
EXPECT_THAT(model1->nwrap, model2->nwrap);
@@ -851,8 +818,8 @@ TEST_F(DiscardVisualTest, DiscardVisualEquivalent) {
EXPECT_STREQ(model1->names + adr1, model2->names + adr2);
}
mjData *d1 = mj_makeData(model1);
mjData *d2 = mj_makeData(model2);
mjData* d1 = mj_makeData(model1);
mjData* d2 = mj_makeData(model2);
for (int i = 0; i < 100; i++) {
mj_step(model1, d1);
mj_step(model2, d2);
@@ -877,8 +844,7 @@ TEST_F(LengthRangeTest, LengthRangeThreading) {
size_t error_sz = 1024;
std::string field = "";
static const char* const kLengthrangePath =
"user/testdata/lengthrange.xml";
static const char* const kLengthrangePath = "user/testdata/lengthrange.xml";
const std::string xml_path1 = GetTestDataFilePath(kLengthrangePath);
mjSpec* spec = mj_parseXML(xml_path1.c_str(), 0, error, error_sz);
@@ -1022,20 +988,17 @@ TEST_F(MujocoTest, NestedMeshDir) {
sizeof(cube));
std::array<char, 1024> error;
mjModel* child_model = LoadModelFromString(child_xml, error.data(),
error.size(), vfs.get());
EXPECT_THAT(child_model, NotNull()) << error.data();
mj_deleteModel(child_model);
MjModelPtr child_model =
LoadModelFromString(child_xml, error.data(), error.size(), vfs.get());
EXPECT_THAT(child_model.get(), NotNull()) << error.data();
mjModel* parent_model = LoadModelFromString(parent_xml, error.data(),
error.size(), vfs.get());
EXPECT_THAT(parent_model, NotNull()) << error.data();
mj_deleteModel(parent_model);
MjModelPtr parent_model =
LoadModelFromString(parent_xml, error.data(), error.size(), vfs.get());
EXPECT_THAT(parent_model.get(), NotNull()) << error.data();
mjModel* grandparent_model = LoadModelFromString(
MjModelPtr grandparent_model = LoadModelFromString(
grandparent_xml, error.data(), error.size(), vfs.get());
EXPECT_THAT(grandparent_model, NotNull()) << error.data();
mj_deleteModel(grandparent_model);
EXPECT_THAT(grandparent_model.get(), NotNull()) << error.data();
mj_deleteVFS(vfs.get());
}
@@ -1087,8 +1050,8 @@ TEST_F(DelayBufferTest, ActuatorDelayBufferSizes) {
</actuator>
</mujoco>
)";
mjModel* m = LoadModelFromString(xml);
ASSERT_THAT(m, NotNull());
MjModelPtr m = LoadModelFromString(xml);
ASSERT_THAT(m.get(), NotNull());
ASSERT_EQ(m->nu, 3);
// nhistory = (2+2*3) + (2+2*10) = 8 + 22 = 30
@@ -1101,8 +1064,6 @@ TEST_F(DelayBufferTest, ActuatorDelayBufferSizes) {
EXPECT_EQ(m->actuator_historyadr[0], -1);
EXPECT_EQ(m->actuator_historyadr[1], 0);
EXPECT_EQ(m->actuator_historyadr[2], 8);
mj_deleteModel(m);
}
} // namespace