#include "ShaderCompiler.h" #include #include #include #include #include namespace { int gFailures = 0; void Expect(bool condition, const std::string& message) { if (condition) return; ++gFailures; std::cerr << "FAIL: " << message << "\n"; } std::filesystem::path MakeTestRoot(const std::string& name) { const std::filesystem::path root = std::filesystem::temp_directory_path() / ("shader-compiler-lookup-" + name + "-" + std::to_string(std::filesystem::file_time_type::clock::now().time_since_epoch().count())); std::filesystem::create_directories(root); return root; } void WriteFakeSlangCompiler(const std::filesystem::path& path) { std::filesystem::create_directories(path.parent_path()); std::ofstream output(path, std::ios::binary); output << "not a real compiler"; } std::string ReadEnvironmentVariable(const char* name) { #if defined(_MSC_VER) char* value = nullptr; std::size_t size = 0; if (_dupenv_s(&value, &size, name) != 0 || value == nullptr) return std::string(); std::string text(value); std::free(value); return text; #else const char* value = std::getenv(name); return value ? std::string(value) : std::string(); #endif } void WriteEnvironmentVariable(const char* name, const std::string& value) { #if defined(_MSC_VER) _putenv_s(name, value.c_str()); #else if (value.empty()) unsetenv(name); else setenv(name, value.c_str(), 1); #endif } class ScopedEnvironmentVariable { public: ScopedEnvironmentVariable(const char* name, const std::string& value) : mName(name), mOriginal(ReadEnvironmentVariable(name)) { WriteEnvironmentVariable(name, value); } ~ScopedEnvironmentVariable() { WriteEnvironmentVariable(mName.c_str(), mOriginal); } ScopedEnvironmentVariable(const ScopedEnvironmentVariable&) = delete; ScopedEnvironmentVariable& operator=(const ScopedEnvironmentVariable&) = delete; private: std::string mName; std::string mOriginal; }; bool SamePath(const std::filesystem::path& left, const std::filesystem::path& right) { std::error_code leftError; std::error_code rightError; return std::filesystem::weakly_canonical(left, leftError) == std::filesystem::weakly_canonical(right, rightError); } void TestSlangRootWinsOverThirdPartyRoot() { const std::filesystem::path root = MakeTestRoot("slang-root"); const std::filesystem::path expected = root / "env-slang" / "bin" / "slangc.exe"; const std::filesystem::path thirdPartyCompiler = root / "third-party" / "slang-2026.8-windows-x86_64" / "bin" / "slangc.exe"; WriteFakeSlangCompiler(expected); WriteFakeSlangCompiler(thirdPartyCompiler); ScopedEnvironmentVariable slangRoot("SLANG_ROOT", (root / "env-slang").string()); ScopedEnvironmentVariable thirdPartyRoot("THIRD_PARTY_ROOT", (root / "third-party").string()); std::filesystem::path actual; std::string error; Expect(ShaderCompiler::FindSlangCompilerPath(root, actual, error), "SLANG_ROOT compiler is found"); Expect(SamePath(actual, expected), "SLANG_ROOT has priority over THIRD_PARTY_ROOT"); std::filesystem::remove_all(root); } void TestThirdPartyRootUsesCMakeBundleLayout() { const std::filesystem::path root = MakeTestRoot("third-party-root"); const std::filesystem::path thirdPartyRootPath = root / "video-io-3rdParty"; const std::filesystem::path expected = thirdPartyRootPath / "slang-2026.8-windows-x86_64" / "bin" / "slangc.exe"; WriteFakeSlangCompiler(expected); ScopedEnvironmentVariable slangRoot("SLANG_ROOT", ""); ScopedEnvironmentVariable thirdPartyRoot("THIRD_PARTY_ROOT", thirdPartyRootPath.string()); std::filesystem::path actual; std::string error; Expect(ShaderCompiler::FindSlangCompilerPath(root, actual, error), "THIRD_PARTY_ROOT compiler is found"); Expect(SamePath(actual, expected), "THIRD_PARTY_ROOT matches CMake's Slang bundle layout"); std::filesystem::remove_all(root); } void TestRepoVideoIoBundleWinsOverLegacyRepoBundle() { const std::filesystem::path root = MakeTestRoot("repo-video-io"); const std::filesystem::path expected = root / "video-io-3rdParty" / "slang-2026.8-windows-x86_64" / "bin" / "slangc.exe"; const std::filesystem::path legacy = root / "3rdParty" / "slang-2026.8-windows-x86_64" / "bin" / "slangc.exe"; WriteFakeSlangCompiler(expected); WriteFakeSlangCompiler(legacy); ScopedEnvironmentVariable slangRoot("SLANG_ROOT", ""); ScopedEnvironmentVariable thirdPartyRoot("THIRD_PARTY_ROOT", ""); std::filesystem::path actual; std::string error; Expect(ShaderCompiler::FindSlangCompilerPath(root, actual, error), "repo video-io-3rdParty compiler is found"); Expect(SamePath(actual, expected), "repo video-io-3rdParty has priority over repo 3rdParty"); std::filesystem::remove_all(root); } void TestPackagedSlangFolderIsAccepted() { const std::filesystem::path root = MakeTestRoot("packaged"); const std::filesystem::path expected = root / "3rdParty" / "slang" / "bin" / "slangc.exe"; WriteFakeSlangCompiler(expected); ScopedEnvironmentVariable slangRoot("SLANG_ROOT", ""); ScopedEnvironmentVariable thirdPartyRoot("THIRD_PARTY_ROOT", ""); std::filesystem::path actual; std::string error; Expect(ShaderCompiler::FindSlangCompilerPath(root, actual, error), "packaged 3rdParty/slang compiler is found"); Expect(SamePath(actual, expected), "packaged Slang install layout is accepted"); std::filesystem::remove_all(root); } void TestLegacyThirdPartyScanStillAcceptsVersionedFolders() { const std::filesystem::path root = MakeTestRoot("legacy-scan"); const std::filesystem::path expected = root / "3rdParty" / "slang-custom-build" / "bin" / "slangc.exe"; WriteFakeSlangCompiler(expected); ScopedEnvironmentVariable slangRoot("SLANG_ROOT", ""); ScopedEnvironmentVariable thirdPartyRoot("THIRD_PARTY_ROOT", ""); std::filesystem::path actual; std::string error; Expect(ShaderCompiler::FindSlangCompilerPath(root, actual, error), "legacy 3rdParty scan finds versioned Slang folders"); Expect(SamePath(actual, expected), "legacy scan returns the discovered compiler path"); std::filesystem::remove_all(root); } } int main() { TestSlangRootWinsOverThirdPartyRoot(); TestThirdPartyRootUsesCMakeBundleLayout(); TestRepoVideoIoBundleWinsOverLegacyRepoBundle(); TestPackagedSlangFolderIsAccepted(); TestLegacyThirdPartyScanStillAcceptsVersionedFolders(); if (gFailures != 0) { std::cerr << gFailures << " ShaderCompiler lookup test failure(s).\n"; return 1; } std::cout << "ShaderCompiler lookup tests passed.\n"; return 0; }