@@ -521,7 +521,8 @@ TEST_F(XMLReaderTest, InvalidDoubleOrientation) {
521521 if (orient1 == orient2) continue ;
522522 std::string xml = prefix + field + orient1 + orient2 + suffix;
523523 std::array<char , 1024 > error;
524- MjModelPtr model = LoadModelFromString (xml.c_str (), error.data (), error.size ());
524+ MjModelPtr model =
525+ LoadModelFromString (xml.c_str (), error.data (), error.size ());
525526 ASSERT_THAT (model.get (), IsNull ());
526527 EXPECT_THAT (
527528 error.data (),
@@ -1836,7 +1837,8 @@ TEST_F(XMLReaderTest, AttachSpecAssets) {
18361837 mj_addBufferVFS (vfs.get (), " xml_child.xml" , xml_child, sizeof (xml_child));
18371838
18381839 std::array<char , 1024 > er;
1839- MjModelPtr model = LoadModelFromString (xml_parent, er.data (), er.size (), vfs.get ());
1840+ MjModelPtr model =
1841+ LoadModelFromString (xml_parent, er.data (), er.size (), vfs.get ());
18401842 EXPECT_THAT (model.get (), NotNull ()) << er.data ();
18411843
18421844 MjModelPtr expected = LoadModelFromString (xml_expected, er.data (), er.size ());
@@ -1890,7 +1892,8 @@ TEST_F(XMLReaderTest, InvalidAttach) {
18901892 mj_addBufferVFS (vfs.get (), " child.xml" , xml_child, sizeof (xml_child));
18911893
18921894 std::array<char , 1024 > er;
1893- MjModelPtr model = LoadModelFromString (xml_parent, er.data (), er.size (), vfs.get ());
1895+ MjModelPtr model =
1896+ LoadModelFromString (xml_parent, er.data (), er.size (), vfs.get ());
18941897
18951898 EXPECT_THAT (model.get (), IsNull ()) << er.data ();
18961899 EXPECT_THAT (er.data (), HasSubstr (" repeated name '_actuator' in actuator" ));
@@ -1999,7 +2002,8 @@ TEST_F(XMLReaderTest, ResizeKeyframeAfterParsing) {
19992002 mj_addBufferVFS (vfs.get (), " child.xml" , child_xml, sizeof (child_xml));
20002003
20012004 std::array<char , 1024 > error;
2002- MjModelPtr m = LoadModelFromString (parent_xml, error.data (), error.size (), vfs.get ());
2005+ MjModelPtr m =
2006+ LoadModelFromString (parent_xml, error.data (), error.size (), vfs.get ());
20032007 EXPECT_THAT (m.get (), NotNull ()) << error.data ();
20042008 mj_deleteVFS (vfs.get ());
20052009}
@@ -3725,7 +3729,7 @@ TEST_F(ActuatorParseTest, ActuatorDelayParsed) {
37253729 ASSERT_EQ (model->nu , 3 );
37263730 // actuator_history[2*i] = nsample, actuator_history[2*i+1] = interp
37273731 EXPECT_EQ (model->actuator_history [0 ], 0 ); // jnt1 nsample
3728- EXPECT_EQ (model->actuator_history [1 ], 0 ); // jnt1 interp (no buffer, default 0)
3732+ EXPECT_EQ (model->actuator_history [1 ], 0 ); // jnt1 interp (no buffer, def 0)
37293733 EXPECT_EQ (model->actuator_history [2 ], 3 ); // jnt2 nsample
37303734 EXPECT_EQ (model->actuator_history [3 ], 0 ); // jnt2 interp (ZOH)
37313735 EXPECT_EQ (model->actuator_history [4 ], 10 ); // jnt3 nsample
@@ -3785,6 +3789,7 @@ TEST_F(ActuatorParseTest, ActuatorDelayRequiresHistory) {
37853789 EXPECT_THAT (error.data (),
37863790 HasSubstr (" setting delay > 0 without a history buffer" ));
37873791}
3792+
37883793TEST_F (ActuatorParseTest, DampingArmatureDefaultsPropagate) {
37893794 static constexpr char xml[] = R"(
37903795 <mujoco>
@@ -3884,5 +3889,106 @@ TEST_F(XMLReaderTest, AttachConflictXMLMergeUnmergableError) {
38843889 HasSubstr (" gravity: parent has 0 0 -10, child has 0 0 0" ));
38853890}
38863891
3892+ TEST_F (XMLReaderTest, SelfAttach) {
3893+ static constexpr char xml[] = R"(
3894+ <mujoco model="self-attach-test">
3895+ <worldbody>
3896+ <body name="body1">
3897+ <geom name="geom1" size="1"/>
3898+ </body>
3899+ <body name="body2">
3900+ <attach body="body1" prefix="attached_"/>
3901+ </body>
3902+ </worldbody>
3903+ </mujoco>
3904+ )" ;
3905+ std::array<char , 1024 > error;
3906+ mjSpec* spec = mj_parseXMLString (xml, nullptr , error.data (), error.size ());
3907+ ASSERT_THAT (spec, NotNull ()) << error.data ();
3908+
3909+ mjModel* m = mj_compile (spec, nullptr );
3910+ ASSERT_THAT (m, NotNull ());
3911+
3912+ int body2_id = mj_name2id (m, mjOBJ_BODY, " body2" );
3913+ int attached_body1_id = mj_name2id (m, mjOBJ_BODY, " attached_body1" );
3914+
3915+ EXPECT_GE (body2_id, 0 );
3916+ EXPECT_GE (attached_body1_id, 0 );
3917+ EXPECT_EQ (m->body_parentid [attached_body1_id], body2_id);
3918+
3919+ mj_deleteModel (m);
3920+ mj_deleteSpec (spec);
3921+ }
3922+
3923+ TEST_F (XMLReaderTest, SelfAttachCollisionError) {
3924+ static constexpr char xml[] = R"(
3925+ <mujoco model="self-attach-collision">
3926+ <worldbody>
3927+ <body name="body1"/>
3928+ <body name="attached_body1"/>
3929+ <body name="body2">
3930+ <attach body="body1" prefix="attached_"/>
3931+ </body>
3932+ </worldbody>
3933+ </mujoco>
3934+ )" ;
3935+ std::array<char , 1024 > error;
3936+ mjSpec* spec = mj_parseXMLString (xml, nullptr , error.data (), error.size ());
3937+ EXPECT_THAT (spec, IsNull ());
3938+ EXPECT_THAT (
3939+ error.data (),
3940+ HasSubstr (" cannot self-attach: element attached_body1 already exists" ));
3941+ }
3942+
3943+ TEST_F (XMLReaderTest, SelfAttachMissingError) {
3944+ static constexpr char xml[] = R"(
3945+ <mujoco model="self-attach-missing">
3946+ <worldbody>
3947+ <body name="body1">
3948+ <attach body="nonexistent" prefix="attached_"/>
3949+ </body>
3950+ </worldbody>
3951+ </mujoco>
3952+ )" ;
3953+ std::array<char , 1024 > error;
3954+ mjSpec* spec = mj_parseXMLString (xml, nullptr , error.data (), error.size ());
3955+ EXPECT_THAT (spec, IsNull ());
3956+ EXPECT_THAT (error.data (), HasSubstr (" could not find body 'nonexistent' in "
3957+ " the current model for self-attachment" ));
3958+ }
3959+
3960+ TEST_F (XMLReaderTest, SelfAttachFrame) {
3961+ static constexpr char xml[] = R"(
3962+ <mujoco model="self-attach-frame-test">
3963+ <worldbody>
3964+ <frame name="frame1">
3965+ <body name="body1" pos="-1 0 0">
3966+ <geom name="geom1" type="box" size="1 1 1"/>
3967+ </body>
3968+ </frame>
3969+ <body name="body2">
3970+ <attach frame="frame1" prefix="attached_"/>
3971+ </body>
3972+ </worldbody>
3973+ </mujoco>
3974+ )" ;
3975+ std::array<char , 1024 > error;
3976+ mjSpec* spec = mj_parseXMLString (xml, nullptr , error.data (), error.size ());
3977+ ASSERT_THAT (spec, NotNull ()) << error.data ();
3978+
3979+ mjModel* m = mj_compile (spec, nullptr );
3980+ ASSERT_THAT (m, NotNull ()) << mjs_getError (spec);
3981+
3982+ int body2_id = mj_name2id (m, mjOBJ_BODY, " body2" );
3983+ int attached_body1_id = mj_name2id (m, mjOBJ_BODY, " attached_body1" );
3984+
3985+ EXPECT_GE (body2_id, 0 );
3986+ EXPECT_GE (attached_body1_id, 0 );
3987+ EXPECT_EQ (m->body_parentid [attached_body1_id], body2_id);
3988+
3989+ mj_deleteModel (m);
3990+ mj_deleteSpec (spec);
3991+ }
3992+
38873993} // namespace
38883994} // namespace mujoco
0 commit comments