// Copyright 2023 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. // Tests for user/user_resource.cc #include #include #include #include #include #include #include #include #include #include "src/cc/array_safety.h" #include #include #include "src/engine/engine_plugin.h" #include "src/engine/engine_util_misc.h" #include "src/user/user_resource.h" #include "test/fixture.h" namespace mujoco { namespace { using ::testing::HasSubstr; using ::testing::IsNull; using ::testing::NotNull; using ::testing::StrEq; using ResourceTest = MujocoTest; int open_nop(mjResource* resource) { return 1; } int open_str(mjResource* resource) { if (std::strcmp(resource->name, "str:file")) { return 0; } resource->data = mju_malloc(100*sizeof(char)); std::strcpy((char*) resource->data, "Hello World"); return 1; } int read_nop(mjResource* resource, const void** buffer) { return 0; } int read_str(mjResource* resource, const void** buffer) { *buffer = resource->data; return std::strlen((const char*) resource->data) + 1; } void close_nop(mjResource* resource) { } void close_str(mjResource* resource) { mju_free(resource->data); resource->data = nullptr; } TEST_F(ResourceTest, RegisterProviderSuccess) { mjpResourceProvider provider = { .prefix = "my-prefix.123+45", .open = open_nop, .read = read_nop, .close = close_nop, }; int count1 = mjp_resourceProviderCount(); int i = mjp_registerResourceProvider(&provider); int count2 = mjp_resourceProviderCount(); EXPECT_GT(i, 0); EXPECT_EQ(count2 - count1, 1); } TEST_F(ResourceTest, RegisterProviderMultipleSuccess) { mjpResourceProvider provider = { .prefix = "my-prefix.123+44", .open = open_nop, .read = read_nop, .close = close_nop, }; mjpResourceProvider provider2 = { .prefix = "my-prefix.123+46", .open = open_nop, .read = read_nop, .close = close_nop, }; mjpResourceProvider provider3 = { .prefix = "my-prefix.123+41", .open = open_nop, .read = read_nop, .close = close_nop, }; int count1 = mjp_resourceProviderCount(); int i = mjp_registerResourceProvider(&provider); int i2 = mjp_registerResourceProvider(&provider2); int i3 = mjp_registerResourceProvider(&provider3); int count2 = mjp_resourceProviderCount(); EXPECT_GT(i, 0); EXPECT_GT(i2, 0); EXPECT_GT(i3, 0); EXPECT_EQ(count2 - count1, 3); } TEST_F(ResourceTest, RegisterProviderMissingCallbacks) { mjpResourceProvider provider = { .prefix = "myprefix", }; // install warning handler static char warning[1024]; warning[0] = '\0'; mju_user_warning = [](const char* msg) { util::strcpy_arr(warning, msg); }; int i = mjp_registerResourceProvider(&provider); // warning message related to missing callbacks EXPECT_THAT(warning, HasSubstr("callback")); EXPECT_LT(i, 1); } TEST_F(ResourceTest, RegisterProviderMissingPrefix) { mjpResourceProvider provider = { .prefix = "", .open = open_nop, .read = read_nop, .close = close_nop, }; // install warning handler static char warning[1024]; warning[0] = '\0'; mju_user_warning = [](const char* msg) { util::strcpy_arr(warning, msg); }; int i = mjp_registerResourceProvider(&provider); // warning message related to missing prefix EXPECT_THAT(warning, HasSubstr("prefix")); EXPECT_LT(i, 1); } TEST_F(ResourceTest, RegisterProviderInvalidPrefix1) { mjpResourceProvider provider = { .prefix = "1invalid", .open = open_nop, .read = read_nop, .close = close_nop, }; // install warning handler static char warning[1024]; warning[0] = '\0'; mju_user_warning = [](const char* msg) { util::strcpy_arr(warning, msg); }; int i = mjp_registerResourceProvider(&provider); // warning message related to missing prefix EXPECT_THAT(warning, HasSubstr("prefix")); EXPECT_LT(i, 1); } TEST_F(ResourceTest, RegisterProviderInvalidPrefix2) { mjpResourceProvider provider = { .prefix = "invalid:", .open = open_nop, .read = read_nop, .close = close_nop, }; // install warning handler static char warning[1024]; warning[0] = '\0'; mju_user_warning = [](const char* msg) { util::strcpy_arr(warning, msg); }; int i = mjp_registerResourceProvider(&provider); // warning message related to missing prefix EXPECT_THAT(warning, HasSubstr("prefix")); EXPECT_LT(i, 1); } TEST_F(ResourceTest, RegisterProviderSame) { mjpResourceProvider provider = { .prefix = "prefix", .open = open_nop, .read = read_nop, .close = close_nop, }; mjpResourceProvider provider2 = { .prefix = "prefix", .open = open_nop, .read = read_nop, .close = close_nop, }; int i1 = mjp_registerResourceProvider(&provider); int count1 = mjp_resourceProviderCount(); int i2 = mjp_registerResourceProvider(&provider2); int count2 = mjp_resourceProviderCount(); EXPECT_EQ(i1, i2); EXPECT_EQ(count1, count2); } TEST_F(ResourceTest, RegisterProviderSameCase) { mjpResourceProvider provider = { .prefix = "prefix", .open = open_nop, .read = read_nop, .close = close_nop, }; mjpResourceProvider provider2 = { .prefix = "PREFIX", .open = open_nop, .read = read_nop, .close = close_nop, }; int i1 = mjp_registerResourceProvider(&provider); int count1 = mjp_resourceProviderCount(); int i2 = mjp_registerResourceProvider(&provider2); int count2 = mjp_resourceProviderCount(); EXPECT_EQ(i1, i2); EXPECT_EQ(count1, count2); } TEST_F(ResourceTest, GeneralTest) { mjpResourceProvider provider = { .prefix = "str", .open = open_str, .read = read_str, .close = close_str, }; // register resource provider int i = mjp_registerResourceProvider(&provider); EXPECT_GT(i, 0); // open resource mjResource* resource = mju_openResource("", "str:file", nullptr, nullptr, 0); ASSERT_THAT(resource, NotNull()); const char* buffer = NULL; int bytes = mju_readResource(resource, (const void**) &buffer); EXPECT_EQ(bytes, std::strlen("Hello World") + 1); EXPECT_THAT(buffer, StrEq("Hello World")); mju_closeResource(resource); } TEST_F(ResourceTest, GeneralFailureTest) { mjpResourceProvider provider = { .prefix = "str", .open = open_str, .read = read_str, .close = close_str, }; // register resource provider int i = mjp_registerResourceProvider(&provider); EXPECT_GT(i, 0); static std::array error; // open resource mjResource* resource = mju_openResource("", "str:notfound", nullptr, error.data(), error.size()); ASSERT_THAT(resource, IsNull()); EXPECT_THAT(error.data(), HasSubstr("Error opening file")); } TEST_F(ResourceTest, NameWithValidPrefix) { mjpResourceProvider provider = { .prefix = "nop", .open = open_nop, .read = read_nop, .close = close_nop, }; // register resource provider int i = mjp_registerResourceProvider(&provider); EXPECT_GT(i, 0); // install warning handler static char warning[1024]; warning[0] = '\0'; mju_user_warning = [](const char* msg) { util::strcpy_arr(warning, msg); }; // open resource mjResource* resource = mju_openResource("", "nop:found", nullptr, nullptr, 0); ASSERT_THAT(resource, NotNull()); mju_closeResource(resource); } TEST_F(ResourceTest, NameWithUpperCasePrefix) { mjpResourceProvider provider = { .prefix = "nop", .open = open_nop, .read = read_nop, .close = close_nop, }; // register resource provider int i = mjp_registerResourceProvider(&provider); EXPECT_GT(i, 0); // install warning handler static char warning[1024]; warning[0] = '\0'; mju_user_warning = [](const char* msg) { util::strcpy_arr(warning, msg); }; // open resource mjResource* resource = mju_openResource("", "NOP:found", nullptr, nullptr, 0); ASSERT_THAT(resource, NotNull()); mju_closeResource(resource); } TEST_F(ResourceTest, NameWithInvalidPrefix) { mjpResourceProvider provider = { .prefix = "nop", .open = open_nop, .read = read_nop, .close = close_nop, }; // register resource provider int i = mjp_registerResourceProvider(&provider); EXPECT_GT(i, 0); // install warning handler static char warning[1024]; warning[0] = '\0'; mju_user_warning = [](const char* msg) { util::strcpy_arr(warning, msg); }; // open resource mjResource* resource = mju_openResource("", "nopfound", nullptr, nullptr, 0); ASSERT_THAT(resource, IsNull()); } TEST_F(ResourceTest, GetResourceDir) { const std::vector> cases = { { "foo/bar/baz", "foo/bar/" }, { "foo/bar/", "foo/bar/" }, { "foo/bar", "foo/" }, { "/foo/bar", "/foo/" }, { "/foo", "/" }, { "/", "/" }, { "", "" }, }; const char* dir = nullptr; int ndir = 0; mjpResourceProvider provider; provider.prefix = "provider"; for (const auto& [name, expected] : cases) { mjResource resource; resource.provider = nullptr; resource.name = const_cast(name.c_str()); mju_getResourceDir(&resource, &dir, &ndir); EXPECT_THAT(std::string(dir, ndir), expected); } } TEST_F(ResourceTest, GetResourceDirProvider) { const std::vector> cases = { { "provider:/foo/bar", "provider:/foo/" }, { "provider:/foo/", "provider:/foo/" }, { "provider:foo/", "provider:foo/" }, { "provider:foo", "provider:" }, { "provider:/", "provider:/" }, }; const char* dir = nullptr; int ndir = 0; mjpResourceProvider provider; provider.prefix = "provider"; for (const auto& [name, expected] : cases) { mjResource resource; resource.provider = &provider; resource.name = const_cast(name.c_str()); mju_getResourceDir(&resource, &dir, &ndir); EXPECT_THAT(std::string(dir, ndir), expected); } } TEST_F(ResourceTest, GetResourceDirNullResource) { const char* dir = nullptr; int ndir = 0; mju_getResourceDir(nullptr, &dir, &ndir); EXPECT_THAT(dir, IsNull()); EXPECT_EQ(ndir, 0); } TEST_F(ResourceTest, GetResourceDirNullName) { const char* dir = nullptr; int ndir = 0; mjResource resource; resource.name = nullptr; resource.provider = nullptr; mju_getResourceDir(nullptr, &dir, &ndir); EXPECT_THAT(dir, IsNull()); EXPECT_EQ(ndir, 0); } TEST_F(ResourceTest, OSFilesystemTimestamps) { time_t t; // some random file const char* const file = "engine/testdata/collision_box/boxbox_deep.xml"; const std::string xml_path = GetTestDataFilePath(file); mjResource* resource = mju_openResource("", xml_path.c_str(), nullptr, nullptr, 0); mju_decodeBase64((uint8_t*) &t, resource->timestamp); // equal timestamps EXPECT_EQ(mju_isModifiedResource(resource, resource->timestamp), 0); std::array test_timestamp; // older resource timestamp t++; mju_encodeBase64(test_timestamp.data(), (uint8_t*) &t, sizeof(time_t)); EXPECT_EQ(mju_isModifiedResource(resource, test_timestamp.data()), -1); // newer resource timestamp t -= 2; mju_encodeBase64(test_timestamp.data(), (uint8_t*) &t, sizeof(time_t)); EXPECT_EQ(mju_isModifiedResource(resource, test_timestamp.data()), 1); mju_closeResource(resource); } } // namespace } // namespace mujoco