#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace XCEngine::Resources; namespace { class BlockingMeshLoader : public IResourceLoader { public: ResourceType GetResourceType() const override { return ResourceType::Mesh; } XCEngine::Containers::Array GetSupportedExtensions() const override { XCEngine::Containers::Array extensions; extensions.PushBack("mesh"); return extensions; } bool CanLoad(const XCEngine::Containers::String& path) const override { (void)path; return true; } LoadResult Load(const XCEngine::Containers::String& path, const ImportSettings* settings = nullptr) override { (void)settings; ++m_loadCalls; { std::lock_guard lock(m_mutex); m_started = true; } m_condition.notify_all(); { std::unique_lock lock(m_mutex); m_condition.wait(lock, [this]() { return m_allowCompletion; }); } auto* mesh = new Mesh(); IResource::ConstructParams params = {}; params.name = "BlockingMesh"; params.path = path; params.guid = ResourceGUID::Generate(path); mesh->Initialize(params); const StaticMeshVertex vertices[3] = {}; const XCEngine::Core::uint32 indices[3] = {0, 1, 2}; mesh->SetVertexData(vertices, sizeof(vertices), 3, sizeof(StaticMeshVertex), VertexAttribute::Position); mesh->SetIndexData(indices, sizeof(indices), 3, true); return LoadResult(mesh); } ImportSettings* GetDefaultSettings() const override { return nullptr; } bool WaitForStart(std::chrono::milliseconds timeout) { std::unique_lock lock(m_mutex); return m_condition.wait_for(lock, timeout, [this]() { return m_started; }); } void AllowCompletion() { { std::lock_guard lock(m_mutex); m_allowCompletion = true; } m_condition.notify_all(); } int GetLoadCalls() const { return m_loadCalls.load(); } private: std::atomic m_loadCalls{0}; mutable std::mutex m_mutex; std::condition_variable m_condition; bool m_started = false; bool m_allowCompletion = false; }; bool PumpAsyncLoadsUntil(ResourceManager& manager, const std::function& condition, std::chrono::milliseconds timeout = std::chrono::milliseconds(2000)) { const auto deadline = std::chrono::steady_clock::now() + timeout; while (!condition() && std::chrono::steady_clock::now() < deadline) { manager.UpdateAsyncLoads(); std::this_thread::sleep_for(std::chrono::milliseconds(5)); } manager.UpdateAsyncLoads(); return condition(); } bool DirectoryHasEntries(const std::filesystem::path& directoryPath) { std::error_code ec; if (!std::filesystem::exists(directoryPath, ec) || !std::filesystem::is_directory(directoryPath, ec)) { return false; } return std::filesystem::directory_iterator(directoryPath) != std::filesystem::directory_iterator(); } std::vector ListArtifactEntries(const std::filesystem::path& artifactsRoot) { namespace fs = std::filesystem; std::vector entries; std::error_code ec; if (!fs::exists(artifactsRoot, ec) || !fs::is_directory(artifactsRoot, ec)) { return entries; } for (const auto& shardEntry : fs::directory_iterator(artifactsRoot, ec)) { if (ec) { break; } if (!shardEntry.is_directory()) { entries.push_back(shardEntry.path()); continue; } for (const auto& artifactEntry : fs::directory_iterator(shardEntry.path(), ec)) { if (ec) { break; } entries.push_back(artifactEntry.path()); } } std::sort(entries.begin(), entries.end()); return entries; } TEST(ResourceManager_Test, ConcurrentAsyncLoadsCoalesceSameMeshPath) { ResourceManager& manager = ResourceManager::Get(); manager.Initialize(); struct LoaderGuard { ResourceManager* manager = nullptr; IResourceLoader* loader = nullptr; ~LoaderGuard() { if (manager != nullptr && loader != nullptr) { manager->RegisterLoader(loader); } } } loaderGuard{ &manager, manager.GetLoader(ResourceType::Mesh) }; BlockingMeshLoader blockingLoader; manager.RegisterLoader(&blockingLoader); std::mutex resultsMutex; std::vector callbackResources; std::atomic callbackCount{0}; const auto callback = [&](LoadResult result) { EXPECT_TRUE(result); EXPECT_NE(result.resource, nullptr); { std::lock_guard lock(resultsMutex); callbackResources.push_back(result.resource); } ++callbackCount; }; manager.LoadAsync("Meshes/concurrent.mesh", ResourceType::Mesh, callback); manager.LoadAsync("Meshes/concurrent.mesh", ResourceType::Mesh, callback); ASSERT_TRUE(blockingLoader.WaitForStart(std::chrono::milliseconds(1000))); EXPECT_EQ(blockingLoader.GetLoadCalls(), 1); blockingLoader.AllowCompletion(); ASSERT_TRUE(PumpAsyncLoadsUntil( manager, [&]() { return callbackCount.load() == 2 && manager.GetAsyncPendingCount() == 0; }, std::chrono::milliseconds(2000))); EXPECT_EQ(blockingLoader.GetLoadCalls(), 1); ASSERT_EQ(callbackResources.size(), 2u); EXPECT_EQ(callbackResources[0], callbackResources[1]); manager.Shutdown(); } TEST(ResourceManager_Test, AssetLookupFallbackRefreshesSnapshotForNewProjectAsset) { namespace fs = std::filesystem; ResourceManager& manager = ResourceManager::Get(); manager.Initialize(); const fs::path projectRoot = fs::temp_directory_path() / "xc_resource_manager_asset_lookup_test"; const fs::path assetsDir = projectRoot / "Assets"; const fs::path materialPath = assetsDir / "runtime.material"; fs::remove_all(projectRoot); fs::create_directories(assetsDir); manager.SetResourceRoot(projectRoot.string().c_str()); { std::ofstream materialFile(materialPath); ASSERT_TRUE(materialFile.is_open()); materialFile << "{\n"; materialFile << " \"renderQueue\": \"geometry\"\n"; materialFile << "}\n"; } AssetRef assetRef; EXPECT_TRUE(manager.TryGetAssetRef("Assets/runtime.material", ResourceType::Material, assetRef)); EXPECT_TRUE(assetRef.IsValid()); XCEngine::Containers::String resolvedPath; EXPECT_TRUE(manager.TryResolveAssetPath(assetRef, resolvedPath)); EXPECT_EQ(std::string(resolvedPath.CStr()), "Assets/runtime.material"); manager.SetResourceRoot(""); manager.Shutdown(); fs::remove_all(projectRoot); } TEST(ProjectAssetIndex_Test, RefreshesSnapshotThroughImportServiceOnCacheMiss) { namespace fs = std::filesystem; AssetImportService importService; importService.Initialize(); const fs::path projectRoot = fs::temp_directory_path() / "xc_project_asset_index_refresh_test"; const fs::path assetsDir = projectRoot / "Assets"; const fs::path materialPath = assetsDir / "runtime.material"; fs::remove_all(projectRoot); fs::create_directories(assetsDir); importService.SetProjectRoot(projectRoot.string().c_str()); ProjectAssetIndex assetIndex; assetIndex.RefreshFrom(importService); { std::ofstream materialFile(materialPath); ASSERT_TRUE(materialFile.is_open()); materialFile << "{\n"; materialFile << " \"renderQueue\": \"geometry\"\n"; materialFile << "}\n"; } AssetRef assetRef; EXPECT_TRUE(assetIndex.TryGetAssetRef(importService, "Assets/runtime.material", ResourceType::Material, assetRef)); EXPECT_TRUE(assetRef.IsValid()); XCEngine::Containers::String resolvedPath; EXPECT_TRUE(assetIndex.TryResolveAssetPath(importService, assetRef, resolvedPath)); EXPECT_EQ(std::string(resolvedPath.CStr()), "Assets/runtime.material"); importService.Shutdown(); fs::remove_all(projectRoot); } TEST(AssetImportService_Test, BootstrapProjectBuildsLookupSnapshotAndReportsStatus) { namespace fs = std::filesystem; AssetImportService importService; importService.Initialize(); const fs::path projectRoot = fs::temp_directory_path() / "xc_asset_import_service_bootstrap_test"; const fs::path assetsDir = projectRoot / "Assets"; const fs::path materialPath = assetsDir / "runtime.material"; fs::remove_all(projectRoot); fs::create_directories(assetsDir); { std::ofstream materialFile(materialPath); ASSERT_TRUE(materialFile.is_open()); materialFile << "{\n"; materialFile << " \"renderQueue\": \"geometry\"\n"; materialFile << "}\n"; } importService.SetProjectRoot(projectRoot.string().c_str()); EXPECT_TRUE(importService.BootstrapProject()); AssetImportService::LookupSnapshot snapshot; importService.BuildLookupSnapshot(snapshot); EXPECT_GE(snapshot.assetGuidByPathKey.size(), 2u); EXPECT_GE(snapshot.assetPathByGuid.size(), 2u); EXPECT_NE(snapshot.assetGuidByPathKey.find("assets/runtime.material"), snapshot.assetGuidByPathKey.end()); AssetRef assetRef; EXPECT_TRUE(importService.TryGetAssetRef("Assets/runtime.material", ResourceType::Material, assetRef)); EXPECT_TRUE(assetRef.IsValid()); const auto snapshotPathIt = snapshot.assetPathByGuid.find(assetRef.assetGuid); ASSERT_NE(snapshotPathIt, snapshot.assetPathByGuid.end()); EXPECT_EQ(std::string(snapshotPathIt->second.CStr()), "Assets/runtime.material"); const AssetImportService::ImportStatusSnapshot status = importService.GetLastImportStatus(); EXPECT_TRUE(status.HasValue()); EXPECT_FALSE(status.inProgress); EXPECT_TRUE(status.success); EXPECT_EQ(std::string(status.operation.CStr()), "Bootstrap Project"); EXPECT_EQ(std::string(status.targetPath.CStr()), projectRoot.string()); EXPECT_GT(status.startedAtMs, 0u); EXPECT_GE(status.completedAtMs, status.startedAtMs); EXPECT_EQ(status.durationMs, status.completedAtMs - status.startedAtMs); importService.Shutdown(); fs::remove_all(projectRoot); } TEST(AssetImportService_Test, RebuildLibraryCacheKeepsStableAssetRefs) { namespace fs = std::filesystem; AssetImportService importService; importService.Initialize(); const fs::path projectRoot = fs::temp_directory_path() / "xc_asset_import_service_rebuild_test"; const fs::path assetsDir = projectRoot / "Assets"; const fs::path materialPath = assetsDir / "runtime.material"; fs::remove_all(projectRoot); fs::create_directories(assetsDir); { std::ofstream materialFile(materialPath); ASSERT_TRUE(materialFile.is_open()); materialFile << "{\n"; materialFile << " \"renderQueue\": \"geometry\"\n"; materialFile << "}\n"; } importService.SetProjectRoot(projectRoot.string().c_str()); ASSERT_TRUE(importService.BootstrapProject()); AssetRef firstAssetRef; ASSERT_TRUE(importService.TryGetAssetRef("Assets/runtime.material", ResourceType::Material, firstAssetRef)); ASSERT_TRUE(firstAssetRef.IsValid()); const fs::path libraryRoot(importService.GetLibraryRoot().CStr()); EXPECT_TRUE(fs::exists(libraryRoot / "assets.db")); EXPECT_TRUE(fs::exists(libraryRoot / "artifacts.db")); EXPECT_TRUE(importService.RebuildLibraryCache()); EXPECT_TRUE(fs::exists(libraryRoot / "assets.db")); EXPECT_TRUE(fs::exists(libraryRoot / "artifacts.db")); AssetRef secondAssetRef; ASSERT_TRUE(importService.TryGetAssetRef("Assets/runtime.material", ResourceType::Material, secondAssetRef)); ASSERT_TRUE(secondAssetRef.IsValid()); EXPECT_EQ(firstAssetRef.assetGuid, secondAssetRef.assetGuid); EXPECT_EQ(firstAssetRef.localID, secondAssetRef.localID); EXPECT_EQ(firstAssetRef.resourceType, secondAssetRef.resourceType); importService.Shutdown(); fs::remove_all(projectRoot); } TEST(AssetImportService_Test, EnsureArtifactExposesContainerEntryRuntimeLoadPath) { namespace fs = std::filesystem; AssetImportService importService; importService.Initialize(); const fs::path projectRoot = fs::temp_directory_path() / "xc_asset_import_service_entry_runtime_path_test"; const fs::path assetsDir = projectRoot / "Assets"; const fs::path materialPath = assetsDir / "runtime.material"; fs::remove_all(projectRoot); fs::create_directories(assetsDir); { std::ofstream materialFile(materialPath); ASSERT_TRUE(materialFile.is_open()); materialFile << "{\n"; materialFile << " \"renderQueue\": \"geometry\"\n"; materialFile << "}\n"; } importService.SetProjectRoot(projectRoot.string().c_str()); AssetImportService::ImportedAsset importedAsset; ASSERT_TRUE(importService.EnsureArtifact("Assets/runtime.material", ResourceType::Material, importedAsset)); ASSERT_TRUE(importedAsset.exists); ASSERT_TRUE(importedAsset.artifactReady); EXPECT_EQ(importedAsset.artifactStorageKind, ArtifactStorageKind::SingleFileContainer); EXPECT_EQ(importedAsset.mainEntryName, "main"); EXPECT_FALSE(importedAsset.artifactMainPath.Empty()); EXPECT_FALSE(importedAsset.artifactMainEntryPath.Empty()); EXPECT_EQ(importedAsset.runtimeLoadPath, importedAsset.artifactMainEntryPath); EXPECT_NE(importedAsset.artifactMainEntryPath, importedAsset.artifactMainPath); EXPECT_NE(std::string(importedAsset.runtimeLoadPath.CStr()).find("@entry=main"), std::string::npos); { const fs::path artifactDbPath = fs::path(importService.GetLibraryRoot().CStr()) / "artifacts.db"; std::ifstream artifactDbInput(artifactDbPath, std::ios::binary); ASSERT_TRUE(artifactDbInput.is_open()); std::stringstream artifactDbBuffer; artifactDbBuffer << artifactDbInput.rdbuf(); const std::string artifactDbText = artifactDbBuffer.str(); EXPECT_NE(artifactDbText.find("# schema=2"), std::string::npos); EXPECT_NE(artifactDbText.find("storageKind\tmainEntryName"), std::string::npos); } MaterialLoader loader; LoadResult loadResult = loader.Load(importedAsset.runtimeLoadPath); ASSERT_TRUE(loadResult); ASSERT_NE(loadResult.resource, nullptr); delete loadResult.resource; importService.Shutdown(); fs::remove_all(projectRoot); } TEST(ResourceManager_Test, RebuildProjectAssetCacheRefreshesLookupState) { namespace fs = std::filesystem; ResourceManager& manager = ResourceManager::Get(); manager.Initialize(); const fs::path projectRoot = fs::temp_directory_path() / "xc_resource_manager_rebuild_cache_test"; const fs::path assetsDir = projectRoot / "Assets"; const fs::path materialPath = assetsDir / "runtime.material"; fs::remove_all(projectRoot); fs::create_directories(assetsDir); { std::ofstream materialFile(materialPath); ASSERT_TRUE(materialFile.is_open()); materialFile << "{\n"; materialFile << " \"renderQueue\": \"geometry\"\n"; materialFile << "}\n"; } manager.SetResourceRoot(projectRoot.string().c_str()); AssetRef firstAssetRef; ASSERT_TRUE(manager.TryGetAssetRef("Assets/runtime.material", ResourceType::Material, firstAssetRef)); ASSERT_TRUE(firstAssetRef.IsValid()); const fs::path libraryRoot(manager.GetProjectLibraryRoot().CStr()); EXPECT_TRUE(fs::exists(libraryRoot / "assets.db")); EXPECT_TRUE(manager.RebuildProjectAssetCache()); EXPECT_TRUE(fs::exists(libraryRoot / "assets.db")); AssetRef secondAssetRef; ASSERT_TRUE(manager.TryGetAssetRef("Assets/runtime.material", ResourceType::Material, secondAssetRef)); ASSERT_TRUE(secondAssetRef.IsValid()); EXPECT_EQ(firstAssetRef.assetGuid, secondAssetRef.assetGuid); manager.SetResourceRoot(""); manager.Shutdown(); fs::remove_all(projectRoot); } TEST(ResourceManager_Test, AssetImportServiceMigratesLegacyLibraryDatabasePathsToLibraryRoot) { namespace fs = std::filesystem; AssetImportService importService; importService.Initialize(); const fs::path projectRoot = fs::temp_directory_path() / "xc_library_db_path_migration_test"; const fs::path assetsDir = projectRoot / "Assets"; const fs::path legacySourceDbPath = projectRoot / "Library" / "SourceAssetDB" / "assets.db"; const fs::path legacyArtifactDbPath = projectRoot / "Library" / "ArtifactDB" / "artifacts.db"; fs::remove_all(projectRoot); fs::create_directories(assetsDir); fs::create_directories(legacySourceDbPath.parent_path()); fs::create_directories(legacyArtifactDbPath.parent_path()); { std::ofstream sourceDbFile(legacySourceDbPath); ASSERT_TRUE(sourceDbFile.is_open()); sourceDbFile << "# legacy source database\n"; } { std::ofstream artifactDbFile(legacyArtifactDbPath); ASSERT_TRUE(artifactDbFile.is_open()); artifactDbFile << "# legacy artifact database\n"; } importService.SetProjectRoot(projectRoot.string().c_str()); const fs::path libraryRoot(importService.GetLibraryRoot().CStr()); EXPECT_TRUE(fs::exists(libraryRoot / "assets.db")); EXPECT_TRUE(fs::exists(libraryRoot / "artifacts.db")); EXPECT_FALSE(fs::exists(legacySourceDbPath)); EXPECT_FALSE(fs::exists(legacyArtifactDbPath)); importService.Shutdown(); fs::remove_all(projectRoot); } TEST(ResourceManager_Test, SetResourceRootBootstrapsProjectAssetCache) { namespace fs = std::filesystem; ResourceManager& manager = ResourceManager::Get(); manager.Initialize(); const fs::path projectRoot = fs::temp_directory_path() / "xc_resource_manager_bootstrap_test"; const fs::path assetsDir = projectRoot / "Assets"; const fs::path materialPath = assetsDir / "runtime.material"; fs::remove_all(projectRoot); fs::create_directories(assetsDir); { std::ofstream materialFile(materialPath); ASSERT_TRUE(materialFile.is_open()); materialFile << "{\n"; materialFile << " \"renderQueue\": \"geometry\"\n"; materialFile << "}\n"; } manager.SetResourceRoot(projectRoot.string().c_str()); AssetRef assetRef; EXPECT_TRUE(manager.TryGetAssetRef("Assets/runtime.material", ResourceType::Material, assetRef)); EXPECT_TRUE(assetRef.IsValid()); const AssetImportService::ImportStatusSnapshot status = manager.GetProjectAssetImportStatus(); EXPECT_TRUE(status.HasValue()); EXPECT_FALSE(status.inProgress); EXPECT_TRUE(status.success); EXPECT_EQ(std::string(status.operation.CStr()), "Bootstrap Project"); EXPECT_GT(status.startedAtMs, 0u); EXPECT_GE(status.completedAtMs, status.startedAtMs); EXPECT_EQ(status.durationMs, status.completedAtMs - status.startedAtMs); manager.SetResourceRoot(""); manager.Shutdown(); fs::remove_all(projectRoot); } TEST(AssetImportService_Test, ClearLibraryAndReimportAllAssetsManageArtifactsExplicitly) { namespace fs = std::filesystem; AssetImportService importService; importService.Initialize(); const fs::path projectRoot = fs::temp_directory_path() / "xc_asset_import_service_tooling_test"; const fs::path assetsDir = projectRoot / "Assets"; const fs::path materialPath = assetsDir / "runtime.material"; fs::remove_all(projectRoot); fs::create_directories(assetsDir); { std::ofstream materialFile(materialPath); ASSERT_TRUE(materialFile.is_open()); materialFile << "{\n"; materialFile << " \"renderQueue\": \"geometry\"\n"; materialFile << "}\n"; } importService.SetProjectRoot(projectRoot.string().c_str()); ResourceType importType = ResourceType::Unknown; EXPECT_TRUE(importService.TryGetImportableResourceType("Assets/runtime.material", importType)); EXPECT_EQ(importType, ResourceType::Material); const fs::path libraryRoot(importService.GetLibraryRoot().CStr()); EXPECT_TRUE(importService.ReimportAllAssets()); EXPECT_TRUE(DirectoryHasEntries(libraryRoot / "Artifacts")); EXPECT_TRUE(importService.ClearLibraryCache()); EXPECT_FALSE(DirectoryHasEntries(libraryRoot / "Artifacts")); EXPECT_TRUE(importService.ReimportAllAssets()); EXPECT_TRUE(DirectoryHasEntries(libraryRoot / "Artifacts")); importService.Shutdown(); fs::remove_all(projectRoot); } TEST(AssetImportService_Test, OnlyShaderAuthoringFilesAreImportableShaderSources) { namespace fs = std::filesystem; AssetImportService importService; importService.Initialize(); const fs::path projectRoot = fs::temp_directory_path() / "xc_asset_import_service_shader_source_scope"; const fs::path assetsDir = projectRoot / "Assets"; const fs::path shaderPath = assetsDir / "runtime.shader"; const fs::path includePath = assetsDir / "shared.hlsl"; fs::remove_all(projectRoot); fs::create_directories(assetsDir); { std::ofstream shaderFile(shaderPath); ASSERT_TRUE(shaderFile.is_open()); shaderFile << "Shader \"Test/Runtime\" { SubShader { Pass { HLSLPROGRAM #pragma vertex MainVS #pragma fragment MainPS float4 MainVS() : SV_POSITION { return 0; } float4 MainPS() : SV_TARGET { return 1; } ENDHLSL } } }"; } { std::ofstream includeFile(includePath); ASSERT_TRUE(includeFile.is_open()); includeFile << "float4 SharedHelper() { return 1; }"; } importService.SetProjectRoot(projectRoot.string().c_str()); ResourceType shaderType = ResourceType::Unknown; EXPECT_TRUE(importService.TryGetImportableResourceType("Assets/runtime.shader", shaderType)); EXPECT_EQ(shaderType, ResourceType::Shader); ResourceType includeType = ResourceType::Unknown; EXPECT_FALSE(importService.TryGetImportableResourceType("Assets/shared.hlsl", includeType)); EXPECT_EQ(includeType, ResourceType::Unknown); importService.Shutdown(); fs::remove_all(projectRoot); } TEST(AssetImportService_Test, ImportStatusTracksExplicitOperationsAndRefreshCleanup) { namespace fs = std::filesystem; AssetImportService importService; importService.Initialize(); const fs::path projectRoot = fs::temp_directory_path() / "xc_asset_import_service_status_test"; const fs::path assetsDir = projectRoot / "Assets"; const fs::path materialPath = assetsDir / "runtime.material"; fs::remove_all(projectRoot); fs::create_directories(assetsDir); { std::ofstream materialFile(materialPath); ASSERT_TRUE(materialFile.is_open()); materialFile << "{\n"; materialFile << " \"renderQueue\": \"geometry\"\n"; materialFile << "}\n"; } importService.SetProjectRoot(projectRoot.string().c_str()); EXPECT_FALSE(importService.GetLastImportStatus().HasValue()); EXPECT_TRUE(importService.ReimportAllAssets()); const AssetImportService::ImportStatusSnapshot reimportStatus = importService.GetLastImportStatus(); EXPECT_TRUE(reimportStatus.HasValue()); EXPECT_FALSE(reimportStatus.inProgress); EXPECT_TRUE(reimportStatus.success); EXPECT_EQ(std::string(reimportStatus.operation.CStr()), "Reimport All Assets"); EXPECT_EQ(reimportStatus.importedAssetCount, 1u); EXPECT_GT(reimportStatus.startedAtMs, 0u); EXPECT_GE(reimportStatus.completedAtMs, reimportStatus.startedAtMs); EXPECT_EQ(reimportStatus.durationMs, reimportStatus.completedAtMs - reimportStatus.startedAtMs); const fs::path libraryRoot(importService.GetLibraryRoot().CStr()); const std::vector artifactEntries = ListArtifactEntries(libraryRoot / "Artifacts"); ASSERT_EQ(artifactEntries.size(), 1u); EXPECT_TRUE(fs::exists(artifactEntries.front())); std::error_code ec; fs::remove(materialPath, ec); ec.clear(); fs::remove(fs::path(materialPath.string() + ".meta"), ec); importService.Refresh(); const AssetImportService::ImportStatusSnapshot refreshStatus = importService.GetLastImportStatus(); EXPECT_TRUE(refreshStatus.HasValue()); EXPECT_TRUE(refreshStatus.success); EXPECT_EQ(std::string(refreshStatus.operation.CStr()), "Refresh"); EXPECT_GE(refreshStatus.removedArtifactCount, 1u); EXPECT_GT(refreshStatus.startedAtMs, 0u); EXPECT_GE(refreshStatus.completedAtMs, refreshStatus.startedAtMs); EXPECT_EQ(refreshStatus.durationMs, refreshStatus.completedAtMs - refreshStatus.startedAtMs); EXPECT_FALSE(fs::exists(artifactEntries.front())); importService.Shutdown(); fs::remove_all(projectRoot); } TEST(ResourceManager_Test, ReimportProjectAssetBuildsArtifactForSelectedPath) { namespace fs = std::filesystem; ResourceManager& manager = ResourceManager::Get(); manager.Initialize(); const fs::path projectRoot = fs::temp_directory_path() / "xc_resource_manager_reimport_asset_test"; const fs::path assetsDir = projectRoot / "Assets"; const fs::path materialPath = assetsDir / "runtime.material"; fs::remove_all(projectRoot); fs::create_directories(assetsDir); { std::ofstream materialFile(materialPath); ASSERT_TRUE(materialFile.is_open()); materialFile << "{\n"; materialFile << " \"renderQueue\": \"geometry\"\n"; materialFile << "}\n"; } manager.SetResourceRoot(projectRoot.string().c_str()); EXPECT_TRUE(manager.CanReimportProjectAsset("Assets/runtime.material")); const fs::path libraryRoot(manager.GetProjectLibraryRoot().CStr()); EXPECT_TRUE(manager.ReimportProjectAsset("Assets/runtime.material")); EXPECT_TRUE(DirectoryHasEntries(libraryRoot / "Artifacts")); manager.SetResourceRoot(""); manager.Shutdown(); fs::remove_all(projectRoot); } } // namespace