// Copyright 2026 DeepMind Technologies Limited // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Checks that the attribute defaults declared in mjcf.schema agree with the // C default-constructors: every bound numeric attribute is compared against a // freshly-constructed spec element. Coverage is total, so a nonzero // constructor default that the schema does not declare is a failure: the // schema cannot silently under-declare. Fields whose constructor value is // the mjNAN "unset" sentinel are marked in the table and asserted NaN. #include #include #include #include #include #include #include "test/fixture.h" #include "src/xml/generated/mjcf_default_table.inc" namespace mujoco { namespace { using SchemaDefaultsTest = MujocoTest; TEST_F(SchemaDefaultsTest, DeclaredDefaultsMatchConstructors) { mjSpec* spec = mj_makeSpec(); mjsBody* world = mjs_findBody(spec, "world"); ASSERT_NE(world, nullptr); mjsBody* body = mjs_addBody(world, nullptr); std::map objects = { {"mjOption", &spec->option}, {"mjVisual", &spec->visual}, {"mjStatistic", &spec->stat}, {"mjSpec", spec}, {"mjLROpt", &spec->compiler.LRopt}, {"mjsCompiler", &spec->compiler}, {"mjsFlex", mjs_addFlex(spec)}, {"mjsHField", mjs_addHField(spec)}, {"mjsKey", mjs_addKey(spec)}, {"mjsNumeric", mjs_addNumeric(spec)}, {"mjsBody", body}, {"mjsFrame", mjs_addFrame(body, nullptr)}, {"mjsJoint", mjs_addJoint(body, nullptr)}, {"mjsGeom", mjs_addGeom(body, nullptr)}, {"mjsSite", mjs_addSite(body, nullptr)}, {"mjsCamera", mjs_addCamera(body, nullptr)}, {"mjsLight", mjs_addLight(body, nullptr)}, {"mjsPair", mjs_addPair(spec, nullptr)}, {"mjsEquality", mjs_addEquality(spec, nullptr)}, {"mjsTendon", mjs_addTendon(spec, nullptr)}, {"mjsActuator", mjs_addActuator(spec, nullptr)}, {"mjsSensor", mjs_addSensor(spec)}, {"mjsMesh", mjs_addMesh(spec, nullptr)}, {"mjsSkin", mjs_addSkin(spec)}, {"mjsMaterial", mjs_addMaterial(spec, nullptr)}, {"mjsTexture", mjs_addTexture(spec)}, }; for (int t = 0; t < kDefaultTablesN; t++) { const mjXDefaultTable& table = kDefaultTables[t]; auto it = objects.find(table.structname); ASSERT_NE(it, objects.end()) << "no factory for " << table.structname; const char* base = static_cast(it->second); for (int i = 0; i < table.n; i++) { const mjXDefaultEntry& entry = table.entries[i]; const char* field = base + entry.offset; for (int j = 0; j < entry.len; j++) { double expected = j < entry.ndecl ? entry.value[j] : 0; double actual = 0; if (entry.unset && j == 0) { // the constructor holds the mjNAN "unset" sentinel in the first // slot (the rest are zeros), deliberately undeclared in the schema double v = entry.kind == 4 ? (double)reinterpret_cast(field)[j] : reinterpret_cast(field)[j]; EXPECT_TRUE(std::isnan(v)) << table.structname << "." << entry.attr << "[" << j << "]"; continue; } switch (entry.kind) { case 0: // double actual = reinterpret_cast(field)[j]; break; case 1: // float: compare at float precision actual = reinterpret_cast(field)[j]; expected = static_cast(expected); break; case 2: // int-sized, including enums actual = reinterpret_cast(field)[j]; break; case 3: // byte actual = reinterpret_cast(field)[j]; break; case 4: // mjtNum: compare at mjtNum precision actual = reinterpret_cast(field)[j]; expected = static_cast(expected); break; } EXPECT_EQ(actual, expected) << table.structname << "." << entry.attr << "[" << j << "]"; } } } mj_deleteSpec(spec); } } // namespace } // namespace mujoco