Files
Mujoco_WASM/test/user/user_resource_test.cc
T
Haroon Qureshi cb9a9c159c Remove getdir from mjpResourceProvider.
All known implementations were effectively the same as the default fallback. It was just adding unneeded complexity. Removing this now will help with upcoming improvements to resource providers.

PiperOrigin-RevId: 855163273
Change-Id: I529fc18d0d03a8dc676c909ec9e4b1da1454d414
2026-01-12 04:07:42 -08:00

488 lines
12 KiB
C++

// 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 <array>
#include <cstdint>
#include <cstring>
#include <ctime>
#include <string>
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/cc/array_safety.h"
#include <mujoco/mjplugin.h>
#include <mujoco/mujoco.h>
#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<char, 1024> error;
// open resource
mjResource* resource = mju_openResource("", "str:notfound", nullptr,
error.data(), error.size());
ASSERT_THAT(resource, IsNull());
EXPECT_THAT(error.data(), HasSubstr("could not open"));
}
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<std::pair<std::string, std::string>> 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<char*>(name.c_str());
mju_getResourceDir(&resource, &dir, &ndir);
EXPECT_THAT(std::string(dir, ndir), expected);
}
}
TEST_F(ResourceTest, GetResourceDirProvider) {
const std::vector<std::pair<std::string, std::string>> 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<char*>(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<char, 512> 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