Refactor mjVFS and resource management.
Resource operations (e.g. mju_openResource, mju_readResource, and mju_closeResource, etc.) are now all handled by a VFS instance. It is now up to the VFS to determine which provider to use in order to handle those operations. This allows us to dynamically add/remove (aka "mount") providers to a VFS to handle special requests. mj_addFileVFS and mj_addBufferVFS have been reimplemented as two such use-cases. Moreover, we expose the mounting behaviour with two new functions: mj_mountVFS and mj_unmountVFS. PiperOrigin-RevId: 861550939 Change-Id: I070eb4bcc2982466c8f368f7918005538baa5185
This commit is contained in:
committed by
Copybara-Service
parent
e977b0d660
commit
0ddbb46fa1
@@ -301,7 +301,7 @@ TEST_F(ResourceTest, GeneralFailureTest) {
|
||||
error.data(), error.size());
|
||||
ASSERT_THAT(resource, IsNull());
|
||||
|
||||
EXPECT_THAT(error.data(), HasSubstr("could not open"));
|
||||
EXPECT_THAT(error.data(), HasSubstr("Error opening file"));
|
||||
}
|
||||
|
||||
TEST_F(ResourceTest, NameWithValidPrefix) {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using ::testing::IsNull;
|
||||
using ::testing::NotNull;
|
||||
using UserVfsTest = MujocoTest;
|
||||
|
||||
@@ -36,6 +37,46 @@ static bool HasFile(const mjVFS* vfs, const std::string& filename) {
|
||||
return result;
|
||||
}
|
||||
|
||||
struct TestProvider : public mjpResourceProvider {
|
||||
// The TestProvider will increment the value at `addr` by these amounts when
|
||||
// each callback is invoked. This can be used to verity that the correct
|
||||
// callbacks are being invoked when expected.
|
||||
enum CallbackValues {
|
||||
Mounted = 100,
|
||||
Unmounted = 200,
|
||||
Opened = 300,
|
||||
Read = 400,
|
||||
Closed = 500,
|
||||
};
|
||||
|
||||
explicit TestProvider(int* addr) {
|
||||
mjp_defaultResourceProvider(this);
|
||||
prefix = "test";
|
||||
data = addr;
|
||||
|
||||
mount = [](mjResource* res) {
|
||||
*(int*)res->provider->data += Mounted;
|
||||
return 1;
|
||||
};
|
||||
unmount = [](mjResource* res) {
|
||||
*(int*)res->provider->data += Unmounted;
|
||||
return 1;
|
||||
};
|
||||
open = [](mjResource* res) {
|
||||
*(int*)res->provider->data += Opened;
|
||||
return 1;
|
||||
};
|
||||
read = [](mjResource* res, const void** out) {
|
||||
*(int*)res->provider->data += Read;
|
||||
*out = res->provider->data;
|
||||
return (int)sizeof(int);
|
||||
};
|
||||
close = [](mjResource* res) {
|
||||
*(int*)res->provider->data += Closed;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(UserVfsTest, AddFile) {
|
||||
constexpr char path[] = "engine/testdata/actuation/";
|
||||
const std::string dir = GetTestDataFilePath(path);
|
||||
@@ -245,5 +286,126 @@ TEST_F(UserVfsTest, Timestamps) {
|
||||
mj_deleteVFS(&vfs);
|
||||
}
|
||||
|
||||
TEST_F(UserVfsTest, MountUnmount) {
|
||||
int test = 0;
|
||||
int expect = 0;
|
||||
TestProvider provider(&test);
|
||||
|
||||
mjVFS vfs;
|
||||
mj_defaultVFS(&vfs);
|
||||
mjResource* res = mju_openResource("", "/some/path/foo", &vfs, nullptr, 0);
|
||||
EXPECT_THAT(res, IsNull());
|
||||
EXPECT_EQ(test, expect);
|
||||
|
||||
mj_mountVFS(&vfs, "/some/path", &provider);
|
||||
expect += TestProvider::Mounted;
|
||||
EXPECT_EQ(test, expect);
|
||||
|
||||
res = mju_openResource("", "/some/path/foo", &vfs, nullptr, 0);
|
||||
expect += TestProvider::Opened;
|
||||
EXPECT_THAT(res, NotNull());
|
||||
EXPECT_EQ(test, expect);
|
||||
|
||||
const void* buffer = nullptr;
|
||||
const int size = mju_readResource(res, &buffer);
|
||||
expect += TestProvider::Read;
|
||||
EXPECT_THAT(buffer, NotNull());
|
||||
EXPECT_GT(size, 0);
|
||||
EXPECT_EQ(test, expect);
|
||||
|
||||
mju_closeResource(res);
|
||||
expect += TestProvider::Closed;
|
||||
EXPECT_EQ(test, expect);
|
||||
|
||||
mj_unmountVFS(&vfs, "/some/path");
|
||||
expect += TestProvider::Unmounted;
|
||||
EXPECT_EQ(test, expect);
|
||||
|
||||
mj_deleteVFS(&vfs);
|
||||
}
|
||||
|
||||
TEST_F(UserVfsTest, AutoMountProviders) {
|
||||
int test = 0;
|
||||
int expect = 0;
|
||||
|
||||
TestProvider provider(&test);
|
||||
mjp_registerResourceProvider(&provider);
|
||||
|
||||
mjVFS vfs;
|
||||
mj_defaultVFS(&vfs);
|
||||
|
||||
mjResource* res = mju_openResource("", "test:foo", &vfs, nullptr, 0);
|
||||
expect += TestProvider::Mounted + TestProvider::Opened;
|
||||
EXPECT_THAT(res, NotNull());
|
||||
|
||||
mju_closeResource(res);
|
||||
expect += TestProvider::Closed;
|
||||
EXPECT_EQ(test, expect);
|
||||
|
||||
mj_deleteVFS(&vfs);
|
||||
expect += TestProvider::Unmounted;
|
||||
EXPECT_EQ(test, expect);
|
||||
}
|
||||
|
||||
TEST_F(UserVfsTest, StackedMounts) {
|
||||
int test1 = 0;
|
||||
int test2 = 1000;
|
||||
int test3 = 1000000;
|
||||
int expect1 = test1;
|
||||
int expect2 = test2;
|
||||
int expect3 = test3;
|
||||
|
||||
TestProvider provider1(&test1);
|
||||
TestProvider provider2(&test2);
|
||||
TestProvider provider3(&test3);
|
||||
|
||||
mjVFS vfs;
|
||||
mj_defaultVFS(&vfs);
|
||||
|
||||
mj_mountVFS(&vfs, "/some/path", &provider1);
|
||||
mj_mountVFS(&vfs, "/some/path/further/down/very/deep", &provider2);
|
||||
mj_mountVFS(&vfs, "/some/path/further/down", &provider3);
|
||||
expect1 += TestProvider::Mounted;
|
||||
expect2 += TestProvider::Mounted;
|
||||
expect3 += TestProvider::Mounted;
|
||||
EXPECT_EQ(test1, expect1);
|
||||
EXPECT_EQ(test2, expect2);
|
||||
EXPECT_EQ(test3, expect3);
|
||||
|
||||
mjResource* res1 = mju_openResource("", "/some/path/foo", &vfs, nullptr, 0);
|
||||
expect1 += TestProvider::Opened;
|
||||
EXPECT_EQ(test1, expect1);
|
||||
EXPECT_EQ(test2, expect2);
|
||||
EXPECT_EQ(test3, expect3);
|
||||
|
||||
mjResource* res2 =
|
||||
mju_openResource("", "/some/path/further/down/foo", &vfs, nullptr, 0);
|
||||
expect3 += TestProvider::Opened;
|
||||
EXPECT_EQ(test1, expect1);
|
||||
EXPECT_EQ(test2, expect2);
|
||||
EXPECT_EQ(test3, expect3);
|
||||
|
||||
mjResource* res3 = mju_openResource(
|
||||
"", "/some/path/further/down/very/deep/foo", &vfs, nullptr, 0);
|
||||
expect2 += TestProvider::Opened;
|
||||
EXPECT_EQ(test1, expect1);
|
||||
EXPECT_EQ(test2, expect2);
|
||||
EXPECT_EQ(test3, expect3);
|
||||
|
||||
mju_closeResource(res1);
|
||||
mju_closeResource(res2);
|
||||
mju_closeResource(res3);
|
||||
expect1 += TestProvider::Closed;
|
||||
expect2 += TestProvider::Closed;
|
||||
expect3 += TestProvider::Closed;
|
||||
|
||||
mj_deleteVFS(&vfs);
|
||||
expect1 += TestProvider::Unmounted;
|
||||
expect2 += TestProvider::Unmounted;
|
||||
expect3 += TestProvider::Unmounted;
|
||||
EXPECT_EQ(test1, expect1);
|
||||
EXPECT_EQ(test2, expect2);
|
||||
EXPECT_EQ(test3, expect3);
|
||||
}
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
|
||||
@@ -126,7 +126,7 @@ TEST_F(LoadXmlTest, InvalidFileFails) {
|
||||
std::array<char, 1000> error;
|
||||
mjSpec* spec = mj_parseXML("invalid", nullptr, error.data(), error.size());
|
||||
EXPECT_THAT(spec, IsNull()) << "Expected model loading to fail.";
|
||||
EXPECT_THAT(error.data(), HasSubstr("No such file or directory"));
|
||||
EXPECT_THAT(error.data(), HasSubstr("Error opening file"));
|
||||
}
|
||||
|
||||
TEST_F(MujocoTest, SaveXmlShortString) {
|
||||
|
||||
Reference in New Issue
Block a user