diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index 4e7c77c5..91d02bb2 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -1390,7 +1390,7 @@ void mjXWriter::Body(XMLElement* elem, mjCBody* body) { // write plugin if (body->is_plugin) { - OnePlugin(elem, body); + OnePlugin(InsertEnd(elem, "plugin"), body); } // write child bodies recursively diff --git a/test/fixture.h b/test/fixture.h index d658bae4..35cc0449 100644 --- a/test/fixture.h +++ b/test/fixture.h @@ -19,6 +19,7 @@ #include #include #include +#include namespace mujoco { @@ -65,5 +66,29 @@ const std::string SaveAndReadXml(const mjModel* model); std::vector GetCtrlNoise(const mjModel* m, int nsteps, mjtNum ctrlnoise = 0.01); +// Installs elasticity plugins +// TODO(quaglino): load all plugins with a macro +class PluginTest : public MujocoTest { + public: + // load plugin library + PluginTest() : MujocoTest() { + #if defined(_WIN32) || defined(__CYGWIN__) + mj_loadPluginLibrary(( + std::string(std::getenv("MUJOCO_PLUGIN_DIR")) + + std::string("\\elasticity.dll")).c_str()); + #else + #if defined(__APPLE__) + mj_loadPluginLibrary(( + std::string(std::getenv("MUJOCO_PLUGIN_DIR")) + + std::string("/libelasticity.dylib")).c_str()); + #else + mj_loadPluginLibrary(( + std::string(std::getenv("MUJOCO_PLUGIN_DIR")) + + std::string("/libelasticity.so")).c_str()); + #endif + #endif + } +}; + } // namespace mujoco #endif // MUJOCO_TEST_FIXTURE_H_ diff --git a/test/plugin/elasticity/elasticity_test.cc b/test/plugin/elasticity/elasticity_test.cc index 2c72dc06..2c6d8858 100644 --- a/test/plugin/elasticity/elasticity_test.cc +++ b/test/plugin/elasticity/elasticity_test.cc @@ -29,28 +29,6 @@ namespace mujoco { namespace { -class PluginTest : public MujocoTest { - public: - // load plugin library - PluginTest() : MujocoTest() { - #if defined(_WIN32) || defined(__CYGWIN__) - mj_loadPluginLibrary(( - std::string(std::getenv("MUJOCO_PLUGIN_DIR")) + - std::string("\\elasticity.dll")).c_str()); - #else - #if defined(__APPLE__) - mj_loadPluginLibrary(( - std::string(std::getenv("MUJOCO_PLUGIN_DIR")) + - std::string("/libelasticity.dylib")).c_str()); - #else - mj_loadPluginLibrary(( - std::string(std::getenv("MUJOCO_PLUGIN_DIR")) + - std::string("/libelasticity.so")).c_str()); - #endif - #endif - } -}; - // -------------------------------- solid ----------------------------------- TEST_F(PluginTest, ElasticEnergy) { static constexpr char cantilever_xml[] = R"( diff --git a/test/xml/CMakeLists.txt b/test/xml/CMakeLists.txt index 1a59bfa8..537a991f 100644 --- a/test/xml/CMakeLists.txt +++ b/test/xml/CMakeLists.txt @@ -18,7 +18,12 @@ target_link_libraries(xml_api_test fixture gmock) mujoco_test(xml_native_reader_test) target_link_libraries(xml_native_reader_test fixture gmock) -mujoco_test(xml_native_writer_test) +mujoco_test( + xml_native_writer_test + PROPERTIES + ENVIRONMENT + "MUJOCO_PLUGIN_DIR=$" +) target_link_libraries( xml_native_writer_test fixture diff --git a/test/xml/xml_native_writer_test.cc b/test/xml/xml_native_writer_test.cc index 2c20bdd3..6b12e83e 100644 --- a/test/xml/xml_native_writer_test.cc +++ b/test/xml/xml_native_writer_test.cc @@ -14,6 +14,7 @@ // Tests for xml/xml_native_writer.cc. +#include #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) #include #endif @@ -959,17 +960,23 @@ static constexpr int kFieldSize = 500; // The maximum spacing between a normalised floating point number x and an // adjacent normalised number is 2 epsilon |x|; a factor 10 is added accounting // for losses during non-idempotent operations such as vector normalizations. -template T Compare(T val1, T val2) { - T error; +template +auto Compare(T val1, T val2) { + using ReturnType = + std::conditional_t, float, double>; + ReturnType error; if (mju_abs(val1) <= 1 || mju_abs(val2) <= 1) { - // Asbolute precision for small numbers + // Absolute precision for small numbers error = mju_abs(val1-val2); } else { // Relative precision for larger numbers - T magnitude = mju_max(mju_abs(val1), mju_abs(val2)); + ReturnType magnitude = mju_abs(val1) + mju_abs(val2); error = mju_abs(val1/magnitude - val2/magnitude) / magnitude; } - return error < 2*10*std::numeric_limits::epsilon() ? 0 : error; + ReturnType safety_factor = 10; + return error < safety_factor * std::numeric_limits::epsilon() + ? 0 + : error; } mjtNum CompareModel(const mjModel* m1, const mjModel* m2, @@ -1015,7 +1022,7 @@ mjtNum CompareModel(const mjModel* m1, const mjModel* m2, return maxdif; } -TEST_F(XMLWriterTest, WriteReadCompare) { +TEST_F(PluginTest, WriteReadCompare) { FullFloatPrecision increase_precision; // Loop over all xml files in data std::vector paths = {GetTestDataFilePath("."), @@ -1028,7 +1035,7 @@ TEST_F(XMLWriterTest, WriteReadCompare) { // if file is meant to fail, skip it if (absl::StrContains(p.path().string(), "malformed_") || - absl::StrContains(p.path().string(), "plugin")) { + absl::StrContains(p.path().string(), "touch_grid")) { continue; } @@ -1051,12 +1058,17 @@ TEST_F(XMLWriterTest, WriteReadCompare) { // if failing because assets are missing, accept the test ASSERT_THAT(error.data(), HasSubstr("file")) << error.data(); } else { + // for a particularly difficult example, relax the tolerance + mjtNum tol = + absl::StrContains(p.path().string(), "belt.xml") ? 1e-13 : 0; + // compare and delete - char field[kFieldSize] = ""; - mjtNum result = CompareModel(m, mtemp, field); - EXPECT_LE(result, 0) << "Loaded and saved models are different!\n" - << "Affected file " << p.path().string() << '\n' - << "Different field: " << field << '\n'; + char field[kFieldSize] = ""; + mjtNum result = CompareModel(m, mtemp, field); + EXPECT_LE(result, tol) + << "Loaded and saved models are different!\n" + << "Affected file " << p.path().string() << '\n' + << "Different field: " << field << '\n'; mj_deleteModel(mtemp); }