feat(tests): added parenting tests

This commit is contained in:
maelstrom 2025-12-15 13:31:10 +01:00
parent 47ad44bb83
commit e6a1e6f57b
6 changed files with 138 additions and 16 deletions

View file

@ -7,6 +7,11 @@ class NoSuchInstance : public Error {
inline NoSuchInstance(std::string className) : Error("NoSuchInstance", "Cannot create instance of unknown type " + className) {}
};
class NotCreatableInstance : public Error {
public:
inline NotCreatableInstance(std::string className) : Error("NotCreatableInstance", "Instance class " + className + " is not creatable") {}
};
class NoSuchService : public Error {
public:
inline NoSuchService(std::string className) : Error("NoSuchService", "Unknown service type " + className) {}

View file

@ -524,4 +524,17 @@ std::string Instance::GetFullName() {
}
return currentName;
}
result<std::shared_ptr<Instance>, NoSuchInstance, NotCreatableInstance> Instance::New(std::string className) {
const InstanceType* type = INSTANCE_MAP[className];
if (type == nullptr) {
return NoSuchInstance(className);
}
if (type->flags & (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE) || type->constructor == nullptr)
return NotCreatableInstance(className);
return type->constructor();
}

View file

@ -114,6 +114,9 @@ public:
inline void AddChild(std::shared_ptr<Instance> object) { object->SetParent(this->shared_from_this()); }
nullable std::shared_ptr<Instance> FindFirstChild(std::string);
std::string GetFullName();
// Dynamically create an instance
static result<std::shared_ptr<Instance>, NoSuchInstance, NotCreatableInstance> New(std::string className);
// Properties
result<Variant, MemberNotFound> GetProperty(std::string name);

View file

@ -6,6 +6,7 @@ add_executable(obtest
src/lua/luasched.cpp
src/lua/luasignal.cpp
src/lua/luageneric.cpp
src/objectmodel/basic.cpp
)
target_link_libraries(obtest PRIVATE openblocks Catch2::Catch2WithMain)
target_include_directories(obtest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)

View file

@ -18,9 +18,6 @@ public:
void testRunStarting(Catch::TestRunInfo const&) override {
// TODO: Make physicsInit optional in headless environments
physicsInit();
gTestModel = DataModel::New();
gTestModel->Init(true);
Logger::initTest(&testLogOutput);
}
@ -33,21 +30,12 @@ public:
void testCasePartialStarting(const Catch::TestCaseInfo &testInfo, uint64_t partNumber) override {
// Clear the log output prior to each test
testLogOutput.str("");
gTestModel = DataModel::New();
gTestModel->Init(true);
}
void testCasePartialEnded(const Catch::TestCaseStats &testCaseStats, uint64_t partNumber) override {
auto ctx = gTestModel->GetService<ScriptContext>();
ctx->DebugClearSleepingThreads();
// Clean up remaining scripts from ServerScriptService
for (auto& obj : gTestModel->GetService<ServerScriptService>()->GetChildren()) {
obj->Destroy();
}
// Also clear workspace
for (auto& obj : gTestModel->GetService<Workspace>()->GetChildren()) {
obj->Destroy();
}
void testCasePartialEnded(const Catch::TestCaseStats &testCaseStats, uint64_t partNumber) override {
}
};

View file

@ -0,0 +1,112 @@
// Basic operations such as instantiation, re-parenting, and destruction
#include "common.h"
#include "error/instance.h"
#include "objects/model.h"
#include "objects/part/part.h"
#include <catch2/catch_test_macros.hpp>
static auto& m = gDataModel;
TEST_CASE("Construction") {
auto folder = Model::New();
m->AddChild(folder);
SECTION("Constructing container") {
bool found = false;
for (auto& obj : m->GetChildren()) {
if (obj == folder) {
found = true;
}
}
REQUIRE(found);
REQUIRE(folder->GetParent() != nullptr);
REQUIRE(folder->GetParent() == m);
}
SECTION("Constructing Part") {
auto part = Part::New();
folder->AddChild(part);
REQUIRE(folder->GetChildren().size() == 1);
REQUIRE(folder->GetChildren()[0] == part);
REQUIRE(part->GetParent() != nullptr);
REQUIRE(part->GetParent() == folder);
}
SECTION("Dynamic construction of part") {
auto result = Instance::New("Part");
REQUIRE(result.isSuccess());
}
SECTION("Invalid construction of service") {
auto result = Instance::New("Workspace");
REQUIRE(result.isError());
REQUIRE(std::holds_alternative<NotCreatableInstance>(result.error().value()));
}
SECTION("Invalid construction of nonexistent type") {
auto result = Instance::New("__INVALID");
REQUIRE(result.isError());
REQUIRE(std::holds_alternative<NoSuchInstance>(result.error().value()));
}
}
TEST_CASE("Parenting") {
auto folder = Model::New();
m->AddChild(folder);
SECTION("Reparent part to another folder") {
auto folder2 = Model::New();
m->AddChild(folder2);
auto part = Part::New();
folder->AddChild(part);
// Verify initial folder
REQUIRE(folder->GetChildren().size() == 1);
REQUIRE(folder->GetChildren()[0] == part);
REQUIRE(part->GetParent() != nullptr);
REQUIRE(part->GetParent() == folder);
folder2->AddChild(part); // AddChild just internally calls SetParent, so it should automatically take care of cleanup
// Verify new folder
REQUIRE(folder->GetChildren().size() == 0);
REQUIRE(folder2->GetChildren().size() == 1);
REQUIRE(folder2->GetChildren()[0] == part);
REQUIRE(part->GetParent() != nullptr);
REQUIRE(part->GetParent() == folder2);
}
SECTION("Unparenting") {
auto part = Part::New();
folder->AddChild(part);
part->SetParent(nullptr);
REQUIRE(folder->GetChildren().size() == 0);
REQUIRE(part->GetParent() == nullptr);
}
SECTION("Nested reparent") {
auto folder2 = Model::New();
m->AddChild(folder2);
auto part = Part::New();
folder->AddChild(part);
auto part2 = Part::New();
part->AddChild(part2);
REQUIRE(part->GetChildren().size() == 1);
REQUIRE(part->GetChildren()[0] == part2);
REQUIRE(part2->GetParent() == part);
folder2->AddChild(part); // AddChild just internally calls SetParent, so it should automatically take care of cleanup
// Make sure nothing changed
REQUIRE(part->GetChildren().size() == 1);
REQUIRE(part->GetChildren()[0] == part2);
REQUIRE(part2->GetParent() == part);
}
}