From 1f368e4a297ba602c188a4e4a533503abfd79674 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Fri, 11 Jul 2025 13:27:55 -0500 Subject: [PATCH 1/4] Get real path to layer & driver binaries Queries the real path of loaded layers and drivers so that logging messages contain the exact path used, instead of the path the loader gave to dlopen/ LoadLibrary. This ensures that logs are as accurate as can be, as the dynamic linker may not respect the passed in library (due to environment variables or other mechanisms which change which binary is loaded). This requires normalizing paths before comparison, as the path given to dlopen/LoadLibrary may represent the same location as the queried path, but might not be identical strings. Normalization is done using the realpath() function on platforms that support it, and a fallback implementation is used elsewhere. The implementation only removes extra directory separators ("//"), extra current directory specifiers ("/./"), and removes relative to parent directory specifiers ("/foo/../"). --- CMakeLists.txt | 13 +++ loader/loader.c | 273 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 286 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 659049ef2..b9d434af6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -258,6 +258,19 @@ if (NOT (WIN32 OR APPLE)) endif() endif() +if (NOT (WIN32)) + # Check for the existance of the realpath() command + include(CheckFunctionExists) + + check_function_exists(realpath HAVE_REALPATH) + + if (HAVE_REALPATH) + target_compile_definitions(loader_common_options INTERFACE HAVE_REALPATH) + else() + message(INFO "Platform support for realpath() is missing. Using fallback path normalization implementation.") + endif() +endif() + option(LOADER_CODEGEN "Enable vulkan loader code generation") if(LOADER_CODEGEN) find_package(Python3 REQUIRED) diff --git a/loader/loader.c b/loader/loader.c index 9832bb7b0..85e3223af 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -387,6 +387,269 @@ void free_string_list(const struct loader_instance *inst, struct loader_string_l memset(string_list, 0, sizeof(struct loader_string_list)); } +// In place modify the passed in path to do the following: +// If HAVE_REALPATH is defined, then this simply calls realpath() so its behavior is defined by realpath() +// Else: +// * Windows-only: Replace forward slashes with backwards slashes (platform correct directory separator) +// * Replace contiguous directory separators with a single directory separator +// * Replace "/./" separator with "/" (where / is the platform correct directory separator) +// * Replace "//../" with just "/" (where / is the platform correct directory separator) +VkResult normalize_path(const struct loader_instance *inst, char **passed_in_path) { + // passed_in_path doesn't point to anything, can't modify inplace so just return + if (passed_in_path == NULL) { + return VK_SUCCESS; + } + +// POSIX systems has the realpath() function to do this for us, fallback to basic normalization on other platforms +#if defined(HAVE_REALPATH) + char *path = loader_instance_heap_calloc(inst, PATH_MAX, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (NULL == path) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + char *ret = realpath(*passed_in_path, path); + if (NULL == ret) { + // error path + int error_code = errno; + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, + "normalize_path: Call to realpath() failed with error code %d when given the path %s", error_code, + *passed_in_path); + loader_instance_heap_free(inst, path); + } else { + // Replace string pointed to by passed_in_path with the one given to us by realpath() + loader_instance_heap_free(inst, *passed_in_path); + *passed_in_path = path; + } + return VK_SUCCESS; + +// Windows has GetFullPathName which does essentially the same thing. Note that we call GetFullPathNameA because the path has +// already been converted from the wide char format when it was initially gotten +#elif defined(WIN32) + VkResult res = VK_SUCCESS; + DWORD path_len = (DWORD)strlen(*passed_in_path) + 1; + char *path = loader_instance_heap_calloc(inst, (size_t)path_len, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (NULL == path) { + res = VK_ERROR_OUT_OF_HOST_MEMORY; + goto out; + } + DWORD actual_len = GetFullPathNameA(*passed_in_path, path_len, path, NULL); + if (actual_len == 0) { + size_t last_error = (size_t)GetLastError(); + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, + "normalize_path: Call to GetFullPathNameA() failed with error code %llu when given the path %s", last_error, + *passed_in_path); + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + } + + // If path_len wasn't big enough, need to realloc and call again + // actual_len doesn't include null terminator + if (actual_len + 1 > path_len) { + path = loader_instance_heap_realloc(inst, path, path_len, actual_len + 1, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (NULL == path) { + res = VK_ERROR_OUT_OF_HOST_MEMORY; + goto out; + } + // store the updated allocation size (sans null terminator) + path_len = actual_len + 1; + + actual_len = GetFullPathNameA(*passed_in_path, path_len, path, NULL); + if (actual_len == 0) { + size_t last_error = (size_t)GetLastError(); + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, + "normalize_path: Call to GetFullPathNameA() failed with error code %llu when given the path %s", last_error, + *passed_in_path); + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + // actual_len doesn't include null terminator + } else if (actual_len + 1 != path_len) { + loader_log(inst, VULKAN_LOADER_DEBUG_BIT, 0, + "normalize_path: Call to GetFullPathNameA() with too small of a buffer when given the path %s after the " + "initial call to GetFullPathNameA() failed for the same reason. Buffer size is %llu, actual size is %llu", + *passed_in_path, (size_t)path_len, (size_t)actual_len); + res = VK_ERROR_INITIALIZATION_FAILED; + goto out; + } + } + // Replace string pointed to by passed_in_path with the one given to us by realpath() + loader_instance_heap_free(inst, *passed_in_path); + *passed_in_path = path; +out: + if (VK_SUCCESS != res) { + if (NULL != path) { + loader_instance_heap_free(inst, path); + } + } + return res; + +#else + (void)inst; + char *path = *passed_in_path; + size_t path_len = strlen(path) + 1; + + size_t output_index = 0; + // Iterate through the string up to the last character, excluding the null terminator + for (size_t i = 0; i < path_len - 1; i++) { + if (i + 1 < path_len && path[i] == DIRECTORY_SYMBOL && path[i + 1] == DIRECTORY_SYMBOL) { + continue; + } else if (i + 2 < path_len && path[i] == DIRECTORY_SYMBOL && path[i + 1] == '.' && path[i + 2] == DIRECTORY_SYMBOL) { + i += 1; + } else { + path[output_index++] = path[i]; + } + } + // Add null terminator and set the new length + path[output_index++] = '\0'; + path_len = output_index; + + // Loop while there are still ..'s in the path. Easiest implementation resolves them one by one, which requires quadratic + // iteration through the string + char *directory_stack = loader_stack_alloc(path_len); + if (directory_stack == NULL) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + + size_t top_of_stack = 0; + + // Iterate through the path, push characters as we see them, if we find a "..", pop off the top of the directory stack until the + // current directory is gone. + for (size_t i = 0; i < path_len - 1; i++) { + // if the next part of path is "/../" we need to pop from the directory stack until we hit the previous directory symbol. + if (i + 3 < path_len && path[i] == DIRECTORY_SYMBOL && path[i + 1] == '.' && path[i + 2] == '.' && path_len && + path[i + 3] == DIRECTORY_SYMBOL) { + // Pop until we hit the next directory symbol in the stack + while (top_of_stack > 0 && directory_stack[top_of_stack - 1] != DIRECTORY_SYMBOL) { + top_of_stack--; + directory_stack[top_of_stack] = '\0'; + } + // Amend the directory stack so that the top isn't a directory separator + if (top_of_stack > 0 && directory_stack[top_of_stack - 1] == DIRECTORY_SYMBOL) { + top_of_stack--; + directory_stack[top_of_stack] = '\0'; + } + i += 2; // need to skip the second dot & directory separator + } else { + // push characters as we come across them + directory_stack[top_of_stack++] = path[i]; + } + } + + // Can't forget the null terminator + directory_stack[top_of_stack] = '\0'; + + // We now have the path without any ..'s, so just copy it out + loader_strncpy(path, path_len, directory_stack, path_len); + path[top_of_stack] = '\0'; + path_len = top_of_stack + 1; + + return VK_SUCCESS; +#endif +} + +// Queries the path to the library that lib_handle & gipa are assoicated with, allocating a string to hold it and returning it in +// out_path +VkResult get_library_path_of_dl_handle(const struct loader_instance *inst, loader_platform_dl_handle lib_handle, + PFN_vkGetInstanceProcAddr gipa, char **out_path) { +#if COMMON_UNIX_PLATFORMS + (void)lib_handle; + Dl_info dl_info = {0}; + if (dladdr(gipa, &dl_info) != 0 && NULL != dl_info.dli_fname) { + return loader_copy_to_new_str(inst, dl_info.dli_fname, out_path); + } + return VK_SUCCESS; + +#elif defined(WIN32) + (void)gipa; + size_t module_file_name_len = MAX_PATH; // start with reasonably large buffer + wchar_t *buffer_utf16 = (wchar_t *)loader_stack_alloc(module_file_name_len * sizeof(wchar_t)); + DWORD ret = GetModuleFileNameW(lib_handle, buffer_utf16, (DWORD)module_file_name_len); + if (ret == 0) { + return VK_SUCCESS; + } + while (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + module_file_name_len *= 2; + buffer_utf16 = (wchar_t *)loader_stack_alloc(module_file_name_len * sizeof(wchar_t)); + ret = GetModuleFileNameW(lib_handle, buffer_utf16, (DWORD)module_file_name_len); + if (ret == 0) { + return VK_SUCCESS; + } + } + + // Need to convert from utf16 to utf8 + int buffer_utf8_size = WideCharToMultiByte(CP_UTF8, 0, buffer_utf16, -1, NULL, 0, NULL, NULL); + if (buffer_utf8_size <= 0) { + return VK_SUCCESS; + } + + char *buffer_utf8 = loader_instance_heap_calloc(inst, buffer_utf8_size, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE); + if (NULL == buffer_utf8) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + if (WideCharToMultiByte(CP_UTF8, 0, buffer_utf16, -1, buffer_utf8, buffer_utf8_size, NULL, NULL) != buffer_utf8_size) { + return VK_SUCCESS; + } + + // Successfully got the 'real' path to the library. + *out_path = buffer_utf8; + return VK_SUCCESS; + +#else + // Do nothing, platform doesn't handle getting the path to a library +#endif +} + +// Find and replace the path that was loaded using the lib_name path with the real path of the library. This is done to provide +// accurate logging info for users. +// This function prints a warning if there is a mismatch between the lib_name path and the real path. +VkResult fixup_library_binary_path(const struct loader_instance *inst, char **lib_name, loader_platform_dl_handle lib_handle, + PFN_vkGetInstanceProcAddr gipa) { + if (lib_name == NULL) { + // do nothing as we got an invalid lib_path pointer + return VK_SUCCESS; + } + + bool system_path = true; + size_t lib_name_len = strlen(*lib_name) + 1; + for (size_t i = 0; i < lib_name_len; i++) { + if ((*lib_name)[i] == DIRECTORY_SYMBOL) { + system_path = false; + break; + } + } + + if (!system_path) { + // The OS path we get for a binary is normalized, so we need to normalize the path passed into LoadLibrary/dlopen so that + // mismatches are minimized. EG, do not warn when we give dlopen/LoadLibrary "/foo/./bar" but get "/foo/bar" as the loaded + // binary path from the OS. + VkResult res = normalize_path(inst, lib_name); + if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { + return res; + } + } + char *os_determined_lib_name = NULL; + VkResult res = get_library_path_of_dl_handle(inst, lib_handle, gipa, &os_determined_lib_name); + if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { + return res; + } + + if (NULL != os_determined_lib_name) { + if (0 != strcmp(os_determined_lib_name, *lib_name)) { + // Paths do not match, so we need to replace lib_name with the real path + if (!system_path) { + // Only warn when the library_path is relative or absolute, not system. EG lib_name had no directory separators + loader_log(inst, VULKAN_LOADER_WARN_BIT | VULKAN_LOADER_LAYER_BIT, 0, + "Path to given binary %s was found to differ from OS loaded path %s", *lib_name, os_determined_lib_name); + } + loader_instance_heap_free(inst, *lib_name); + *lib_name = os_determined_lib_name; + } else { + // Paths match, so just need to free temporary allocation + loader_instance_heap_free(inst, os_determined_lib_name); + } + } + + return res; +} + // Given string of three part form "maj.min.pat" convert to a vulkan version number. // Also can understand four part form "variant.major.minor.patch" if provided. uint32_t loader_parse_version_string(char *vers_str) { @@ -1996,6 +2259,11 @@ VkResult loader_scanned_icd_add(const struct loader_instance *inst, struct loade } icd_tramp_list->count++; + // Uses OS calls to find the 'true' path to the binary, for more accurate logging later on. + res = fixup_library_binary_path(inst, &(new_scanned_icd->lib_name), new_scanned_icd->handle, fp_get_proc_addr); + if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { + goto out; + } out: if (res != VK_SUCCESS) { if (NULL != handle) { @@ -4807,6 +5075,11 @@ VkResult loader_create_instance_chain(const VkInstanceCreateInfo *pCreateInfo, c chain_info.u.pLayerInfo = &layer_instance_link_info[num_activated_layers]; + res = fixup_library_binary_path(inst, &(layer_prop->lib_name), layer_prop->lib_handle, cur_gipa); + if (res == VK_ERROR_OUT_OF_HOST_MEMORY) { + return res; + } + activated_layers[num_activated_layers].name = layer_prop->info.layerName; activated_layers[num_activated_layers].manifest = layer_prop->manifest_file_name; activated_layers[num_activated_layers].library = layer_prop->lib_name; From cb211fe5027451994c0a09f7f82824f55c2e2f70 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Tue, 15 Jul 2025 16:42:17 -0500 Subject: [PATCH 2/4] Generate VkResult operator<< overload --- scripts/generate_source.py | 7 + .../vk_result_to_string_generator.py | 79 +++++++++++ .../generated/vk_result_to_string_helper.h | 133 ++++++++++++++++++ tests/framework/test_environment.h | 2 + tests/framework/test_util.h | 107 -------------- 5 files changed, 221 insertions(+), 107 deletions(-) create mode 100644 scripts/generators/vk_result_to_string_generator.py create mode 100644 tests/framework/generated/vk_result_to_string_helper.h diff --git a/scripts/generate_source.py b/scripts/generate_source.py index e12f7313d..65661152b 100755 --- a/scripts/generate_source.py +++ b/scripts/generate_source.py @@ -66,6 +66,7 @@ def RunGenerators(api: str, registry: str, directory: str, styleFile: str, targe from generators.dispatch_table_helper_generator import DispatchTableHelperGenerator from generators.helper_file_generator import HelperFileGenerator from generators.loader_extension_generator import LoaderExtensionGenerator + from generators.vk_result_to_string_generator import VkResultToStringGenerator # These set fields that are needed by both OutputGenerator and BaseGenerator, # but are uniform and don't need to be set at a per-generated file level @@ -75,6 +76,7 @@ def RunGenerators(api: str, registry: str, directory: str, styleFile: str, targe # Generated directory and dispatch table helper file name may be API specific (e.g. Vulkan SC) generated_directory = 'loader/generated' dispatch_table_helper_filename = 'vk_dispatch_table_helper.h' + result_to_string_filename = 'vk_result_to_string_helper.h' generators.update({ 'vk_layer_dispatch_table.h': { @@ -101,6 +103,11 @@ def RunGenerators(api: str, registry: str, directory: str, styleFile: str, targe 'generator' : DispatchTableHelperGenerator, 'genCombined': False, 'directory' : 'tests/framework/layer/generated', + }, + f'{result_to_string_filename}': { + 'generator' : VkResultToStringGenerator, + 'genCombined': False, + 'directory' : 'tests/framework/generated', } }) diff --git a/scripts/generators/vk_result_to_string_generator.py b/scripts/generators/vk_result_to_string_generator.py new file mode 100644 index 000000000..9efb4aa24 --- /dev/null +++ b/scripts/generators/vk_result_to_string_generator.py @@ -0,0 +1,79 @@ +#!/usr/bin/python3 -i +# +# Copyright (c) 2015-2021 The Khronos Group Inc. +# Copyright (c) 2015-2021 Valve Corporation +# Copyright (c) 2015-2021 LunarG, Inc. +# Copyright (c) 2015-2021 Google Inc. +# +# 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. +# +# Author: Mark Lobodzinski +# Author: Charles Giessen + +import os +from base_generator import BaseGenerator + +class VkResultToStringGenerator(BaseGenerator): + def __init__(self): + BaseGenerator.__init__(self) + + def generate(self): + out = [] + + out.append(f'''#pragma once +// *** THIS FILE IS GENERATED - DO NOT EDIT *** +// See {os.path.basename(__file__)} for modifications + +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * 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. + * + + */ + +#include +#include + +#include + +''') + + self.OutputVkResultToStringHelper(out) + out.append('\n') + + self.write(''.join(out)) + + # Create a dispatch table from the corresponding table_type and append it to out + def OutputVkResultToStringHelper(self, out: list): + out.append('inline std::ostream& operator<<(std::ostream& os, const VkResult& result) {\n') + out.append(' switch (result) {\n') + for field in self.vk.enums['VkResult'].fields: + out.append(f' case({field.name}):\n') + out.append(f' return os << "{field.name}";\n') + out.append(' default:\n') + out.append(' return os << static_cast(result);\n') + out.append(' }\n') + out.append(' }\n') diff --git a/tests/framework/generated/vk_result_to_string_helper.h b/tests/framework/generated/vk_result_to_string_helper.h new file mode 100644 index 000000000..88dfa6a46 --- /dev/null +++ b/tests/framework/generated/vk_result_to_string_helper.h @@ -0,0 +1,133 @@ +#pragma once +// *** THIS FILE IS GENERATED - DO NOT EDIT *** +// See vk_result_to_string_generator.py for modifications + +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * 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. + * + + */ + +#include +#include + +#include + +inline std::ostream& operator<<(std::ostream& os, const VkResult& result) { + switch (result) { + case (VK_SUCCESS): + return os << "VK_SUCCESS"; + case (VK_NOT_READY): + return os << "VK_NOT_READY"; + case (VK_TIMEOUT): + return os << "VK_TIMEOUT"; + case (VK_EVENT_SET): + return os << "VK_EVENT_SET"; + case (VK_EVENT_RESET): + return os << "VK_EVENT_RESET"; + case (VK_INCOMPLETE): + return os << "VK_INCOMPLETE"; + case (VK_ERROR_OUT_OF_HOST_MEMORY): + return os << "VK_ERROR_OUT_OF_HOST_MEMORY"; + case (VK_ERROR_OUT_OF_DEVICE_MEMORY): + return os << "VK_ERROR_OUT_OF_DEVICE_MEMORY"; + case (VK_ERROR_INITIALIZATION_FAILED): + return os << "VK_ERROR_INITIALIZATION_FAILED"; + case (VK_ERROR_DEVICE_LOST): + return os << "VK_ERROR_DEVICE_LOST"; + case (VK_ERROR_MEMORY_MAP_FAILED): + return os << "VK_ERROR_MEMORY_MAP_FAILED"; + case (VK_ERROR_LAYER_NOT_PRESENT): + return os << "VK_ERROR_LAYER_NOT_PRESENT"; + case (VK_ERROR_EXTENSION_NOT_PRESENT): + return os << "VK_ERROR_EXTENSION_NOT_PRESENT"; + case (VK_ERROR_FEATURE_NOT_PRESENT): + return os << "VK_ERROR_FEATURE_NOT_PRESENT"; + case (VK_ERROR_INCOMPATIBLE_DRIVER): + return os << "VK_ERROR_INCOMPATIBLE_DRIVER"; + case (VK_ERROR_TOO_MANY_OBJECTS): + return os << "VK_ERROR_TOO_MANY_OBJECTS"; + case (VK_ERROR_FORMAT_NOT_SUPPORTED): + return os << "VK_ERROR_FORMAT_NOT_SUPPORTED"; + case (VK_ERROR_FRAGMENTED_POOL): + return os << "VK_ERROR_FRAGMENTED_POOL"; + case (VK_ERROR_UNKNOWN): + return os << "VK_ERROR_UNKNOWN"; + case (VK_ERROR_VALIDATION_FAILED): + return os << "VK_ERROR_VALIDATION_FAILED"; + case (VK_ERROR_OUT_OF_POOL_MEMORY): + return os << "VK_ERROR_OUT_OF_POOL_MEMORY"; + case (VK_ERROR_INVALID_EXTERNAL_HANDLE): + return os << "VK_ERROR_INVALID_EXTERNAL_HANDLE"; + case (VK_ERROR_FRAGMENTATION): + return os << "VK_ERROR_FRAGMENTATION"; + case (VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS): + return os << "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; + case (VK_PIPELINE_COMPILE_REQUIRED): + return os << "VK_PIPELINE_COMPILE_REQUIRED"; + case (VK_ERROR_NOT_PERMITTED): + return os << "VK_ERROR_NOT_PERMITTED"; + case (VK_ERROR_SURFACE_LOST_KHR): + return os << "VK_ERROR_SURFACE_LOST_KHR"; + case (VK_ERROR_NATIVE_WINDOW_IN_USE_KHR): + return os << "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"; + case (VK_SUBOPTIMAL_KHR): + return os << "VK_SUBOPTIMAL_KHR"; + case (VK_ERROR_OUT_OF_DATE_KHR): + return os << "VK_ERROR_OUT_OF_DATE_KHR"; + case (VK_ERROR_INCOMPATIBLE_DISPLAY_KHR): + return os << "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR"; + case (VK_ERROR_INVALID_SHADER_NV): + return os << "VK_ERROR_INVALID_SHADER_NV"; + case (VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR): + return os << "VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR"; + case (VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR): + return os << "VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR"; + case (VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR): + return os << "VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR"; + case (VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR): + return os << "VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR"; + case (VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR): + return os << "VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR"; + case (VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR): + return os << "VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR"; + case (VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT): + return os << "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT"; + case (VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT): + return os << "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; + case (VK_THREAD_IDLE_KHR): + return os << "VK_THREAD_IDLE_KHR"; + case (VK_THREAD_DONE_KHR): + return os << "VK_THREAD_DONE_KHR"; + case (VK_OPERATION_DEFERRED_KHR): + return os << "VK_OPERATION_DEFERRED_KHR"; + case (VK_OPERATION_NOT_DEFERRED_KHR): + return os << "VK_OPERATION_NOT_DEFERRED_KHR"; + case (VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR): + return os << "VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR"; + case (VK_ERROR_COMPRESSION_EXHAUSTED_EXT): + return os << "VK_ERROR_COMPRESSION_EXHAUSTED_EXT"; + case (VK_INCOMPATIBLE_SHADER_BINARY_EXT): + return os << "VK_INCOMPATIBLE_SHADER_BINARY_EXT"; + case (VK_PIPELINE_BINARY_MISSING_KHR): + return os << "VK_PIPELINE_BINARY_MISSING_KHR"; + case (VK_ERROR_NOT_ENOUGH_SPACE_KHR): + return os << "VK_ERROR_NOT_ENOUGH_SPACE_KHR"; + default: + return os << static_cast(result); + } +} diff --git a/tests/framework/test_environment.h b/tests/framework/test_environment.h index a7b9f15b2..98ba141d1 100644 --- a/tests/framework/test_environment.h +++ b/tests/framework/test_environment.h @@ -57,6 +57,8 @@ #include "layer/test_layer.h" +#include "generated/vk_result_to_string_helper.h" + #include FRAMEWORK_CONFIG_HEADER // Useful defines diff --git a/tests/framework/test_util.h b/tests/framework/test_util.h index 06d62d45d..2f4b641f0 100644 --- a/tests/framework/test_util.h +++ b/tests/framework/test_util.h @@ -345,113 +345,6 @@ struct FRAMEWORK_EXPORT DispatchableHandle { T handle = nullptr; }; -// Stream operator for VkResult so GTEST will print out error codes as strings (automatically) -inline std::ostream& operator<<(std::ostream& os, const VkResult& result) { - switch (result) { - case (VK_SUCCESS): - return os << "VK_SUCCESS"; - case (VK_NOT_READY): - return os << "VK_NOT_READY"; - case (VK_TIMEOUT): - return os << "VK_TIMEOUT"; - case (VK_EVENT_SET): - return os << "VK_EVENT_SET"; - case (VK_EVENT_RESET): - return os << "VK_EVENT_RESET"; - case (VK_INCOMPLETE): - return os << "VK_INCOMPLETE"; - case (VK_ERROR_OUT_OF_HOST_MEMORY): - return os << "VK_ERROR_OUT_OF_HOST_MEMORY"; - case (VK_ERROR_OUT_OF_DEVICE_MEMORY): - return os << "VK_ERROR_OUT_OF_DEVICE_MEMORY"; - case (VK_ERROR_INITIALIZATION_FAILED): - return os << "VK_ERROR_INITIALIZATION_FAILED"; - case (VK_ERROR_DEVICE_LOST): - return os << "VK_ERROR_DEVICE_LOST"; - case (VK_ERROR_MEMORY_MAP_FAILED): - return os << "VK_ERROR_MEMORY_MAP_FAILED"; - case (VK_ERROR_LAYER_NOT_PRESENT): - return os << "VK_ERROR_LAYER_NOT_PRESENT"; - case (VK_ERROR_EXTENSION_NOT_PRESENT): - return os << "VK_ERROR_EXTENSION_NOT_PRESENT"; - case (VK_ERROR_FEATURE_NOT_PRESENT): - return os << "VK_ERROR_FEATURE_NOT_PRESENT"; - case (VK_ERROR_INCOMPATIBLE_DRIVER): - return os << "VK_ERROR_INCOMPATIBLE_DRIVER"; - case (VK_ERROR_TOO_MANY_OBJECTS): - return os << "VK_ERROR_TOO_MANY_OBJECTS"; - case (VK_ERROR_FORMAT_NOT_SUPPORTED): - return os << "VK_ERROR_FORMAT_NOT_SUPPORTED"; - case (VK_ERROR_FRAGMENTED_POOL): - return os << "VK_ERROR_FRAGMENTED_POOL"; - case (VK_ERROR_UNKNOWN): - return os << "VK_ERROR_UNKNOWN"; - case (VK_ERROR_OUT_OF_POOL_MEMORY): - return os << "VK_ERROR_OUT_OF_POOL_MEMORY"; - case (VK_ERROR_INVALID_EXTERNAL_HANDLE): - return os << "VK_ERROR_INVALID_EXTERNAL_HANDLE"; - case (VK_ERROR_FRAGMENTATION): - return os << "VK_ERROR_FRAGMENTATION"; - case (VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS): - return os << "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; - case (VK_ERROR_SURFACE_LOST_KHR): - return os << "VK_ERROR_SURFACE_LOST_KHR"; - case (VK_ERROR_NATIVE_WINDOW_IN_USE_KHR): - return os << "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"; - case (VK_SUBOPTIMAL_KHR): - return os << "VK_SUBOPTIMAL_KHR"; - case (VK_ERROR_OUT_OF_DATE_KHR): - return os << "VK_ERROR_OUT_OF_DATE_KHR"; - case (VK_ERROR_INCOMPATIBLE_DISPLAY_KHR): - return os << "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR"; - case (VK_ERROR_VALIDATION_FAILED_EXT): - return os << "VK_ERROR_VALIDATION_FAILED_EXT"; - case (VK_ERROR_INVALID_SHADER_NV): - return os << "VK_ERROR_INVALID_SHADER_NV"; - case (VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT): - return os << "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT"; - case (VK_ERROR_NOT_PERMITTED_EXT): - return os << "VK_ERROR_NOT_PERMITTED_EXT"; - case (VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT): - return os << "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; - case (VK_THREAD_IDLE_KHR): - return os << "VK_THREAD_IDLE_KHR"; - case (VK_THREAD_DONE_KHR): - return os << "VK_THREAD_DONE_KHR"; - case (VK_OPERATION_DEFERRED_KHR): - return os << "VK_OPERATION_DEFERRED_KHR"; - case (VK_OPERATION_NOT_DEFERRED_KHR): - return os << "VK_OPERATION_NOT_DEFERRED_KHR"; - case (VK_PIPELINE_COMPILE_REQUIRED_EXT): - return os << "VK_PIPELINE_COMPILE_REQUIRED_EXT"; - case (VK_RESULT_MAX_ENUM): - return os << "VK_RESULT_MAX_ENUM"; - case (VK_ERROR_COMPRESSION_EXHAUSTED_EXT): - return os << "VK_ERROR_COMPRESSION_EXHAUSTED_EXT"; - case (VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR): - return os << "VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR"; - case (VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR): - return os << "VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR"; - case (VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR): - return os << "VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR"; - case (VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR): - return os << "VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR"; - case (VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR): - return os << "VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR"; - case (VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR): - return os << "VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR"; - case (VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR): - return os << "VK_ERROR_INVALID_VIDEO_STD_PARAMETERS_KHR"; - case (VK_ERROR_INCOMPATIBLE_SHADER_BINARY_EXT): - return os << "VK_ERROR_INCOMPATIBLE_SHADER_BINARY_EXT"; - case (VK_PIPELINE_BINARY_MISSING_KHR): - return os << "VK_PIPELINE_BINARY_MISSING_KHR"; - case (VK_ERROR_NOT_ENOUGH_SPACE_KHR): - return os << "VK_ERROR_NOT_ENOUGH_SPACE_KHR"; - } - return os << static_cast(result); -} - const char* get_platform_wsi_extension([[maybe_unused]] const char* api_selection); bool string_eq(const char* a, const char* b) noexcept; From e2822d34bea31679013e006c9498813caf3783ad Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Mon, 21 Jul 2025 11:07:47 -0500 Subject: [PATCH 3/4] Large refactor of test framework Makes many changes to test infrastructure: * Split test_util into separate files for maintainability * Move new files into tests/framework/util * Move FolderManager into separate file from test_environment * Rename COMMON_UNIX_PLATFORMS to TESTING_COMMON_UNIX_PLATFORMS * Rewrote unix path redirection logic, relies on FileSystemManager * Relative paths are actually relative now * Split unsecured folder location into unsecured driver/implicit layer/explicit layer/settings * Fix bugs in tests that are attributable to old path redirection logic. * Use normalized paths in test framework - ie normalize paths so they can be compared easily * Make running with elevated privileges a framework level setting * Split tests that change elevated privileges into separate tests * Framework can set home & xdg env-vars at startup * Platform specific folders are only defined on those platforms * PlatformShim::add_manifest is now windows only, reflecting that only windows implemented it --- .../dispatch_table_helper_generator.py | 2 +- tests/CMakeLists.txt | 8 +- tests/framework/CMakeLists.txt | 53 +- tests/framework/README.md | 3 +- tests/framework/framework_config.h.in | 2 +- tests/framework/icd/test_icd.cpp | 7 + tests/framework/icd/test_icd.h | 11 +- tests/framework/json_writer.h | 164 ---- .../generated/vk_dispatch_table_helper.h | 2 +- tests/framework/layer/layer_util.h | 8 +- tests/framework/layer/test_layer.cpp | 9 +- tests/framework/layer/test_layer.h | 10 +- tests/framework/shim/shim.h | 80 +- tests/framework/shim/shim_common.cpp | 114 +-- tests/framework/shim/unix_shim.cpp | 102 ++- tests/framework/shim/windows_shim.cpp | 12 +- tests/framework/test_environment.cpp | 591 ++++-------- tests/framework/test_environment.h | 321 ++----- tests/framework/test_util.cpp | 375 -------- tests/framework/test_util.h | 861 ------------------ tests/framework/util/CMakeLists.txt | 85 ++ tests/framework/util/builder_defines.h | 64 ++ tests/framework/util/dispatch_table.cpp | 174 ++++ tests/framework/util/dispatch_table.h | 178 ++++ tests/framework/util/dispatchable_handle.h | 61 ++ .../util/dynamic_library_wrapper.cpp | 165 ++++ .../framework/util/dynamic_library_wrapper.h | 71 ++ tests/framework/util/env_var_wrapper.cpp | 184 ++++ tests/framework/util/env_var_wrapper.h | 85 ++ tests/framework/util/equality_helpers.cpp | 206 +++++ tests/framework/util/equality_helpers.h | 83 ++ tests/framework/util/folder_manager.cpp | 243 +++++ tests/framework/util/folder_manager.h | 103 +++ tests/framework/util/functions.h | 54 ++ tests/framework/util/get_executable_path.cpp | 136 +++ tests/framework/util/get_executable_path.h | 32 + tests/framework/util/json_writer.cpp | 152 ++++ tests/framework/util/json_writer.h | 75 ++ tests/framework/util/manifest_builders.cpp | 163 ++++ tests/framework/util/manifest_builders.h | 112 +++ tests/framework/util/platform_wsi.cpp | 72 ++ tests/framework/util/platform_wsi.h | 29 + tests/framework/util/test_defines.h | 97 ++ .../framework/util/vulkan_object_wrappers.cpp | 112 +++ tests/framework/util/vulkan_object_wrappers.h | 101 ++ tests/framework/util/wide_char_handling.cpp | 65 ++ tests/framework/util/wide_char_handling.h | 42 + .../dynamic_loader_behavior/dynamic_library.h | 9 +- .../test_dynamic_linking.cpp | 2 +- tests/loader_alloc_callback_tests.cpp | 4 +- tests/loader_envvar_tests.cpp | 261 ++++-- tests/loader_layer_tests.cpp | 188 ++-- tests/loader_regression_tests.cpp | 54 +- tests/loader_settings_tests.cpp | 186 ++-- tests/loader_testing_main.cpp | 10 +- tests/loader_unknown_ext_tests.cpp | 2 + tests/loader_version_tests.cpp | 6 +- 57 files changed, 3802 insertions(+), 2599 deletions(-) delete mode 100644 tests/framework/json_writer.h delete mode 100644 tests/framework/test_util.cpp delete mode 100644 tests/framework/test_util.h create mode 100644 tests/framework/util/CMakeLists.txt create mode 100644 tests/framework/util/builder_defines.h create mode 100644 tests/framework/util/dispatch_table.cpp create mode 100644 tests/framework/util/dispatch_table.h create mode 100644 tests/framework/util/dispatchable_handle.h create mode 100644 tests/framework/util/dynamic_library_wrapper.cpp create mode 100644 tests/framework/util/dynamic_library_wrapper.h create mode 100644 tests/framework/util/env_var_wrapper.cpp create mode 100644 tests/framework/util/env_var_wrapper.h create mode 100644 tests/framework/util/equality_helpers.cpp create mode 100644 tests/framework/util/equality_helpers.h create mode 100644 tests/framework/util/folder_manager.cpp create mode 100644 tests/framework/util/folder_manager.h create mode 100644 tests/framework/util/functions.h create mode 100644 tests/framework/util/get_executable_path.cpp create mode 100644 tests/framework/util/get_executable_path.h create mode 100644 tests/framework/util/json_writer.cpp create mode 100644 tests/framework/util/json_writer.h create mode 100644 tests/framework/util/manifest_builders.cpp create mode 100644 tests/framework/util/manifest_builders.h create mode 100644 tests/framework/util/platform_wsi.cpp create mode 100644 tests/framework/util/platform_wsi.h create mode 100644 tests/framework/util/test_defines.h create mode 100644 tests/framework/util/vulkan_object_wrappers.cpp create mode 100644 tests/framework/util/vulkan_object_wrappers.h create mode 100644 tests/framework/util/wide_char_handling.cpp create mode 100644 tests/framework/util/wide_char_handling.h diff --git a/scripts/generators/dispatch_table_helper_generator.py b/scripts/generators/dispatch_table_helper_generator.py index 410938a92..cf0060990 100644 --- a/scripts/generators/dispatch_table_helper_generator.py +++ b/scripts/generators/dispatch_table_helper_generator.py @@ -60,7 +60,7 @@ def generate(self): #include #include #include -#include "vk_layer_dispatch_table.h" +#include "loader/generated/vk_layer_dispatch_table.h" ''') diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8145f638d..e719dfce4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -68,6 +68,8 @@ endif() option(ENABLE_LIVE_VERIFICATION_TESTS "Enable tests which expect to run on live drivers. Meant for manual verification only" OFF) +set(TEST_EXECUTION_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/executing_tests") + include(GoogleTest) add_subdirectory(framework) @@ -94,7 +96,11 @@ add_executable( loader_testing_main.cpp loader_fuzz_tests.cpp ) -target_link_libraries(test_fuzzing PUBLIC testing_dependencies vulkan) +target_link_libraries(test_fuzzing PUBLIC testing_dependencies) +# testing_dependencies already links to vulkan when linking statically +if (NOT APPLE_STATIC_LOADER) + target_link_libraries(test_fuzzing PUBLIC vulkan) +endif() target_include_directories(test_fuzzing PUBLIC ${CMAKE_SOURCE_DIR}/loader ${CMAKE_SOURCE_DIR}/loader/generated) # Threading tests live in separate executabe just for threading tests as it'll need support diff --git a/tests/framework/CMakeLists.txt b/tests/framework/CMakeLists.txt index 0dcf46eb8..0e6bff12a 100644 --- a/tests/framework/CMakeLists.txt +++ b/tests/framework/CMakeLists.txt @@ -15,38 +15,15 @@ # limitations under the License. # ~~~ -add_library(testing_framework_util STATIC test_util.cpp) -target_link_libraries(testing_framework_util PUBLIC loader_common_options Vulkan::Headers) - -if(UNIX OR APPLE) - target_link_libraries(testing_framework_util PUBLIC ${CMAKE_DL_LIBS}) -endif() - -if(UNIX) - target_compile_options(testing_framework_util PUBLIC -fPIC) -endif() -# Gives access to all headers in the framework folder, in the framework binary, and in the whole project (mainly for loader/generated) -target_include_directories(testing_framework_util PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}/loader/generated") +#configure the framework_config.h.in file - used to locate all the binaries generated so that it can be used in the tests +#setup framework_config_temp.h.in in the current binary directory +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/framework_config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/framework_config_temp.h.in") -if (UNIX) - if (LOADER_ENABLE_ADDRESS_SANITIZER) - target_compile_options(testing_framework_util PUBLIC -fsanitize=address,undefined) - target_link_options(testing_framework_util PUBLIC -fsanitize=address,undefined) - endif() - if (LOADER_ENABLE_THREAD_SANITIZER) - target_compile_options(testing_framework_util PUBLIC -fsanitize=thread) - target_link_options(testing_framework_util PUBLIC -fsanitize=thread) - target_compile_options(gtest PUBLIC -fsanitize=thread) - target_link_options(gtest PUBLIC -fsanitize=thread) - endif() -endif() +# setup framework_config_$ using framework_config_temp.h.in as a source +file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/framework_config_$.h" INPUT "${CMAKE_CURRENT_BINARY_DIR}/framework_config_temp.h.in") -if (MSVC) - # silence hidden class member warnings in test framework - target_compile_options(testing_framework_util PUBLIC /wd4458) - # Make sure exception handling is enabled for the test framework - target_compile_options(testing_framework_util PUBLIC /EHsc) -endif() +# Create a variable to hold the path to the correct header file, used as a compiler definition in the utils target +set(FRAMEWORK_CONFIG_HEADER_PATH "${CMAKE_CURRENT_BINARY_DIR}/framework_config_$.h") function(AddSharedLibrary LIBRARY_NAME) set(singleValueArgs DEF_FILE) @@ -63,26 +40,14 @@ function(AddSharedLibrary LIBRARY_NAME) endif() endfunction() +add_subdirectory(util) add_subdirectory(data) add_subdirectory(shim) add_subdirectory(icd) add_subdirectory(layer) -#configure the framework_config.h.in file - used to locate all the binaries generated so that it can be used in the tests -#setup framework_config_temp.h.in in the current binary directory -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/framework_config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/framework_config_temp.h.in") - -# setup framework_config_$ using framework_config_temp.h.in as a source -file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/framework_config_$.h" INPUT "${CMAKE_CURRENT_BINARY_DIR}/framework_config_temp.h.in") - -# Add a compiler definition for the path to framework_config.h with the correct config -target_compile_definitions(testing_framework_util PUBLIC FRAMEWORK_CONFIG_HEADER="framework_config_$.h") - add_library(testing_dependencies STATIC test_environment.cpp test_environment.h) target_link_libraries(testing_dependencies PUBLIC gtest Vulkan::Headers testing_framework_util shim-library) target_include_directories(testing_dependencies PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) -if (APPLE_STATIC_LOADER) - target_compile_definitions(testing_dependencies PUBLIC "APPLE_STATIC_LOADER=1") - target_link_libraries(testing_dependencies PUBLIC vulkan) -endif() + diff --git a/tests/framework/README.md b/tests/framework/README.md index 1dc5b73a4..20e1209ae 100644 --- a/tests/framework/README.md +++ b/tests/framework/README.md @@ -136,8 +136,7 @@ There are many utilities that the test framework and tests have access to. These * Environment Variable Wrapper: `EnvVarWrapper` for creating, setting, getting, and removing environment variables in a RAII manner * Windows API error handling helpers * filesystem abstractions: - * `create_folder`/`delete_folder` - * `FolderManager` + * `Folder` * Creates a new folder with the given name at construction time. * Allows writing manifests and files (eg, icd or layer binaries) * Automatically destroys the folder and all contained files at destruction diff --git a/tests/framework/framework_config.h.in b/tests/framework/framework_config.h.in index b70e42b11..8c0c1f78e 100644 --- a/tests/framework/framework_config.h.in +++ b/tests/framework/framework_config.h.in @@ -26,7 +26,7 @@ */ #pragma once -#define FRAMEWORK_BUILD_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" +#define TEST_EXECUTION_DIRECTORY "${TEST_EXECUTION_DIRECTORY}" #define FRAMEWORK_VULKAN_LIBRARY_PATH "$" diff --git a/tests/framework/icd/test_icd.cpp b/tests/framework/icd/test_icd.cpp index 7e09c6d61..b657672b7 100644 --- a/tests/framework/icd/test_icd.cpp +++ b/tests/framework/icd/test_icd.cpp @@ -29,6 +29,13 @@ #include "test_icd.h" +#include +#include + +#include + +#include "equality_helpers.h" + // export vk_icdGetInstanceProcAddr #if !defined(TEST_ICD_EXPORT_ICD_GIPA) #define TEST_ICD_EXPORT_ICD_GIPA 0 diff --git a/tests/framework/icd/test_icd.h b/tests/framework/icd/test_icd.h index 33b1dc714..57ee27d4b 100644 --- a/tests/framework/icd/test_icd.h +++ b/tests/framework/icd/test_icd.h @@ -27,7 +27,13 @@ #pragma once -#include "test_util.h" +#include +#include +#include + +#include "util/dispatchable_handle.h" +#include "util/platform_wsi.h" +#include "util/functions.h" #include "layer/layer_util.h" @@ -70,6 +76,9 @@ inline std::ostream& operator<<(std::ostream& os, const InterfaceVersionCheck& r } return os << static_cast(result); } + +using VulkanUUID = std::array; + // clang-format on // Move only type because it holds a DispatchableHandle diff --git a/tests/framework/json_writer.h b/tests/framework/json_writer.h deleted file mode 100644 index 4a4cd02a0..000000000 --- a/tests/framework/json_writer.h +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2023 The Khronos Group Inc. - * Copyright (c) 2023 Valve Corporation - * Copyright (c) 2023 LunarG, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and/or associated documentation files (the "Materials"), to - * deal in the Materials without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Materials, and to permit persons to whom the Materials are - * furnished to do so, subject to the following conditions: - * - * The above copyright notice(s) and this permission notice shall be included in - * all copies or substantial portions of the Materials. - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE - * USE OR OTHER DEALINGS IN THE MATERIALS. - * - * Author: Charles Giessen - */ - -#pragma once - -#include -#include -#include - -// Utility class to simplify the writing of JSON manifest files - -struct JsonWriter { - std::string output; - - // the bool represents whether an object has been written & a comma is needed - std::stack stack; - - void StartObject() { - CommaAndNewLine(); - Indent(); - output += "{"; - stack.push(false); - } - void StartKeyedObject(std::string const& key) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": {"; - stack.push(false); - } - void EndObject() { - stack.pop(); - output += "\n"; - Indent(); - output += "}"; - } - void StartKeyedArray(std::string const& key) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": ["; - stack.push(false); - } - void StartArray() { - CommaAndNewLine(); - Indent(); - output += "["; - stack.push(false); - } - void EndArray() { - stack.pop(); - output += "\n"; - Indent(); - output += "]"; - } - - void AddKeyedString(std::string const& key, std::string const& value) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": \"" + escape(value) + "\""; - } - void AddString(std::string const& value) { - CommaAndNewLine(); - Indent(); - output += "\"" + escape(value) + "\""; - } -#if defined(WIN32) - void AddKeyedString(std::string const& key, std::wstring const& value) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": \"" + escape(narrow(value)) + "\""; - } - void AddString(std::wstring const& value) { - CommaAndNewLine(); - Indent(); - output += "\"" + escape(narrow(value)) + "\""; - } -#endif - - void AddKeyedBool(std::string const& key, bool value) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": " + (value ? "true" : "false"); - } - void AddBool(bool value) { - CommaAndNewLine(); - Indent(); - output += std::string(value ? "true" : "false"); - } - - void AddKeyedNumber(std::string const& key, double number) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": " + std::to_string(number); - } - void AddNumber(double number) { - CommaAndNewLine(); - Indent(); - output += std::to_string(number); - } - - void AddKeyedInteger(std::string const& key, int64_t number) { - CommaAndNewLine(); - Indent(); - output += "\"" + key + "\": " + std::to_string(number); - } - void AddInteger(int64_t number) { - CommaAndNewLine(); - Indent(); - output += std::to_string(number); - } - - // Json doesn't allow `\` in strings, it must be escaped. Thus we have to convert '\\' to '\\\\' in strings - static std::string escape(std::string const& in_path) { - std::string out; - for (auto& c : in_path) { - if (c == '\\') - out += "\\\\"; - else - out += c; - } - return out; - } - static std::string escape(std::filesystem::path const& in_path) { return escape(narrow(in_path.native())); } - - private: - void CommaAndNewLine() { - if (stack.size() > 0) { - if (stack.top() == false) { - stack.top() = true; - } else { - output += ","; - } - output += "\n"; - } - } - void Indent() { - for (uint32_t i = 0; i < stack.size(); i++) { - output += '\t'; - } - } -}; diff --git a/tests/framework/layer/generated/vk_dispatch_table_helper.h b/tests/framework/layer/generated/vk_dispatch_table_helper.h index 498352781..4b54ba2f7 100644 --- a/tests/framework/layer/generated/vk_dispatch_table_helper.h +++ b/tests/framework/layer/generated/vk_dispatch_table_helper.h @@ -28,7 +28,7 @@ #include #include #include -#include "vk_layer_dispatch_table.h" +#include "loader/generated/vk_layer_dispatch_table.h" // clang-format off static inline void layer_init_device_dispatch_table(VkDevice device, VkLayerDispatchTable *table, PFN_vkGetDeviceProcAddr gpa) { diff --git a/tests/framework/layer/layer_util.h b/tests/framework/layer/layer_util.h index 3f4bc755a..7107b4642 100644 --- a/tests/framework/layer/layer_util.h +++ b/tests/framework/layer/layer_util.h @@ -27,7 +27,9 @@ #pragma once -#include "test_util.h" +#include "builder_defines.h" + +#include "vulkan_object_wrappers.h" struct LayerDefinition { BUILDER_VALUE(std::string, layerName) @@ -38,10 +40,10 @@ struct LayerDefinition { VkLayerProperties get() const noexcept { VkLayerProperties props{}; - copy_string_to_char_array(layerName, &props.layerName[0], VK_MAX_EXTENSION_NAME_SIZE); + layerName.copy(props.layerName, VK_MAX_EXTENSION_NAME_SIZE); props.specVersion = specVersion; props.implementationVersion = implementationVersion; - copy_string_to_char_array(description, &props.description[0], VK_MAX_DESCRIPTION_SIZE); + description.copy(props.description, VK_MAX_DESCRIPTION_SIZE); return props; } }; diff --git a/tests/framework/layer/test_layer.cpp b/tests/framework/layer/test_layer.cpp index f891927ad..b98f543f4 100644 --- a/tests/framework/layer/test_layer.cpp +++ b/tests/framework/layer/test_layer.cpp @@ -27,6 +27,13 @@ #include "test_layer.h" +#include + +#include + +#include "util/test_defines.h" +#include "util/equality_helpers.h" + #include "generated/vk_dispatch_table_helper.h" // export the enumeration functions instance|device+layer|extension @@ -243,7 +250,7 @@ VKAPI_ATTR VkResult VKAPI_CALL test_vkCreateInstance(const VkInstanceCreateInfo* VkLayerInstanceCreateInfo* chain_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO); PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr; - PFN_vk_icdGetPhysicalDeviceProcAddr fpGetPhysicalDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetPhysicalDeviceProcAddr; + PFN_GetPhysicalDeviceProcAddr fpGetPhysicalDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetPhysicalDeviceProcAddr; PFN_vkCreateInstance fpCreateInstance = (PFN_vkCreateInstance)fpGetInstanceProcAddr(NULL, "vkCreateInstance"); if (fpCreateInstance == NULL) { return VK_ERROR_INITIALIZATION_FAILED; diff --git a/tests/framework/layer/test_layer.h b/tests/framework/layer/test_layer.h index 14e60bcc8..085be617d 100644 --- a/tests/framework/layer/test_layer.h +++ b/tests/framework/layer/test_layer.h @@ -27,14 +27,18 @@ #pragma once -#include "test_util.h" - #include +#include -#include "layer/layer_util.h" +#include +#include #include "loader/generated/vk_layer_dispatch_table.h" +#include "util/functions.h" + +#include "layer_util.h" + /* Interface Version 0 */ diff --git a/tests/framework/shim/shim.h b/tests/framework/shim/shim.h index bc47c3987..4dbd472c6 100644 --- a/tests/framework/shim/shim.h +++ b/tests/framework/shim/shim.h @@ -27,13 +27,19 @@ #pragma once -#include "test_util.h" +#include +#include +#include +#include #include #include -#include +#include -#if defined(WIN32) +#if defined(_WIN32) + +#include +#include #include #include #include @@ -46,10 +52,18 @@ #include #endif -enum class ManifestCategory { implicit_layer, explicit_layer, icd, settings }; +#include "util/test_defines.h" + +#if TESTING_COMMON_UNIX_PLATFORMS +#include +#endif + +#include "util/folder_manager.h" +#include "util/functions.h" + enum class GpuType { unspecified, integrated, discrete, external }; -#if defined(WIN32) +#if defined(_WIN32) #define VK_VARIANT_REG_STR "" #define VK_VARIANT_REG_STR_W L"" @@ -117,15 +131,15 @@ struct D3DKMT_Adapter { D3DKMT_Adapter& add_path(std::filesystem::path src, std::vector& dest); }; -#elif COMMON_UNIX_PLATFORMS +#elif TESTING_COMMON_UNIX_PLATFORMS struct DirEntry { DIR* directory = nullptr; - std::string folder_path; + fs::Folder* folder_represented; std::vector contents; // the current item being read by an app (incremented by readdir, reset to zero by opendir & closedir) size_t current_index = 0; - bool is_fake_path = false; // true when this entry is for folder redirection + // bool is_fake_path = false; // true when this entry is for folder redirection }; #endif @@ -136,12 +150,9 @@ struct FrameworkEnvironment; // forward declaration // defined in the .cpp wont be found by the rest of the application struct PlatformShim { PlatformShim() { fputs_stderr_log.reserve(65536); } - PlatformShim(GetFoldersFunc get_folders_by_name_function) : get_folders_by_name_function(get_folders_by_name_function) { - fputs_stderr_log.reserve(65536); - } // Used to get info about which drivers & layers have been added to folders - GetFoldersFunc get_folders_by_name_function; + fs::FileSystemManager* file_system_manager; // Captures the output to stderr from fputs & fputc - aka the output of loader_log() std::string fputs_stderr_log; @@ -149,28 +160,25 @@ struct PlatformShim { // Test Framework interface void reset(); - void redirect_all_paths(std::filesystem::path const& path); - void redirect_category(std::filesystem::path const& new_path, ManifestCategory category); - // fake paths are paths that the loader normally looks in but actually point to locations inside the test framework - void set_fake_path(ManifestCategory category, std::filesystem::path const& path); + // void set_fake_path(ManifestCategory category, std::filesystem::path const& path); // known paths are real paths but since the test framework guarantee's the order files are found in, files in these paths // need to be ordered correctly - void add_known_path(std::filesystem::path const& path); - - void add_manifest(ManifestCategory category, std::filesystem::path const& path); - void add_unsecured_manifest(ManifestCategory category, std::filesystem::path const& path); + // void add_known_path(std::filesystem::path const& path); void clear_logs() { fputs_stderr_log.clear(); } bool find_in_log(std::string const& search_text) const { return fputs_stderr_log.find(search_text) != std::string::npos; } // platform specific shim interface -#if defined(WIN32) +#if defined(_WIN32) // Control Platform Elevation Level void set_elevated_privilege(bool elev) { elevation_level = (elev) ? SECURITY_MANDATORY_HIGH_RID : SECURITY_MANDATORY_LOW_RID; } unsigned long elevation_level = SECURITY_MANDATORY_LOW_RID; + void add_manifest_to_registry(ManifestCategory category, std::filesystem::path const& path); + void add_unsecured_manifest_to_registry(ManifestCategory category, std::filesystem::path const& path); + void add_dxgi_adapter(GpuType gpu_preference, DXGI_ADAPTER_DESC1 desc1); void add_d3dkmt_adapter(D3DKMT_Adapter const& adapter); void set_app_package_path(std::filesystem::path const& path); @@ -202,27 +210,18 @@ struct PlatformShim { size_t created_key_count = 0; std::vector created_keys; -#elif COMMON_UNIX_PLATFORMS - bool is_fake_path(std::filesystem::path const& path); - std::filesystem::path const& get_real_path_from_fake_path(std::filesystem::path const& path); +#elif TESTING_COMMON_UNIX_PLATFORMS + // Add a filename that dlopen can "find" with no absolute or relative path given. + // EG, dlopen("libfoobar.so") gets turned into dlopen(actual_path) + void add_system_library(std::string const& filename, std::filesystem::path const& actual_path); - void redirect_path(std::filesystem::path const& path, std::filesystem::path const& new_path); - void remove_redirect(std::filesystem::path const& path); + // Returns the real path of the system library "filename", if it exists. Else returns empty path + std::filesystem::path get_system_library(std::string const& filename); - bool is_known_path(std::filesystem::path const& path); - void remove_known_path(std::filesystem::path const& path); - - void redirect_dlopen_name(std::filesystem::path const& filename, std::filesystem::path const& actual_path); - bool is_dlopen_redirect_name(std::filesystem::path const& filename); - - std::filesystem::path query_default_redirect_path(ManifestCategory category); + std::unordered_map system_library_redirection_map; void set_app_package_path(std::filesystem::path const& path); - std::unordered_map redirection_map; - std::unordered_map dlopen_redirection_map; - std::unordered_set known_path_set; - void set_elevated_privilege(bool elev) { use_fake_elevation = elev; } bool use_fake_elevation = false; @@ -232,6 +231,7 @@ struct PlatformShim { std::string bundle_contents; #endif #endif + bool is_finished_setup = false; bool is_during_destruction = false; }; @@ -240,12 +240,12 @@ std::string category_path_name(ManifestCategory category); extern "C" { // dynamically link on windows and macos -#if defined(WIN32) || defined(__APPLE__) -using PFN_get_platform_shim = PlatformShim* (*)(GetFoldersFunc get_folders_by_name_function); +#if defined(_WIN32) || defined(__APPLE__) +using PFN_get_platform_shim = PlatformShim* (*)(); #define GET_PLATFORM_SHIM_STR "get_platform_shim" #elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__GNU__) || defined(__QNX__) // statically link on linux -PlatformShim* get_platform_shim(GetFoldersFunc get_folders_by_name_function); +PlatformShim* get_platform_shim(); #endif } diff --git a/tests/framework/shim/shim_common.cpp b/tests/framework/shim/shim_common.cpp index a21902ceb..f3d021fb6 100644 --- a/tests/framework/shim/shim_common.cpp +++ b/tests/framework/shim/shim_common.cpp @@ -27,20 +27,16 @@ #include "shim.h" -void PlatformShim::redirect_all_paths(std::filesystem::path const& path) { - redirect_category(path, ManifestCategory::implicit_layer); - redirect_category(path, ManifestCategory::explicit_layer); - redirect_category(path, ManifestCategory::icd); -} +#include "env_var_wrapper.h" std::vector parse_env_var_list(std::string const& var) { std::vector items; size_t start = 0; size_t len = 0; for (size_t i = 0; i < var.size(); i++) { -#if defined(WIN32) +#if defined(_WIN32) if (var[i] == ';') { -#elif COMMON_UNIX_PLATFORMS +#elif TESTING_COMMON_UNIX_PLATFORMS if (var[i] == ':') { #endif if (len != 0) { @@ -58,7 +54,7 @@ std::vector parse_env_var_list(std::string const& var) { return items; } -#if defined(WIN32) +#if defined(_WIN32) D3DKMT_Adapter& D3DKMT_Adapter::add_driver_manifest_path(std::filesystem::path const& src) { return add_path(src, driver_paths); } D3DKMT_Adapter& D3DKMT_Adapter::add_implicit_layer_manifest_path(std::filesystem::path const& src) { @@ -91,10 +87,7 @@ void PlatformShim::reset() { hkey_current_user_settings.clear(); } -void PlatformShim::set_fake_path([[maybe_unused]] ManifestCategory category, [[maybe_unused]] std::filesystem::path const& path) {} -void PlatformShim::add_known_path([[maybe_unused]] std::filesystem::path const& path) {} - -void PlatformShim::add_manifest(ManifestCategory category, std::filesystem::path const& path) { +void PlatformShim::add_manifest_to_registry(ManifestCategory category, std::filesystem::path const& path) { if (category == ManifestCategory::settings) { hkey_local_machine_settings.emplace_back(path); } else if (category == ManifestCategory::implicit_layer) { @@ -106,7 +99,7 @@ void PlatformShim::add_manifest(ManifestCategory category, std::filesystem::path } } -void PlatformShim::add_unsecured_manifest(ManifestCategory category, std::filesystem::path const& path) { +void PlatformShim::add_unsecured_manifest_to_registry(ManifestCategory category, std::filesystem::path const& path) { if (category == ManifestCategory::settings) { hkey_current_user_settings.emplace_back(path); } else if (category == ManifestCategory::implicit_layer) { @@ -146,9 +139,7 @@ void PlatformShim::add_CM_Device_ID([[maybe_unused]] std::wstring const& id, [[m // // add_key_value_string(id_key, "VulkanLayerName", layer_path.c_str()); } -void PlatformShim::redirect_category(std::filesystem::path const&, ManifestCategory) {} - -#elif COMMON_UNIX_PLATFORMS +#elif TESTING_COMMON_UNIX_PLATFORMS #include #include @@ -162,93 +153,12 @@ std::string category_path_name(ManifestCategory category) { return "icd.d"; } -void PlatformShim::reset() { redirection_map.clear(); } - -bool PlatformShim::is_fake_path(std::filesystem::path const& path) { return redirection_map.count(path) > 0; } -std::filesystem::path const& PlatformShim::get_real_path_from_fake_path(std::filesystem::path const& path) { - return redirection_map.at(path); -} -void PlatformShim::redirect_path(std::filesystem::path const& path, std::filesystem::path const& new_path) { - redirection_map[path] = new_path; -} -void PlatformShim::remove_redirect(std::filesystem::path const& path) { redirection_map.erase(path); } - -bool PlatformShim::is_known_path(std::filesystem::path const& path) { return known_path_set.count(path) > 0; } -void PlatformShim::add_known_path(std::filesystem::path const& path) { known_path_set.insert(path); } -void PlatformShim::remove_known_path(std::filesystem::path const& path) { known_path_set.erase(path); } - -void PlatformShim::add_manifest([[maybe_unused]] ManifestCategory category, [[maybe_unused]] std::filesystem::path const& path) {} -void PlatformShim::add_unsecured_manifest([[maybe_unused]] ManifestCategory category, - [[maybe_unused]] std::filesystem::path const& path) {} - -void PlatformShim::set_app_package_path(std::filesystem::path const& path) {} - -void parse_and_add_env_var_override(std::vector& paths, std::string env_var_contents) { - auto parsed_paths = parse_env_var_list(env_var_contents); - paths.insert(paths.end(), parsed_paths.begin(), parsed_paths.end()); -} - -void PlatformShim::redirect_category(std::filesystem::path const& new_path, ManifestCategory category) { - std::vector paths; - auto home = std::filesystem::path(get_env_var("HOME")); - if (category == ManifestCategory::settings) { - redirect_path(home / ".local/share/vulkan" / category_path_name(category), new_path); - return; - } - - if (!home.empty()) { - paths.push_back((home / ".config")); - paths.push_back((home / ".local/share")); - } - // Don't report errors on apple - these env-vars are not suppose to be defined - bool report_errors = true; -#if defined(__APPLE__) - report_errors = false; -#endif - parse_and_add_env_var_override(paths, get_env_var("XDG_CONFIG_HOME", report_errors)); - if (category == ManifestCategory::explicit_layer) { - parse_and_add_env_var_override(paths, get_env_var("VK_LAYER_PATH", false)); // don't report failure - } - if (category == ManifestCategory::implicit_layer) { - parse_and_add_env_var_override(paths, get_env_var("VK_IMPLICIT_LAYER_PATH", false)); // don't report failure - } - parse_and_add_env_var_override(paths, FALLBACK_DATA_DIRS); - parse_and_add_env_var_override(paths, FALLBACK_CONFIG_DIRS); - - auto sys_conf_dir = std::string(SYSCONFDIR); - if (!sys_conf_dir.empty()) { - paths.push_back(sys_conf_dir); - } -#if defined(EXTRASYSCONFDIR) - // EXTRASYSCONFDIR default is /etc, if SYSCONFDIR wasn't defined, it will have /etc put - // as its default. Don't want to double add it - auto extra_sys_conf_dir = std::string(EXTRASYSCONFDIR); - if (!extra_sys_conf_dir.empty() && sys_conf_dir != extra_sys_conf_dir) { - paths.push_back(extra_sys_conf_dir); - } -#endif - - for (auto& path : paths) { - if (!path.empty()) { - redirect_path(std::filesystem::path(path) / "vulkan" / category_path_name(category), new_path); - } - } -} - -void PlatformShim::set_fake_path(ManifestCategory category, std::filesystem::path const& path) { - // use /etc as the 'redirection path' by default since its always searched - redirect_path(std::filesystem::path(SYSCONFDIR) / "vulkan" / category_path_name(category), path); -} - -void PlatformShim::redirect_dlopen_name(std::filesystem::path const& filename, std::filesystem::path const& actual_path) { - dlopen_redirection_map[filename] = actual_path; -} - -bool PlatformShim::is_dlopen_redirect_name(std::filesystem::path const& filename) { - return dlopen_redirection_map.count(filename) == 1; +void PlatformShim::add_system_library(std::string const& filename, std::filesystem::path const& actual_path) { + system_library_redirection_map[filename] = actual_path; } -std::filesystem::path PlatformShim::query_default_redirect_path(ManifestCategory category) { - return std::filesystem::path(SYSCONFDIR) / "vulkan" / category_path_name(category); +std::filesystem::path PlatformShim::get_system_library(std::string const& filename) { + return system_library_redirection_map.count(filename) == 1 ? system_library_redirection_map.at(filename) + : std::filesystem::path{}; } #endif diff --git a/tests/framework/shim/unix_shim.cpp b/tests/framework/shim/unix_shim.cpp index a3abba5a8..da3fd151a 100644 --- a/tests/framework/shim/unix_shim.cpp +++ b/tests/framework/shim/unix_shim.cpp @@ -28,21 +28,26 @@ #include "shim.h" #include +#include #if defined(__APPLE__) #include #endif +#include + +#include "util/folder_manager.h" + PlatformShim platform_shim; extern "C" { #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__GNU__) || defined(__QNX__) -PlatformShim* get_platform_shim(GetFoldersFunc get_folders_by_name_function) { - platform_shim = PlatformShim(get_folders_by_name_function); +PlatformShim* get_platform_shim() { + platform_shim = PlatformShim(); return &platform_shim; } #elif defined(__APPLE__) -FRAMEWORK_EXPORT PlatformShim* get_platform_shim(GetFoldersFunc get_folders_by_name_function) { - platform_shim = PlatformShim(get_folders_by_name_function); +FRAMEWORK_EXPORT PlatformShim* get_platform_shim() { + platform_shim = PlatformShim(); return &platform_shim; } #endif @@ -141,20 +146,16 @@ FRAMEWORK_EXPORT DIR* OPENDIR_FUNC_NAME(const char* path_name) { #if !defined(__APPLE__) if (!real_opendir) real_opendir = (PFN_OPENDIR)dlsym(RTLD_NEXT, "opendir"); #endif - if (platform_shim.is_during_destruction) { + if (platform_shim.is_during_destruction || !platform_shim.is_finished_setup) { return real_opendir(path_name); } - DIR* dir; - if (platform_shim.is_fake_path(path_name)) { - auto real_path_name = platform_shim.get_real_path_from_fake_path(std::filesystem::path(path_name)); - dir = real_opendir(real_path_name.c_str()); - platform_shim.dir_entries.push_back(DirEntry{dir, std::string(path_name), {}, 0, true}); - } else if (platform_shim.is_known_path(path_name)) { - dir = real_opendir(path_name); - platform_shim.dir_entries.push_back(DirEntry{dir, std::string(path_name), {}, 0, false}); - } else { - dir = real_opendir(path_name); + DIR* dir = nullptr; + + if (auto* folder = platform_shim.file_system_manager->get_folder_for_given_path(path_name); folder != nullptr) { + dir = real_opendir(folder->location().c_str()); + platform_shim.dir_entries.push_back(DirEntry{dir, folder, {}, 0}); } + // Dont pass through as this allows non-test paths to escape containment return dir; } @@ -171,18 +172,19 @@ FRAMEWORK_EXPORT struct dirent* READDIR_FUNC_NAME(DIR* dir_stream) { } #endif - if (platform_shim.is_during_destruction) { + if (platform_shim.is_during_destruction || !platform_shim.is_finished_setup) { return real_readdir(dir_stream); } auto it = std::find_if(platform_shim.dir_entries.begin(), platform_shim.dir_entries.end(), [dir_stream](DirEntry const& entry) { return entry.directory == dir_stream; }); + // Folder has not been opened with opendir previously, probably not a loader call so we should ignore it if (it == platform_shim.dir_entries.end()) { return real_readdir(dir_stream); } // Folder was found but this is the first file to be read from it if (it->current_index == 0) { - std::vector folder_contents; + std::vector readdir_contents; std::vector dirent_filenames; while (true) { errno = 0; @@ -198,21 +200,17 @@ FRAMEWORK_EXPORT struct dirent* READDIR_FUNC_NAME(DIR* dir_stream) { (dir_entry->d_name[0] == '.' && dir_entry->d_name[1] == '\0')) { continue; } - folder_contents.push_back(dir_entry); + readdir_contents.push_back(dir_entry); dirent_filenames.push_back(&dir_entry->d_name[0]); } - auto real_path = it->folder_path; - if (it->is_fake_path) { - real_path = platform_shim.redirection_map.at(it->folder_path); - } - auto filenames = platform_shim.get_folders_by_name_function(real_path.c_str()); - // Add the dirent structures in the order they appear in the FolderManager - // Ignore anything which wasn't in the FolderManager - for (auto const& file : filenames) { + // Add the dirent structures in the order they appear in the list of files provided by the Folder + // Ignore anything which wasn't in the Folder + auto folder_contents = it->folder_represented->get_files(); + for (auto const& file : folder_contents) { for (size_t i = 0; i < dirent_filenames.size(); i++) { if (file == dirent_filenames.at(i)) { - it->contents.push_back(folder_contents.at(i)); + it->contents.push_back(readdir_contents.at(i)); break; } } @@ -226,7 +224,7 @@ FRAMEWORK_EXPORT int CLOSEDIR_FUNC_NAME(DIR* dir_stream) { #if !defined(__APPLE__) if (!real_closedir) real_closedir = (PFN_CLOSEDIR)dlsym(RTLD_NEXT, "closedir"); #endif - if (platform_shim.is_during_destruction) { + if (platform_shim.is_during_destruction || !platform_shim.is_finished_setup) { return real_closedir(dir_stream); } auto it = std::find_if(platform_shim.dir_entries.begin(), platform_shim.dir_entries.end(), @@ -243,48 +241,62 @@ FRAMEWORK_EXPORT int ACCESS_FUNC_NAME(const char* in_pathname, int mode) { #if !defined(__APPLE__) if (!real_access) real_access = (PFN_ACCESS)dlsym(RTLD_NEXT, "access"); #endif - std::filesystem::path path{in_pathname}; - if (!path.has_parent_path()) { + if (platform_shim.is_during_destruction || !platform_shim.is_finished_setup) { return real_access(in_pathname, mode); } - if (platform_shim.is_fake_path(path.parent_path())) { - std::filesystem::path real_path = platform_shim.get_real_path_from_fake_path(path.parent_path()); + std::filesystem::path path{in_pathname}; + if (!path.has_parent_path()) { + return real_access(in_pathname, mode); + } else if (auto real_path = platform_shim.file_system_manager->get_real_path_of_redirected_path(path.parent_path()); + !real_path.empty()) { real_path /= path.filename(); return real_access(real_path.c_str(), mode); + } else if (platform_shim.file_system_manager->is_folder_path(path.parent_path())) { + return real_access(in_pathname, mode); + } else { + return -1; } - return real_access(in_pathname, mode); } FRAMEWORK_EXPORT FILE* FOPEN_FUNC_NAME(const char* in_filename, const char* mode) { #if !defined(__APPLE__) if (!real_fopen) real_fopen = (PFN_FOPEN)dlsym(RTLD_NEXT, "fopen"); #endif - std::filesystem::path path{in_filename}; - if (!path.has_parent_path()) { + if (platform_shim.is_during_destruction || !platform_shim.is_finished_setup) { return real_fopen(in_filename, mode); } - FILE* f_ptr; - if (platform_shim.is_fake_path(path.parent_path())) { - auto real_path = platform_shim.get_real_path_from_fake_path(path.parent_path()) / path.filename(); - f_ptr = real_fopen(real_path.c_str(), mode); + std::filesystem::path path{in_filename}; + if (!path.has_parent_path()) { + return real_fopen(in_filename, mode); + } else if (auto real_path = platform_shim.file_system_manager->get_real_path_of_redirected_path(path.parent_path()); + !real_path.empty()) { + real_path /= path.filename(); + return real_fopen(real_path.c_str(), mode); } else { - f_ptr = real_fopen(in_filename, mode); + return real_fopen(in_filename, mode); } - - return f_ptr; } FRAMEWORK_EXPORT void* DLOPEN_FUNC_NAME(const char* in_filename, int flags) { #if !defined(__APPLE__) if (!real_dlopen) real_dlopen = (PFN_DLOPEN)dlsym(RTLD_NEXT, "dlopen"); #endif + if (platform_shim.is_during_destruction || !platform_shim.is_finished_setup) { + return real_dlopen(in_filename, flags); + } - if (platform_shim.is_dlopen_redirect_name(in_filename)) { - return real_dlopen(platform_shim.dlopen_redirection_map[in_filename].c_str(), flags); + std::filesystem::path path{in_filename}; + if (auto real_path = platform_shim.get_system_library(path); !real_path.empty()) { + return real_dlopen(real_path.c_str(), flags); + } else if (auto real_path = platform_shim.file_system_manager->get_real_path_of_redirected_path(path.parent_path()); + !real_path.empty()) { + real_path /= path.filename(); + return real_dlopen(real_path.c_str(), flags); + } else { + return real_dlopen(in_filename, flags); } - return real_dlopen(in_filename, flags); } FRAMEWORK_EXPORT uid_t GETEUID_FUNC_NAME(void) { diff --git a/tests/framework/shim/windows_shim.cpp b/tests/framework/shim/windows_shim.cpp index 1ca1a68e2..1c6bc0b9f 100644 --- a/tests/framework/shim/windows_shim.cpp +++ b/tests/framework/shim/windows_shim.cpp @@ -31,6 +31,10 @@ #include #endif +#include + +#include + #include #include @@ -38,6 +42,9 @@ #include "detours.h" +#include "util/dynamic_library_wrapper.h" +#include "util/wide_char_handling.h" + static PlatformShim platform_shim; extern "C" { @@ -512,8 +519,5 @@ BOOL WINAPI DllMain([[maybe_unused]] HINSTANCE hinst, DWORD dwReason, [[maybe_un } return TRUE; } -FRAMEWORK_EXPORT PlatformShim *get_platform_shim(GetFoldersFunc get_folders_by_name_function) { - platform_shim = PlatformShim(get_folders_by_name_function); - return &platform_shim; -} +FRAMEWORK_EXPORT PlatformShim *get_platform_shim() { return &platform_shim; } } diff --git a/tests/framework/test_environment.cpp b/tests/framework/test_environment.cpp index 0dea3f794..261d21149 100644 --- a/tests/framework/test_environment.cpp +++ b/tests/framework/test_environment.cpp @@ -27,151 +27,11 @@ #include "test_environment.h" +#include #include +#include -std::filesystem::path get_loader_path() { - auto loader_path = std::filesystem::path(FRAMEWORK_VULKAN_LIBRARY_PATH); - auto env_var_res = get_env_var("VK_LOADER_TEST_LOADER_PATH", false); - if (!env_var_res.empty()) { - loader_path = std::filesystem::path(env_var_res); - } - return loader_path; -} - -void init_vulkan_functions(VulkanFunctions& funcs) { -#if defined(APPLE_STATIC_LOADER) -#define GPA(name) name -#else -#define GPA(name) funcs.loader.get_symbol(#name) -#endif - - // clang-format off - funcs.vkGetInstanceProcAddr = GPA(vkGetInstanceProcAddr); - funcs.vkEnumerateInstanceExtensionProperties = GPA(vkEnumerateInstanceExtensionProperties); - funcs.vkEnumerateInstanceLayerProperties = GPA(vkEnumerateInstanceLayerProperties); - funcs.vkEnumerateInstanceVersion = GPA(vkEnumerateInstanceVersion); - funcs.vkCreateInstance = GPA(vkCreateInstance); - funcs.vkDestroyInstance = GPA(vkDestroyInstance); - funcs.vkEnumeratePhysicalDevices = GPA(vkEnumeratePhysicalDevices); - funcs.vkEnumeratePhysicalDeviceGroups = GPA(vkEnumeratePhysicalDeviceGroups); - funcs.vkGetPhysicalDeviceFeatures = GPA(vkGetPhysicalDeviceFeatures); - funcs.vkGetPhysicalDeviceFeatures2 = GPA(vkGetPhysicalDeviceFeatures2); - funcs.vkGetPhysicalDeviceFormatProperties = GPA(vkGetPhysicalDeviceFormatProperties); - funcs.vkGetPhysicalDeviceFormatProperties2 = GPA(vkGetPhysicalDeviceFormatProperties2); - funcs.vkGetPhysicalDeviceImageFormatProperties = GPA(vkGetPhysicalDeviceImageFormatProperties); - funcs.vkGetPhysicalDeviceImageFormatProperties2 = GPA(vkGetPhysicalDeviceImageFormatProperties2); - funcs.vkGetPhysicalDeviceSparseImageFormatProperties = GPA(vkGetPhysicalDeviceSparseImageFormatProperties); - funcs.vkGetPhysicalDeviceSparseImageFormatProperties2 = GPA(vkGetPhysicalDeviceSparseImageFormatProperties2); - funcs.vkGetPhysicalDeviceProperties = GPA(vkGetPhysicalDeviceProperties); - funcs.vkGetPhysicalDeviceProperties2 = GPA(vkGetPhysicalDeviceProperties2); - funcs.vkGetPhysicalDeviceQueueFamilyProperties = GPA(vkGetPhysicalDeviceQueueFamilyProperties); - funcs.vkGetPhysicalDeviceQueueFamilyProperties2 = GPA(vkGetPhysicalDeviceQueueFamilyProperties2); - funcs.vkGetPhysicalDeviceMemoryProperties = GPA(vkGetPhysicalDeviceMemoryProperties); - funcs.vkGetPhysicalDeviceMemoryProperties2 = GPA(vkGetPhysicalDeviceMemoryProperties2); - funcs.vkGetPhysicalDeviceSurfaceSupportKHR = GPA(vkGetPhysicalDeviceSurfaceSupportKHR); - funcs.vkGetPhysicalDeviceSurfaceFormatsKHR = GPA(vkGetPhysicalDeviceSurfaceFormatsKHR); - funcs.vkGetPhysicalDeviceSurfacePresentModesKHR = GPA(vkGetPhysicalDeviceSurfacePresentModesKHR); - funcs.vkGetPhysicalDeviceSurfaceCapabilitiesKHR = GPA(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); - funcs.vkEnumerateDeviceExtensionProperties = GPA(vkEnumerateDeviceExtensionProperties); - funcs.vkEnumerateDeviceLayerProperties = GPA(vkEnumerateDeviceLayerProperties); - funcs.vkGetPhysicalDeviceExternalBufferProperties = GPA(vkGetPhysicalDeviceExternalBufferProperties); - funcs.vkGetPhysicalDeviceExternalFenceProperties = GPA(vkGetPhysicalDeviceExternalFenceProperties); - funcs.vkGetPhysicalDeviceExternalSemaphoreProperties = GPA(vkGetPhysicalDeviceExternalSemaphoreProperties); - - funcs.vkDestroySurfaceKHR = GPA(vkDestroySurfaceKHR); - funcs.vkGetDeviceProcAddr = GPA(vkGetDeviceProcAddr); - funcs.vkCreateDevice = GPA(vkCreateDevice); - - funcs.vkCreateHeadlessSurfaceEXT = GPA(vkCreateHeadlessSurfaceEXT); - funcs.vkCreateDisplayPlaneSurfaceKHR = GPA(vkCreateDisplayPlaneSurfaceKHR); - funcs.vkGetPhysicalDeviceDisplayPropertiesKHR = GPA(vkGetPhysicalDeviceDisplayPropertiesKHR); - funcs.vkGetPhysicalDeviceDisplayPlanePropertiesKHR = GPA(vkGetPhysicalDeviceDisplayPlanePropertiesKHR); - funcs.vkGetDisplayPlaneSupportedDisplaysKHR = GPA(vkGetDisplayPlaneSupportedDisplaysKHR); - funcs.vkGetDisplayModePropertiesKHR = GPA(vkGetDisplayModePropertiesKHR); - funcs.vkCreateDisplayModeKHR = GPA(vkCreateDisplayModeKHR); - funcs.vkGetDisplayPlaneCapabilitiesKHR = GPA(vkGetDisplayPlaneCapabilitiesKHR); - funcs.vkGetPhysicalDevicePresentRectanglesKHR = GPA(vkGetPhysicalDevicePresentRectanglesKHR); - funcs.vkGetPhysicalDeviceDisplayProperties2KHR = GPA(vkGetPhysicalDeviceDisplayProperties2KHR); - funcs.vkGetPhysicalDeviceDisplayPlaneProperties2KHR = GPA(vkGetPhysicalDeviceDisplayPlaneProperties2KHR); - funcs.vkGetDisplayModeProperties2KHR = GPA(vkGetDisplayModeProperties2KHR); - funcs.vkGetDisplayPlaneCapabilities2KHR = GPA(vkGetDisplayPlaneCapabilities2KHR); - funcs.vkGetPhysicalDeviceSurfaceCapabilities2KHR = GPA(vkGetPhysicalDeviceSurfaceCapabilities2KHR); - funcs.vkGetPhysicalDeviceSurfaceFormats2KHR = GPA(vkGetPhysicalDeviceSurfaceFormats2KHR); - -#if defined(VK_USE_PLATFORM_ANDROID_KHR) - funcs.vkCreateAndroidSurfaceKHR = GPA(vkCreateAndroidSurfaceKHR); -#endif // VK_USE_PLATFORM_ANDROID_KHR -#if defined(VK_USE_PLATFORM_DIRECTFB_EXT) - funcs.vkCreateDirectFBSurfaceEXT = GPA(vkCreateDirectFBSurfaceEXT); - funcs.vkGetPhysicalDeviceDirectFBPresentationSupportEXT = GPA(vkGetPhysicalDeviceDirectFBPresentationSupportEXT); -#endif // VK_USE_PLATFORM_DIRECTFB_EXT -#if defined(VK_USE_PLATFORM_FUCHSIA) - funcs.vkCreateImagePipeSurfaceFUCHSIA = GPA(vkCreateImagePipeSurfaceFUCHSIA); -#endif // VK_USE_PLATFORM_FUCHSIA -#if defined(VK_USE_PLATFORM_GGP) - funcs.vkCreateStreamDescriptorSurfaceGGP = GPA(vkCreateStreamDescriptorSurfaceGGP); -#endif // VK_USE_PLATFORM_GGP -#if defined(VK_USE_PLATFORM_IOS_MVK) - funcs.vkCreateIOSSurfaceMVK = GPA(vkCreateIOSSurfaceMVK); -#endif // VK_USE_PLATFORM_IOS_MVK -#if defined(VK_USE_PLATFORM_MACOS_MVK) - funcs.vkCreateMacOSSurfaceMVK = GPA(vkCreateMacOSSurfaceMVK); -#endif // VK_USE_PLATFORM_MACOS_MVK -#if defined(VK_USE_PLATFORM_METAL_EXT) - funcs.vkCreateMetalSurfaceEXT = GPA(vkCreateMetalSurfaceEXT); -#endif // VK_USE_PLATFORM_METAL_EXT -#if defined(VK_USE_PLATFORM_SCREEN_QNX) - funcs.vkCreateScreenSurfaceQNX = GPA(vkCreateScreenSurfaceQNX); - funcs.vkGetPhysicalDeviceScreenPresentationSupportQNX = GPA(vkGetPhysicalDeviceScreenPresentationSupportQNX); -#endif // VK_USE_PLATFORM_SCREEN_QNX -#if defined(VK_USE_PLATFORM_WAYLAND_KHR) - funcs.vkCreateWaylandSurfaceKHR = GPA(vkCreateWaylandSurfaceKHR); - funcs.vkGetPhysicalDeviceWaylandPresentationSupportKHR = GPA(vkGetPhysicalDeviceWaylandPresentationSupportKHR); -#endif // VK_USE_PLATFORM_WAYLAND_KHR -#if defined(VK_USE_PLATFORM_XCB_KHR) - funcs.vkCreateXcbSurfaceKHR = GPA(vkCreateXcbSurfaceKHR); - funcs.vkGetPhysicalDeviceXcbPresentationSupportKHR = GPA(vkGetPhysicalDeviceXcbPresentationSupportKHR); -#endif // VK_USE_PLATFORM_XCB_KHR -#if defined(VK_USE_PLATFORM_XLIB_KHR) - funcs.vkCreateXlibSurfaceKHR = GPA(vkCreateXlibSurfaceKHR); - funcs.vkGetPhysicalDeviceXlibPresentationSupportKHR = GPA(vkGetPhysicalDeviceXlibPresentationSupportKHR); -#endif // VK_USE_PLATFORM_XLIB_KHR -#if defined(VK_USE_PLATFORM_WIN32_KHR) - funcs.vkCreateWin32SurfaceKHR = GPA(vkCreateWin32SurfaceKHR); - funcs.vkGetPhysicalDeviceWin32PresentationSupportKHR = GPA(vkGetPhysicalDeviceWin32PresentationSupportKHR); -#endif // VK_USE_PLATFORM_WIN32_KHR - funcs.vkDestroyDevice = GPA(vkDestroyDevice); - funcs.vkGetDeviceQueue = GPA(vkGetDeviceQueue); -#undef GPA - // clang-format on -} - -#if defined(APPLE_STATIC_LOADER) -VulkanFunctions::VulkanFunctions() { -#else -VulkanFunctions::VulkanFunctions() : loader(get_loader_path()) { -#endif - init_vulkan_functions(*this); -} - -void VulkanFunctions::load_instance_functions(VkInstance instance) { - vkCreateDebugReportCallbackEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")); - vkDestroyDebugReportCallbackEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT")); - vkCreateDebugUtilsMessengerEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); - vkDestroyDebugUtilsMessengerEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")); -} - -DeviceFunctions::DeviceFunctions(const VulkanFunctions& vulkan_functions, VkDevice device) { - vkGetDeviceProcAddr = vulkan_functions.vkGetDeviceProcAddr; - vkDestroyDevice = load(device, "vkDestroyDevice"); - vkGetDeviceQueue = load(device, "vkGetDeviceQueue"); - vkCreateCommandPool = load(device, "vkCreateCommandPool"); - vkAllocateCommandBuffers = load(device, "vkAllocateCommandBuffers"); - vkDestroyCommandPool = load(device, "vkDestroyCommandPool"); - vkCreateSwapchainKHR = load(device, "vkCreateSwapchainKHR"); - vkGetSwapchainImagesKHR = load(device, "vkGetSwapchainImagesKHR"); - vkDestroySwapchainKHR = load(device, "vkDestroySwapchainKHR"); -} +#include "json_writer.h" InstWrapper::InstWrapper(VulkanFunctions& functions, VkAllocationCallbacks* callbacks) noexcept : functions(&functions), callbacks(callbacks) {} @@ -341,139 +201,23 @@ bool FindPrefixPostfixStringOnLine(DebugUtilsLogger const& env_log, const char* return env_log.find_prefix_then_postfix(prefix, postfix); } -namespace fs { -FolderManager::FolderManager(std::filesystem::path root_path, std::string name) noexcept : folder(root_path / name) { - clear(); - // Don't actually create the folder yet, as we will do it on demand -} -FolderManager::~FolderManager() noexcept { clear(); } -FolderManager::FolderManager(FolderManager&& other) noexcept : actually_created(other.actually_created), folder(other.folder) { - other.folder.clear(); -} -FolderManager& FolderManager::operator=(FolderManager&& other) noexcept { - folder = other.folder; - actually_created = other.actually_created; - other.folder.clear(); - return *this; -} - -void FolderManager::check_if_first_use() { - if (!actually_created) { - if (!::testing::internal::InDeathTestChild()) { - std::error_code err; - if (!std::filesystem::create_directories(folder, err)) { - std::cerr << "Failed to create folder " << folder << " because " << err.message() << "\n"; - } - } - actually_created = true; - } -} - -std::filesystem::path FolderManager::write_manifest(std::filesystem::path const& name, std::string const& contents) { - check_if_first_use(); - std::filesystem::path out_path = folder / name; - if (!::testing::internal::InDeathTestChild()) { - auto file = std::ofstream(out_path, std::ios_base::trunc | std::ios_base::out); - if (!file) { - std::cerr << "Failed to create manifest " << name << " at " << out_path << "\n"; - return out_path; - } - file << contents << std::endl; - } - insert_file_to_tracking(name); - return out_path; -} - -// close file handle, delete file, remove `name` from managed file list. -void FolderManager::remove(std::filesystem::path const& name) { - check_if_first_use(); - std::filesystem::path out_path = folder / name; - if (!::testing::internal::InDeathTestChild()) { - std::error_code err; - if (!std::filesystem::remove(out_path, err)) { - std::cerr << "Failed to remove file " << name << " at " << out_path << " because " << err.message() << "\n"; - } - } - - auto found = std::find(added_files.begin(), added_files.end(), name); - if (found != added_files.end()) { - added_files.erase(found); - } else { - std::cout << "File " << name << " not in tracked files of folder " << folder << ".\n"; - } -} - -// copy file into this folder -std::filesystem::path FolderManager::copy_file(std::filesystem::path const& file, std::filesystem::path const& new_name) { - check_if_first_use(); - insert_file_to_tracking(new_name); - - auto new_filepath = folder / new_name; - if (!::testing::internal::InDeathTestChild()) { - std::error_code err; - if (!std::filesystem::copy_file(file, new_filepath, err)) { - std::cerr << "Failed to copy file " << file << " to " << new_filepath << " because " << err.message() << "\n"; - } - } - return new_filepath; -} - -std::vector FolderManager::get_files() const { return added_files; } - -std::filesystem::path FolderManager::add_symlink(std::filesystem::path const& target, std::filesystem::path const& link_name) { - check_if_first_use(); - - if (!::testing::internal::InDeathTestChild()) { - std::error_code err; - std::filesystem::create_symlink(target, folder / link_name, err); - if (err.value() != 0) { - std::cerr << "Failed to create symlink with target" << target << " with name " << folder / link_name << " because " - << err.message() << "\n"; - } - } - insert_file_to_tracking(link_name); - return folder / link_name; -} -void FolderManager::insert_file_to_tracking(std::filesystem::path const& name) { - auto found = std::find(added_files.begin(), added_files.end(), name); - if (found != added_files.end()) { - std::cout << "Overwriting manifest " << name << ". Was this intended?\n"; - } else { - added_files.emplace_back(name); - } -} - -void FolderManager::clear() const noexcept { - if (!::testing::internal::InDeathTestChild()) { - std::error_code err; - std::filesystem::remove_all(folder, err); - if (err.value() != 0) { - std::cerr << "Failed to remove folder " << folder << " because " << err.message() << "\n"; - } - } -} - -} // namespace fs - -PlatformShimWrapper::PlatformShimWrapper(GetFoldersFunc get_folders_by_name_function, const char* log_filter) noexcept +PlatformShimWrapper::PlatformShimWrapper(fs::FileSystemManager& file_system_manager, const char* log_filter) noexcept : loader_logging{"VK_LOADER_DEBUG"} { #if defined(WIN32) || defined(__APPLE__) shim_library = LibraryWrapper(SHIM_LIBRARY_NAME); - PFN_get_platform_shim get_platform_shim_func = shim_library.get_symbol(GET_PLATFORM_SHIM_STR); - assert(get_platform_shim_func != NULL && "Must be able to get \"platform_shim\""); - platform_shim = get_platform_shim_func(get_folders_by_name_function); + PFN_get_platform_shim get_platform_shim = shim_library.get_symbol(GET_PLATFORM_SHIM_STR); + assert(get_platform_shim != NULL && "Must be able to get \"platform_shim\""); + platform_shim = get_platform_shim(); #elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__GNU__) - platform_shim = get_platform_shim(get_folders_by_name_function); + platform_shim = get_platform_shim(); #endif - platform_shim->reset(); + platform_shim->file_system_manager = &file_system_manager; if (log_filter) { loader_logging.set_new_value(log_filter); } } -PlatformShimWrapper::~PlatformShimWrapper() noexcept { platform_shim->reset(); } - TestICDHandle::TestICDHandle() noexcept {} TestICDHandle::TestICDHandle(std::filesystem::path const& icd_path) noexcept : icd_library(icd_path) { proc_addr_get_test_icd = icd_library.get_symbol(GET_TEST_ICD_FUNC_STR); @@ -487,7 +231,7 @@ TestICD& TestICDHandle::reset_icd() noexcept { assert(proc_addr_reset_icd != NULL && "symbol must be loaded before use"); return *proc_addr_reset_icd(); } -std::filesystem::path TestICDHandle::get_icd_full_path() noexcept { return icd_library.lib_path; } +std::filesystem::path TestICDHandle::get_icd_full_path() noexcept { return icd_library.get_path(); } std::filesystem::path TestICDHandle::get_icd_manifest_path() noexcept { return manifest_path; } std::filesystem::path TestICDHandle::get_shimmed_manifest_path() noexcept { return shimmed_manifest_path; } @@ -504,78 +248,101 @@ TestLayer& TestLayerHandle::reset_layer() noexcept { assert(proc_addr_reset_layer != NULL && "symbol must be loaded before use"); return *proc_addr_reset_layer(); } -std::filesystem::path TestLayerHandle::get_layer_full_path() noexcept { return layer_library.lib_path; } +std::filesystem::path TestLayerHandle::get_layer_full_path() noexcept { return layer_library.get_path(); } std::filesystem::path TestLayerHandle::get_layer_manifest_path() noexcept { return manifest_path; } std::filesystem::path TestLayerHandle::get_shimmed_manifest_path() noexcept { return shimmed_manifest_path; } FrameworkEnvironment::FrameworkEnvironment() noexcept : FrameworkEnvironment(FrameworkSettings{}) {} FrameworkEnvironment::FrameworkEnvironment(FrameworkSettings const& settings) noexcept - : settings(settings), - test_folder(std::filesystem::path(FRAMEWORK_BUILD_DIRECTORY), - std::string(::testing::UnitTest::GetInstance()->GetInstance()->current_test_suite()->name()) + "_" + - ::testing::UnitTest::GetInstance()->current_test_info()->name()), - platform_shim( - [this](const char* folder_name) -> std::vector { - for (auto& folder : folders) { - if (folder.location() == folder_name) { - return folder.get_files(); - } - } - return {}; - }, - settings.log_filter) { - // Clean out test folder in case a previous run's files are still around - test_folder.clear(); - - // This order is important, it matches the enum ManifestLocation, used to index the folders vector - folders.emplace_back(test_folder.location(), std::string("null_dir")); - folders.emplace_back(test_folder.location(), std::string("icd_manifests")); - folders.emplace_back(test_folder.location(), std::string("icd_env_vars_manifests")); - folders.emplace_back(test_folder.location(), std::string("explicit_layer_manifests")); - folders.emplace_back(test_folder.location(), std::string("explicit_env_var_layer_folder")); - folders.emplace_back(test_folder.location(), std::string("explicit_add_env_var_layer_folder")); - folders.emplace_back(test_folder.location(), std::string("implicit_layer_manifests")); - folders.emplace_back(test_folder.location(), std::string("implicit_env_var_layer_manifests")); - folders.emplace_back(test_folder.location(), std::string("implicit_add_env_var_layer_manifests")); - folders.emplace_back(test_folder.location(), std::string("override_layer_manifests")); - folders.emplace_back(test_folder.location(), std::string("app_package_manifests")); - folders.emplace_back(test_folder.location(), std::string("macos_bundle")); - folders.emplace_back(test_folder.location(), std::string("unsecured_location")); - folders.emplace_back(test_folder.location(), std::string("settings_location")); - - platform_shim->redirect_all_paths(get_folder(ManifestLocation::null).location()); - if (settings.enable_default_search_paths) { - platform_shim->set_fake_path(ManifestCategory::icd, get_folder(ManifestLocation::driver).location()); - platform_shim->set_fake_path(ManifestCategory::explicit_layer, get_folder(ManifestLocation::explicit_layer).location()); - platform_shim->set_fake_path(ManifestCategory::implicit_layer, get_folder(ManifestLocation::implicit_layer).location()); -#if COMMON_UNIX_PLATFORMS - auto home = get_env_var("HOME"); - auto unsecured_location = get_folder(ManifestLocation::unsecured_location).location(); - platform_shim->redirect_path(home + "/.local/share/vulkan/icd.d", unsecured_location); - platform_shim->redirect_path(home + "/.local/share/vulkan/implicit_layer.d", unsecured_location); - platform_shim->redirect_path(home + "/.local/share/vulkan/explicit_layer.d", unsecured_location); + : settings(settings), platform_shim(file_system_manager, settings.log_filter) { + // Setup default path redirections + + platform_shim->set_elevated_privilege(settings.run_as_if_with_elevated_privleges); + +#if TESTING_COMMON_UNIX_PLATFORMS + if (!settings.home_env_var.empty()) env_var_home.set_new_value(settings.home_env_var); +#if !defined(__APPLE__) + if (!settings.xdg_config_home_env_var.empty()) env_var_xdg_config_home.set_new_value(settings.xdg_config_home_env_var); + if (!settings.xdg_config_dirs_env_var.empty()) env_var_xdg_config_dirs.set_new_value(settings.xdg_config_dirs_env_var); + if (!settings.xdg_data_home_env_var.empty()) env_var_xdg_data_home.set_new_value(settings.xdg_data_home_env_var); + if (!settings.xdg_data_dirs_env_var.empty()) env_var_xdg_data_dirs.set_new_value(settings.xdg_data_dirs_env_var); #endif +#endif + + const std::array, 4> secured_redirection_map = { + std::pair{"icd.d", ManifestLocation::driver}, + std::pair{"implicit_layer.d", ManifestLocation::implicit_layer}, + std::pair{"explicit_layer.d", ManifestLocation::explicit_layer}, + std::pair{"loader_settings.d", ManifestLocation::settings_location}, + }; + + const std::array, 4> unsecured_redirection_map = { + std::pair{"icd.d", ManifestLocation::unsecured_driver}, + std::pair{"implicit_layer.d", ManifestLocation::unsecured_implicit_layer}, + std::pair{"explicit_layer.d", ManifestLocation::unsecured_explicit_layer}, + std::pair{"loader_settings.d", ManifestLocation::unsecured_settings}, + }; + +#if TESTING_COMMON_UNIX_PLATFORMS && !defined(__APPLE__) + // Always are searching SYSCONFDIR on unix (but not apple, which is handled separately) + secure_manifest_base_location = SYSCONFDIR; + + for (auto const& [redirect, location] : secured_redirection_map) { + file_system_manager.add_path_redirect(secure_manifest_base_location + "/vulkan/" + redirect, location); } -#if COMMON_UNIX_PLATFORMS - if (settings.secure_loader_settings) { - platform_shim->redirect_path("/etc/vulkan/loader_settings.d", get_folder(ManifestLocation::settings_location).location()); - } else { - platform_shim->redirect_path(get_env_var("HOME") + "/.local/share/vulkan/loader_settings.d", - get_folder(ManifestLocation::settings_location).location()); + + // Determines which unsecure path should be used + if (!settings.run_as_if_with_elevated_privleges) { + if (!settings.xdg_config_home_env_var.empty()) { + auto env_var_list = split_env_var_as_list(settings.xdg_config_home_env_var); + unsecure_manifest_base_location = env_var_list.at(0); + if (!env_var_list.empty()) { + for (auto const& [redirect, location] : unsecured_redirection_map) { + file_system_manager.add_path_redirect(env_var_list.at(0) + "/vulkan/" + redirect, location); + } + } + } else if (!settings.xdg_data_home_env_var.empty()) { + auto env_var_list = split_env_var_as_list(settings.xdg_data_home_env_var); + if (!env_var_list.empty()) { + unsecure_manifest_base_location = env_var_list.at(0); + for (auto const& [redirect, location] : unsecured_redirection_map) { + file_system_manager.add_path_redirect(env_var_list.at(0) + "/vulkan/" + redirect, location); + } + } + } else { + std::string home = settings.home_env_var; + for (auto const& [redirect, location] : unsecured_redirection_map) { + unsecure_manifest_base_location = home + "/.config"; + file_system_manager.add_path_redirect(home + "/.config/vulkan/" + redirect, location); + } + } } #endif #if defined(__APPLE__) + // Since XDG env-var shouldn't ever be defined on apple, FALLBACK_DATA_DIRS takes over as the 'global' location to search, eg + // /usr/local/share + auto env_var_list = split_env_var_as_list(FALLBACK_DATA_DIRS); + assert(env_var_list.size() > 0 && "FALLBACK_DATA_DIRS was set to an empty path"); + secure_manifest_base_location = env_var_list.at(0); + for (auto const& [redirect, location] : secured_redirection_map) { + file_system_manager.add_path_redirect(secure_manifest_base_location + "/vulkan/" + redirect, location); + } + + std::string home = settings.home_env_var; + unsecure_manifest_base_location = home + "/.config"; + for (auto const& [redirect, location] : unsecured_redirection_map) { + file_system_manager.add_path_redirect(home + "/.config/vulkan/" + redirect, location); + } + // Necessary since bundles look in sub folders for manifests, not the test framework folder itself auto bundle_location = get_folder(ManifestLocation::macos_bundle).location(); - platform_shim->redirect_path(bundle_location / "vulkan/icd.d", bundle_location); - platform_shim->redirect_path(bundle_location / "vulkan/explicit_layer.d", bundle_location); - platform_shim->redirect_path(bundle_location / "vulkan/implicit_layer.d", bundle_location); + file_system_manager.add_path_redirect(bundle_location / "vulkan/icd.d", ManifestLocation::macos_bundle); + file_system_manager.add_path_redirect(bundle_location / "vulkan/explicit_layer.d", ManifestLocation::macos_bundle); + file_system_manager.add_path_redirect(bundle_location / "vulkan/implicit_layer.d", ManifestLocation::macos_bundle); #endif - // only set the settings file if there are elements in the app_specific_settings vector - if (!settings.loader_settings.app_specific_settings.empty()) { - update_loader_settings(settings.loader_settings); - } + + platform_shim->is_finished_setup = true; } FrameworkEnvironment::~FrameworkEnvironment() { @@ -588,23 +355,27 @@ FrameworkEnvironment::~FrameworkEnvironment() { TestICD& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcept { size_t cur_icd_index = icds.size(); - fs::FolderManager* fs_ptr = &get_folder(ManifestLocation::driver); + fs::Folder* fs_ptr = &get_folder(ManifestLocation::driver); switch (icd_details.discovery_type) { case (ManifestDiscoveryType::env_var): case (ManifestDiscoveryType::add_env_var): fs_ptr = &get_folder(ManifestLocation::driver_env_var); break; +#if defined(WIN32) case (ManifestDiscoveryType::windows_app_package): fs_ptr = &get_folder(ManifestLocation::windows_app_package); break; +#endif case (ManifestDiscoveryType::override_folder): fs_ptr = &get_folder(ManifestLocation::override_layer); break; +#if defined(__APPLE__) case (ManifestDiscoveryType::macos_bundle): fs_ptr = &get_folder(ManifestLocation::macos_bundle); break; +#endif case (ManifestDiscoveryType::unsecured_generic): - fs_ptr = &get_folder(ManifestLocation::unsecured_location); + fs_ptr = &get_folder(ManifestLocation::unsecured_driver); break; case (ManifestDiscoveryType::null_dir): case (ManifestDiscoveryType::none): @@ -623,12 +394,9 @@ TestICD& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcept { new_lib_name += icd_details.icd_manifest.lib_path.extension(); auto new_driver_location = folder.copy_file(icd_details.icd_manifest.lib_path, new_lib_name); -#if COMMON_UNIX_PLATFORMS +#if TESTING_COMMON_UNIX_PLATFORMS if (icd_details.library_path_type == LibraryPathType::default_search_paths) { - platform_shim->redirect_dlopen_name(new_lib_name, new_driver_location); - } else if (icd_details.library_path_type == LibraryPathType::relative) { - platform_shim->redirect_dlopen_name(std::filesystem::path(SYSCONFDIR) / "vulkan" / "icd.d" / "." / new_lib_name, - new_driver_location); + platform_shim->add_system_library(new_lib_name, new_driver_location); } #endif #if defined(WIN32) @@ -658,10 +426,20 @@ TestICD& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcept { icds.back().shimmed_manifest_path = icds.back().manifest_path; switch (icd_details.discovery_type) { case (ManifestDiscoveryType::generic): - platform_shim->add_manifest(ManifestCategory::icd, icds.back().manifest_path); -#if COMMON_UNIX_PLATFORMS +#if defined(WIN32) + platform_shim->add_manifest_to_registry(ManifestCategory::icd, icds.back().manifest_path); +#elif TESTING_COMMON_UNIX_PLATFORMS icds.back().shimmed_manifest_path = - platform_shim->query_default_redirect_path(ManifestCategory::icd) / new_manifest_path; + file_system_manager.get_path_redirect_by_manifest_location(ManifestLocation::driver) / new_manifest_path; +#endif + break; + case (ManifestDiscoveryType::unsecured_generic): +#if defined(WIN32) + platform_shim->add_unsecured_manifest_to_registry(ManifestCategory::icd, icds.back().manifest_path); +#elif TESTING_COMMON_UNIX_PLATFORMS + icds.back().shimmed_manifest_path = + file_system_manager.get_path_redirect_by_manifest_location(ManifestLocation::unsecured_driver) / + new_manifest_path; #endif break; case (ManifestDiscoveryType::env_var): @@ -670,7 +448,6 @@ TestICD& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcept { } else { env_var_vk_icd_filenames.add_to_list(folder.location() / new_manifest_path); } - platform_shim->add_known_path(folder.location()); break; case (ManifestDiscoveryType::add_env_var): if (icd_details.is_dir) { @@ -678,18 +455,17 @@ TestICD& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcept { } else { add_env_var_vk_icd_filenames.add_to_list(folder.location() / new_manifest_path); } - platform_shim->add_known_path(folder.location()); - break; - case (ManifestDiscoveryType::macos_bundle): - platform_shim->add_manifest(ManifestCategory::icd, icds.back().manifest_path); break; - case (ManifestDiscoveryType::unsecured_generic): - platform_shim->add_unsecured_manifest(ManifestCategory::icd, icds.back().manifest_path); break; + +#if defined(WIN32) case (ManifestDiscoveryType::windows_app_package): platform_shim->set_app_package_path(folder.location()); break; - +#endif +#if defined(__APPLE__) + case (ManifestDiscoveryType::macos_bundle): // macos_bundle contents are always discoverable by the loader +#endif case (ManifestDiscoveryType::override_folder): // should be found through override layer/settings file, not 'normal' // search paths case (ManifestDiscoveryType::null_dir): @@ -720,7 +496,8 @@ void FrameworkEnvironment::add_explicit_layer(TestLayerDetails layer_details) no } void FrameworkEnvironment::add_layer_impl(TestLayerDetails layer_details, ManifestCategory category) { - fs::FolderManager* fs_ptr = &get_folder(ManifestLocation::explicit_layer); + fs::Folder* fs_ptr = &get_folder(ManifestLocation::explicit_layer); + EnvVarWrapper* env_var_to_use = nullptr; switch (layer_details.discovery_type) { case (ManifestDiscoveryType::generic): if (category == ManifestCategory::implicit_layer) fs_ptr = &get_folder(ManifestLocation::implicit_layer); @@ -728,53 +505,48 @@ void FrameworkEnvironment::add_layer_impl(TestLayerDetails layer_details, Manife case (ManifestDiscoveryType::env_var): if (category == ManifestCategory::explicit_layer) { fs_ptr = &get_folder(ManifestLocation::explicit_layer_env_var); - if (layer_details.is_dir) { - env_var_vk_layer_paths.add_to_list(fs_ptr->location()); - } else { - env_var_vk_layer_paths.add_to_list(fs_ptr->location() / layer_details.json_name); - } - } - if (category == ManifestCategory::implicit_layer) { + env_var_to_use = &env_var_vk_layer_paths; + } else if (category == ManifestCategory::implicit_layer) { fs_ptr = &get_folder(ManifestLocation::implicit_layer_env_var); - if (layer_details.is_dir) { - env_var_vk_implicit_layer_paths.add_to_list(fs_ptr->location()); - } else { - env_var_vk_implicit_layer_paths.add_to_list(fs_ptr->location() / layer_details.json_name); - } + env_var_to_use = &env_var_vk_implicit_layer_paths; + } + if (layer_details.is_dir) { + env_var_to_use->add_to_list(fs_ptr->location()); + } else { + env_var_to_use->add_to_list(fs_ptr->location() / layer_details.json_name); } - platform_shim->add_known_path(fs_ptr->location()); break; case (ManifestDiscoveryType::add_env_var): if (category == ManifestCategory::explicit_layer) { fs_ptr = &get_folder(ManifestLocation::explicit_layer_add_env_var); - if (layer_details.is_dir) { - add_env_var_vk_layer_paths.add_to_list(fs_ptr->location()); - } else { - add_env_var_vk_layer_paths.add_to_list(fs_ptr->location() / layer_details.json_name); - } - } - if (category == ManifestCategory::implicit_layer) { + env_var_to_use = &add_env_var_vk_layer_paths; + } else if (category == ManifestCategory::implicit_layer) { fs_ptr = &get_folder(ManifestLocation::implicit_layer_add_env_var); - if (layer_details.is_dir) { - add_env_var_vk_implicit_layer_paths.add_to_list(fs_ptr->location()); - } else { - add_env_var_vk_implicit_layer_paths.add_to_list(fs_ptr->location() / layer_details.json_name); - } + env_var_to_use = &add_env_var_vk_implicit_layer_paths; + } + if (layer_details.is_dir) { + env_var_to_use->add_to_list(fs_ptr->location()); + } else { + env_var_to_use->add_to_list(fs_ptr->location() / layer_details.json_name); } - platform_shim->add_known_path(fs_ptr->location()); break; case (ManifestDiscoveryType::override_folder): fs_ptr = &get_folder(ManifestLocation::override_layer); break; +#if defined(__APPLE__) case (ManifestDiscoveryType::macos_bundle): fs_ptr = &(get_folder(ManifestLocation::macos_bundle)); break; +#endif case (ManifestDiscoveryType::unsecured_generic): - fs_ptr = &(get_folder(ManifestLocation::unsecured_location)); + fs_ptr = &(get_folder(category == ManifestCategory::implicit_layer ? ManifestLocation::unsecured_implicit_layer + : ManifestLocation::unsecured_explicit_layer)); break; +#if defined(WIN32) case (ManifestDiscoveryType::windows_app_package): fs_ptr = &(get_folder(ManifestLocation::windows_app_package)); break; +#endif case (ManifestDiscoveryType::none): case (ManifestDiscoveryType::null_dir): fs_ptr = &(get_folder(ManifestLocation::null)); @@ -791,14 +563,9 @@ void FrameworkEnvironment::add_layer_impl(TestLayerDetails layer_details, Manife auto new_layer_location = folder.copy_file(layer.lib_path, new_lib_path); -#if COMMON_UNIX_PLATFORMS +#if TESTING_COMMON_UNIX_PLATFORMS if (layer_details.library_path_type == LibraryPathType::default_search_paths) { - platform_shim->redirect_dlopen_name(new_lib_path, new_layer_location); - } - if (layer_details.library_path_type == LibraryPathType::relative) { - platform_shim->redirect_dlopen_name( - std::filesystem::path(SYSCONFDIR) / "vulkan" / category_path_name(category) / "." / new_lib_path, - new_layer_location); + platform_shim->add_system_library(new_lib_path, new_layer_location); } #endif #if defined(WIN32) @@ -828,22 +595,28 @@ void FrameworkEnvironment::add_layer_impl(TestLayerDetails layer_details, Manife if (layer_details.discovery_type != ManifestDiscoveryType::none) { // Write a manifest file to a folder as long as the discovery type isn't none auto layer_manifest_loc = folder.write_manifest(layer_details.json_name, layer_details.layer_manifest.get_manifest_str()); +#if defined(WIN32) // only add the manifest to the registry if its a generic location (as if it was installed) - both system and user local if (layer_details.discovery_type == ManifestDiscoveryType::generic) { - platform_shim->add_manifest(category, layer_manifest_loc); + platform_shim->add_manifest_to_registry(category, layer_manifest_loc); } if (layer_details.discovery_type == ManifestDiscoveryType::unsecured_generic) { - platform_shim->add_unsecured_manifest(category, layer_manifest_loc); + platform_shim->add_unsecured_manifest_to_registry(category, layer_manifest_loc); } if (layer_details.discovery_type == ManifestDiscoveryType::windows_app_package) { platform_shim->set_app_package_path(folder.location()); } +#endif for (size_t i = new_layers_start; i < layers.size(); i++) { layers.at(i).manifest_path = layer_manifest_loc; layers.at(i).shimmed_manifest_path = layer_manifest_loc; -#if COMMON_UNIX_PLATFORMS +#if TESTING_COMMON_UNIX_PLATFORMS if (layer_details.discovery_type == ManifestDiscoveryType::generic) { - layers.at(i).shimmed_manifest_path = platform_shim->query_default_redirect_path(category) / layer_details.json_name; + layers.at(i).shimmed_manifest_path = + ((category == ManifestCategory::implicit_layer) + ? file_system_manager.get_path_redirect_by_manifest_location(ManifestLocation::implicit_layer) + : file_system_manager.get_path_redirect_by_manifest_location(ManifestLocation::explicit_layer)) / + layer_details.json_name; } #endif } @@ -944,36 +717,41 @@ std::string get_loader_settings_file_contents(const LoaderSettings& loader_setti writer.EndObject(); return writer.output; } -void FrameworkEnvironment::write_settings_file(std::string const& file_contents) { - auto out_path = get_folder(ManifestLocation::settings_location).write_manifest("vk_loader_settings.json", file_contents); +void FrameworkEnvironment::write_settings_file(std::string const& file_contents, bool write_to_secure_location) { + auto location = write_to_secure_location ? ManifestLocation::settings_location : ManifestLocation::unsecured_settings; + auto out_path = get_folder(location).write_manifest("vk_loader_settings.json", file_contents); #if defined(WIN32) - platform_shim->hkey_current_user_settings.clear(); - platform_shim->hkey_local_machine_settings.clear(); + if (write_to_secure_location) { + platform_shim->hkey_local_machine_settings.clear(); + platform_shim->add_manifest_to_registry(ManifestCategory::settings, out_path); + } else { + platform_shim->hkey_current_user_settings.clear(); + platform_shim->add_unsecured_manifest_to_registry(ManifestCategory::settings, out_path); + } #endif - if (settings.secure_loader_settings) - platform_shim->add_manifest(ManifestCategory::settings, out_path); - else - platform_shim->add_unsecured_manifest(ManifestCategory::settings, out_path); } -void FrameworkEnvironment::update_loader_settings(const LoaderSettings& settings) noexcept { - write_settings_file(get_loader_settings_file_contents(settings)); +void FrameworkEnvironment::update_loader_settings(const LoaderSettings& settings, bool write_to_secure_location) noexcept { + write_settings_file(get_loader_settings_file_contents(settings), write_to_secure_location); } void FrameworkEnvironment::remove_loader_settings() { get_folder(ManifestLocation::settings_location).remove("vk_loader_settings.json"); } +void FrameworkEnvironment::write_file_from_string(std::string const& source_string, ManifestCategory category, + ManifestLocation location, std::string const& file_name) { + auto out_path = get_folder(location).write_manifest(file_name, source_string); + +#if defined(WIN32) + // Only writes to the hkey_local_machine registries, doesn't support hkey_current_user registries + platform_shim->add_manifest_to_registry(category, out_path); +#endif +} void FrameworkEnvironment::write_file_from_source(const char* source_file, ManifestCategory category, ManifestLocation location, std::string const& file_name) { std::fstream file{source_file, std::ios_base::in}; ASSERT_TRUE(file.is_open()); std::stringstream file_stream; file_stream << file.rdbuf(); - - auto out_path = get_folder(location).write_manifest(file_name, file_stream.str()); - - if (settings.secure_loader_settings) - platform_shim->add_manifest(category, out_path); - else - platform_shim->add_unsecured_manifest(category, out_path); + write_file_from_string(file_stream.str(), category, location, file_name); } TestICD& FrameworkEnvironment::get_test_icd(size_t index) noexcept { return icds[index].get_test_icd(); } @@ -998,19 +776,24 @@ std::filesystem::path FrameworkEnvironment::get_shimmed_layer_manifest_path(size return layers[index].get_shimmed_manifest_path(); } -fs::FolderManager& FrameworkEnvironment::get_folder(ManifestLocation location) noexcept { - // index it directly using the enum location since they will always be in that order - return folders.at(static_cast(location)); +fs::Folder& FrameworkEnvironment::get_folder(ManifestLocation location) noexcept { + return file_system_manager.get_folder(location); } -fs::FolderManager const& FrameworkEnvironment::get_folder(ManifestLocation location) const noexcept { - return folders.at(static_cast(location)); +fs::Folder const& FrameworkEnvironment::get_folder(ManifestLocation location) const noexcept { + return file_system_manager.get_folder(location); } #if defined(__APPLE__) void FrameworkEnvironment::setup_macos_bundle() noexcept { - platform_shim->bundle_contents = get_folder(ManifestLocation::macos_bundle).location(); + platform_shim->bundle_contents = file_system_manager.get_folder(ManifestLocation::macos_bundle).location(); } #endif +void FrameworkEnvironment::add_symlink(ManifestLocation location, std::filesystem::path const& target, + std::filesystem::path const& link_name) { + auto symlinked_path = get_folder(location).add_symlink(target, link_name); + file_system_manager.add_path_redirect(symlinked_path, location); +} + std::vector FrameworkEnvironment::GetInstanceExtensions(uint32_t expected_count, const char* layer_name) { uint32_t count = 0; VkResult res = vulkan_functions.vkEnumerateInstanceExtensionProperties(layer_name, &count, nullptr); diff --git a/tests/framework/test_environment.h b/tests/framework/test_environment.h index 98ba141d1..0a1f89e0d 100644 --- a/tests/framework/test_environment.h +++ b/tests/framework/test_environment.h @@ -46,10 +46,18 @@ #endif #endif -// Use the NDK's header on Android #include "gtest/gtest.h" -#include "test_util.h" +#include "util/builder_defines.h" +#include "util/dispatch_table.h" +#include "util/dynamic_library_wrapper.h" +#include "util/env_var_wrapper.h" +#include "util/equality_helpers.h" +#include "util/folder_manager.h" +#include "util/functions.h" +#include "util/manifest_builders.h" +#include "util/test_defines.h" +#include "util/vulkan_object_wrappers.h" #include "shim/shim.h" @@ -59,15 +67,6 @@ #include "generated/vk_result_to_string_helper.h" -#include FRAMEWORK_CONFIG_HEADER - -// Useful defines -#if COMMON_UNIX_PLATFORMS -#define HOME_DIR "/home/fake_home" -#define USER_LOCAL_SHARE_DIR HOME_DIR "/.local/share" -#define ETC_DIR "/etc" -#endif - // handle checking template void handle_assert_has_value(T const& handle) { @@ -113,152 +112,26 @@ void handle_assert_equal(size_t count, T left[], T right[]) { } } -// VulkanFunctions - loads vulkan functions for tests to use - -struct VulkanFunctions { -#if !defined(APPLE_STATIC_LOADER) - LibraryWrapper loader; -#endif - // Pre-Instance - PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr; - PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties = nullptr; - PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties = nullptr; - PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion = nullptr; - PFN_vkCreateInstance vkCreateInstance = nullptr; - - // Instance - PFN_vkDestroyInstance vkDestroyInstance = nullptr; - PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices = nullptr; - PFN_vkEnumeratePhysicalDeviceGroups vkEnumeratePhysicalDeviceGroups = nullptr; - PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures = nullptr; - PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2 = nullptr; - PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties = nullptr; - PFN_vkGetPhysicalDeviceFormatProperties2 vkGetPhysicalDeviceFormatProperties2 = nullptr; - PFN_vkGetPhysicalDeviceImageFormatProperties vkGetPhysicalDeviceImageFormatProperties = nullptr; - PFN_vkGetPhysicalDeviceImageFormatProperties2 vkGetPhysicalDeviceImageFormatProperties2 = nullptr; - PFN_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImageFormatProperties = nullptr; - PFN_vkGetPhysicalDeviceSparseImageFormatProperties2 vkGetPhysicalDeviceSparseImageFormatProperties2 = nullptr; - PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties = nullptr; - PFN_vkGetPhysicalDeviceProperties2 vkGetPhysicalDeviceProperties2 = nullptr; - PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties = nullptr; - PFN_vkGetPhysicalDeviceQueueFamilyProperties2 vkGetPhysicalDeviceQueueFamilyProperties2 = nullptr; - PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties = nullptr; - PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2 = nullptr; - PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR = nullptr; - PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR = nullptr; - PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR = nullptr; - PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR = nullptr; - PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties = nullptr; - PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties = nullptr; - PFN_vkGetPhysicalDeviceExternalBufferProperties vkGetPhysicalDeviceExternalBufferProperties = nullptr; - PFN_vkGetPhysicalDeviceExternalFenceProperties vkGetPhysicalDeviceExternalFenceProperties = nullptr; - PFN_vkGetPhysicalDeviceExternalSemaphoreProperties vkGetPhysicalDeviceExternalSemaphoreProperties = nullptr; - - PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = nullptr; - PFN_vkCreateDevice vkCreateDevice = nullptr; - - // WSI - PFN_vkCreateHeadlessSurfaceEXT vkCreateHeadlessSurfaceEXT = nullptr; - PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR = nullptr; - PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR = nullptr; - PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR = nullptr; - PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR = nullptr; - PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR = nullptr; - PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR = nullptr; - PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR = nullptr; - PFN_vkGetPhysicalDevicePresentRectanglesKHR vkGetPhysicalDevicePresentRectanglesKHR = nullptr; - PFN_vkGetPhysicalDeviceDisplayProperties2KHR vkGetPhysicalDeviceDisplayProperties2KHR = nullptr; - PFN_vkGetPhysicalDeviceDisplayPlaneProperties2KHR vkGetPhysicalDeviceDisplayPlaneProperties2KHR = nullptr; - PFN_vkGetDisplayModeProperties2KHR vkGetDisplayModeProperties2KHR = nullptr; - PFN_vkGetDisplayPlaneCapabilities2KHR vkGetDisplayPlaneCapabilities2KHR = nullptr; - PFN_vkGetPhysicalDeviceSurfaceCapabilities2KHR vkGetPhysicalDeviceSurfaceCapabilities2KHR = nullptr; - PFN_vkGetPhysicalDeviceSurfaceFormats2KHR vkGetPhysicalDeviceSurfaceFormats2KHR = nullptr; - -#if defined(VK_USE_PLATFORM_ANDROID_KHR) - PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR = nullptr; -#endif // VK_USE_PLATFORM_ANDROID_KHR -#if defined(VK_USE_PLATFORM_DIRECTFB_EXT) - PFN_vkCreateDirectFBSurfaceEXT vkCreateDirectFBSurfaceEXT = nullptr; - PFN_vkGetPhysicalDeviceDirectFBPresentationSupportEXT vkGetPhysicalDeviceDirectFBPresentationSupportEXT = nullptr; -#endif // VK_USE_PLATFORM_DIRECTFB_EXT -#if defined(VK_USE_PLATFORM_FUCHSIA) - PFN_vkCreateImagePipeSurfaceFUCHSIA vkCreateImagePipeSurfaceFUCHSIA = nullptr; -#endif // VK_USE_PLATFORM_FUCHSIA -#if defined(VK_USE_PLATFORM_GGP) - PFN_vkCreateStreamDescriptorSurfaceGGP vkCreateStreamDescriptorSurfaceGGP = nullptr; -#endif // VK_USE_PLATFORM_GGP -#if defined(VK_USE_PLATFORM_IOS_MVK) - PFN_vkCreateIOSSurfaceMVK vkCreateIOSSurfaceMVK = nullptr; -#endif // VK_USE_PLATFORM_IOS_MVK -#if defined(VK_USE_PLATFORM_MACOS_MVK) - PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK = nullptr; -#endif // VK_USE_PLATFORM_MACOS_MVK -#if defined(VK_USE_PLATFORM_METAL_EXT) - PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT = nullptr; -#endif // VK_USE_PLATFORM_METAL_EXT -#if defined(VK_USE_PLATFORM_SCREEN_QNX) - PFN_vkCreateScreenSurfaceQNX vkCreateScreenSurfaceQNX = nullptr; - PFN_vkGetPhysicalDeviceScreenPresentationSupportQNX vkGetPhysicalDeviceScreenPresentationSupportQNX = nullptr; -#endif // VK_USE_PLATFORM_SCREEN_QNX -#if defined(VK_USE_PLATFORM_WAYLAND_KHR) - PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR = nullptr; - PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR = nullptr; -#endif // VK_USE_PLATFORM_WAYLAND_KHR -#if defined(VK_USE_PLATFORM_XCB_KHR) - PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR = nullptr; - PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR = nullptr; -#endif // VK_USE_PLATFORM_XCB_KHR -#if defined(VK_USE_PLATFORM_XLIB_KHR) - PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR = nullptr; - PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR = nullptr; -#endif // VK_USE_PLATFORM_XLIB_KHR -#if defined(VK_USE_PLATFORM_WIN32_KHR) - PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR = nullptr; - PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR vkGetPhysicalDeviceWin32PresentationSupportKHR = nullptr; -#endif // VK_USE_PLATFORM_WIN32_KHR - PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR = nullptr; - - // instance extensions functions (can only be loaded with a valid instance) - PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = nullptr; // Null unless the extension is enabled - PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT = nullptr; // Null unless the extension is enabled - PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT = nullptr; // Null unless the extension is enabled - PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT = nullptr; // Null unless the extension is enabled - - // device functions - PFN_vkDestroyDevice vkDestroyDevice = nullptr; - PFN_vkGetDeviceQueue vkGetDeviceQueue = nullptr; - - VulkanFunctions(); - - void load_instance_functions(VkInstance instance); - - FromVoidStarFunc load(VkInstance inst, const char* func_name) const { - return FromVoidStarFunc(vkGetInstanceProcAddr(inst, func_name)); +template +bool check_permutation(std::initializer_list expected, std::array const& returned) { + if (expected.size() != returned.size()) return false; + for (uint32_t i = 0; i < expected.size(); i++) { + auto found = std::find_if(std::begin(returned), std::end(returned), + [&](T elem) { return string_eq(*(expected.begin() + i), elem.layerName); }); + if (found == std::end(returned)) return false; } - - FromVoidStarFunc load(VkDevice device, const char* func_name) const { - return FromVoidStarFunc(vkGetDeviceProcAddr(device, func_name)); - } -}; - -struct DeviceFunctions { - PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = nullptr; - PFN_vkDestroyDevice vkDestroyDevice = nullptr; - PFN_vkGetDeviceQueue vkGetDeviceQueue = nullptr; - PFN_vkCreateCommandPool vkCreateCommandPool = nullptr; - PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers = nullptr; - PFN_vkDestroyCommandPool vkDestroyCommandPool = nullptr; - PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR = nullptr; - PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR = nullptr; - PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR = nullptr; - - DeviceFunctions() = default; - DeviceFunctions(const VulkanFunctions& vulkan_functions, VkDevice device); - - FromVoidStarFunc load(VkDevice device, const char* func_name) const { - return FromVoidStarFunc(vkGetDeviceProcAddr(device, func_name)); + return true; +} +template +bool check_permutation(std::initializer_list expected, std::vector const& returned) { + if (expected.size() != returned.size()) return false; + for (uint32_t i = 0; i < expected.size(); i++) { + auto found = std::find_if(std::begin(returned), std::end(returned), + [&](T elem) { return string_eq(*(expected.begin() + i), elem.layerName); }); + if (found == std::end(returned)) return false; } -}; + return true; +} // InstWrapper & DeviceWrapper - used to make creating instances & devices easier when writing tests struct InstWrapper { @@ -465,50 +338,6 @@ VkResult CreateDebugUtilsMessenger(DebugUtilsWrapper& debug_utils); void FillDebugUtilsCreateDetails(InstanceCreateInfo& create_info, DebugUtilsLogger& logger); void FillDebugUtilsCreateDetails(InstanceCreateInfo& create_info, DebugUtilsWrapper& wrapper); -namespace fs { - -int create_folder(std::filesystem::path const& path); -int delete_folder(std::filesystem::path const& folder); - -class FolderManager { - public: - explicit FolderManager(std::filesystem::path root_path, std::string name) noexcept; - ~FolderManager() noexcept; - FolderManager(FolderManager const&) = delete; - FolderManager& operator=(FolderManager const&) = delete; - FolderManager(FolderManager&& other) noexcept; - FolderManager& operator=(FolderManager&& other) noexcept; - - // Add a manifest to the folder - std::filesystem::path write_manifest(std::filesystem::path const& name, std::string const& contents); - - // close file handle, delete file, remove `name` from managed file list. - void remove(std::filesystem::path const& name); - - // Remove all contents in the path - void clear() const noexcept; - - // copy file into this folder with name `new_name`. Returns the full path of the file that was copied - std::filesystem::path copy_file(std::filesystem::path const& file, std::filesystem::path const& new_name); - - // location of the managed folder - std::filesystem::path location() const { return folder; } - - std::vector get_files() const; - - // Create a symlink in this folder to target with the filename set to link_name - std::filesystem::path add_symlink(std::filesystem::path const& target, std::filesystem::path const& link_name); - - private: - bool actually_created = false; - std::filesystem::path folder; - std::vector added_files; - - void insert_file_to_tracking(std::filesystem::path const& name); - void check_if_first_use(); -}; -} // namespace fs - struct LoaderSettingsLayerConfiguration { BUILDER_VALUE(std::string, name) BUILDER_VALUE(std::filesystem::path, path) @@ -532,6 +361,8 @@ struct LoaderSettingsDriverConfiguration { BUILDER_VALUE(std::filesystem::path, path) }; +using VulkanUUID = std::array; + struct LoaderSettingsDeviceConfiguration { BUILDER_VALUE(VulkanUUID, deviceUUID) BUILDER_VALUE(std::string, deviceName) @@ -560,8 +391,7 @@ struct LoaderSettings { struct FrameworkEnvironment; // forward declaration struct PlatformShimWrapper { - PlatformShimWrapper(GetFoldersFunc get_folders_by_name_function, const char* log_filter) noexcept; - ~PlatformShimWrapper() noexcept; + PlatformShimWrapper(fs::FileSystemManager& file_system_manager, const char* log_filter) noexcept; PlatformShimWrapper(PlatformShimWrapper const&) = delete; PlatformShimWrapper& operator=(PlatformShimWrapper const&) = delete; @@ -610,25 +440,6 @@ struct TestLayerHandle { shimmed_manifest_path; // path to where the loader will find the manifest file (eg /usr/local/share/vulkan/<...>) }; -// Controls whether to create a manifest and where to put it -enum class ManifestDiscoveryType { - generic, // put the manifest in the regular locations - unsecured_generic, // put the manifest in a user folder rather than system - none, // Do not write the manifest anywhere (for Direct Driver Loading) - null_dir, // put the manifest in the 'null_dir' which the loader does not search in (D3DKMT for instance) - env_var, // use the corresponding env-var for it - add_env_var, // use the corresponding add-env-var for it - override_folder, // add to a special folder for the override layer to use - windows_app_package, // let the app package search find it - macos_bundle, // place it in a location only accessible to macos bundles -}; - -enum class LibraryPathType { - absolute, // default for testing - the exact path of the binary - relative, // Relative to the manifest file - default_search_paths, // Dont add any path information to the library_path - force the use of the default search paths -}; - struct TestICDDetails { TestICDDetails(ManifestICD icd_manifest) noexcept : icd_manifest(icd_manifest) {} TestICDDetails(std::filesystem::path icd_binary_path, uint32_t api_version = VK_API_VERSION_1_0) noexcept { @@ -657,30 +468,19 @@ struct TestLayerDetails { BUILDER_VALUE_WITH_DEFAULT(LibraryPathType, library_path_type, LibraryPathType::absolute); }; -// Locations manifests can go in the test framework -// If this enum is added to - the contructor of FrameworkEnvironment also needs to be updated with the new enum value -enum class ManifestLocation { - null = 0, - driver = 1, - driver_env_var = 2, - explicit_layer = 3, - explicit_layer_env_var = 4, - explicit_layer_add_env_var = 5, - implicit_layer = 6, - implicit_layer_env_var = 7, - implicit_layer_add_env_var = 8, - override_layer = 9, - windows_app_package = 10, - macos_bundle = 11, - unsecured_location = 12, - settings_location = 13, -}; - struct FrameworkSettings { BUILDER_VALUE_WITH_DEFAULT(const char*, log_filter, "all"); - BUILDER_VALUE_WITH_DEFAULT(bool, enable_default_search_paths, true); - BUILDER_VALUE(LoaderSettings, loader_settings); - BUILDER_VALUE(bool, secure_loader_settings); + BUILDER_VALUE_WITH_DEFAULT(bool, run_as_if_with_elevated_privleges, false); + +#if TESTING_COMMON_UNIX_PLATFORMS + BUILDER_VALUE_WITH_DEFAULT(std::string, home_env_var, "/home/fake_home"); +#if !defined(__APPLE__) + BUILDER_VALUE(std::string, xdg_config_home_env_var); + BUILDER_VALUE(std::string, xdg_config_dirs_env_var); + BUILDER_VALUE(std::string, xdg_data_home_env_var); + BUILDER_VALUE(std::string, xdg_data_dirs_env_var); +#endif +#endif }; struct FrameworkEnvironment { @@ -699,12 +499,20 @@ struct FrameworkEnvironment { void add_fake_implicit_layer(ManifestLayer layer_manifest, const std::string& json_name) noexcept; void add_fake_explicit_layer(ManifestLayer layer_manifest, const std::string& json_name) noexcept; - // resets the current settings with the values contained in loader_settings - void write_settings_file(std::string const& file_contents); - // apply any changes made to FrameworkEnvironment's loader_settings member - void update_loader_settings(const LoaderSettings& loader_settings) noexcept; + // Resets the current settings with the values contained in loader_settings. + // Write_to_secure_location determines whether to write to the secure or unsecure settings folder. + void write_settings_file(std::string const& file_contents, bool write_to_secure_location); + + // Apply any changes made to FrameworkEnvironment's loader_settings member. + // By default writes to the secure settings location + void update_loader_settings(const LoaderSettings& loader_settings, bool write_to_secure_location = true) noexcept; + void remove_loader_settings(); + // Creates a file called `file_name` for the given `category` in the given `location` with `source_string` as the contents + void write_file_from_string(std::string const& source_string, ManifestCategory category, ManifestLocation location, + std::string const& file_name); + // Creates a file called `file_name` for the given `category` in the given `location` with contents copied from `source_file` void write_file_from_source(const char* source_file, ManifestCategory category, ManifestLocation location, std::string const& file_name); @@ -720,16 +528,18 @@ struct FrameworkEnvironment { std::filesystem::path get_layer_manifest_path(size_t index = 0) noexcept; std::filesystem::path get_shimmed_layer_manifest_path(size_t index = 0) noexcept; - fs::FolderManager& get_folder(ManifestLocation location) noexcept; - fs::FolderManager const& get_folder(ManifestLocation location) const noexcept; + fs::Folder& get_folder(ManifestLocation location) noexcept; + fs::Folder const& get_folder(ManifestLocation location) const noexcept; #if defined(__APPLE__) // Set the path of the app bundle to the appropriate test framework bundle void setup_macos_bundle() noexcept; #endif + void add_symlink(ManifestLocation location, std::filesystem::path const& target, std::filesystem::path const& link_name); + FrameworkSettings settings; - fs::FolderManager test_folder; + fs::FileSystemManager file_system_manager; // Query the global extensions // Optional: use layer_name to query the extensions of a specific layer @@ -738,7 +548,6 @@ struct FrameworkEnvironment { std::vector GetLayerProperties(uint32_t count); PlatformShimWrapper platform_shim; - std::vector folders; std::vector icds; std::vector layers; @@ -753,6 +562,18 @@ struct FrameworkEnvironment { EnvVarWrapper env_var_vk_implicit_layer_paths{"VK_IMPLICIT_LAYER_PATH"}; EnvVarWrapper add_env_var_vk_implicit_layer_paths{"VK_ADD_IMPLICIT_LAYER_PATH"}; +#if TESTING_COMMON_UNIX_PLATFORMS + EnvVarWrapper env_var_home{"HOME", "/home/fake_home"}; +#if !defined(__APPLE__) + EnvVarWrapper env_var_xdg_config_home{"XDG_CONFIG_HOME"}; + EnvVarWrapper env_var_xdg_config_dirs{"XDG_CONFIG_DIRS"}; + EnvVarWrapper env_var_xdg_data_home{"XDG_DATA_HOME"}; + EnvVarWrapper env_var_xdg_data_dirs{"XDG_DATA_DIRS"}; +#endif + std::string secure_manifest_base_location; + std::string unsecure_manifest_base_location; +#endif + LoaderSettings loader_settings; // the current settings written to disk private: void add_layer_impl(TestLayerDetails layer_details, ManifestCategory category); diff --git a/tests/framework/test_util.cpp b/tests/framework/test_util.cpp deleted file mode 100644 index 9e45b4143..000000000 --- a/tests/framework/test_util.cpp +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright (c) 2021-2023 The Khronos Group Inc. - * Copyright (c) 2021-2023 Valve Corporation - * Copyright (c) 2021-2023 LunarG, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and/or associated documentation files (the "Materials"), to - * deal in the Materials without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Materials, and to permit persons to whom the Materials are - * furnished to do so, subject to the following conditions: - * - * The above copyright notice(s) and this permission notice shall be included in - * all copies or substantial portions of the Materials. - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE - * USE OR OTHER DEALINGS IN THE MATERIALS. - * - * Author: Charles Giessen - */ - -#include "test_util.h" - -#include - -#if defined(WIN32) -#include -#include -const char* win_api_error_str(LSTATUS status) { - if (status == ERROR_INVALID_FUNCTION) return "ERROR_INVALID_FUNCTION"; - if (status == ERROR_FILE_NOT_FOUND) return "ERROR_FILE_NOT_FOUND"; - if (status == ERROR_PATH_NOT_FOUND) return "ERROR_PATH_NOT_FOUND"; - if (status == ERROR_TOO_MANY_OPEN_FILES) return "ERROR_TOO_MANY_OPEN_FILES"; - if (status == ERROR_ACCESS_DENIED) return "ERROR_ACCESS_DENIED"; - if (status == ERROR_INVALID_HANDLE) return "ERROR_INVALID_HANDLE"; - if (status == ERROR_ENVVAR_NOT_FOUND) return "ERROR_ENVVAR_NOT_FOUND"; - if (status == ERROR_SETENV_FAILED) return "ERROR_SETENV_FAILED"; - return "UNKNOWN ERROR"; -} - -void print_error_message(LSTATUS status, const char* function_name, std::string optional_message) { - LPVOID lpMsgBuf; - DWORD dw = GetLastError(); - - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, dw, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&lpMsgBuf), 0, nullptr); - - std::cerr << function_name << " failed with " << win_api_error_str(status) << ": " - << std::string(reinterpret_cast(lpMsgBuf)); - if (optional_message != "") { - std::cerr << " | " << optional_message; - } - std::cerr << "\n"; - LocalFree(lpMsgBuf); -} - -void EnvVarWrapper::set_env_var() { - BOOL ret = SetEnvironmentVariableW(widen(name).c_str(), widen(cur_value).c_str()); - if (ret == 0) { - print_error_message(ERROR_SETENV_FAILED, "SetEnvironmentVariableW"); - } -} -void EnvVarWrapper::remove_env_var() const { SetEnvironmentVariableW(widen(name).c_str(), nullptr); } -std::string get_env_var(std::string const& name, bool report_failure) { - std::wstring name_utf16 = widen(name); - DWORD value_size = GetEnvironmentVariableW(name_utf16.c_str(), nullptr, 0); - if (0 == value_size) { - if (report_failure) print_error_message(ERROR_ENVVAR_NOT_FOUND, "GetEnvironmentVariableW"); - return {}; - } - std::wstring value(value_size, L'\0'); - if (GetEnvironmentVariableW(name_utf16.c_str(), &value[0], value_size) != value_size - 1) { - return {}; - } - return narrow(value); -} -#elif COMMON_UNIX_PLATFORMS - -void EnvVarWrapper::set_env_var() { setenv(name.c_str(), cur_value.c_str(), 1); } -void EnvVarWrapper::remove_env_var() const { unsetenv(name.c_str()); } -std::string get_env_var(std::string const& name, bool report_failure) { - char* ret = getenv(name.c_str()); - if (ret == nullptr) { - if (report_failure) std::cerr << "Failed to get environment variable:" << name << "\n"; - return std::string(); - } - return ret; -} -#endif - -template -void print_object_of_t(JsonWriter& writer, const char* object_name, std::vector const& vec) { - if (vec.size() == 0) return; - writer.StartKeyedObject(object_name); - for (auto& element : vec) { - element.get_manifest_str(writer); - } - writer.EndObject(); -} - -template -void print_array_of_t(JsonWriter& writer, const char* object_name, std::vector const& vec) { - if (vec.size() == 0) return; - writer.StartKeyedArray(object_name); - for (auto& element : vec) { - element.get_manifest_str(writer); - } - writer.EndArray(); -} -void print_vector_of_strings(JsonWriter& writer, const char* object_name, std::vector const& strings) { - if (strings.size() == 0) return; - writer.StartKeyedArray(object_name); - for (auto const& str : strings) { - writer.AddString(std::filesystem::path(str).native()); - } - writer.EndArray(); -} -void print_vector_of_strings(JsonWriter& writer, const char* object_name, std::vector const& paths) { - if (paths.size() == 0) return; - writer.StartKeyedArray(object_name); - for (auto const& path : paths) { - writer.AddString(path.native()); - } - writer.EndArray(); -} - -std::string to_text(bool b) { return b ? std::string("true") : std::string("false"); } - -std::string ManifestICD::get_manifest_str() const { - JsonWriter writer; - writer.StartObject(); - writer.AddKeyedString("file_format_version", file_format_version.get_version_str()); - writer.StartKeyedObject("ICD"); - writer.AddKeyedString("library_path", lib_path.native()); - writer.AddKeyedString("api_version", version_to_string(api_version)); - writer.AddKeyedBool("is_portability_driver", is_portability_driver); - if (!library_arch.empty()) writer.AddKeyedString("library_arch", library_arch); - writer.EndObject(); - writer.EndObject(); - return writer.output; -} - -void ManifestLayer::LayerDescription::Extension::get_manifest_str(JsonWriter& writer) const { - writer.StartObject(); - writer.AddKeyedString("name", name); - writer.AddKeyedString("spec_version", std::to_string(spec_version)); - writer.AddKeyedString("spec_version", std::to_string(spec_version)); - print_vector_of_strings(writer, "entrypoints", entrypoints); - writer.EndObject(); -} - -void ManifestLayer::LayerDescription::get_manifest_str(JsonWriter& writer) const { - writer.AddKeyedString("name", name); - writer.AddKeyedString("type", get_type_str(type)); - if (!lib_path.empty()) { - writer.AddKeyedString("library_path", lib_path.native()); - } - writer.AddKeyedString("api_version", version_to_string(api_version)); - writer.AddKeyedString("implementation_version", std::to_string(implementation_version)); - writer.AddKeyedString("description", description); - print_object_of_t(writer, "functions", functions); - print_array_of_t(writer, "instance_extensions", instance_extensions); - print_array_of_t(writer, "device_extensions", device_extensions); - if (!enable_environment.empty()) { - writer.StartKeyedObject("enable_environment"); - writer.AddKeyedString(enable_environment, "1"); - writer.EndObject(); - } - if (!disable_environment.empty()) { - writer.StartKeyedObject("disable_environment"); - writer.AddKeyedString(disable_environment, "1"); - writer.EndObject(); - } - print_vector_of_strings(writer, "component_layers", component_layers); - print_vector_of_strings(writer, "blacklisted_layers", blacklisted_layers); - print_vector_of_strings(writer, "override_paths", override_paths); - print_vector_of_strings(writer, "app_keys", app_keys); - print_object_of_t(writer, "pre_instance_functions", pre_instance_functions); - if (!library_arch.empty()) { - writer.AddKeyedString("library_arch", library_arch); - } -} - -VkLayerProperties ManifestLayer::LayerDescription::get_layer_properties() const { - VkLayerProperties properties{}; - copy_string_to_char_array(name, properties.layerName, VK_MAX_EXTENSION_NAME_SIZE); - copy_string_to_char_array(description, properties.description, VK_MAX_EXTENSION_NAME_SIZE); - properties.implementationVersion = implementation_version; - properties.specVersion = api_version; - return properties; -} - -std::string ManifestLayer::get_manifest_str() const { - JsonWriter writer; - writer.StartObject(); - writer.AddKeyedString("file_format_version", file_format_version.get_version_str()); - if (layers.size() == 1) { - writer.StartKeyedObject("layer"); - layers.at(0).get_manifest_str(writer); - writer.EndObject(); - } else { - writer.StartKeyedArray("layers"); - for (size_t i = 0; i < layers.size(); i++) { - writer.StartObject(); - layers.at(i).get_manifest_str(writer); - writer.EndObject(); - } - writer.EndArray(); - } - writer.EndObject(); - return writer.output; -} - -const char* get_platform_wsi_extension([[maybe_unused]] const char* api_selection) { -#if defined(VK_USE_PLATFORM_ANDROID_KHR) - return "VK_KHR_android_surface"; -#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) - return "VK_EXT_directfb_surface"; -#elif defined(VK_USE_PLATFORM_FUCHSIA) - return "VK_FUCHSIA_imagepipe_surface"; -#elif defined(VK_USE_PLATFORM_GGP) - return "VK_GGP_stream_descriptor_surface"; -#elif defined(VK_USE_PLATFORM_IOS_MVK) - return "VK_MVK_ios_surface"; -#elif defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT) -#if defined(VK_USE_PLATFORM_MACOS_MVK) - if (string_eq(api_selection, "VK_USE_PLATFORM_MACOS_MVK")) return "VK_MVK_macos_surface"; -#endif -#if defined(VK_USE_PLATFORM_METAL_EXT) - if (string_eq(api_selection, "VK_USE_PLATFORM_METAL_EXT")) return "VK_EXT_metal_surface"; - return "VK_EXT_metal_surface"; -#endif -#elif defined(VK_USE_PLATFORM_SCREEN_QNX) - return "VK_QNX_screen_surface"; -#elif defined(VK_USE_PLATFORM_VI_NN) - return "VK_NN_vi_surface"; -#elif defined(VK_USE_PLATFORM_XCB_KHR) || defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR) -#if defined(VK_USE_PLATFORM_XCB_KHR) - if (string_eq(api_selection, "VK_USE_PLATFORM_XCB_KHR")) return "VK_KHR_xcb_surface"; -#endif -#if defined(VK_USE_PLATFORM_XLIB_KHR) - if (string_eq(api_selection, "VK_USE_PLATFORM_XLIB_KHR")) return "VK_KHR_xlib_surface"; -#endif -#if defined(VK_USE_PLATFORM_WAYLAND_KHR) - if (string_eq(api_selection, "VK_USE_PLATFORM_WAYLAND_KHR")) return "VK_KHR_wayland_surface"; -#endif -#if defined(VK_USE_PLATFORM_XCB_KHR) - return "VK_KHR_xcb_surface"; -#endif -#elif defined(VK_USE_PLATFORM_WIN32_KHR) - return "VK_KHR_win32_surface"; -#else - return "VK_KHR_display"; -#endif -} - -bool string_eq(const char* a, const char* b) noexcept { return a && b && strcmp(a, b) == 0; } -bool string_eq(const char* a, const char* b, size_t len) noexcept { return a && b && strncmp(a, b, len) == 0; } - -InstanceCreateInfo::InstanceCreateInfo() { - instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; -} - -VkInstanceCreateInfo* InstanceCreateInfo::get() noexcept { - if (fill_in_application_info) { - application_info.pApplicationName = app_name.c_str(); - application_info.pEngineName = engine_name.c_str(); - application_info.applicationVersion = app_version; - application_info.engineVersion = engine_version; - application_info.apiVersion = api_version; - instance_info.pApplicationInfo = &application_info; - } - instance_info.flags = flags; - instance_info.enabledLayerCount = static_cast(enabled_layers.size()); - instance_info.ppEnabledLayerNames = enabled_layers.data(); - instance_info.enabledExtensionCount = static_cast(enabled_extensions.size()); - instance_info.ppEnabledExtensionNames = enabled_extensions.data(); - return &instance_info; -} -InstanceCreateInfo& InstanceCreateInfo::set_api_version(uint32_t major, uint32_t minor, uint32_t patch) { - this->api_version = VK_MAKE_API_VERSION(0, major, minor, patch); - return *this; -} -InstanceCreateInfo& InstanceCreateInfo::setup_WSI(const char* api_selection) { - add_extensions({"VK_KHR_surface", get_platform_wsi_extension(api_selection)}); - return *this; -} - -DeviceQueueCreateInfo::DeviceQueueCreateInfo() { queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; } -DeviceQueueCreateInfo::DeviceQueueCreateInfo(const VkDeviceQueueCreateInfo* create_info) { - queue_create_info = *create_info; - for (uint32_t i = 0; i < create_info->queueCount; i++) { - priorities.push_back(create_info->pQueuePriorities[i]); - } -} - -VkDeviceQueueCreateInfo DeviceQueueCreateInfo::get() noexcept { - queue_create_info.pQueuePriorities = priorities.data(); - queue_create_info.queueCount = 1; - queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - return queue_create_info; -} - -DeviceCreateInfo::DeviceCreateInfo(const VkDeviceCreateInfo* create_info) { - dev = *create_info; - for (uint32_t i = 0; i < create_info->enabledExtensionCount; i++) { - enabled_extensions.push_back(create_info->ppEnabledExtensionNames[i]); - } - for (uint32_t i = 0; i < create_info->enabledLayerCount; i++) { - enabled_layers.push_back(create_info->ppEnabledLayerNames[i]); - } - for (uint32_t i = 0; i < create_info->queueCreateInfoCount; i++) { - device_queue_infos.push_back(create_info->pQueueCreateInfos[i]); - } -} - -VkDeviceCreateInfo* DeviceCreateInfo::get() noexcept { - dev.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - dev.enabledLayerCount = static_cast(enabled_layers.size()); - dev.ppEnabledLayerNames = enabled_layers.data(); - dev.enabledExtensionCount = static_cast(enabled_extensions.size()); - dev.ppEnabledExtensionNames = enabled_extensions.data(); - uint32_t index = 0; - for (auto& queue : queue_info_details) { - queue.queue_create_info.queueFamilyIndex = index++; - queue.queue_create_info.queueCount = 1; - device_queue_infos.push_back(queue.get()); - } - - dev.queueCreateInfoCount = static_cast(device_queue_infos.size()); - dev.pQueueCreateInfos = device_queue_infos.data(); - return &dev; -} - -#if defined(WIN32) -std::string narrow(const std::wstring& utf16) { - if (utf16.empty()) { - return {}; - } - int size = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.size()), nullptr, 0, nullptr, nullptr); - if (size <= 0) { - return {}; - } - std::string utf8(size, '\0'); - if (WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.size()), &utf8[0], size, nullptr, nullptr) != size) { - return {}; - } - return utf8; -} - -std::wstring widen(const std::string& utf8) { - if (utf8.empty()) { - return {}; - } - int size = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), nullptr, 0); - if (size <= 0) { - return {}; - } - std::wstring utf16(size, L'\0'); - if (MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), &utf16[0], size) != size) { - return {}; - } - return utf16; -} -#else -std::string narrow(const std::string& utf16) { return utf16; } -std::string widen(const std::string& utf8) { return utf8; } -#endif diff --git a/tests/framework/test_util.h b/tests/framework/test_util.h deleted file mode 100644 index 2f4b641f0..000000000 --- a/tests/framework/test_util.h +++ /dev/null @@ -1,861 +0,0 @@ -/* - * Copyright (c) 2021-2023 The Khronos Group Inc. - * Copyright (c) 2021-2023 Valve Corporation - * Copyright (c) 2021-2023 LunarG, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and/or associated documentation files (the "Materials"), to - * deal in the Materials without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Materials, and to permit persons to whom the Materials are - * furnished to do so, subject to the following conditions: - * - * The above copyright notice(s) and this permission notice shall be included in - * all copies or substantial portions of the Materials. - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE - * USE OR OTHER DEALINGS IN THE MATERIALS. - * - * Author: Charles Giessen - */ - -/* - * Contains all the utilities needed to make the framework and tests work. - * Contains: - * All the standard library includes and main platform specific includes - * Dll export macro - * Manifest ICD & Layer structs - * per-platform library loading - mirrors the vk_loader_platform - * LibraryWrapper - RAII wrapper for a library - * DispatchableHandle - RAII wrapper for vulkan dispatchable handle objects - * ostream overload for VkResult - prettifies googletest output - * Instance & Device create info helpers - * operator == overloads for many vulkan structs - more concise tests - */ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -// Set of platforms with a common set of functionality which is queried throughout the program -#if defined(__linux__) || defined(__APPLE__) || defined(__Fuchsia__) || defined(__QNX__) || defined(__FreeBSD__) || \ - defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__GNU__) -#define COMMON_UNIX_PLATFORMS 1 -#else -#define COMMON_UNIX_PLATFORMS 0 -#endif - -#if defined(WIN32) -#include -#include -#include -#elif COMMON_UNIX_PLATFORMS -#include -#include -#include -#include -#include - -// Prevent macro collisions from -#undef major -#undef minor - -#endif - -#include -#include -#include - -#include FRAMEWORK_CONFIG_HEADER - -#if defined(__GNUC__) && __GNUC__ >= 4 -#define FRAMEWORK_EXPORT __attribute__((visibility("default"))) -#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590) -#define FRAMEWORK_EXPORT __attribute__((visibility("default"))) -#elif defined(WIN32) -#define FRAMEWORK_EXPORT __declspec(dllexport) -#else -#define FRAMEWORK_EXPORT -#endif - -// Define it here so that json_writer.h has access to these functions -#if defined(WIN32) -// Convert an UTF-16 wstring to an UTF-8 string -std::string narrow(const std::wstring& utf16); -// Convert an UTF-8 string to an UTF-16 wstring -std::wstring widen(const std::string& utf8); -#else -// Do nothing passthrough for the sake of Windows & UTF-16 -std::string narrow(const std::string& utf16); -// Do nothing passthrough for the sake of Windows & UTF-16 -std::string widen(const std::string& utf8); -#endif - -#include "json_writer.h" - -using GetFoldersFunc = std::function(const char*)>; - -// get_env_var() - returns a std::string of `name`. if report_failure is true, then it will log to stderr that it didn't find the -// env-var -// NOTE: This is only intended for test framework code, all test code MUST use EnvVarWrapper -std::string get_env_var(std::string const& name, bool report_failure = true); - -/* - * Wrapper around Environment Variables with common operations - * Since Environment Variables leak between tests, there needs to be RAII code to remove them during test cleanup - - */ - -// Wrapper to set & remove env-vars automatically -struct EnvVarWrapper { - // Constructor which unsets the env-var - EnvVarWrapper(std::string const& name) noexcept : name(name) { - initial_value = get_env_var(name, false); - remove_env_var(); - } - // Constructor which set the env-var to the specified value - EnvVarWrapper(std::string const& name, std::string const& value) noexcept : name(name), cur_value(value) { - initial_value = get_env_var(name, false); - set_env_var(); - } - ~EnvVarWrapper() noexcept { - remove_env_var(); - if (!initial_value.empty()) { - set_new_value(initial_value); - } - } - - // delete copy operators - EnvVarWrapper(const EnvVarWrapper&) = delete; - EnvVarWrapper& operator=(const EnvVarWrapper&) = delete; - - void set_new_value(std::string const& value) { - cur_value = value; - set_env_var(); - } - void add_to_list(std::string const& list_item) { - if (!cur_value.empty()) { - cur_value += OS_ENV_VAR_LIST_SEPARATOR; - } - cur_value += list_item; - set_env_var(); - } -#if defined(WIN32) - void add_to_list(std::wstring const& list_item) { - if (!cur_value.empty()) { - cur_value += OS_ENV_VAR_LIST_SEPARATOR; - } - cur_value += narrow(list_item); - set_env_var(); - } -#endif - void remove_value() const { remove_env_var(); } - const char* get() const { return name.c_str(); } - const char* value() const { return cur_value.c_str(); } - - private: - std::string name; - std::string cur_value; - std::string initial_value; - - void set_env_var(); - void remove_env_var() const; -#if defined(WIN32) - // Environment variable list separator - not for filesystem paths - const char OS_ENV_VAR_LIST_SEPARATOR = ';'; -#elif COMMON_UNIX_PLATFORMS - // Environment variable list separator - not for filesystem paths - const char OS_ENV_VAR_LIST_SEPARATOR = ':'; -#endif -}; - -// Windows specific error handling logic -#if defined(WIN32) -const long ERROR_SETENV_FAILED = 10543; // chosen at random, attempts to not conflict -const long ERROR_REMOVEDIRECTORY_FAILED = 10544; // chosen at random, attempts to not conflict -const char* win_api_error_str(LSTATUS status); -void print_error_message(LSTATUS status, const char* function_name, std::string optional_message = ""); -#endif - -// copy the contents of a std::string into a char array and add a null terminator at the end -// src - std::string to read from -// dst - char array to write to -// size_dst - number of characters in the dst array -inline void copy_string_to_char_array(std::string const& src, char* dst, size_t size_dst) { dst[src.copy(dst, size_dst - 1)] = 0; } - -#if defined(WIN32) -typedef HMODULE test_platform_dl_handle; -inline test_platform_dl_handle test_platform_open_library(const wchar_t* lib_path) { - // Try loading the library the original way first. - test_platform_dl_handle lib_handle = LoadLibraryW(lib_path); - if (lib_handle == nullptr && GetLastError() == ERROR_MOD_NOT_FOUND) { - // If that failed, then try loading it with broader search folders. - lib_handle = LoadLibraryExW(lib_path, nullptr, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR); - } - return lib_handle; -} -inline void test_platform_open_library_print_error(std::filesystem::path const& libPath) { - std::wcerr << L"Unable to open library: " << libPath << L" due to: " << std::to_wstring(GetLastError()) << L"\n"; -} -inline void test_platform_close_library(test_platform_dl_handle library) { FreeLibrary(library); } -inline void* test_platform_get_proc_address(test_platform_dl_handle library, const char* name) { - assert(library); - assert(name); - return reinterpret_cast(GetProcAddress(library, name)); -} -inline char* test_platform_get_proc_address_error(const char* name) { - static char errorMsg[120]; - snprintf(errorMsg, 119, "Failed to find function \"%s\" in dynamic library", name); - return errorMsg; -} - -#elif COMMON_UNIX_PLATFORMS - -typedef void* test_platform_dl_handle; -inline test_platform_dl_handle test_platform_open_library(const char* libPath) { return dlopen(libPath, RTLD_LAZY | RTLD_LOCAL); } -inline void test_platform_open_library_print_error(std::filesystem::path const& libPath) { - std::wcerr << "Unable to open library: " << libPath << " due to: " << dlerror() << "\n"; -} -inline void test_platform_close_library(test_platform_dl_handle library) { - char* loader_disable_dynamic_library_unloading_env_var = getenv("VK_LOADER_DISABLE_DYNAMIC_LIBRARY_UNLOADING"); - if (NULL == loader_disable_dynamic_library_unloading_env_var || - 0 != strncmp(loader_disable_dynamic_library_unloading_env_var, "1", 2)) { - dlclose(library); - } -} -inline void* test_platform_get_proc_address(test_platform_dl_handle library, const char* name) { - assert(library); - assert(name); - return dlsym(library, name); -} -inline const char* test_platform_get_proc_address_error([[maybe_unused]] const char* name) { return dlerror(); } -#endif - -class FromVoidStarFunc { - private: - void* function; - - public: - FromVoidStarFunc(void* function) : function(function) {} - FromVoidStarFunc(PFN_vkVoidFunction function) : function(reinterpret_cast(function)) {} - - template - operator T() { - return reinterpret_cast(function); - } -}; - -struct LibraryWrapper { - explicit LibraryWrapper() noexcept {} - explicit LibraryWrapper(std::filesystem::path const& lib_path) noexcept : lib_path(lib_path) { - lib_handle = test_platform_open_library(lib_path.native().c_str()); - if (lib_handle == nullptr) { - test_platform_open_library_print_error(lib_path); - assert(lib_handle != nullptr && "Must be able to open library"); - } - } - ~LibraryWrapper() noexcept { - if (lib_handle != nullptr) { - test_platform_close_library(lib_handle); - lib_handle = nullptr; - } - } - LibraryWrapper(LibraryWrapper const& wrapper) = delete; - LibraryWrapper& operator=(LibraryWrapper const& wrapper) = delete; - LibraryWrapper(LibraryWrapper&& wrapper) noexcept : lib_handle(wrapper.lib_handle), lib_path(wrapper.lib_path) { - wrapper.lib_handle = nullptr; - } - LibraryWrapper& operator=(LibraryWrapper&& wrapper) noexcept { - if (this != &wrapper) { - if (lib_handle != nullptr) { - test_platform_close_library(lib_handle); - } - lib_handle = wrapper.lib_handle; - lib_path = wrapper.lib_path; - wrapper.lib_handle = nullptr; - } - return *this; - } - FromVoidStarFunc get_symbol(const char* symbol_name) const { - assert(lib_handle != nullptr && "Cannot get symbol with null library handle"); - void* symbol = test_platform_get_proc_address(lib_handle, symbol_name); - if (symbol == nullptr) { - fprintf(stderr, "Unable to open symbol %s: %s\n", symbol_name, test_platform_get_proc_address_error(symbol_name)); - assert(symbol != nullptr && "Must be able to get symbol"); - } - return FromVoidStarFunc(symbol); - } - - explicit operator bool() const noexcept { return lib_handle != nullptr; } - - test_platform_dl_handle lib_handle = nullptr; - std::filesystem::path lib_path; -}; - -template -PFN_vkVoidFunction to_vkVoidFunction(T func) { - return reinterpret_cast(func); -} -template -struct FRAMEWORK_EXPORT DispatchableHandle { - DispatchableHandle() { - auto ptr_handle = new VK_LOADER_DATA; - set_loader_magic_value(ptr_handle); - handle = reinterpret_cast(ptr_handle); - } - ~DispatchableHandle() { - if (handle) { - delete reinterpret_cast(handle); - } - handle = nullptr; - } - DispatchableHandle(DispatchableHandle const&) = delete; - DispatchableHandle& operator=(DispatchableHandle const&) = delete; - DispatchableHandle(DispatchableHandle&& other) noexcept : handle(other.handle) { other.handle = nullptr; } - DispatchableHandle& operator=(DispatchableHandle&& other) noexcept { - if (handle) { - delete reinterpret_cast(handle); - } - handle = other.handle; - other.handle = nullptr; - return *this; - } - bool operator==(T base_handle) { return base_handle == handle; } - bool operator!=(T base_handle) { return base_handle != handle; } - - T handle = nullptr; -}; - -const char* get_platform_wsi_extension([[maybe_unused]] const char* api_selection); - -bool string_eq(const char* a, const char* b) noexcept; -bool string_eq(const char* a, const char* b, size_t len) noexcept; - -inline std::string version_to_string(uint32_t version) { - std::string out = std::to_string(VK_API_VERSION_MAJOR(version)) + "." + std::to_string(VK_API_VERSION_MINOR(version)) + "." + - std::to_string(VK_API_VERSION_PATCH(version)); - if (VK_API_VERSION_VARIANT(version) != 0) out += std::to_string(VK_API_VERSION_VARIANT(version)) + "." + out; - return out; -} - -// Macro to ease the definition of variables with builder member functions -// type = type of the variable -// name = name of the variable -// default_value = value to default initialize, use {} if nothing else makes sense -#define BUILDER_VALUE_WITH_DEFAULT(type, name, default_value) \ - type name = default_value; \ - auto set_##name(type const& name)->decltype(*this) { \ - this->name = name; \ - return *this; \ - } - -#define BUILDER_VALUE(type, name) BUILDER_VALUE_WITH_DEFAULT(type, name, {}) - -// Macro to ease the definition of vectors with builder member functions -// type = type of the variable -// name = name of the variable -// singular_name = used for the `add_singular_name` member function -#define BUILDER_VECTOR(type, name, singular_name) \ - std::vector name; \ - auto add_##singular_name(type const& singular_name)->decltype(*this) { \ - this->name.push_back(singular_name); \ - return *this; \ - } \ - auto add_##singular_name##s(std::vector const& singular_name)->decltype(*this) { \ - for (auto& elem : singular_name) this->name.push_back(elem); \ - return *this; \ - } -// Like BUILDER_VECTOR but for move only types - where passing in means giving up ownership -#define BUILDER_VECTOR_MOVE_ONLY(type, name, singular_name) \ - std::vector name; \ - auto add_##singular_name(type&& singular_name)->decltype(*this) { \ - this->name.push_back(std::move(singular_name)); \ - return *this; \ - } - -struct ManifestVersion { - BUILDER_VALUE_WITH_DEFAULT(uint32_t, major, 1) - BUILDER_VALUE_WITH_DEFAULT(uint32_t, minor, 0) - BUILDER_VALUE_WITH_DEFAULT(uint32_t, patch, 0) - - std::string get_version_str() const noexcept { - return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch); - } -}; - -// ManifestICD builder -struct ManifestICD { - BUILDER_VALUE(ManifestVersion, file_format_version) - BUILDER_VALUE(uint32_t, api_version) - BUILDER_VALUE(std::filesystem::path, lib_path) - BUILDER_VALUE(bool, is_portability_driver) - BUILDER_VALUE(std::string, library_arch) - std::string get_manifest_str() const; -}; - -// ManifestLayer builder -struct ManifestLayer { - struct LayerDescription { - enum class Type { INSTANCE, GLOBAL, DEVICE }; - std::string get_type_str(Type layer_type) const { - if (layer_type == Type::GLOBAL) - return "GLOBAL"; - else if (layer_type == Type::DEVICE) - return "DEVICE"; - else // default - return "INSTANCE"; - } - struct FunctionOverride { - BUILDER_VALUE(std::string, vk_func) - BUILDER_VALUE(std::string, override_name) - - void get_manifest_str(JsonWriter& writer) const { writer.AddKeyedString(vk_func, override_name); } - }; - struct Extension { - Extension() noexcept {} - Extension(std::string name, uint32_t spec_version = 0, std::vector entrypoints = {}) noexcept - : name(name), spec_version(spec_version), entrypoints(entrypoints) {} - std::string name; - uint32_t spec_version = 0; - std::vector entrypoints; - void get_manifest_str(JsonWriter& writer) const; - }; - BUILDER_VALUE(std::string, name) - BUILDER_VALUE_WITH_DEFAULT(Type, type, Type::INSTANCE) - BUILDER_VALUE(std::filesystem::path, lib_path) - BUILDER_VALUE_WITH_DEFAULT(uint32_t, api_version, VK_API_VERSION_1_0) - BUILDER_VALUE(uint32_t, implementation_version) - BUILDER_VALUE(std::string, description) - BUILDER_VECTOR(FunctionOverride, functions, function) - BUILDER_VECTOR(Extension, instance_extensions, instance_extension) - BUILDER_VECTOR(Extension, device_extensions, device_extension) - BUILDER_VALUE(std::string, enable_environment) - BUILDER_VALUE(std::string, disable_environment) - BUILDER_VECTOR(std::string, component_layers, component_layer) - BUILDER_VECTOR(std::string, blacklisted_layers, blacklisted_layer) - BUILDER_VECTOR(std::filesystem::path, override_paths, override_path) - BUILDER_VECTOR(FunctionOverride, pre_instance_functions, pre_instance_function) - BUILDER_VECTOR(std::string, app_keys, app_key) - BUILDER_VALUE(std::string, library_arch) - - void get_manifest_str(JsonWriter& writer) const; - VkLayerProperties get_layer_properties() const; - }; - BUILDER_VALUE(ManifestVersion, file_format_version) - BUILDER_VECTOR(LayerDescription, layers, layer) - - std::string get_manifest_str() const; -}; - -struct Extension { - BUILDER_VALUE(std::string, extensionName) - BUILDER_VALUE_WITH_DEFAULT(uint32_t, specVersion, VK_API_VERSION_1_0) - - Extension(const char* name, uint32_t specVersion = VK_API_VERSION_1_0) noexcept - : extensionName(name), specVersion(specVersion) {} - Extension(std::string extensionName, uint32_t specVersion = VK_API_VERSION_1_0) noexcept - : extensionName(extensionName), specVersion(specVersion) {} - - VkExtensionProperties get() const noexcept { - VkExtensionProperties props{}; - copy_string_to_char_array(extensionName, &props.extensionName[0], VK_MAX_EXTENSION_NAME_SIZE); - props.specVersion = specVersion; - return props; - } -}; - -struct MockQueueFamilyProperties { - BUILDER_VALUE(VkQueueFamilyProperties, properties) - BUILDER_VALUE(bool, support_present) - - VkQueueFamilyProperties get() const noexcept { return properties; } -}; - -struct InstanceCreateInfo { - BUILDER_VALUE(VkInstanceCreateInfo, instance_info) - BUILDER_VALUE(VkApplicationInfo, application_info) - BUILDER_VALUE(std::string, app_name) - BUILDER_VALUE(std::string, engine_name) - BUILDER_VALUE(uint32_t, flags) - BUILDER_VALUE(uint32_t, app_version) - BUILDER_VALUE(uint32_t, engine_version) - BUILDER_VALUE_WITH_DEFAULT(uint32_t, api_version, VK_API_VERSION_1_0) - BUILDER_VECTOR(const char*, enabled_layers, layer) - BUILDER_VECTOR(const char*, enabled_extensions, extension) - // tell the get() function to not provide `application_info` - BUILDER_VALUE_WITH_DEFAULT(bool, fill_in_application_info, true) - - InstanceCreateInfo(); - - VkInstanceCreateInfo* get() noexcept; - - InstanceCreateInfo& set_api_version(uint32_t major, uint32_t minor, uint32_t patch); - - InstanceCreateInfo& setup_WSI(const char* api_selection = nullptr); -}; - -struct DeviceQueueCreateInfo { - DeviceQueueCreateInfo(); - DeviceQueueCreateInfo(const VkDeviceQueueCreateInfo* create_info); - - BUILDER_VALUE(VkDeviceQueueCreateInfo, queue_create_info) - BUILDER_VECTOR(float, priorities, priority) - - VkDeviceQueueCreateInfo get() noexcept; -}; - -struct DeviceCreateInfo { - DeviceCreateInfo() = default; - DeviceCreateInfo(const VkDeviceCreateInfo* create_info); - - BUILDER_VALUE(VkDeviceCreateInfo, dev) - BUILDER_VECTOR(const char*, enabled_extensions, extension) - BUILDER_VECTOR(const char*, enabled_layers, layer) - BUILDER_VECTOR(DeviceQueueCreateInfo, queue_info_details, device_queue) - - VkDeviceCreateInfo* get() noexcept; - - private: - std::vector device_queue_infos; -}; - -inline bool operator==(const VkExtent3D& a, const VkExtent3D& b) { - return a.width == b.width && a.height == b.height && a.depth == b.depth; -} -inline bool operator!=(const VkExtent3D& a, const VkExtent3D& b) { return !(a == b); } - -inline bool operator==(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties& b) { - return a.minImageTransferGranularity == b.minImageTransferGranularity && a.queueCount == b.queueCount && - a.queueFlags == b.queueFlags && a.timestampValidBits == b.timestampValidBits; -} -inline bool operator!=(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties& b) { return !(a == b); } - -inline bool operator==(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties2& b) { return a == b.queueFamilyProperties; } -inline bool operator!=(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties2& b) { return a != b.queueFamilyProperties; } - -inline bool operator==(const VkLayerProperties& a, const VkLayerProperties& b) { - return string_eq(a.layerName, b.layerName, 256) && string_eq(a.description, b.description, 256) && - a.implementationVersion == b.implementationVersion && a.specVersion == b.specVersion; -} -inline bool operator!=(const VkLayerProperties& a, const VkLayerProperties& b) { return !(a == b); } - -inline bool operator==(const VkExtensionProperties& a, const VkExtensionProperties& b) { - return string_eq(a.extensionName, b.extensionName, 256) && a.specVersion == b.specVersion; -} -inline bool operator!=(const VkExtensionProperties& a, const VkExtensionProperties& b) { return !(a == b); } - -inline bool operator==(const VkPhysicalDeviceFeatures& feats1, const VkPhysicalDeviceFeatures2& feats2) { - return feats1.robustBufferAccess == feats2.features.robustBufferAccess && - feats1.fullDrawIndexUint32 == feats2.features.fullDrawIndexUint32 && - feats1.imageCubeArray == feats2.features.imageCubeArray && feats1.independentBlend == feats2.features.independentBlend && - feats1.geometryShader == feats2.features.geometryShader && - feats1.tessellationShader == feats2.features.tessellationShader && - feats1.sampleRateShading == feats2.features.sampleRateShading && feats1.dualSrcBlend == feats2.features.dualSrcBlend && - feats1.logicOp == feats2.features.logicOp && feats1.multiDrawIndirect == feats2.features.multiDrawIndirect && - feats1.drawIndirectFirstInstance == feats2.features.drawIndirectFirstInstance && - feats1.depthClamp == feats2.features.depthClamp && feats1.depthBiasClamp == feats2.features.depthBiasClamp && - feats1.fillModeNonSolid == feats2.features.fillModeNonSolid && feats1.depthBounds == feats2.features.depthBounds && - feats1.wideLines == feats2.features.wideLines && feats1.largePoints == feats2.features.largePoints && - feats1.alphaToOne == feats2.features.alphaToOne && feats1.multiViewport == feats2.features.multiViewport && - feats1.samplerAnisotropy == feats2.features.samplerAnisotropy && - feats1.textureCompressionETC2 == feats2.features.textureCompressionETC2 && - feats1.textureCompressionASTC_LDR == feats2.features.textureCompressionASTC_LDR && - feats1.textureCompressionBC == feats2.features.textureCompressionBC && - feats1.occlusionQueryPrecise == feats2.features.occlusionQueryPrecise && - feats1.pipelineStatisticsQuery == feats2.features.pipelineStatisticsQuery && - feats1.vertexPipelineStoresAndAtomics == feats2.features.vertexPipelineStoresAndAtomics && - feats1.fragmentStoresAndAtomics == feats2.features.fragmentStoresAndAtomics && - feats1.shaderTessellationAndGeometryPointSize == feats2.features.shaderTessellationAndGeometryPointSize && - feats1.shaderImageGatherExtended == feats2.features.shaderImageGatherExtended && - feats1.shaderStorageImageExtendedFormats == feats2.features.shaderStorageImageExtendedFormats && - feats1.shaderStorageImageMultisample == feats2.features.shaderStorageImageMultisample && - feats1.shaderStorageImageReadWithoutFormat == feats2.features.shaderStorageImageReadWithoutFormat && - feats1.shaderStorageImageWriteWithoutFormat == feats2.features.shaderStorageImageWriteWithoutFormat && - feats1.shaderUniformBufferArrayDynamicIndexing == feats2.features.shaderUniformBufferArrayDynamicIndexing && - feats1.shaderSampledImageArrayDynamicIndexing == feats2.features.shaderSampledImageArrayDynamicIndexing && - feats1.shaderStorageBufferArrayDynamicIndexing == feats2.features.shaderStorageBufferArrayDynamicIndexing && - feats1.shaderStorageImageArrayDynamicIndexing == feats2.features.shaderStorageImageArrayDynamicIndexing && - feats1.shaderClipDistance == feats2.features.shaderClipDistance && - feats1.shaderCullDistance == feats2.features.shaderCullDistance && - feats1.shaderFloat64 == feats2.features.shaderFloat64 && feats1.shaderInt64 == feats2.features.shaderInt64 && - feats1.shaderInt16 == feats2.features.shaderInt16 && - feats1.shaderResourceResidency == feats2.features.shaderResourceResidency && - feats1.shaderResourceMinLod == feats2.features.shaderResourceMinLod && - feats1.sparseBinding == feats2.features.sparseBinding && - feats1.sparseResidencyBuffer == feats2.features.sparseResidencyBuffer && - feats1.sparseResidencyImage2D == feats2.features.sparseResidencyImage2D && - feats1.sparseResidencyImage3D == feats2.features.sparseResidencyImage3D && - feats1.sparseResidency2Samples == feats2.features.sparseResidency2Samples && - feats1.sparseResidency4Samples == feats2.features.sparseResidency4Samples && - feats1.sparseResidency8Samples == feats2.features.sparseResidency8Samples && - feats1.sparseResidency16Samples == feats2.features.sparseResidency16Samples && - feats1.sparseResidencyAliased == feats2.features.sparseResidencyAliased && - feats1.variableMultisampleRate == feats2.features.variableMultisampleRate && - feats1.inheritedQueries == feats2.features.inheritedQueries; -} - -inline bool operator==(const VkPhysicalDeviceMemoryProperties& props1, const VkPhysicalDeviceMemoryProperties2& props2) { - bool equal = true; - equal = equal && props1.memoryTypeCount == props2.memoryProperties.memoryTypeCount; - equal = equal && props1.memoryHeapCount == props2.memoryProperties.memoryHeapCount; - for (uint32_t i = 0; i < props1.memoryHeapCount; ++i) { - equal = equal && props1.memoryHeaps[i].size == props2.memoryProperties.memoryHeaps[i].size; - equal = equal && props1.memoryHeaps[i].flags == props2.memoryProperties.memoryHeaps[i].flags; - } - for (uint32_t i = 0; i < props1.memoryTypeCount; ++i) { - equal = equal && props1.memoryTypes[i].propertyFlags == props2.memoryProperties.memoryTypes[i].propertyFlags; - equal = equal && props1.memoryTypes[i].heapIndex == props2.memoryProperties.memoryTypes[i].heapIndex; - } - return equal; -} -inline bool operator==(const VkSparseImageFormatProperties& props1, const VkSparseImageFormatProperties& props2) { - return props1.aspectMask == props2.aspectMask && props1.imageGranularity.width == props2.imageGranularity.width && - props1.imageGranularity.height == props2.imageGranularity.height && - props1.imageGranularity.depth == props2.imageGranularity.depth && props1.flags == props2.flags; -} -inline bool operator==(const VkSparseImageFormatProperties& props1, const VkSparseImageFormatProperties2& props2) { - return props1 == props2.properties; -} -inline bool operator==(const VkExternalMemoryProperties& props1, const VkExternalMemoryProperties& props2) { - return props1.externalMemoryFeatures == props2.externalMemoryFeatures && - props1.exportFromImportedHandleTypes == props2.exportFromImportedHandleTypes && - props1.compatibleHandleTypes == props2.compatibleHandleTypes; -} -inline bool operator==(const VkExternalSemaphoreProperties& props1, const VkExternalSemaphoreProperties& props2) { - return props1.externalSemaphoreFeatures == props2.externalSemaphoreFeatures && - props1.exportFromImportedHandleTypes == props2.exportFromImportedHandleTypes && - props1.compatibleHandleTypes == props2.compatibleHandleTypes; -} -inline bool operator==(const VkExternalFenceProperties& props1, const VkExternalFenceProperties& props2) { - return props1.externalFenceFeatures == props2.externalFenceFeatures && - props1.exportFromImportedHandleTypes == props2.exportFromImportedHandleTypes && - props1.compatibleHandleTypes == props2.compatibleHandleTypes; -} -inline bool operator==(const VkSurfaceCapabilitiesKHR& props1, const VkSurfaceCapabilitiesKHR& props2) { - return props1.minImageCount == props2.minImageCount && props1.maxImageCount == props2.maxImageCount && - props1.currentExtent.width == props2.currentExtent.width && props1.currentExtent.height == props2.currentExtent.height && - props1.minImageExtent.width == props2.minImageExtent.width && - props1.minImageExtent.height == props2.minImageExtent.height && - props1.maxImageExtent.width == props2.maxImageExtent.width && - props1.maxImageExtent.height == props2.maxImageExtent.height && - props1.maxImageArrayLayers == props2.maxImageArrayLayers && props1.supportedTransforms == props2.supportedTransforms && - props1.currentTransform == props2.currentTransform && props1.supportedCompositeAlpha == props2.supportedCompositeAlpha && - props1.supportedUsageFlags == props2.supportedUsageFlags; -} -inline bool operator==(const VkSurfacePresentScalingCapabilitiesEXT& caps1, const VkSurfacePresentScalingCapabilitiesEXT& caps2) { - return caps1.supportedPresentScaling == caps2.supportedPresentScaling && - caps1.supportedPresentGravityX == caps2.supportedPresentGravityX && - caps1.supportedPresentGravityY == caps2.supportedPresentGravityY && - caps1.minScaledImageExtent.width == caps2.minScaledImageExtent.width && - caps1.minScaledImageExtent.height == caps2.minScaledImageExtent.height && - caps1.maxScaledImageExtent.width == caps2.maxScaledImageExtent.width && - caps1.maxScaledImageExtent.height == caps2.maxScaledImageExtent.height; -} -inline bool operator==(const VkSurfaceFormatKHR& format1, const VkSurfaceFormatKHR& format2) { - return format1.format == format2.format && format1.colorSpace == format2.colorSpace; -} -inline bool operator==(const VkSurfaceFormatKHR& format1, const VkSurfaceFormat2KHR& format2) { - return format1 == format2.surfaceFormat; -} -inline bool operator==(const VkDisplayPropertiesKHR& props1, const VkDisplayPropertiesKHR& props2) { - return props1.display == props2.display && props1.physicalDimensions.width == props2.physicalDimensions.width && - props1.physicalDimensions.height == props2.physicalDimensions.height && - props1.physicalResolution.width == props2.physicalResolution.width && - props1.physicalResolution.height == props2.physicalResolution.height && - props1.supportedTransforms == props2.supportedTransforms && props1.planeReorderPossible == props2.planeReorderPossible && - props1.persistentContent == props2.persistentContent; -} -inline bool operator==(const VkDisplayPropertiesKHR& props1, const VkDisplayProperties2KHR& props2) { - return props1 == props2.displayProperties; -} -inline bool operator==(const VkDisplayModePropertiesKHR& disp1, const VkDisplayModePropertiesKHR& disp2) { - return disp1.displayMode == disp2.displayMode && disp1.parameters.visibleRegion.width == disp2.parameters.visibleRegion.width && - disp1.parameters.visibleRegion.height == disp2.parameters.visibleRegion.height && - disp1.parameters.refreshRate == disp2.parameters.refreshRate; -} - -inline bool operator==(const VkDisplayModePropertiesKHR& disp1, const VkDisplayModeProperties2KHR& disp2) { - return disp1 == disp2.displayModeProperties; -} -inline bool operator==(const VkDisplayPlaneCapabilitiesKHR& caps1, const VkDisplayPlaneCapabilitiesKHR& caps2) { - return caps1.supportedAlpha == caps2.supportedAlpha && caps1.minSrcPosition.x == caps2.minSrcPosition.x && - caps1.minSrcPosition.y == caps2.minSrcPosition.y && caps1.maxSrcPosition.x == caps2.maxSrcPosition.x && - caps1.maxSrcPosition.y == caps2.maxSrcPosition.y && caps1.minSrcExtent.width == caps2.minSrcExtent.width && - caps1.minSrcExtent.height == caps2.minSrcExtent.height && caps1.maxSrcExtent.width == caps2.maxSrcExtent.width && - caps1.maxSrcExtent.height == caps2.maxSrcExtent.height && caps1.minDstPosition.x == caps2.minDstPosition.x && - caps1.minDstPosition.y == caps2.minDstPosition.y && caps1.maxDstPosition.x == caps2.maxDstPosition.x && - caps1.maxDstPosition.y == caps2.maxDstPosition.y && caps1.minDstExtent.width == caps2.minDstExtent.width && - caps1.minDstExtent.height == caps2.minDstExtent.height && caps1.maxDstExtent.width == caps2.maxDstExtent.width && - caps1.maxDstExtent.height == caps2.maxDstExtent.height; -} - -inline bool operator==(const VkDisplayPlaneCapabilitiesKHR& caps1, const VkDisplayPlaneCapabilities2KHR& caps2) { - return caps1 == caps2.capabilities; -} -inline bool operator==(const VkDisplayPlanePropertiesKHR& props1, const VkDisplayPlanePropertiesKHR& props2) { - return props1.currentDisplay == props2.currentDisplay && props1.currentStackIndex == props2.currentStackIndex; -} -inline bool operator==(const VkDisplayPlanePropertiesKHR& props1, const VkDisplayPlaneProperties2KHR& props2) { - return props1 == props2.displayPlaneProperties; -} -inline bool operator==(const VkExtent2D& ext1, const VkExtent2D& ext2) { - return ext1.height == ext2.height && ext1.width == ext2.width; -} -// Allow comparison of vectors of different types as long as their elements are comparable (just has to make sure to only apply when -// T != U) -template >> -bool operator==(const std::vector& a, const std::vector& b) { - return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](const auto& left, const auto& right) { return left == right; }); -} - -struct VulkanFunction { - std::string name; - PFN_vkVoidFunction function = nullptr; -}; - -using VulkanUUID = std::array; - -template -bool check_permutation(std::initializer_list expected, std::array const& returned) { - if (expected.size() != returned.size()) return false; - for (uint32_t i = 0; i < expected.size(); i++) { - auto found = std::find_if(std::begin(returned), std::end(returned), - [&](T elem) { return string_eq(*(expected.begin() + i), elem.layerName); }); - if (found == std::end(returned)) return false; - } - return true; -} -template -bool check_permutation(std::initializer_list expected, std::vector const& returned) { - if (expected.size() != returned.size()) return false; - for (uint32_t i = 0; i < expected.size(); i++) { - auto found = std::find_if(std::begin(returned), std::end(returned), - [&](T elem) { return string_eq(*(expected.begin() + i), elem.layerName); }); - if (found == std::end(returned)) return false; - } - return true; -} - -inline bool contains(std::vector const& vec, const char* name) { - return std::any_of(std::begin(vec), std::end(vec), - [name](VkExtensionProperties const& elem) { return string_eq(name, elem.extensionName); }); -} -inline bool contains(std::vector const& vec, const char* name) { - return std::any_of(std::begin(vec), std::end(vec), - [name](VkLayerProperties const& elem) { return string_eq(name, elem.layerName); }); -} - -#if defined(__linux__) || defined(__GNU__) - -// find application path + name. Path cannot be longer than 1024, returns NULL if it is greater than that. -inline std::string test_platform_executable_path() { - std::string buffer; - buffer.resize(1024); - ssize_t count = readlink("/proc/self/exe", &buffer[0], buffer.size()); - if (count == -1) return NULL; - if (count == 0) return NULL; - buffer[count] = '\0'; - buffer.resize(count); - return buffer; -} -#elif defined(__APPLE__) -#include -inline std::string test_platform_executable_path() { - std::string buffer; - buffer.resize(1024); - pid_t pid = getpid(); - int ret = proc_pidpath(pid, &buffer[0], static_cast(buffer.size())); - if (ret <= 0) return NULL; - buffer[ret] = '\0'; - buffer.resize(ret); - return buffer; -} -#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) -#include -inline std::string test_platform_executable_path() { - int mib[] = { - CTL_KERN, -#if defined(__NetBSD__) - KERN_PROC_ARGS, - -1, - KERN_PROC_PATHNAME, -#else - KERN_PROC, - KERN_PROC_PATHNAME, - -1, -#endif - }; - std::string buffer; - buffer.resize(1024); - size_t size = buffer.size(); - if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), &buffer[0], &size, NULL, 0) < 0) { - return NULL; - } - buffer.resize(size); - - return buffer; -} -#elif defined(__Fuchsia__) || defined(__OpenBSD__) -inline std::string test_platform_executable_path() { return {}; } -#elif defined(__QNX__) - -#ifndef SYSCONFDIR -#define SYSCONFDIR "/etc" -#endif - -#include -#include - -inline std::string test_platform_executable_path() { - std::string buffer; - buffer.resize(1024); - int fd = open("/proc/self/exefile", O_RDONLY); - ssize_t rdsize; - - if (fd == -1) { - return NULL; - } - - rdsize = read(fd, &buffer[0], buffer.size()); - if (rdsize < 0) { - return NULL; - } - buffer[rdsize] = 0x00; - close(fd); - buffer.resize(rdsize); - - return buffer; -} -#endif // defined (__QNX__) -#if defined(WIN32) -inline std::string test_platform_executable_path() { - std::string buffer; - buffer.resize(1024); - DWORD ret = GetModuleFileName(NULL, static_cast(&buffer[0]), (DWORD)buffer.size()); - if (ret == 0) return NULL; - if (ret > buffer.size()) return NULL; - buffer.resize(ret); - buffer[ret] = '\0'; - return narrow(std::filesystem::path(buffer).native()); -} - -#endif diff --git a/tests/framework/util/CMakeLists.txt b/tests/framework/util/CMakeLists.txt new file mode 100644 index 000000000..3860f9932 --- /dev/null +++ b/tests/framework/util/CMakeLists.txt @@ -0,0 +1,85 @@ +# ~~~ +# Copyright (c) 2021 Valve Corporation +# Copyright (c) 2021 LunarG, Inc. +# +# 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. +# ~~~ + +add_library(testing_framework_util STATIC + builder_defines.h + dispatch_table.cpp + dispatch_table.h + dispatchable_handle.h + dynamic_library_wrapper.cpp + dynamic_library_wrapper.h + env_var_wrapper.cpp + env_var_wrapper.h + equality_helpers.cpp + equality_helpers.h + folder_manager.cpp + folder_manager.h + functions.h + get_executable_path.cpp + get_executable_path.h + json_writer.cpp + json_writer.h + manifest_builders.cpp + manifest_builders.h + platform_wsi.cpp + platform_wsi.h + test_defines.h + vulkan_object_wrappers.cpp + vulkan_object_wrappers.h + wide_char_handling.cpp + wide_char_handling.h + ) +target_link_libraries(testing_framework_util PUBLIC loader_common_options Vulkan::Headers gtest) + +if(UNIX OR APPLE) + target_link_libraries(testing_framework_util PUBLIC ${CMAKE_DL_LIBS}) +endif() + +if(UNIX) + target_compile_options(testing_framework_util PUBLIC -fPIC) +endif() +# Gives access to all headers in this folder, the framework folder, the framework binary folder, and the loader/generated folder +target_include_directories(testing_framework_util + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/.." "${CMAKE_CURRENT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}") + +if (UNIX) + if (LOADER_ENABLE_ADDRESS_SANITIZER) + target_compile_options(testing_framework_util PUBLIC -fsanitize=address,undefined) + target_link_options(testing_framework_util PUBLIC -fsanitize=address,undefined) + endif() + if (LOADER_ENABLE_THREAD_SANITIZER) + target_compile_options(testing_framework_util PUBLIC -fsanitize=thread) + target_link_options(testing_framework_util PUBLIC -fsanitize=thread) + target_compile_options(gtest PUBLIC -fsanitize=thread) + target_link_options(gtest PUBLIC -fsanitize=thread) + endif() +endif() + +if (MSVC) + # silence hidden class member warnings in test framework + target_compile_options(testing_framework_util PUBLIC /wd4458) + # Make sure exception handling is enabled for the test framework + target_compile_options(testing_framework_util PUBLIC /EHsc) +endif() + +# Add a compiler definition for the path to framework_config.h with the correct config +target_compile_definitions(testing_framework_util PUBLIC FRAMEWORK_CONFIG_HEADER="${FRAMEWORK_CONFIG_HEADER_PATH}") + +if (APPLE_STATIC_LOADER) + target_compile_definitions(testing_framework_util PUBLIC "APPLE_STATIC_LOADER=1") + target_link_libraries(testing_framework_util PRIVATE vulkan) +endif() diff --git a/tests/framework/util/builder_defines.h b/tests/framework/util/builder_defines.h new file mode 100644 index 000000000..c727c81b9 --- /dev/null +++ b/tests/framework/util/builder_defines.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +// Macro to ease the definition of variables with builder member functions +// type = type of the variable +// name = name of the variable +// default_value = value to default initialize, use {} if nothing else makes sense +#define BUILDER_VALUE_WITH_DEFAULT(type, name, default_value) \ + type name = default_value; \ + auto set_##name(type const& name)->decltype(*this) { \ + this->name = name; \ + return *this; \ + } + +#define BUILDER_VALUE(type, name) BUILDER_VALUE_WITH_DEFAULT(type, name, {}) + +// Macro to ease the definition of vectors with builder member functions +// type = type of the variable +// name = name of the variable +// singular_name = used for the `add_singular_name` member function +#define BUILDER_VECTOR(type, name, singular_name) \ + std::vector name; \ + auto add_##singular_name(type const& singular_name)->decltype(*this) { \ + this->name.push_back(singular_name); \ + return *this; \ + } \ + auto add_##singular_name##s(std::vector const& singular_name)->decltype(*this) { \ + for (auto& elem : singular_name) this->name.push_back(elem); \ + return *this; \ + } +// Like BUILDER_VECTOR but for move only types - where passing in means giving up ownership +#define BUILDER_VECTOR_MOVE_ONLY(type, name, singular_name) \ + std::vector name; \ + auto add_##singular_name(type&& singular_name)->decltype(*this) { \ + this->name.push_back(std::move(singular_name)); \ + return *this; \ + } diff --git a/tests/framework/util/dispatch_table.cpp b/tests/framework/util/dispatch_table.cpp new file mode 100644 index 000000000..60901afc1 --- /dev/null +++ b/tests/framework/util/dispatch_table.cpp @@ -0,0 +1,174 @@ +// VulkanFunctions - loads vulkan functions for tests to use +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "dispatch_table.h" + +#include "env_var_wrapper.h" + +std::filesystem::path get_loader_path() { + auto loader_path = std::filesystem::path(FRAMEWORK_VULKAN_LIBRARY_PATH); + auto env_var_res = get_env_var("VK_LOADER_TEST_LOADER_PATH", false); + if (!env_var_res.empty()) { + loader_path = std::filesystem::path(env_var_res); + } + return loader_path; +} + +void init_vulkan_functions(VulkanFunctions& funcs) { +#if defined(APPLE_STATIC_LOADER) +#define GPA(name) name +#else +#define GPA(name) funcs.loader.get_symbol(#name) +#endif + + // clang-format off + funcs.vkGetInstanceProcAddr = GPA(vkGetInstanceProcAddr); + funcs.vkEnumerateInstanceExtensionProperties = GPA(vkEnumerateInstanceExtensionProperties); + funcs.vkEnumerateInstanceLayerProperties = GPA(vkEnumerateInstanceLayerProperties); + funcs.vkEnumerateInstanceVersion = GPA(vkEnumerateInstanceVersion); + funcs.vkCreateInstance = GPA(vkCreateInstance); + funcs.vkDestroyInstance = GPA(vkDestroyInstance); + funcs.vkEnumeratePhysicalDevices = GPA(vkEnumeratePhysicalDevices); + funcs.vkEnumeratePhysicalDeviceGroups = GPA(vkEnumeratePhysicalDeviceGroups); + funcs.vkGetPhysicalDeviceFeatures = GPA(vkGetPhysicalDeviceFeatures); + funcs.vkGetPhysicalDeviceFeatures2 = GPA(vkGetPhysicalDeviceFeatures2); + funcs.vkGetPhysicalDeviceFormatProperties = GPA(vkGetPhysicalDeviceFormatProperties); + funcs.vkGetPhysicalDeviceFormatProperties2 = GPA(vkGetPhysicalDeviceFormatProperties2); + funcs.vkGetPhysicalDeviceImageFormatProperties = GPA(vkGetPhysicalDeviceImageFormatProperties); + funcs.vkGetPhysicalDeviceImageFormatProperties2 = GPA(vkGetPhysicalDeviceImageFormatProperties2); + funcs.vkGetPhysicalDeviceSparseImageFormatProperties = GPA(vkGetPhysicalDeviceSparseImageFormatProperties); + funcs.vkGetPhysicalDeviceSparseImageFormatProperties2 = GPA(vkGetPhysicalDeviceSparseImageFormatProperties2); + funcs.vkGetPhysicalDeviceProperties = GPA(vkGetPhysicalDeviceProperties); + funcs.vkGetPhysicalDeviceProperties2 = GPA(vkGetPhysicalDeviceProperties2); + funcs.vkGetPhysicalDeviceQueueFamilyProperties = GPA(vkGetPhysicalDeviceQueueFamilyProperties); + funcs.vkGetPhysicalDeviceQueueFamilyProperties2 = GPA(vkGetPhysicalDeviceQueueFamilyProperties2); + funcs.vkGetPhysicalDeviceMemoryProperties = GPA(vkGetPhysicalDeviceMemoryProperties); + funcs.vkGetPhysicalDeviceMemoryProperties2 = GPA(vkGetPhysicalDeviceMemoryProperties2); + funcs.vkGetPhysicalDeviceSurfaceSupportKHR = GPA(vkGetPhysicalDeviceSurfaceSupportKHR); + funcs.vkGetPhysicalDeviceSurfaceFormatsKHR = GPA(vkGetPhysicalDeviceSurfaceFormatsKHR); + funcs.vkGetPhysicalDeviceSurfacePresentModesKHR = GPA(vkGetPhysicalDeviceSurfacePresentModesKHR); + funcs.vkGetPhysicalDeviceSurfaceCapabilitiesKHR = GPA(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); + funcs.vkEnumerateDeviceExtensionProperties = GPA(vkEnumerateDeviceExtensionProperties); + funcs.vkEnumerateDeviceLayerProperties = GPA(vkEnumerateDeviceLayerProperties); + funcs.vkGetPhysicalDeviceExternalBufferProperties = GPA(vkGetPhysicalDeviceExternalBufferProperties); + funcs.vkGetPhysicalDeviceExternalFenceProperties = GPA(vkGetPhysicalDeviceExternalFenceProperties); + funcs.vkGetPhysicalDeviceExternalSemaphoreProperties = GPA(vkGetPhysicalDeviceExternalSemaphoreProperties); + + funcs.vkDestroySurfaceKHR = GPA(vkDestroySurfaceKHR); + funcs.vkGetDeviceProcAddr = GPA(vkGetDeviceProcAddr); + funcs.vkCreateDevice = GPA(vkCreateDevice); + + funcs.vkCreateHeadlessSurfaceEXT = GPA(vkCreateHeadlessSurfaceEXT); + funcs.vkCreateDisplayPlaneSurfaceKHR = GPA(vkCreateDisplayPlaneSurfaceKHR); + funcs.vkGetPhysicalDeviceDisplayPropertiesKHR = GPA(vkGetPhysicalDeviceDisplayPropertiesKHR); + funcs.vkGetPhysicalDeviceDisplayPlanePropertiesKHR = GPA(vkGetPhysicalDeviceDisplayPlanePropertiesKHR); + funcs.vkGetDisplayPlaneSupportedDisplaysKHR = GPA(vkGetDisplayPlaneSupportedDisplaysKHR); + funcs.vkGetDisplayModePropertiesKHR = GPA(vkGetDisplayModePropertiesKHR); + funcs.vkCreateDisplayModeKHR = GPA(vkCreateDisplayModeKHR); + funcs.vkGetDisplayPlaneCapabilitiesKHR = GPA(vkGetDisplayPlaneCapabilitiesKHR); + funcs.vkGetPhysicalDevicePresentRectanglesKHR = GPA(vkGetPhysicalDevicePresentRectanglesKHR); + funcs.vkGetPhysicalDeviceDisplayProperties2KHR = GPA(vkGetPhysicalDeviceDisplayProperties2KHR); + funcs.vkGetPhysicalDeviceDisplayPlaneProperties2KHR = GPA(vkGetPhysicalDeviceDisplayPlaneProperties2KHR); + funcs.vkGetDisplayModeProperties2KHR = GPA(vkGetDisplayModeProperties2KHR); + funcs.vkGetDisplayPlaneCapabilities2KHR = GPA(vkGetDisplayPlaneCapabilities2KHR); + funcs.vkGetPhysicalDeviceSurfaceCapabilities2KHR = GPA(vkGetPhysicalDeviceSurfaceCapabilities2KHR); + funcs.vkGetPhysicalDeviceSurfaceFormats2KHR = GPA(vkGetPhysicalDeviceSurfaceFormats2KHR); + +#if defined(VK_USE_PLATFORM_ANDROID_KHR) + funcs.vkCreateAndroidSurfaceKHR = GPA(vkCreateAndroidSurfaceKHR); +#endif // VK_USE_PLATFORM_ANDROID_KHR +#if defined(VK_USE_PLATFORM_DIRECTFB_EXT) + funcs.vkCreateDirectFBSurfaceEXT = GPA(vkCreateDirectFBSurfaceEXT); + funcs.vkGetPhysicalDeviceDirectFBPresentationSupportEXT = GPA(vkGetPhysicalDeviceDirectFBPresentationSupportEXT); +#endif // VK_USE_PLATFORM_DIRECTFB_EXT +#if defined(VK_USE_PLATFORM_FUCHSIA) + funcs.vkCreateImagePipeSurfaceFUCHSIA = GPA(vkCreateImagePipeSurfaceFUCHSIA); +#endif // VK_USE_PLATFORM_FUCHSIA +#if defined(VK_USE_PLATFORM_GGP) + funcs.vkCreateStreamDescriptorSurfaceGGP = GPA(vkCreateStreamDescriptorSurfaceGGP); +#endif // VK_USE_PLATFORM_GGP +#if defined(VK_USE_PLATFORM_IOS_MVK) + funcs.vkCreateIOSSurfaceMVK = GPA(vkCreateIOSSurfaceMVK); +#endif // VK_USE_PLATFORM_IOS_MVK +#if defined(VK_USE_PLATFORM_MACOS_MVK) + funcs.vkCreateMacOSSurfaceMVK = GPA(vkCreateMacOSSurfaceMVK); +#endif // VK_USE_PLATFORM_MACOS_MVK +#if defined(VK_USE_PLATFORM_METAL_EXT) + funcs.vkCreateMetalSurfaceEXT = GPA(vkCreateMetalSurfaceEXT); +#endif // VK_USE_PLATFORM_METAL_EXT +#if defined(VK_USE_PLATFORM_SCREEN_QNX) + funcs.vkCreateScreenSurfaceQNX = GPA(vkCreateScreenSurfaceQNX); + funcs.vkGetPhysicalDeviceScreenPresentationSupportQNX = GPA(vkGetPhysicalDeviceScreenPresentationSupportQNX); +#endif // VK_USE_PLATFORM_SCREEN_QNX +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + funcs.vkCreateWaylandSurfaceKHR = GPA(vkCreateWaylandSurfaceKHR); + funcs.vkGetPhysicalDeviceWaylandPresentationSupportKHR = GPA(vkGetPhysicalDeviceWaylandPresentationSupportKHR); +#endif // VK_USE_PLATFORM_WAYLAND_KHR +#if defined(VK_USE_PLATFORM_XCB_KHR) + funcs.vkCreateXcbSurfaceKHR = GPA(vkCreateXcbSurfaceKHR); + funcs.vkGetPhysicalDeviceXcbPresentationSupportKHR = GPA(vkGetPhysicalDeviceXcbPresentationSupportKHR); +#endif // VK_USE_PLATFORM_XCB_KHR +#if defined(VK_USE_PLATFORM_XLIB_KHR) + funcs.vkCreateXlibSurfaceKHR = GPA(vkCreateXlibSurfaceKHR); + funcs.vkGetPhysicalDeviceXlibPresentationSupportKHR = GPA(vkGetPhysicalDeviceXlibPresentationSupportKHR); +#endif // VK_USE_PLATFORM_XLIB_KHR +#if defined(VK_USE_PLATFORM_WIN32_KHR) + funcs.vkCreateWin32SurfaceKHR = GPA(vkCreateWin32SurfaceKHR); + funcs.vkGetPhysicalDeviceWin32PresentationSupportKHR = GPA(vkGetPhysicalDeviceWin32PresentationSupportKHR); +#endif // VK_USE_PLATFORM_WIN32_KHR + funcs.vkDestroyDevice = GPA(vkDestroyDevice); + funcs.vkGetDeviceQueue = GPA(vkGetDeviceQueue); +#undef GPA + // clang-format on +} + +#if defined(APPLE_STATIC_LOADER) +VulkanFunctions::VulkanFunctions() { +#else +VulkanFunctions::VulkanFunctions() : loader(get_loader_path()) { +#endif + init_vulkan_functions(*this); +} + +void VulkanFunctions::load_instance_functions(VkInstance instance) { + vkCreateDebugReportCallbackEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")); + vkDestroyDebugReportCallbackEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT")); + vkCreateDebugUtilsMessengerEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); + vkDestroyDebugUtilsMessengerEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")); +} + +DeviceFunctions::DeviceFunctions(const VulkanFunctions& vulkan_functions, VkDevice device) { + vkGetDeviceProcAddr = vulkan_functions.vkGetDeviceProcAddr; + vkDestroyDevice = load(device, "vkDestroyDevice"); + vkGetDeviceQueue = load(device, "vkGetDeviceQueue"); + vkCreateCommandPool = load(device, "vkCreateCommandPool"); + vkAllocateCommandBuffers = load(device, "vkAllocateCommandBuffers"); + vkDestroyCommandPool = load(device, "vkDestroyCommandPool"); + vkCreateSwapchainKHR = load(device, "vkCreateSwapchainKHR"); + vkGetSwapchainImagesKHR = load(device, "vkGetSwapchainImagesKHR"); + vkDestroySwapchainKHR = load(device, "vkDestroySwapchainKHR"); +} diff --git a/tests/framework/util/dispatch_table.h b/tests/framework/util/dispatch_table.h new file mode 100644 index 000000000..265416532 --- /dev/null +++ b/tests/framework/util/dispatch_table.h @@ -0,0 +1,178 @@ +// VulkanFunctions - loads vulkan functions for tests to use +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +#include "functions.h" +#include "dynamic_library_wrapper.h" + +struct VulkanFunctions { +#if !defined(APPLE_STATIC_LOADER) + LibraryWrapper loader; +#endif + // Pre-Instance + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr; + PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties = nullptr; + PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties = nullptr; + PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion = nullptr; + PFN_vkCreateInstance vkCreateInstance = nullptr; + + // Instance + PFN_vkDestroyInstance vkDestroyInstance = nullptr; + PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices = nullptr; + PFN_vkEnumeratePhysicalDeviceGroups vkEnumeratePhysicalDeviceGroups = nullptr; + PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures = nullptr; + PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2 = nullptr; + PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties = nullptr; + PFN_vkGetPhysicalDeviceFormatProperties2 vkGetPhysicalDeviceFormatProperties2 = nullptr; + PFN_vkGetPhysicalDeviceImageFormatProperties vkGetPhysicalDeviceImageFormatProperties = nullptr; + PFN_vkGetPhysicalDeviceImageFormatProperties2 vkGetPhysicalDeviceImageFormatProperties2 = nullptr; + PFN_vkGetPhysicalDeviceSparseImageFormatProperties vkGetPhysicalDeviceSparseImageFormatProperties = nullptr; + PFN_vkGetPhysicalDeviceSparseImageFormatProperties2 vkGetPhysicalDeviceSparseImageFormatProperties2 = nullptr; + PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties = nullptr; + PFN_vkGetPhysicalDeviceProperties2 vkGetPhysicalDeviceProperties2 = nullptr; + PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties = nullptr; + PFN_vkGetPhysicalDeviceQueueFamilyProperties2 vkGetPhysicalDeviceQueueFamilyProperties2 = nullptr; + PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties = nullptr; + PFN_vkGetPhysicalDeviceMemoryProperties2 vkGetPhysicalDeviceMemoryProperties2 = nullptr; + PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR = nullptr; + PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR = nullptr; + PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR = nullptr; + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR = nullptr; + PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties = nullptr; + PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties = nullptr; + PFN_vkGetPhysicalDeviceExternalBufferProperties vkGetPhysicalDeviceExternalBufferProperties = nullptr; + PFN_vkGetPhysicalDeviceExternalFenceProperties vkGetPhysicalDeviceExternalFenceProperties = nullptr; + PFN_vkGetPhysicalDeviceExternalSemaphoreProperties vkGetPhysicalDeviceExternalSemaphoreProperties = nullptr; + + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = nullptr; + PFN_vkCreateDevice vkCreateDevice = nullptr; + + // WSI + PFN_vkCreateHeadlessSurfaceEXT vkCreateHeadlessSurfaceEXT = nullptr; + PFN_vkCreateDisplayPlaneSurfaceKHR vkCreateDisplayPlaneSurfaceKHR = nullptr; + PFN_vkGetPhysicalDeviceDisplayPropertiesKHR vkGetPhysicalDeviceDisplayPropertiesKHR = nullptr; + PFN_vkGetPhysicalDeviceDisplayPlanePropertiesKHR vkGetPhysicalDeviceDisplayPlanePropertiesKHR = nullptr; + PFN_vkGetDisplayPlaneSupportedDisplaysKHR vkGetDisplayPlaneSupportedDisplaysKHR = nullptr; + PFN_vkGetDisplayModePropertiesKHR vkGetDisplayModePropertiesKHR = nullptr; + PFN_vkCreateDisplayModeKHR vkCreateDisplayModeKHR = nullptr; + PFN_vkGetDisplayPlaneCapabilitiesKHR vkGetDisplayPlaneCapabilitiesKHR = nullptr; + PFN_vkGetPhysicalDevicePresentRectanglesKHR vkGetPhysicalDevicePresentRectanglesKHR = nullptr; + PFN_vkGetPhysicalDeviceDisplayProperties2KHR vkGetPhysicalDeviceDisplayProperties2KHR = nullptr; + PFN_vkGetPhysicalDeviceDisplayPlaneProperties2KHR vkGetPhysicalDeviceDisplayPlaneProperties2KHR = nullptr; + PFN_vkGetDisplayModeProperties2KHR vkGetDisplayModeProperties2KHR = nullptr; + PFN_vkGetDisplayPlaneCapabilities2KHR vkGetDisplayPlaneCapabilities2KHR = nullptr; + PFN_vkGetPhysicalDeviceSurfaceCapabilities2KHR vkGetPhysicalDeviceSurfaceCapabilities2KHR = nullptr; + PFN_vkGetPhysicalDeviceSurfaceFormats2KHR vkGetPhysicalDeviceSurfaceFormats2KHR = nullptr; + +#if defined(VK_USE_PLATFORM_ANDROID_KHR) + PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR = nullptr; +#endif // VK_USE_PLATFORM_ANDROID_KHR +#if defined(VK_USE_PLATFORM_DIRECTFB_EXT) + PFN_vkCreateDirectFBSurfaceEXT vkCreateDirectFBSurfaceEXT = nullptr; + PFN_vkGetPhysicalDeviceDirectFBPresentationSupportEXT vkGetPhysicalDeviceDirectFBPresentationSupportEXT = nullptr; +#endif // VK_USE_PLATFORM_DIRECTFB_EXT +#if defined(VK_USE_PLATFORM_FUCHSIA) + PFN_vkCreateImagePipeSurfaceFUCHSIA vkCreateImagePipeSurfaceFUCHSIA = nullptr; +#endif // VK_USE_PLATFORM_FUCHSIA +#if defined(VK_USE_PLATFORM_GGP) + PFN_vkCreateStreamDescriptorSurfaceGGP vkCreateStreamDescriptorSurfaceGGP = nullptr; +#endif // VK_USE_PLATFORM_GGP +#if defined(VK_USE_PLATFORM_IOS_MVK) + PFN_vkCreateIOSSurfaceMVK vkCreateIOSSurfaceMVK = nullptr; +#endif // VK_USE_PLATFORM_IOS_MVK +#if defined(VK_USE_PLATFORM_MACOS_MVK) + PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK = nullptr; +#endif // VK_USE_PLATFORM_MACOS_MVK +#if defined(VK_USE_PLATFORM_METAL_EXT) + PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT = nullptr; +#endif // VK_USE_PLATFORM_METAL_EXT +#if defined(VK_USE_PLATFORM_SCREEN_QNX) + PFN_vkCreateScreenSurfaceQNX vkCreateScreenSurfaceQNX = nullptr; + PFN_vkGetPhysicalDeviceScreenPresentationSupportQNX vkGetPhysicalDeviceScreenPresentationSupportQNX = nullptr; +#endif // VK_USE_PLATFORM_SCREEN_QNX +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR = nullptr; + PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR = nullptr; +#endif // VK_USE_PLATFORM_WAYLAND_KHR +#if defined(VK_USE_PLATFORM_XCB_KHR) + PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR = nullptr; + PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR vkGetPhysicalDeviceXcbPresentationSupportKHR = nullptr; +#endif // VK_USE_PLATFORM_XCB_KHR +#if defined(VK_USE_PLATFORM_XLIB_KHR) + PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR = nullptr; + PFN_vkGetPhysicalDeviceXlibPresentationSupportKHR vkGetPhysicalDeviceXlibPresentationSupportKHR = nullptr; +#endif // VK_USE_PLATFORM_XLIB_KHR +#if defined(VK_USE_PLATFORM_WIN32_KHR) + PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR = nullptr; + PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR vkGetPhysicalDeviceWin32PresentationSupportKHR = nullptr; +#endif // VK_USE_PLATFORM_WIN32_KHR + PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR = nullptr; + + // instance extensions functions (can only be loaded with a valid instance) + PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = nullptr; // Null unless the extension is enabled + PFN_vkDestroyDebugUtilsMessengerEXT vkDestroyDebugUtilsMessengerEXT = nullptr; // Null unless the extension is enabled + PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT = nullptr; // Null unless the extension is enabled + PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT = nullptr; // Null unless the extension is enabled + + // device functions + PFN_vkDestroyDevice vkDestroyDevice = nullptr; + PFN_vkGetDeviceQueue vkGetDeviceQueue = nullptr; + + VulkanFunctions(); + + void load_instance_functions(VkInstance instance); + + FromVoidStarFunc load(VkInstance inst, const char* func_name) const { + return FromVoidStarFunc(vkGetInstanceProcAddr(inst, func_name)); + } + + FromVoidStarFunc load(VkDevice device, const char* func_name) const { + return FromVoidStarFunc(vkGetDeviceProcAddr(device, func_name)); + } +}; + +struct DeviceFunctions { + PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = nullptr; + PFN_vkDestroyDevice vkDestroyDevice = nullptr; + PFN_vkGetDeviceQueue vkGetDeviceQueue = nullptr; + PFN_vkCreateCommandPool vkCreateCommandPool = nullptr; + PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers = nullptr; + PFN_vkDestroyCommandPool vkDestroyCommandPool = nullptr; + PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR = nullptr; + PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR = nullptr; + PFN_vkDestroySwapchainKHR vkDestroySwapchainKHR = nullptr; + + DeviceFunctions() = default; + DeviceFunctions(const VulkanFunctions& vulkan_functions, VkDevice device); + + FromVoidStarFunc load(VkDevice device, const char* func_name) const { + return FromVoidStarFunc(vkGetDeviceProcAddr(device, func_name)); + } +}; diff --git a/tests/framework/util/dispatchable_handle.h b/tests/framework/util/dispatchable_handle.h new file mode 100644 index 000000000..98085bb7d --- /dev/null +++ b/tests/framework/util/dispatchable_handle.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +#include "test_defines.h" + +template +struct FRAMEWORK_EXPORT DispatchableHandle { + DispatchableHandle() { + auto ptr_handle = new VK_LOADER_DATA; + set_loader_magic_value(ptr_handle); + handle = reinterpret_cast(ptr_handle); + } + ~DispatchableHandle() { + if (handle) { + delete reinterpret_cast(handle); + } + handle = nullptr; + } + DispatchableHandle(DispatchableHandle const&) = delete; + DispatchableHandle& operator=(DispatchableHandle const&) = delete; + DispatchableHandle(DispatchableHandle&& other) noexcept : handle(other.handle) { other.handle = nullptr; } + DispatchableHandle& operator=(DispatchableHandle&& other) noexcept { + if (handle) { + delete reinterpret_cast(handle); + } + handle = other.handle; + other.handle = nullptr; + return *this; + } + bool operator==(T base_handle) { return base_handle == handle; } + bool operator!=(T base_handle) { return base_handle != handle; } + + T handle = nullptr; +}; diff --git a/tests/framework/util/dynamic_library_wrapper.cpp b/tests/framework/util/dynamic_library_wrapper.cpp new file mode 100644 index 000000000..e868d1607 --- /dev/null +++ b/tests/framework/util/dynamic_library_wrapper.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "dynamic_library_wrapper.h" + +#include "test_defines.h" +#include "env_var_wrapper.h" + +#include +#include +#include + +#include + +namespace detail { + +#if defined(_WIN32) +typedef HMODULE test_platform_dl_handle; + +std::string error_code_to_string(DWORD error_code) { + LPSTR lpMsgBuf{}; + size_t MsgBufSize = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpMsgBuf, 0, nullptr); + std::string code_str(lpMsgBuf, MsgBufSize); + LocalFree(lpMsgBuf); + return code_str; +} +std::wstring wide_error_code_to_string(DWORD error_code) { + LPWSTR lpwMsgBuf{}; + size_t MsgBufSize = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpwMsgBuf, 0, nullptr); + std::wstring code_str(lpwMsgBuf, MsgBufSize); + LocalFree(lpwMsgBuf); + return code_str; +} +#elif TESTING_COMMON_UNIX_PLATFORMS +#include + +typedef void* test_platform_dl_handle; +#endif +} // namespace detail + +LibraryWrapper::LibraryWrapper() noexcept {} +LibraryWrapper::LibraryWrapper(std::filesystem::path const& lib_path) noexcept : lib_path(lib_path.lexically_normal()) { +#if defined(_WIN32) + auto wide_path = lib_path.native(); // Get a std::wstring first + // Try loading the library the original way first. + lib_handle = LoadLibraryW(wide_path.c_str()); + DWORD last_error = GetLastError(); + // If that failed because of path limitations, prepend a special sequence to opt into longer path support + if (lib_handle == nullptr && last_error == ERROR_FILENAME_EXCED_RANGE) { + // "\\?\" is a special prefix for filenames that tells winapi to ignore path limits, so we can workaround path limitations + // without relying on system level changes. + wide_path = L"\\\\?\\" + wide_path; + last_error = 0; + lib_handle = LoadLibraryW(wide_path.c_str()); + last_error = GetLastError(); + } + // If that failed, then try loading it with broader search folders. + if (lib_handle == nullptr && last_error == ERROR_MOD_NOT_FOUND) { + last_error = 0; + lib_handle = + LoadLibraryExW(wide_path.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR); + last_error = GetLastError(); + } + // If neither of the above worked, print an error message and abort + if (lib_handle == nullptr) { + auto error_msg = detail::wide_error_code_to_string(last_error); + std::wcerr << L"Unable to open library: " << wide_path << L" due to: " << error_msg << L"\n"; + abort(); + } +#elif TESTING_COMMON_UNIX_PLATFORMS + lib_handle = dlopen(lib_path.string().c_str(), RTLD_LAZY | RTLD_LOCAL); + if (lib_handle == nullptr) { + std::wcerr << "Unable to open library: " << lib_path << " due to: " << dlerror() << "\n"; + abort(); + } +#else +#error "Unhandled platform in dynamic_library_wrapper.cpp!" +#endif +} + +LibraryWrapper::~LibraryWrapper() noexcept { close_library(); } + +LibraryWrapper::LibraryWrapper(LibraryWrapper&& wrapper) noexcept : lib_handle(wrapper.lib_handle), lib_path(wrapper.lib_path) { + wrapper.lib_handle = nullptr; +} +LibraryWrapper& LibraryWrapper::operator=(LibraryWrapper&& wrapper) noexcept { + if (this != &wrapper) { + lib_handle = wrapper.lib_handle; + lib_path = wrapper.lib_path; + wrapper.lib_handle = nullptr; + } + return *this; +} + +void LibraryWrapper::close_library() noexcept { + auto loader_disable_dynamic_library_unloading_env_var = get_env_var("VK_LOADER_DISABLE_DYNAMIC_LIBRARY_UNLOADING", false); + if (loader_disable_dynamic_library_unloading_env_var.empty() || loader_disable_dynamic_library_unloading_env_var != "1") { + if (lib_handle != nullptr) { +#if defined(_WIN32) + FreeLibrary(lib_handle); +#elif TESTING_COMMON_UNIX_PLATFORMS + dlclose(lib_handle); +#else +#error "Unhandled platform in dynamic_library_wrapper.cpp!" +#endif + lib_handle = nullptr; + } + } +} + +FromVoidStarFunc LibraryWrapper::get_symbol(const char* symbol_name) const { + if (symbol_name == nullptr) { + std::cerr << "LibraryWrapper::get_symbol called with a null symbol_name!\n"; + abort(); + } + if (lib_handle == nullptr) { + std::cerr << "LibraryWrapper::get_symbol called when lib_handle is nullptr!\n"; + abort(); + } + +#if defined(_WIN32) + void* symbol = reinterpret_cast(GetProcAddress(lib_handle, symbol_name)); + if (symbol == nullptr) { + auto last_error = detail::error_code_to_string(GetLastError()); + std::cerr << "Unable to get symbol " << symbol_name << " in library " << lib_path.string() << " with to error code " + << last_error << "\n"; + abort(); + } +#elif TESTING_COMMON_UNIX_PLATFORMS + void* symbol = dlsym(lib_handle, symbol_name); + if (symbol == nullptr) { + std::cerr << "Unable to get symbol " << symbol_name << " in library " << lib_path.string() << " due to " << dlerror() + << "\n"; + abort(); + } +#else +#error "Unhandled platform in dynamic_library_wrapper.cpp!" +#endif + return FromVoidStarFunc(symbol); +} diff --git a/tests/framework/util/dynamic_library_wrapper.h b/tests/framework/util/dynamic_library_wrapper.h new file mode 100644 index 000000000..35b72adbe --- /dev/null +++ b/tests/framework/util/dynamic_library_wrapper.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +#include "test_defines.h" + +#include "functions.h" + +#if defined(_WIN32) +#include +#include +#include +#elif TESTING_COMMON_UNIX_PLATFORMS +#include +#include +#include +#include +#include +#endif + +struct LibraryWrapper { + explicit LibraryWrapper() noexcept; + explicit LibraryWrapper(std::filesystem::path const& lib_path) noexcept; + ~LibraryWrapper() noexcept; + LibraryWrapper(LibraryWrapper const& wrapper) = delete; + LibraryWrapper& operator=(LibraryWrapper const& wrapper) = delete; + LibraryWrapper(LibraryWrapper&& wrapper) noexcept; + LibraryWrapper& operator=(LibraryWrapper&& wrapper) noexcept; + FromVoidStarFunc get_symbol(const char* symbol_name) const; + + std::filesystem::path const& get_path() const { return lib_path; } + explicit operator bool() const noexcept { return lib_handle != nullptr; } + + private: + void close_library() noexcept; + +#if defined(_WIN32) + HMODULE lib_handle = nullptr; +#elif TESTING_COMMON_UNIX_PLATFORMS + void* lib_handle = nullptr; +#else +#error "Unhandled platform in dynamic_library_wrapper.h!" +#endif + std::filesystem::path lib_path; +}; diff --git a/tests/framework/util/env_var_wrapper.cpp b/tests/framework/util/env_var_wrapper.cpp new file mode 100644 index 000000000..f9df38994 --- /dev/null +++ b/tests/framework/util/env_var_wrapper.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "env_var_wrapper.h" + +#include + +#include + +#include "wide_char_handling.h" + +// NOTE: get_env_var() are only intended for test framework code, all test code MUST use +// EnvVarWrapper + +#if defined(_WIN32) +#include +#include +#include + +// Windows specific error handling logic +const long ERROR_SETENV_FAILED = 10543; // chosen at random, attempts to not conflict +const long ERROR_REMOVEDIRECTORY_FAILED = 10544; // chosen at random, attempts to not conflict +const char* win_api_error_str(LSTATUS status) { + if (status == ERROR_INVALID_FUNCTION) return "ERROR_INVALID_FUNCTION"; + if (status == ERROR_FILE_NOT_FOUND) return "ERROR_FILE_NOT_FOUND"; + if (status == ERROR_PATH_NOT_FOUND) return "ERROR_PATH_NOT_FOUND"; + if (status == ERROR_TOO_MANY_OPEN_FILES) return "ERROR_TOO_MANY_OPEN_FILES"; + if (status == ERROR_ACCESS_DENIED) return "ERROR_ACCESS_DENIED"; + if (status == ERROR_INVALID_HANDLE) return "ERROR_INVALID_HANDLE"; + if (status == ERROR_ENVVAR_NOT_FOUND) return "ERROR_ENVVAR_NOT_FOUND"; + if (status == ERROR_SETENV_FAILED) return "ERROR_SETENV_FAILED"; + return "UNKNOWN ERROR"; +} +std::string error_code_to_string(DWORD error_code) { + LPSTR lpMsgBuf{}; + size_t MsgBufSize = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpMsgBuf, 0, nullptr); + std::string code_str(lpMsgBuf, MsgBufSize); + LocalFree(lpMsgBuf); + return code_str; +} + +void print_error_message(LSTATUS status, const char* function_name, std::string optional_message = "") { + DWORD dw = GetLastError(); + + std::cerr << function_name << " failed with " << win_api_error_str(status) << ": " << error_code_to_string(dw); + if (optional_message != "") { + std::cerr << " | " << optional_message; + } + std::cerr << "\n"; +} + +// get_env_var() - returns a std::string of `name`. if report_failure is true, then it will log to stderr that it didn't find the +// env-var +std::string get_env_var(std::string const& name, bool report_failure) { + std::wstring name_utf16 = widen(name); + DWORD value_size = GetEnvironmentVariableW(name_utf16.c_str(), nullptr, 0); + if (0 == value_size) { + if (report_failure) print_error_message(ERROR_ENVVAR_NOT_FOUND, "GetEnvironmentVariableW"); + return {}; + } + std::wstring value(value_size, L'\0'); + if (GetEnvironmentVariableW(name_utf16.c_str(), &value[0], value_size) != value_size - 1) { + return {}; + } + return narrow(value); +} +#elif TESTING_COMMON_UNIX_PLATFORMS + +// get_env_var() - returns a std::string of `name`. if report_failure is true, then it will log to stderr that it didn't find the +// env-var +std::string get_env_var(std::string const& name, bool report_failure) { + char* ret = getenv(name.c_str()); + if (ret == nullptr) { + if (report_failure) std::cerr << "Failed to get environment variable:" << name << "\n"; + return std::string(); + } + return ret; +} +#endif + +std::vector split_env_var_as_list(std::string const& env_var_contents) { + std::vector items; + size_t start = 0; + size_t len = 0; + for (size_t i = 0; i < env_var_contents.size(); i++) { + if (env_var_contents[i] == OS_ENV_VAR_LIST_SEPARATOR) { + if (len != 0) { + // only push back non empty strings + items.push_back(env_var_contents.substr(start, len)); + } + start = i + 1; + len = 0; + } else { + len++; + } + } + items.push_back(env_var_contents.substr(start, len)); + return items; +} + +/* + * Wrapper around Environment Variables with common operations + * Since Environment Variables leak between tests, there needs to be RAII code to remove them during test cleanup + + */ + +EnvVarWrapper::EnvVarWrapper(std::string const& name) noexcept : name(name) { + initial_value = get_env_var(name, false); + remove_env_var(); +} +EnvVarWrapper::EnvVarWrapper(std::string const& name, std::string const& value) noexcept : name(name), cur_value(value) { + initial_value = get_env_var(name, false); + set_env_var(); +} +EnvVarWrapper::~EnvVarWrapper() noexcept { + remove_env_var(); + if (!initial_value.empty()) { + set_new_value(initial_value); + } +} + +void EnvVarWrapper::set_new_value(std::string const& value) { + cur_value = value; + set_env_var(); +} +void EnvVarWrapper::add_to_list(std::string const& list_item) { + if (!cur_value.empty()) { + cur_value += OS_ENV_VAR_LIST_SEPARATOR; + } + cur_value += list_item; + set_env_var(); +} +#if defined(_WIN32) +void EnvVarWrapper::add_to_list(std::wstring const& list_item) { + if (!cur_value.empty()) { + cur_value += OS_ENV_VAR_LIST_SEPARATOR; + } + cur_value += narrow(list_item); + set_env_var(); +} +#endif +void EnvVarWrapper::remove_value() const { remove_env_var(); } +const char* EnvVarWrapper::get() const { return name.c_str(); } +const char* EnvVarWrapper::value() const { return cur_value.c_str(); } + +#if defined(_WIN32) + +void EnvVarWrapper::set_env_var() const { + BOOL ret = SetEnvironmentVariableW(widen(name).c_str(), widen(cur_value).c_str()); + if (ret == 0) { + print_error_message(ERROR_SETENV_FAILED, "SetEnvironmentVariableW"); + } +} +void EnvVarWrapper::remove_env_var() const { SetEnvironmentVariableW(widen(name).c_str(), nullptr); } + +#elif TESTING_COMMON_UNIX_PLATFORMS + +void EnvVarWrapper::set_env_var() const { setenv(name.c_str(), cur_value.c_str(), 1); } +void EnvVarWrapper::remove_env_var() const { unsetenv(name.c_str()); } +#endif diff --git a/tests/framework/util/env_var_wrapper.h b/tests/framework/util/env_var_wrapper.h new file mode 100644 index 000000000..2a0ecca7a --- /dev/null +++ b/tests/framework/util/env_var_wrapper.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include +#include + +#include "test_defines.h" + +// Environment variable list separator - not for filesystem paths +#if defined(_WIN32) +static const char OS_ENV_VAR_LIST_SEPARATOR = ';'; +#elif TESTING_COMMON_UNIX_PLATFORMS +static const char OS_ENV_VAR_LIST_SEPARATOR = ':'; +#else +#error "Unhandled platform in env_var_wrapper.h!" +#endif + +// get_env_var() - returns the contents of env-var `name` as a std::string +// If report_failure is true, then it will log to stderr that it didn't find the env-var +std::string get_env_var(std::string const& name, bool report_failure = true); + +// split_env_var_as_list() - split env_var_contents into separate elements using the platform +// specific env-var list separator. +std::vector split_env_var_as_list(std::string const& env_var_contents); + +/* + * Wrapper around Environment Variables with common operations + * Since Environment Variables leak between tests, there needs to be RAII code to remove them during test cleanup + + */ + +// Wrapper to set & remove env-vars automatically +struct EnvVarWrapper { + // Constructor which unsets the env-var + EnvVarWrapper(std::string const& name) noexcept; + // Constructor which set the env-var to the specified value + EnvVarWrapper(std::string const& name, std::string const& value) noexcept; + ~EnvVarWrapper() noexcept; + + // delete copy operators + EnvVarWrapper(const EnvVarWrapper&) = delete; + EnvVarWrapper& operator=(const EnvVarWrapper&) = delete; + + void set_new_value(std::string const& value); + void add_to_list(std::string const& list_item); +#if defined(_WIN32) + void add_to_list(std::wstring const& list_item); +#endif + void remove_value() const; + const char* get() const; + const char* value() const; + + private: + std::string name; + std::string cur_value; + std::string initial_value; + + void set_env_var() const; + void remove_env_var() const; +}; diff --git a/tests/framework/util/equality_helpers.cpp b/tests/framework/util/equality_helpers.cpp new file mode 100644 index 000000000..4a98599d4 --- /dev/null +++ b/tests/framework/util/equality_helpers.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "equality_helpers.h" + +bool operator==(const VkExtent3D& a, const VkExtent3D& b) { + return a.width == b.width && a.height == b.height && a.depth == b.depth; +} +bool operator!=(const VkExtent3D& a, const VkExtent3D& b) { return !(a == b); } + +bool operator==(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties& b) { + return a.minImageTransferGranularity == b.minImageTransferGranularity && a.queueCount == b.queueCount && + a.queueFlags == b.queueFlags && a.timestampValidBits == b.timestampValidBits; +} +bool operator!=(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties& b) { return !(a == b); } + +bool operator==(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties2& b) { return a == b.queueFamilyProperties; } +bool operator!=(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties2& b) { return a != b.queueFamilyProperties; } + +bool operator==(const VkLayerProperties& a, const VkLayerProperties& b) { + return string_eq(a.layerName, b.layerName, 256) && string_eq(a.description, b.description, 256) && + a.implementationVersion == b.implementationVersion && a.specVersion == b.specVersion; +} +bool operator!=(const VkLayerProperties& a, const VkLayerProperties& b) { return !(a == b); } + +bool operator==(const VkExtensionProperties& a, const VkExtensionProperties& b) { + return string_eq(a.extensionName, b.extensionName, 256) && a.specVersion == b.specVersion; +} +bool operator!=(const VkExtensionProperties& a, const VkExtensionProperties& b) { return !(a == b); } + +bool operator==(const VkPhysicalDeviceFeatures& feats1, const VkPhysicalDeviceFeatures2& feats2) { + return feats1.robustBufferAccess == feats2.features.robustBufferAccess && + feats1.fullDrawIndexUint32 == feats2.features.fullDrawIndexUint32 && + feats1.imageCubeArray == feats2.features.imageCubeArray && feats1.independentBlend == feats2.features.independentBlend && + feats1.geometryShader == feats2.features.geometryShader && + feats1.tessellationShader == feats2.features.tessellationShader && + feats1.sampleRateShading == feats2.features.sampleRateShading && feats1.dualSrcBlend == feats2.features.dualSrcBlend && + feats1.logicOp == feats2.features.logicOp && feats1.multiDrawIndirect == feats2.features.multiDrawIndirect && + feats1.drawIndirectFirstInstance == feats2.features.drawIndirectFirstInstance && + feats1.depthClamp == feats2.features.depthClamp && feats1.depthBiasClamp == feats2.features.depthBiasClamp && + feats1.fillModeNonSolid == feats2.features.fillModeNonSolid && feats1.depthBounds == feats2.features.depthBounds && + feats1.wideLines == feats2.features.wideLines && feats1.largePoints == feats2.features.largePoints && + feats1.alphaToOne == feats2.features.alphaToOne && feats1.multiViewport == feats2.features.multiViewport && + feats1.samplerAnisotropy == feats2.features.samplerAnisotropy && + feats1.textureCompressionETC2 == feats2.features.textureCompressionETC2 && + feats1.textureCompressionASTC_LDR == feats2.features.textureCompressionASTC_LDR && + feats1.textureCompressionBC == feats2.features.textureCompressionBC && + feats1.occlusionQueryPrecise == feats2.features.occlusionQueryPrecise && + feats1.pipelineStatisticsQuery == feats2.features.pipelineStatisticsQuery && + feats1.vertexPipelineStoresAndAtomics == feats2.features.vertexPipelineStoresAndAtomics && + feats1.fragmentStoresAndAtomics == feats2.features.fragmentStoresAndAtomics && + feats1.shaderTessellationAndGeometryPointSize == feats2.features.shaderTessellationAndGeometryPointSize && + feats1.shaderImageGatherExtended == feats2.features.shaderImageGatherExtended && + feats1.shaderStorageImageExtendedFormats == feats2.features.shaderStorageImageExtendedFormats && + feats1.shaderStorageImageMultisample == feats2.features.shaderStorageImageMultisample && + feats1.shaderStorageImageReadWithoutFormat == feats2.features.shaderStorageImageReadWithoutFormat && + feats1.shaderStorageImageWriteWithoutFormat == feats2.features.shaderStorageImageWriteWithoutFormat && + feats1.shaderUniformBufferArrayDynamicIndexing == feats2.features.shaderUniformBufferArrayDynamicIndexing && + feats1.shaderSampledImageArrayDynamicIndexing == feats2.features.shaderSampledImageArrayDynamicIndexing && + feats1.shaderStorageBufferArrayDynamicIndexing == feats2.features.shaderStorageBufferArrayDynamicIndexing && + feats1.shaderStorageImageArrayDynamicIndexing == feats2.features.shaderStorageImageArrayDynamicIndexing && + feats1.shaderClipDistance == feats2.features.shaderClipDistance && + feats1.shaderCullDistance == feats2.features.shaderCullDistance && + feats1.shaderFloat64 == feats2.features.shaderFloat64 && feats1.shaderInt64 == feats2.features.shaderInt64 && + feats1.shaderInt16 == feats2.features.shaderInt16 && + feats1.shaderResourceResidency == feats2.features.shaderResourceResidency && + feats1.shaderResourceMinLod == feats2.features.shaderResourceMinLod && + feats1.sparseBinding == feats2.features.sparseBinding && + feats1.sparseResidencyBuffer == feats2.features.sparseResidencyBuffer && + feats1.sparseResidencyImage2D == feats2.features.sparseResidencyImage2D && + feats1.sparseResidencyImage3D == feats2.features.sparseResidencyImage3D && + feats1.sparseResidency2Samples == feats2.features.sparseResidency2Samples && + feats1.sparseResidency4Samples == feats2.features.sparseResidency4Samples && + feats1.sparseResidency8Samples == feats2.features.sparseResidency8Samples && + feats1.sparseResidency16Samples == feats2.features.sparseResidency16Samples && + feats1.sparseResidencyAliased == feats2.features.sparseResidencyAliased && + feats1.variableMultisampleRate == feats2.features.variableMultisampleRate && + feats1.inheritedQueries == feats2.features.inheritedQueries; +} + +bool operator==(const VkPhysicalDeviceMemoryProperties& props1, const VkPhysicalDeviceMemoryProperties2& props2) { + bool equal = true; + equal = equal && props1.memoryTypeCount == props2.memoryProperties.memoryTypeCount; + equal = equal && props1.memoryHeapCount == props2.memoryProperties.memoryHeapCount; + for (uint32_t i = 0; i < props1.memoryHeapCount; ++i) { + equal = equal && props1.memoryHeaps[i].size == props2.memoryProperties.memoryHeaps[i].size; + equal = equal && props1.memoryHeaps[i].flags == props2.memoryProperties.memoryHeaps[i].flags; + } + for (uint32_t i = 0; i < props1.memoryTypeCount; ++i) { + equal = equal && props1.memoryTypes[i].propertyFlags == props2.memoryProperties.memoryTypes[i].propertyFlags; + equal = equal && props1.memoryTypes[i].heapIndex == props2.memoryProperties.memoryTypes[i].heapIndex; + } + return equal; +} +bool operator==(const VkSparseImageFormatProperties& props1, const VkSparseImageFormatProperties& props2) { + return props1.aspectMask == props2.aspectMask && props1.imageGranularity.width == props2.imageGranularity.width && + props1.imageGranularity.height == props2.imageGranularity.height && + props1.imageGranularity.depth == props2.imageGranularity.depth && props1.flags == props2.flags; +} +bool operator==(const VkSparseImageFormatProperties& props1, const VkSparseImageFormatProperties2& props2) { + return props1 == props2.properties; +} +bool operator==(const VkExternalMemoryProperties& props1, const VkExternalMemoryProperties& props2) { + return props1.externalMemoryFeatures == props2.externalMemoryFeatures && + props1.exportFromImportedHandleTypes == props2.exportFromImportedHandleTypes && + props1.compatibleHandleTypes == props2.compatibleHandleTypes; +} +bool operator==(const VkExternalSemaphoreProperties& props1, const VkExternalSemaphoreProperties& props2) { + return props1.externalSemaphoreFeatures == props2.externalSemaphoreFeatures && + props1.exportFromImportedHandleTypes == props2.exportFromImportedHandleTypes && + props1.compatibleHandleTypes == props2.compatibleHandleTypes; +} +bool operator==(const VkExternalFenceProperties& props1, const VkExternalFenceProperties& props2) { + return props1.externalFenceFeatures == props2.externalFenceFeatures && + props1.exportFromImportedHandleTypes == props2.exportFromImportedHandleTypes && + props1.compatibleHandleTypes == props2.compatibleHandleTypes; +} +bool operator==(const VkSurfaceCapabilitiesKHR& props1, const VkSurfaceCapabilitiesKHR& props2) { + return props1.minImageCount == props2.minImageCount && props1.maxImageCount == props2.maxImageCount && + props1.currentExtent.width == props2.currentExtent.width && props1.currentExtent.height == props2.currentExtent.height && + props1.minImageExtent.width == props2.minImageExtent.width && + props1.minImageExtent.height == props2.minImageExtent.height && + props1.maxImageExtent.width == props2.maxImageExtent.width && + props1.maxImageExtent.height == props2.maxImageExtent.height && + props1.maxImageArrayLayers == props2.maxImageArrayLayers && props1.supportedTransforms == props2.supportedTransforms && + props1.currentTransform == props2.currentTransform && props1.supportedCompositeAlpha == props2.supportedCompositeAlpha && + props1.supportedUsageFlags == props2.supportedUsageFlags; +} +bool operator==(const VkSurfacePresentScalingCapabilitiesEXT& caps1, const VkSurfacePresentScalingCapabilitiesEXT& caps2) { + return caps1.supportedPresentScaling == caps2.supportedPresentScaling && + caps1.supportedPresentGravityX == caps2.supportedPresentGravityX && + caps1.supportedPresentGravityY == caps2.supportedPresentGravityY && + caps1.minScaledImageExtent.width == caps2.minScaledImageExtent.width && + caps1.minScaledImageExtent.height == caps2.minScaledImageExtent.height && + caps1.maxScaledImageExtent.width == caps2.maxScaledImageExtent.width && + caps1.maxScaledImageExtent.height == caps2.maxScaledImageExtent.height; +} +bool operator==(const VkSurfaceFormatKHR& format1, const VkSurfaceFormatKHR& format2) { + return format1.format == format2.format && format1.colorSpace == format2.colorSpace; +} +bool operator==(const VkSurfaceFormatKHR& format1, const VkSurfaceFormat2KHR& format2) { return format1 == format2.surfaceFormat; } +bool operator==(const VkDisplayPropertiesKHR& props1, const VkDisplayPropertiesKHR& props2) { + return props1.display == props2.display && props1.physicalDimensions.width == props2.physicalDimensions.width && + props1.physicalDimensions.height == props2.physicalDimensions.height && + props1.physicalResolution.width == props2.physicalResolution.width && + props1.physicalResolution.height == props2.physicalResolution.height && + props1.supportedTransforms == props2.supportedTransforms && props1.planeReorderPossible == props2.planeReorderPossible && + props1.persistentContent == props2.persistentContent; +} +bool operator==(const VkDisplayPropertiesKHR& props1, const VkDisplayProperties2KHR& props2) { + return props1 == props2.displayProperties; +} +bool operator==(const VkDisplayModePropertiesKHR& disp1, const VkDisplayModePropertiesKHR& disp2) { + return disp1.displayMode == disp2.displayMode && disp1.parameters.visibleRegion.width == disp2.parameters.visibleRegion.width && + disp1.parameters.visibleRegion.height == disp2.parameters.visibleRegion.height && + disp1.parameters.refreshRate == disp2.parameters.refreshRate; +} + +bool operator==(const VkDisplayModePropertiesKHR& disp1, const VkDisplayModeProperties2KHR& disp2) { + return disp1 == disp2.displayModeProperties; +} +bool operator==(const VkDisplayPlaneCapabilitiesKHR& caps1, const VkDisplayPlaneCapabilitiesKHR& caps2) { + return caps1.supportedAlpha == caps2.supportedAlpha && caps1.minSrcPosition.x == caps2.minSrcPosition.x && + caps1.minSrcPosition.y == caps2.minSrcPosition.y && caps1.maxSrcPosition.x == caps2.maxSrcPosition.x && + caps1.maxSrcPosition.y == caps2.maxSrcPosition.y && caps1.minSrcExtent.width == caps2.minSrcExtent.width && + caps1.minSrcExtent.height == caps2.minSrcExtent.height && caps1.maxSrcExtent.width == caps2.maxSrcExtent.width && + caps1.maxSrcExtent.height == caps2.maxSrcExtent.height && caps1.minDstPosition.x == caps2.minDstPosition.x && + caps1.minDstPosition.y == caps2.minDstPosition.y && caps1.maxDstPosition.x == caps2.maxDstPosition.x && + caps1.maxDstPosition.y == caps2.maxDstPosition.y && caps1.minDstExtent.width == caps2.minDstExtent.width && + caps1.minDstExtent.height == caps2.minDstExtent.height && caps1.maxDstExtent.width == caps2.maxDstExtent.width && + caps1.maxDstExtent.height == caps2.maxDstExtent.height; +} + +bool operator==(const VkDisplayPlaneCapabilitiesKHR& caps1, const VkDisplayPlaneCapabilities2KHR& caps2) { + return caps1 == caps2.capabilities; +} +bool operator==(const VkDisplayPlanePropertiesKHR& props1, const VkDisplayPlanePropertiesKHR& props2) { + return props1.currentDisplay == props2.currentDisplay && props1.currentStackIndex == props2.currentStackIndex; +} +bool operator==(const VkDisplayPlanePropertiesKHR& props1, const VkDisplayPlaneProperties2KHR& props2) { + return props1 == props2.displayPlaneProperties; +} +bool operator==(const VkExtent2D& ext1, const VkExtent2D& ext2) { return ext1.height == ext2.height && ext1.width == ext2.width; } diff --git a/tests/framework/util/equality_helpers.h b/tests/framework/util/equality_helpers.h new file mode 100644 index 000000000..5c38f5031 --- /dev/null +++ b/tests/framework/util/equality_helpers.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +#include +#include + +#include + +inline bool string_eq(const char* a, const char* b) noexcept { return a && b && strcmp(a, b) == 0; } +inline bool string_eq(const char* a, const char* b, size_t len) noexcept { return a && b && strncmp(a, b, len) == 0; } + +bool operator==(const VkExtent3D& a, const VkExtent3D& b); +bool operator!=(const VkExtent3D& a, const VkExtent3D& b); + +bool operator==(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties& b); +bool operator!=(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties& b); + +bool operator==(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties2& b); +bool operator!=(const VkQueueFamilyProperties& a, const VkQueueFamilyProperties2& b); + +bool operator==(const VkLayerProperties& a, const VkLayerProperties& b); +bool operator!=(const VkLayerProperties& a, const VkLayerProperties& b); + +bool operator==(const VkExtensionProperties& a, const VkExtensionProperties& b); +bool operator!=(const VkExtensionProperties& a, const VkExtensionProperties& b); + +bool operator==(const VkPhysicalDeviceFeatures& feats1, const VkPhysicalDeviceFeatures2& feats2); + +bool operator==(const VkPhysicalDeviceMemoryProperties& props1, const VkPhysicalDeviceMemoryProperties2& props2); +bool operator==(const VkSparseImageFormatProperties& props1, const VkSparseImageFormatProperties& props2); +bool operator==(const VkSparseImageFormatProperties& props1, const VkSparseImageFormatProperties2& props2); +bool operator==(const VkExternalMemoryProperties& props1, const VkExternalMemoryProperties& props2); +bool operator==(const VkExternalSemaphoreProperties& props1, const VkExternalSemaphoreProperties& props2); +bool operator==(const VkExternalFenceProperties& props1, const VkExternalFenceProperties& props2); +bool operator==(const VkSurfaceCapabilitiesKHR& props1, const VkSurfaceCapabilitiesKHR& props2); +bool operator==(const VkSurfacePresentScalingCapabilitiesEXT& caps1, const VkSurfacePresentScalingCapabilitiesEXT& caps2); +bool operator==(const VkSurfaceFormatKHR& format1, const VkSurfaceFormatKHR& format2); +bool operator==(const VkSurfaceFormatKHR& format1, const VkSurfaceFormat2KHR& format2); +bool operator==(const VkDisplayPropertiesKHR& props1, const VkDisplayPropertiesKHR& props2); +bool operator==(const VkDisplayPropertiesKHR& props1, const VkDisplayProperties2KHR& props2); +bool operator==(const VkDisplayModePropertiesKHR& disp1, const VkDisplayModePropertiesKHR& disp2); + +bool operator==(const VkDisplayModePropertiesKHR& disp1, const VkDisplayModeProperties2KHR& disp2); +bool operator==(const VkDisplayPlaneCapabilitiesKHR& caps1, const VkDisplayPlaneCapabilitiesKHR& caps2); + +bool operator==(const VkDisplayPlaneCapabilitiesKHR& caps1, const VkDisplayPlaneCapabilities2KHR& caps2); +bool operator==(const VkDisplayPlanePropertiesKHR& props1, const VkDisplayPlanePropertiesKHR& props2); +bool operator==(const VkDisplayPlanePropertiesKHR& props1, const VkDisplayPlaneProperties2KHR& props2); +bool operator==(const VkExtent2D& ext1, const VkExtent2D& ext2); + +// Allow comparison of vectors of different types as long as their elements are comparable (just has to make sure to only apply when +// T != U) +template >> +bool operator==(const std::vector& a, const std::vector& b) { + return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](const auto& left, const auto& right) { return left == right; }); +} diff --git a/tests/framework/util/folder_manager.cpp b/tests/framework/util/folder_manager.cpp new file mode 100644 index 000000000..c406309ed --- /dev/null +++ b/tests/framework/util/folder_manager.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "folder_manager.h" + +#include +#include + +#include "gtest/gtest.h" + +#include "util/json_writer.h" +#include "util/env_var_wrapper.h" + +namespace fs { +Folder::Folder(std::filesystem::path root_path, std::string name) noexcept : folder((root_path / name).lexically_normal()) { + clear(); + // Don't actually create the folder yet, as we will do it on demand +} +Folder::~Folder() noexcept { clear(); } +Folder::Folder(Folder&& other) noexcept : actually_created(other.actually_created), folder(other.folder) { other.folder.clear(); } +Folder& Folder::operator=(Folder&& other) noexcept { + folder = other.folder; + actually_created = other.actually_created; + other.folder.clear(); + return *this; +} + +void Folder::check_if_first_use() { + if (!actually_created) { + if (!::testing::internal::InDeathTestChild()) { + std::error_code err; + if (!std::filesystem::create_directories(folder, err)) { + std::cerr << "Failed to create folder " << folder << " because " << err.message() << "\n"; + } + } + actually_created = true; + } +} + +std::filesystem::path Folder::write_manifest(std::filesystem::path const& name, std::string const& contents) { + check_if_first_use(); + std::filesystem::path out_path = (folder / name).lexically_normal(); + if (!::testing::internal::InDeathTestChild()) { + auto file = std::ofstream(out_path, std::ios_base::trunc | std::ios_base::out); + if (!file) { + std::cerr << "Failed to create manifest " << name << " at " << out_path << "\n"; + return out_path; + } + file << contents << std::endl; + } + insert_file_to_tracking(name); + return out_path; +} + +// close file handle, delete file, remove `name` from managed file list. +void Folder::remove(std::filesystem::path const& name) { + check_if_first_use(); + std::filesystem::path out_path = folder / name; + if (!::testing::internal::InDeathTestChild()) { + std::error_code err; + if (!std::filesystem::remove(out_path, err)) { + std::cerr << "Failed to remove file " << name << " at " << out_path << " because " << err.message() << "\n"; + } + } + + auto found = std::find(added_files.begin(), added_files.end(), name); + if (found != added_files.end()) { + added_files.erase(found); + } else { + std::cout << "File " << name << " not in tracked files of folder " << folder << ".\n"; + } +} + +// copy file into this folder +std::filesystem::path Folder::copy_file(std::filesystem::path const& file, std::filesystem::path const& new_name) { + check_if_first_use(); + insert_file_to_tracking(new_name); + + auto new_filepath = folder / new_name; + if (!::testing::internal::InDeathTestChild()) { + std::error_code err; + if (!std::filesystem::copy_file(file, new_filepath, err)) { + std::cerr << "Failed to copy file " << file << " to " << new_filepath << " because " << err.message() << "\n"; + } + } + return new_filepath; +} + +std::vector Folder::get_files() const { return added_files; } + +std::filesystem::path Folder::add_symlink(std::filesystem::path const& target, std::filesystem::path const& link_name) { + check_if_first_use(); + + if (!::testing::internal::InDeathTestChild()) { + std::error_code err; + std::filesystem::create_symlink(target, folder / link_name, err); + if (err.value() != 0) { + std::cerr << "Failed to create symlink with target" << target << " with name " << folder / link_name << " because " + << err.message() << "\n"; + } + } + insert_file_to_tracking(link_name); + return folder / link_name; +} +void Folder::insert_file_to_tracking(std::filesystem::path const& name) { + auto found = std::find(added_files.begin(), added_files.end(), name); + if (found != added_files.end()) { + std::cout << "Overwriting manifest " << name << ". Was this intended?\n"; + } else { + added_files.emplace_back(name); + } +} + +void Folder::clear() const noexcept { + if (!::testing::internal::InDeathTestChild()) { + std::error_code err; + std::filesystem::remove_all(folder, err); + if (err.value() != 0) { + std::cerr << "Failed to remove folder " << folder << " because " << err.message() << "\n"; + } + } +} + +std::filesystem::path category_path_name(ManifestCategory category) { + if (category == ManifestCategory::settings) return "settings.d"; + if (category == ManifestCategory::implicit_layer) return "implicit_layer.d"; + if (category == ManifestCategory::explicit_layer) + return "explicit_layer.d"; + else + return "icd.d"; +} + +FileSystemManager::FileSystemManager() + : root_folder(std::filesystem::path(TEST_EXECUTION_DIRECTORY), + std::string(::testing::UnitTest::GetInstance()->GetInstance()->current_test_suite()->name()) + "_" + + ::testing::UnitTest::GetInstance()->current_test_info()->name()) { + // Clean out test folder in case a previous run's files are still around + root_folder.clear(); + + auto const& root = root_folder.location(); + folders.try_emplace(ManifestLocation::null, root, std::string("null_dir")); + folders.try_emplace(ManifestLocation::driver, root, std::string("icd_manifests")); + folders.try_emplace(ManifestLocation::driver_env_var, root, std::string("icd_env_vars_manifests")); + folders.try_emplace(ManifestLocation::explicit_layer, root, std::string("explicit_layer_manifests")); + folders.try_emplace(ManifestLocation::explicit_layer_env_var, root, std::string("explicit_env_var_layer_folder")); + folders.try_emplace(ManifestLocation::explicit_layer_add_env_var, root, std::string("explicit_add_env_var_layer_folder")); + folders.try_emplace(ManifestLocation::implicit_layer, root, std::string("implicit_layer_manifests")); + folders.try_emplace(ManifestLocation::implicit_layer_env_var, root, std::string("implicit_env_var_layer_manifests")); + folders.try_emplace(ManifestLocation::implicit_layer_add_env_var, root, std::string("implicit_add_env_var_layer_manifests")); + folders.try_emplace(ManifestLocation::override_layer, root, std::string("override_layer_manifests")); +#if defined(_WIN32) + folders.try_emplace(ManifestLocation::windows_app_package, root, std::string("app_package_manifests")); +#endif +#if defined(__APPLE__) + folders.try_emplace(ManifestLocation::macos_bundle, root, std::string("macos_bundle")); +#endif + folders.try_emplace(ManifestLocation::settings_location, root, std::string("settings_location")); + folders.try_emplace(ManifestLocation::unsecured_driver, root, std::string("unsecured_driver")); + folders.try_emplace(ManifestLocation::unsecured_explicit_layer, root, std::string("unsecured_explicit_layer")); + folders.try_emplace(ManifestLocation::unsecured_implicit_layer, root, std::string("unsecured_implicit_layer")); + folders.try_emplace(ManifestLocation::unsecured_settings, root, std::string("unsecured_settings")); +} + +fs::Folder& FileSystemManager::get_folder(ManifestLocation location) noexcept { return folders.at(location); } +fs::Folder const& FileSystemManager::get_folder(ManifestLocation location) const noexcept { return folders.at(location); } + +Folder* FileSystemManager::get_folder_for_given_path(std::filesystem::path const& given_path) noexcept { + auto normalized_path = given_path.lexically_normal(); + for (auto const& [manifest_location, folder] : folders) { + if (folder.location() == normalized_path) { + return &folders.at(manifest_location); + } + } + if (redirected_paths.count(given_path.string()) > 0) { + return &folders.at(redirected_paths.at(normalized_path.string())); + } + for (auto const& [redirected_path_str, redirected_location] : redirected_paths) { + std::filesystem::path redirected_path = redirected_path_str; + const auto mismatch_pair = + std::mismatch(normalized_path.begin(), normalized_path.end(), redirected_path.begin(), redirected_path.end()); + if (mismatch_pair.second == redirected_path.end()) return &folders.at(redirected_location); + } + return nullptr; +} + +bool FileSystemManager::is_folder_path(std::filesystem::path const& path) const noexcept { + for (auto const& [location, folder] : folders) { + if (path == folder.location()) { + return true; + } + } + return false; +} + +void FileSystemManager::add_path_redirect(std::filesystem::path const& redirected_path, ManifestLocation location) { + redirected_paths[redirected_path.string()] = location; +} + +std::filesystem::path FileSystemManager::get_real_path_of_redirected_path(std::filesystem::path const& path) const { + if (redirected_paths.count(path.string()) > 0) { + return folders.at(redirected_paths.at(path.lexically_normal().string())).location(); + } + for (auto const& [redirected_path_str, redirected_location] : redirected_paths) { + std::filesystem::path redirected_path = redirected_path_str; + const auto mismatch_pair = std::mismatch(path.begin(), path.end(), redirected_path.begin(), redirected_path.end()); + if (mismatch_pair.second == redirected_path.end()) return folders.at(redirected_location).location(); + } + return std::filesystem::path{}; +} + +std::filesystem::path FileSystemManager::get_path_redirect_by_manifest_location(ManifestLocation location) const { + for (auto const& [path, redirected_location] : redirected_paths) { + if (redirected_location == location) { + return path; + } + } + return folders.at(ManifestLocation::null).location(); +} + +} // namespace fs diff --git a/tests/framework/util/folder_manager.h b/tests/framework/util/folder_manager.h new file mode 100644 index 000000000..68e69c211 --- /dev/null +++ b/tests/framework/util/folder_manager.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include +#include +#include +#include + +#include "test_defines.h" + +namespace fs { + +class Folder { + public: + explicit Folder(std::filesystem::path root_path, std::string name) noexcept; + ~Folder() noexcept; + Folder(Folder const&) = delete; + Folder& operator=(Folder const&) = delete; + Folder(Folder&& other) noexcept; + Folder& operator=(Folder&& other) noexcept; + + // Add a manifest to the folder + std::filesystem::path write_manifest(std::filesystem::path const& name, std::string const& contents); + + // close file handle, delete file, remove `name` from managed file list. + void remove(std::filesystem::path const& name); + + // Remove all contents in the path + void clear() const noexcept; + + // copy file into this folder with name `new_name`. Returns the full path of the file that was copied + std::filesystem::path copy_file(std::filesystem::path const& file, std::filesystem::path const& new_name); + + // location of the managed folder + std::filesystem::path const& location() const { return folder; } + + std::vector get_files() const; + + // Create a symlink in this folder to target with the filename set to link_name + std::filesystem::path add_symlink(std::filesystem::path const& target, std::filesystem::path const& link_name); + + private: + bool actually_created = false; + std::filesystem::path folder; + std::vector added_files; + + void insert_file_to_tracking(std::filesystem::path const& name); + void check_if_first_use(); +}; + +class FileSystemManager { + fs::Folder root_folder; + std::unordered_map folders; + + // paths that this folder appears as to the loader (ex /usr/share/vulkan/explicit_layer.d is in the ManifestLocation::icd folder + std::unordered_map redirected_paths; + + public: + FileSystemManager(); + + Folder& get_folder(ManifestLocation location) noexcept; + Folder const& get_folder(ManifestLocation location) const noexcept; + + // Gets a pointer to the folder that given_path points to. This includes redirected paths as well as the exact path of folders + Folder* get_folder_for_given_path(std::filesystem::path const& given_path) noexcept; + + bool is_folder_path(std::filesystem::path const& path) const noexcept; + + void add_path_redirect(std::filesystem::path const& apparent_path, ManifestLocation location); + + // Returns the first redirected path pointing at by a manifest location. Returns the NULL location if one isn't found + std::filesystem::path get_path_redirect_by_manifest_location(ManifestLocation location) const; + + // Returns the real path that a redirected path points to. Returns an empty path if no redirect is found + std::filesystem::path get_real_path_of_redirected_path(std::filesystem::path const& redirected_path) const; +}; + +} // namespace fs diff --git a/tests/framework/util/functions.h b/tests/framework/util/functions.h new file mode 100644 index 000000000..5debba9fe --- /dev/null +++ b/tests/framework/util/functions.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +#include + +class FromVoidStarFunc { + void* function; + + public: + FromVoidStarFunc(void* function) : function(function) {} + FromVoidStarFunc(PFN_vkVoidFunction function) : function(reinterpret_cast(function)) {} + + template + operator T() { + return reinterpret_cast(function); + } +}; + +template +PFN_vkVoidFunction to_vkVoidFunction(T func) { + return reinterpret_cast(func); +} + +struct VulkanFunction { + std::string name; + PFN_vkVoidFunction function = nullptr; +}; diff --git a/tests/framework/util/get_executable_path.cpp b/tests/framework/util/get_executable_path.cpp new file mode 100644 index 000000000..9edb5d38d --- /dev/null +++ b/tests/framework/util/get_executable_path.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "get_executable_path.h" + +#if defined(__linux__) || defined(__GNU__) + +#include + +// find application path + name. Path cannot be longer than 1024, returns NULL if it is greater than that. +std::string test_platform_executable_path() { + std::string buffer; + buffer.resize(1024); + ssize_t count = readlink("/proc/self/exe", &buffer[0], buffer.size()); + if (count == -1) return NULL; + if (count == 0) return NULL; + buffer[count] = '\0'; + buffer.resize(count); + return buffer; +} +#elif defined(__APPLE__) + +#include +#include + +std::string test_platform_executable_path() { + std::string buffer; + buffer.resize(1024); + pid_t pid = getpid(); + int ret = proc_pidpath(pid, &buffer[0], static_cast(buffer.size())); + if (ret <= 0) return NULL; + buffer[ret] = '\0'; + buffer.resize(ret); + return buffer; +} +#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) +#include +std::string test_platform_executable_path() { + int mib[] = { + CTL_KERN, +#if defined(__NetBSD__) + KERN_PROC_ARGS, + -1, + KERN_PROC_PATHNAME, +#else + KERN_PROC, + KERN_PROC_PATHNAME, + -1, +#endif + }; + std::string buffer; + buffer.resize(1024); + size_t size = buffer.size(); + if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), &buffer[0], &size, NULL, 0) < 0) { + return NULL; + } + buffer.resize(size); + + return buffer; +} +#elif defined(__Fuchsia__) || defined(__OpenBSD__) +std::string test_platform_executable_path() { return {}; } +#elif defined(__QNX__) + +#ifndef SYSCONFDIR +#define SYSCONFDIR "/etc" +#endif + +#include +#include + +std::string test_platform_executable_path() { + std::string buffer; + buffer.resize(1024); + int fd = open("/proc/self/exefile", O_RDONLY); + ssize_t rdsize; + + if (fd == -1) { + return NULL; + } + + rdsize = read(fd, &buffer[0], buffer.size()); + if (rdsize < 0) { + return NULL; + } + buffer[rdsize] = 0x00; + close(fd); + buffer.resize(rdsize); + + return buffer; +} +#endif // defined (__QNX__) + +#if defined(_WIN32) + +#include + +#include + +#include "wide_char_handling.h" + +std::string test_platform_executable_path() { + std::string buffer; + buffer.resize(1024); + DWORD ret = GetModuleFileName(NULL, static_cast(&buffer[0]), (DWORD)buffer.size()); + if (ret == 0) return NULL; + if (ret > buffer.size()) return NULL; + buffer.resize(ret); + buffer[ret] = '\0'; + return narrow(std::filesystem::path(buffer).native()); +} + +#endif diff --git a/tests/framework/util/get_executable_path.h b/tests/framework/util/get_executable_path.h new file mode 100644 index 000000000..bb296e54f --- /dev/null +++ b/tests/framework/util/get_executable_path.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +// find application path + name. Path cannot be longer than 1024, returns NULL if it is greater than that. +std::string test_platform_executable_path(); diff --git a/tests/framework/util/json_writer.cpp b/tests/framework/util/json_writer.cpp new file mode 100644 index 000000000..1a955c09a --- /dev/null +++ b/tests/framework/util/json_writer.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2023 The Khronos Group Inc. + * Copyright (c) 2023 Valve Corporation + * Copyright (c) 2023 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + * Author: Charles Giessen + */ + +#include "json_writer.h" + +#include "wide_char_handling.h" + +void JsonWriter::StartObject() { + CommaAndNewLine(); + Indent(); + output += "{"; + stack.push(false); +} +void JsonWriter::StartKeyedObject(std::string const& key) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": {"; + stack.push(false); +} +void JsonWriter::EndObject() { + stack.pop(); + output += "\n"; + Indent(); + output += "}"; +} +void JsonWriter::StartKeyedArray(std::string const& key) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": ["; + stack.push(false); +} +void JsonWriter::StartArray() { + CommaAndNewLine(); + Indent(); + output += "["; + stack.push(false); +} +void JsonWriter::EndArray() { + stack.pop(); + output += "\n"; + Indent(); + output += "]"; +} + +void JsonWriter::AddKeyedString(std::string const& key, std::string const& value) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": \"" + escape(value) + "\""; +} +void JsonWriter::AddString(std::string const& value) { + CommaAndNewLine(); + Indent(); + output += "\"" + escape(value) + "\""; +} +#if defined(_WIN32) +void JsonWriter::AddKeyedString(std::string const& key, std::wstring const& value) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": \"" + escape(narrow(value)) + "\""; +} +void JsonWriter::AddString(std::wstring const& value) { + CommaAndNewLine(); + Indent(); + output += "\"" + escape(narrow(value)) + "\""; +} +#endif + +void JsonWriter::AddKeyedBool(std::string const& key, bool value) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": " + (value ? "true" : "false"); +} +void JsonWriter::AddBool(bool value) { + CommaAndNewLine(); + Indent(); + output += std::string(value ? "true" : "false"); +} + +void JsonWriter::AddKeyedNumber(std::string const& key, double number) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": " + std::to_string(number); +} +void JsonWriter::AddNumber(double number) { + CommaAndNewLine(); + Indent(); + output += std::to_string(number); +} + +void JsonWriter::AddKeyedInteger(std::string const& key, int64_t number) { + CommaAndNewLine(); + Indent(); + output += "\"" + key + "\": " + std::to_string(number); +} +void JsonWriter::AddInteger(int64_t number) { + CommaAndNewLine(); + Indent(); + output += std::to_string(number); +} + +// Json doesn't allow `\` in strings, it must be escaped. Thus we have to convert '\\' to '\\\\' in strings +std::string JsonWriter::escape(std::string const& in_path) { + std::string out; + for (auto& c : in_path) { + if (c == '\\') + out += "\\\\"; + else + out += c; + } + return out; +} +std::string JsonWriter::escape(std::filesystem::path const& in_path) { return escape(narrow(in_path.native())); } + +void JsonWriter::CommaAndNewLine() { + if (stack.size() > 0) { + if (stack.top() == false) { + stack.top() = true; + } else { + output += ","; + } + output += "\n"; + } +} +void JsonWriter::Indent() { + for (uint32_t i = 0; i < stack.size(); i++) { + output += '\t'; + } +} diff --git a/tests/framework/util/json_writer.h b/tests/framework/util/json_writer.h new file mode 100644 index 000000000..a5c31545a --- /dev/null +++ b/tests/framework/util/json_writer.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2023 The Khronos Group Inc. + * Copyright (c) 2023 Valve Corporation + * Copyright (c) 2023 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + * Author: Charles Giessen + */ + +#pragma once + +#include +#include +#include +#include + +#include "wide_char_handling.h" + +// Utility class to simplify the writing of JSON manifest files + +struct JsonWriter { + std::string output; + + // the bool represents whether an object has been written & a comma is needed + std::stack stack; + + void StartObject(); + void StartKeyedObject(std::string const& key); + void EndObject(); + void StartKeyedArray(std::string const& key); + void StartArray(); + void EndArray(); + + void AddKeyedString(std::string const& key, std::string const& value); + void AddString(std::string const& value); +#if defined(_WIN32) + void AddKeyedString(std::string const& key, std::wstring const& value); + void AddString(std::wstring const& value); +#endif + + void AddKeyedBool(std::string const& key, bool value); + void AddBool(bool value); + + void AddKeyedNumber(std::string const& key, double number); + void AddNumber(double number); + + void AddKeyedInteger(std::string const& key, int64_t number); + void AddInteger(int64_t number); + + // Json doesn't allow `\` in strings, it must be escaped. Thus we have to convert '\\' to '\\\\' in strings + static std::string escape(std::string const& in_path); + static std::string escape(std::filesystem::path const& in_path); + + private: + void CommaAndNewLine(); + void Indent(); +}; diff --git a/tests/framework/util/manifest_builders.cpp b/tests/framework/util/manifest_builders.cpp new file mode 100644 index 000000000..6666a5769 --- /dev/null +++ b/tests/framework/util/manifest_builders.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "manifest_builders.h" + +#include "json_writer.h" + +template +void print_object_of_t(JsonWriter& writer, const char* object_name, std::vector const& vec) { + if (vec.size() == 0) return; + writer.StartKeyedObject(object_name); + for (auto& element : vec) { + element.get_manifest_str(writer); + } + writer.EndObject(); +} + +template +void print_array_of_t(JsonWriter& writer, const char* object_name, std::vector const& vec) { + if (vec.size() == 0) return; + writer.StartKeyedArray(object_name); + for (auto& element : vec) { + element.get_manifest_str(writer); + } + writer.EndArray(); +} +void print_vector_of_strings(JsonWriter& writer, const char* object_name, std::vector const& strings) { + if (strings.size() == 0) return; + writer.StartKeyedArray(object_name); + for (auto const& str : strings) { + writer.AddString(std::filesystem::path(str).native()); + } + writer.EndArray(); +} +void print_vector_of_strings(JsonWriter& writer, const char* object_name, std::vector const& paths) { + if (paths.size() == 0) return; + writer.StartKeyedArray(object_name); + for (auto const& path : paths) { + writer.AddString(path.native()); + } + writer.EndArray(); +} + +std::string version_to_string(uint32_t version) { + std::string out = std::to_string(VK_API_VERSION_MAJOR(version)) + "." + std::to_string(VK_API_VERSION_MINOR(version)) + "." + + std::to_string(VK_API_VERSION_PATCH(version)); + if (VK_API_VERSION_VARIANT(version) != 0) out += std::to_string(VK_API_VERSION_VARIANT(version)) + "." + out; + return out; +} + +std::string to_text(bool b) { return b ? std::string("true") : std::string("false"); } + +std::string ManifestICD::get_manifest_str() const { + JsonWriter writer; + writer.StartObject(); + writer.AddKeyedString("file_format_version", file_format_version.get_version_str()); + writer.StartKeyedObject("ICD"); + writer.AddKeyedString("library_path", lib_path.native()); + writer.AddKeyedString("api_version", version_to_string(api_version)); + writer.AddKeyedBool("is_portability_driver", is_portability_driver); + if (!library_arch.empty()) writer.AddKeyedString("library_arch", library_arch); + writer.EndObject(); + writer.EndObject(); + return writer.output; +} + +void ManifestLayer::LayerDescription::FunctionOverride::get_manifest_str(JsonWriter& writer) const { + writer.AddKeyedString(vk_func, override_name); +} + +void ManifestLayer::LayerDescription::Extension::get_manifest_str(JsonWriter& writer) const { + writer.StartObject(); + writer.AddKeyedString("name", name); + writer.AddKeyedString("spec_version", std::to_string(spec_version)); + writer.AddKeyedString("spec_version", std::to_string(spec_version)); + print_vector_of_strings(writer, "entrypoints", entrypoints); + writer.EndObject(); +} + +void ManifestLayer::LayerDescription::get_manifest_str(JsonWriter& writer) const { + writer.AddKeyedString("name", name); + writer.AddKeyedString("type", get_type_str(type)); + if (!lib_path.empty()) { + writer.AddKeyedString("library_path", lib_path.native()); + } + writer.AddKeyedString("api_version", version_to_string(api_version)); + writer.AddKeyedString("implementation_version", std::to_string(implementation_version)); + writer.AddKeyedString("description", description); + print_object_of_t(writer, "functions", functions); + print_array_of_t(writer, "instance_extensions", instance_extensions); + print_array_of_t(writer, "device_extensions", device_extensions); + if (!enable_environment.empty()) { + writer.StartKeyedObject("enable_environment"); + writer.AddKeyedString(enable_environment, "1"); + writer.EndObject(); + } + if (!disable_environment.empty()) { + writer.StartKeyedObject("disable_environment"); + writer.AddKeyedString(disable_environment, "1"); + writer.EndObject(); + } + print_vector_of_strings(writer, "component_layers", component_layers); + print_vector_of_strings(writer, "blacklisted_layers", blacklisted_layers); + print_vector_of_strings(writer, "override_paths", override_paths); + print_vector_of_strings(writer, "app_keys", app_keys); + print_object_of_t(writer, "pre_instance_functions", pre_instance_functions); + if (!library_arch.empty()) { + writer.AddKeyedString("library_arch", library_arch); + } +} + +VkLayerProperties ManifestLayer::LayerDescription::get_layer_properties() const { + VkLayerProperties properties{}; + name.copy(properties.layerName, VK_MAX_EXTENSION_NAME_SIZE); + description.copy(properties.description, VK_MAX_EXTENSION_NAME_SIZE); + properties.implementationVersion = implementation_version; + properties.specVersion = api_version; + return properties; +} + +std::string ManifestLayer::get_manifest_str() const { + JsonWriter writer; + writer.StartObject(); + writer.AddKeyedString("file_format_version", file_format_version.get_version_str()); + if (layers.size() == 1) { + writer.StartKeyedObject("layer"); + layers.at(0).get_manifest_str(writer); + writer.EndObject(); + } else { + writer.StartKeyedArray("layers"); + for (size_t i = 0; i < layers.size(); i++) { + writer.StartObject(); + layers.at(i).get_manifest_str(writer); + writer.EndObject(); + } + writer.EndArray(); + } + writer.EndObject(); + return writer.output; +} diff --git a/tests/framework/util/manifest_builders.h b/tests/framework/util/manifest_builders.h new file mode 100644 index 000000000..bc559dda7 --- /dev/null +++ b/tests/framework/util/manifest_builders.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include +#include +#include + +#include + +#include "builder_defines.h" + +// Forward declaration since we take it by reference only +class JsonWriter; + +struct ManifestVersion { + BUILDER_VALUE_WITH_DEFAULT(uint32_t, major, 1) + BUILDER_VALUE_WITH_DEFAULT(uint32_t, minor, 0) + BUILDER_VALUE_WITH_DEFAULT(uint32_t, patch, 0) + + std::string get_version_str() const noexcept { + return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(patch); + } +}; + +// ManifestICD builder +struct ManifestICD { + BUILDER_VALUE(ManifestVersion, file_format_version) + BUILDER_VALUE(uint32_t, api_version) + BUILDER_VALUE(std::filesystem::path, lib_path) + BUILDER_VALUE(bool, is_portability_driver) + BUILDER_VALUE(std::string, library_arch) + std::string get_manifest_str() const; +}; + +// ManifestLayer builder +struct ManifestLayer { + struct LayerDescription { + enum class Type { INSTANCE, GLOBAL, DEVICE }; + std::string get_type_str(Type layer_type) const { + if (layer_type == Type::GLOBAL) + return "GLOBAL"; + else if (layer_type == Type::DEVICE) + return "DEVICE"; + else // default + return "INSTANCE"; + } + struct FunctionOverride { + BUILDER_VALUE(std::string, vk_func) + BUILDER_VALUE(std::string, override_name) + + void get_manifest_str(JsonWriter& writer) const; + }; + struct Extension { + Extension() noexcept {} + Extension(std::string name, uint32_t spec_version = 0, std::vector entrypoints = {}) noexcept + : name(name), spec_version(spec_version), entrypoints(entrypoints) {} + std::string name; + uint32_t spec_version = 0; + std::vector entrypoints; + void get_manifest_str(JsonWriter& writer) const; + }; + BUILDER_VALUE(std::string, name) + BUILDER_VALUE_WITH_DEFAULT(Type, type, Type::INSTANCE) + BUILDER_VALUE(std::filesystem::path, lib_path) + BUILDER_VALUE_WITH_DEFAULT(uint32_t, api_version, VK_API_VERSION_1_0) + BUILDER_VALUE(uint32_t, implementation_version) + BUILDER_VALUE(std::string, description) + BUILDER_VECTOR(FunctionOverride, functions, function) + BUILDER_VECTOR(Extension, instance_extensions, instance_extension) + BUILDER_VECTOR(Extension, device_extensions, device_extension) + BUILDER_VALUE(std::string, enable_environment) + BUILDER_VALUE(std::string, disable_environment) + BUILDER_VECTOR(std::string, component_layers, component_layer) + BUILDER_VECTOR(std::string, blacklisted_layers, blacklisted_layer) + BUILDER_VECTOR(std::filesystem::path, override_paths, override_path) + BUILDER_VECTOR(FunctionOverride, pre_instance_functions, pre_instance_function) + BUILDER_VECTOR(std::string, app_keys, app_key) + BUILDER_VALUE(std::string, library_arch) + + void get_manifest_str(JsonWriter& writer) const; + VkLayerProperties get_layer_properties() const; + }; + BUILDER_VALUE(ManifestVersion, file_format_version) + BUILDER_VECTOR(LayerDescription, layers, layer) + + std::string get_manifest_str() const; +}; diff --git a/tests/framework/util/platform_wsi.cpp b/tests/framework/util/platform_wsi.cpp new file mode 100644 index 000000000..748abdcf2 --- /dev/null +++ b/tests/framework/util/platform_wsi.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "platform_wsi.h" + +#include "equality_helpers.h" + +const char* get_platform_wsi_extension([[maybe_unused]] const char* api_selection) { +#if defined(VK_USE_PLATFORM_ANDROID_KHR) + return "VK_KHR_android_surface"; +#elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) + return "VK_EXT_directfb_surface"; +#elif defined(VK_USE_PLATFORM_FUCHSIA) + return "VK_FUCHSIA_imagepipe_surface"; +#elif defined(VK_USE_PLATFORM_GGP) + return "VK_GGP_stream_descriptor_surface"; +#elif defined(VK_USE_PLATFORM_IOS_MVK) + return "VK_MVK_ios_surface"; +#elif defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT) +#if defined(VK_USE_PLATFORM_MACOS_MVK) + if (string_eq(api_selection, "VK_USE_PLATFORM_MACOS_MVK")) return "VK_MVK_macos_surface"; +#endif +#if defined(VK_USE_PLATFORM_METAL_EXT) + if (string_eq(api_selection, "VK_USE_PLATFORM_METAL_EXT")) return "VK_EXT_metal_surface"; + return "VK_EXT_metal_surface"; +#endif +#elif defined(VK_USE_PLATFORM_SCREEN_QNX) + return "VK_QNX_screen_surface"; +#elif defined(VK_USE_PLATFORM_VI_NN) + return "VK_NN_vi_surface"; +#elif defined(VK_USE_PLATFORM_XCB_KHR) || defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR) +#if defined(VK_USE_PLATFORM_XCB_KHR) + if (string_eq(api_selection, "VK_USE_PLATFORM_XCB_KHR")) return "VK_KHR_xcb_surface"; +#endif +#if defined(VK_USE_PLATFORM_XLIB_KHR) + if (string_eq(api_selection, "VK_USE_PLATFORM_XLIB_KHR")) return "VK_KHR_xlib_surface"; +#endif +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (string_eq(api_selection, "VK_USE_PLATFORM_WAYLAND_KHR")) return "VK_KHR_wayland_surface"; +#endif +#if defined(VK_USE_PLATFORM_XCB_KHR) + return "VK_KHR_xcb_surface"; +#endif +#elif defined(VK_USE_PLATFORM_WIN32_KHR) + return "VK_KHR_win32_surface"; +#else + return "VK_KHR_display"; +#endif +} diff --git a/tests/framework/util/platform_wsi.h b/tests/framework/util/platform_wsi.h new file mode 100644 index 000000000..66b7b507e --- /dev/null +++ b/tests/framework/util/platform_wsi.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +const char* get_platform_wsi_extension([[maybe_unused]] const char* api_selection); diff --git a/tests/framework/util/test_defines.h b/tests/framework/util/test_defines.h new file mode 100644 index 000000000..63d6a32e9 --- /dev/null +++ b/tests/framework/util/test_defines.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#if defined(__GNUC__) && __GNUC__ >= 4 +#define FRAMEWORK_EXPORT __attribute__((visibility("default"))) +#elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590) +#define FRAMEWORK_EXPORT __attribute__((visibility("default"))) +#elif defined(_WIN32) +#define FRAMEWORK_EXPORT __declspec(dllexport) +#else +#define FRAMEWORK_EXPORT +#endif + +// Set of platforms with a common set of functionality which is queried throughout the program +#if defined(__linux__) || defined(__APPLE__) || defined(__Fuchsia__) || defined(__QNX__) || defined(__FreeBSD__) || \ + defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__GNU__) +#define TESTING_COMMON_UNIX_PLATFORMS 1 +#else +#define TESTING_COMMON_UNIX_PLATFORMS 0 +#endif + +#include FRAMEWORK_CONFIG_HEADER + +enum class ManifestCategory { implicit_layer, explicit_layer, icd, settings }; + +// Controls whether to create a manifest and where to put it +enum class ManifestDiscoveryType { + generic, // put the manifest in the regular locations + unsecured_generic, // put the manifest in a user folder rather than system + none, // Do not write the manifest anywhere (for Direct Driver Loading) + null_dir, // put the manifest in the 'null_dir' which the loader does not search in (D3DKMT for instance) + env_var, // use the corresponding env-var for it + add_env_var, // use the corresponding add-env-var for it + override_folder, // add to a special folder for the override layer to use +#if defined(_WIN32) + windows_app_package, // let the app package search find it +#endif +#if defined(__APPLE__) + macos_bundle, // place it in a location only accessible to macos bundles +#endif +}; + +enum class LibraryPathType { + absolute, // default for testing - the exact path of the binary + relative, // Relative to the manifest file + default_search_paths, // Dont add any path information to the library_path - force the use of the default search paths +}; + +// Locations that files can go in the test framework +enum class ManifestLocation { + null, + driver, + driver_env_var, + explicit_layer, + explicit_layer_env_var, + explicit_layer_add_env_var, + implicit_layer, + implicit_layer_env_var, + implicit_layer_add_env_var, + override_layer, +#if defined(_WIN32) + windows_app_package, +#endif +#if defined(__APPLE__) + macos_bundle, +#endif + settings_location, + unsecured_driver, + unsecured_explicit_layer, + unsecured_implicit_layer, + unsecured_settings, +}; diff --git a/tests/framework/util/vulkan_object_wrappers.cpp b/tests/framework/util/vulkan_object_wrappers.cpp new file mode 100644 index 000000000..0223ff68f --- /dev/null +++ b/tests/framework/util/vulkan_object_wrappers.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "vulkan_object_wrappers.h" + +#include "platform_wsi.h" + +VkExtensionProperties Extension::get() const noexcept { + VkExtensionProperties props{}; + extensionName.copy(props.extensionName, VK_MAX_EXTENSION_NAME_SIZE); + props.specVersion = specVersion; + return props; +} + +InstanceCreateInfo::InstanceCreateInfo() { + instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; +} + +VkInstanceCreateInfo* InstanceCreateInfo::get() noexcept { + if (fill_in_application_info) { + application_info.pApplicationName = app_name.c_str(); + application_info.pEngineName = engine_name.c_str(); + application_info.applicationVersion = app_version; + application_info.engineVersion = engine_version; + application_info.apiVersion = api_version; + instance_info.pApplicationInfo = &application_info; + } + instance_info.flags = flags; + instance_info.enabledLayerCount = static_cast(enabled_layers.size()); + instance_info.ppEnabledLayerNames = enabled_layers.data(); + instance_info.enabledExtensionCount = static_cast(enabled_extensions.size()); + instance_info.ppEnabledExtensionNames = enabled_extensions.data(); + return &instance_info; +} +InstanceCreateInfo& InstanceCreateInfo::set_api_version(uint32_t major, uint32_t minor, uint32_t patch) { + this->api_version = VK_MAKE_API_VERSION(0, major, minor, patch); + return *this; +} +InstanceCreateInfo& InstanceCreateInfo::setup_WSI(const char* api_selection) { + add_extensions({"VK_KHR_surface", get_platform_wsi_extension(api_selection)}); + return *this; +} + +DeviceQueueCreateInfo::DeviceQueueCreateInfo() { queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; } +DeviceQueueCreateInfo::DeviceQueueCreateInfo(const VkDeviceQueueCreateInfo* create_info) { + queue_create_info = *create_info; + for (uint32_t i = 0; i < create_info->queueCount; i++) { + priorities.push_back(create_info->pQueuePriorities[i]); + } +} + +VkDeviceQueueCreateInfo DeviceQueueCreateInfo::get() noexcept { + queue_create_info.pQueuePriorities = priorities.data(); + queue_create_info.queueCount = 1; + queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + return queue_create_info; +} + +DeviceCreateInfo::DeviceCreateInfo(const VkDeviceCreateInfo* create_info) { + dev = *create_info; + for (uint32_t i = 0; i < create_info->enabledExtensionCount; i++) { + enabled_extensions.push_back(create_info->ppEnabledExtensionNames[i]); + } + for (uint32_t i = 0; i < create_info->enabledLayerCount; i++) { + enabled_layers.push_back(create_info->ppEnabledLayerNames[i]); + } + for (uint32_t i = 0; i < create_info->queueCreateInfoCount; i++) { + device_queue_infos.push_back(create_info->pQueueCreateInfos[i]); + } +} + +VkDeviceCreateInfo* DeviceCreateInfo::get() noexcept { + dev.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + dev.enabledLayerCount = static_cast(enabled_layers.size()); + dev.ppEnabledLayerNames = enabled_layers.data(); + dev.enabledExtensionCount = static_cast(enabled_extensions.size()); + dev.ppEnabledExtensionNames = enabled_extensions.data(); + uint32_t index = 0; + for (auto& queue : queue_info_details) { + queue.queue_create_info.queueFamilyIndex = index++; + queue.queue_create_info.queueCount = 1; + device_queue_infos.push_back(queue.get()); + } + + dev.queueCreateInfoCount = static_cast(device_queue_infos.size()); + dev.pQueueCreateInfos = device_queue_infos.data(); + return &dev; +} diff --git a/tests/framework/util/vulkan_object_wrappers.h b/tests/framework/util/vulkan_object_wrappers.h new file mode 100644 index 000000000..7cc923515 --- /dev/null +++ b/tests/framework/util/vulkan_object_wrappers.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include +#include + +#include + +#include "builder_defines.h" + +struct Extension { + BUILDER_VALUE(std::string, extensionName) + BUILDER_VALUE_WITH_DEFAULT(uint32_t, specVersion, VK_API_VERSION_1_0) + + Extension(const char* name, uint32_t specVersion = VK_API_VERSION_1_0) noexcept + : extensionName(name), specVersion(specVersion) {} + Extension(std::string extensionName, uint32_t specVersion = VK_API_VERSION_1_0) noexcept + : extensionName(extensionName), specVersion(specVersion) {} + + VkExtensionProperties get() const noexcept; +}; + +struct MockQueueFamilyProperties { + BUILDER_VALUE(VkQueueFamilyProperties, properties) + BUILDER_VALUE(bool, support_present) + + VkQueueFamilyProperties get() const noexcept { return properties; } +}; + +struct InstanceCreateInfo { + BUILDER_VALUE(VkInstanceCreateInfo, instance_info) + BUILDER_VALUE(VkApplicationInfo, application_info) + BUILDER_VALUE(std::string, app_name) + BUILDER_VALUE(std::string, engine_name) + BUILDER_VALUE(uint32_t, flags) + BUILDER_VALUE(uint32_t, app_version) + BUILDER_VALUE(uint32_t, engine_version) + BUILDER_VALUE_WITH_DEFAULT(uint32_t, api_version, VK_API_VERSION_1_0) + BUILDER_VECTOR(const char*, enabled_layers, layer) + BUILDER_VECTOR(const char*, enabled_extensions, extension) + // tell the get() function to not provide `application_info` + BUILDER_VALUE_WITH_DEFAULT(bool, fill_in_application_info, true) + + InstanceCreateInfo(); + + VkInstanceCreateInfo* get() noexcept; + + InstanceCreateInfo& set_api_version(uint32_t major, uint32_t minor, uint32_t patch); + + InstanceCreateInfo& setup_WSI(const char* api_selection = nullptr); +}; + +struct DeviceQueueCreateInfo { + DeviceQueueCreateInfo(); + DeviceQueueCreateInfo(const VkDeviceQueueCreateInfo* create_info); + + BUILDER_VALUE(VkDeviceQueueCreateInfo, queue_create_info) + BUILDER_VECTOR(float, priorities, priority) + + VkDeviceQueueCreateInfo get() noexcept; +}; + +struct DeviceCreateInfo { + DeviceCreateInfo() = default; + DeviceCreateInfo(const VkDeviceCreateInfo* create_info); + + BUILDER_VALUE(VkDeviceCreateInfo, dev) + BUILDER_VECTOR(const char*, enabled_extensions, extension) + BUILDER_VECTOR(const char*, enabled_layers, layer) + BUILDER_VECTOR(DeviceQueueCreateInfo, queue_info_details, device_queue) + + VkDeviceCreateInfo* get() noexcept; + + private: + std::vector device_queue_infos; +}; diff --git a/tests/framework/util/wide_char_handling.cpp b/tests/framework/util/wide_char_handling.cpp new file mode 100644 index 000000000..ac4fb0bff --- /dev/null +++ b/tests/framework/util/wide_char_handling.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#include "wide_char_handling.h" + +#if defined(_WIN32) + +#include "Windows.h" + +std::string narrow(const std::wstring& utf16) { + if (utf16.empty()) { + return {}; + } + int size = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.size()), nullptr, 0, nullptr, nullptr); + if (size <= 0) { + return {}; + } + std::string utf8(size, '\0'); + if (WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast(utf16.size()), &utf8[0], size, nullptr, nullptr) != size) { + return {}; + } + return utf8; +} + +std::wstring widen(const std::string& utf8) { + if (utf8.empty()) { + return {}; + } + int size = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), nullptr, 0); + if (size <= 0) { + return {}; + } + std::wstring utf16(size, L'\0'); + if (MultiByteToWideChar(CP_UTF8, 0, utf8.data(), static_cast(utf8.size()), &utf16[0], size) != size) { + return {}; + } + return utf16; +} +#else +std::string narrow(const std::string& utf16) { return utf16; } +std::string widen(const std::string& utf8) { return utf8; } +#endif diff --git a/tests/framework/util/wide_char_handling.h b/tests/framework/util/wide_char_handling.h new file mode 100644 index 000000000..805afe14c --- /dev/null +++ b/tests/framework/util/wide_char_handling.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 The Khronos Group Inc. + * Copyright (c) 2025 Valve Corporation + * Copyright (c) 2025 LunarG, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and/or associated documentation files (the "Materials"), to + * deal in the Materials without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Materials, and to permit persons to whom the Materials are + * furnished to do so, subject to the following conditions: + * + * The above copyright notice(s) and this permission notice shall be included in + * all copies or substantial portions of the Materials. + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE + * USE OR OTHER DEALINGS IN THE MATERIALS. + * + */ + +#pragma once + +#include + +// Define it here so that json_writer.h has access to these functions +#if defined(_WIN32) +// Convert an UTF-16 wstring to an UTF-8 string +std::string narrow(const std::wstring& utf16); +// Convert an UTF-8 string to an UTF-16 wstring +std::wstring widen(const std::string& utf8); +#else +// Do nothing passthrough for the sake of Windows & UTF-16 +std::string narrow(const std::string& utf16); +// Do nothing passthrough for the sake of Windows & UTF-16 +std::string widen(const std::string& utf8); +#endif diff --git a/tests/live_verification/dynamic_loader_behavior/dynamic_library.h b/tests/live_verification/dynamic_loader_behavior/dynamic_library.h index 1d88d306e..b4bff319b 100644 --- a/tests/live_verification/dynamic_loader_behavior/dynamic_library.h +++ b/tests/live_verification/dynamic_loader_behavior/dynamic_library.h @@ -25,7 +25,12 @@ * Author: Charles Giessen */ -#include "test_util.h" +#include +#include + +#include "util/test_defines.h" +#include "util/functions.h" +#include "util/dynamic_library_wrapper.h" extern "C" { @@ -38,7 +43,7 @@ using InitFunction = void (*)(); FRAMEWORK_EXPORT void init(); }; -#if defined(WIN32) +#if defined(_WIN32) #if !defined(LIB_EXT) #define LIB_EXT "dll" #endif diff --git a/tests/live_verification/dynamic_loader_behavior/test_dynamic_linking.cpp b/tests/live_verification/dynamic_loader_behavior/test_dynamic_linking.cpp index 2d2d6b35c..943917ec3 100644 --- a/tests/live_verification/dynamic_loader_behavior/test_dynamic_linking.cpp +++ b/tests/live_verification/dynamic_loader_behavior/test_dynamic_linking.cpp @@ -42,4 +42,4 @@ int main() { std::cout << "Success\n"; #endif return 0; -} \ No newline at end of file +} diff --git a/tests/loader_alloc_callback_tests.cpp b/tests/loader_alloc_callback_tests.cpp index 1701bfb92..f9eba0f2d 100644 --- a/tests/loader_alloc_callback_tests.cpp +++ b/tests/loader_alloc_callback_tests.cpp @@ -440,7 +440,9 @@ TEST(Allocation, CreateInstanceIntentionalAllocFailInvalidManifests) { auto file_name = std::string("invalid_implicit_layer_") + std::to_string(i) + ".json"; std::filesystem::path new_path = env.get_folder(ManifestLocation::implicit_layer).write_manifest(file_name, invalid_jsons[i]); - env.platform_shim->add_manifest(ManifestCategory::implicit_layer, new_path); +#if defined(WIN32) + env.platform_shim->add_manifest_to_registry(ManifestCategory::implicit_layer, new_path); +#endif } const char* layer_name = "VkLayerImplicit0"; diff --git a/tests/loader_envvar_tests.cpp b/tests/loader_envvar_tests.cpp index c94c2ecdd..85a23ddf2 100644 --- a/tests/loader_envvar_tests.cpp +++ b/tests/loader_envvar_tests.cpp @@ -28,6 +28,8 @@ #include "test_environment.h" +#include "util/wide_char_handling.h" + // Don't support vk_icdNegotiateLoaderICDInterfaceVersion // Loader calls vk_icdGetInstanceProcAddr second // does not support vk_icdGetInstanceProcAddr @@ -120,18 +122,25 @@ TEST(EnvVarICDOverrideSetup, TestOnlyDriverEnvVar) { phys_dev_count = 5; ASSERT_EQ(inst2->vkEnumeratePhysicalDevices(inst2.inst, &phys_dev_count, phys_devs_array.data()), VK_SUCCESS); ASSERT_EQ(phys_dev_count, 5U); +} - env.debug_log.clear(); +// Test VK_DRIVER_FILES environment variable with elelvated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyDriverEnvVarRunningWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::env_var)); + env.get_test_icd(0).add_physical_device("pd0"); - env.platform_shim->set_elevated_privilege(true); + for (uint32_t add = 0; add < 2; ++add) { + env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::env_var)) + .add_physical_device("pd" + std::to_string(add) + "0") + .add_physical_device("pd" + std::to_string(add) + "1"); + } - InstWrapper inst3{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst3.create_info, env.debug_log); - inst3.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); EXPECT_TRUE(env.debug_log.find("vkCreateInstance: Found no drivers!")); - - env.platform_shim->set_elevated_privilege(false); } // Test VK_DRIVER_FILES environment variable containing a path to a folder @@ -157,27 +166,30 @@ TEST(EnvVarICDOverrideSetup, TestOnlyDriverEnvVarInFolder) { .add_physical_device("pd" + std::to_string(add) + "1"); } - env.debug_log.clear(); - InstWrapper inst2{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst2.create_info, env.debug_log); inst2.CheckCreate(); phys_dev_count = 5; ASSERT_EQ(inst2->vkEnumeratePhysicalDevices(inst2.inst, &phys_dev_count, phys_devs_array.data()), VK_SUCCESS); ASSERT_EQ(phys_dev_count, 5U); +} +// Test VK_DRIVER_FILES environment variable containing a path to a folder with elevated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyDriverEnvVarInFolderWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::env_var).set_is_dir(false)); + env.get_test_icd(0).add_physical_device("pd0"); - env.debug_log.clear(); - - env.platform_shim->set_elevated_privilege(true); + for (uint32_t add = 0; add < 2; ++add) { + env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::env_var)) + .add_physical_device("pd" + std::to_string(add) + "0") + .add_physical_device("pd" + std::to_string(add) + "1"); + } - InstWrapper inst3{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst3.create_info, env.debug_log); - inst3.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); EXPECT_TRUE(env.debug_log.find("vkCreateInstance: Found no drivers!")); - - env.platform_shim->set_elevated_privilege(false); } #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__GNU__) || defined(__QNX__) @@ -203,14 +215,13 @@ TEST(EnvVarICDOverrideSetup, XDG) { // Set up a layer path that includes default and user-specified locations, // so that the test app can find them. Include some badly specified elements as well. // Need to redirect the 'home' directory - std::filesystem::path HOME = "/home/fake_home"; - EnvVarWrapper home_env_var{"HOME", HOME}; - EnvVarWrapper xdg_config_dirs_env_var{"XDG_CONFIG_DIRS", ":/tmp/goober:::::/tmp/goober/::::"}; - EnvVarWrapper xdg_config_home_env_var{"XDG_CONFIG_HOME", ":/tmp/goober:::::/tmp/goober2/::::"}; - EnvVarWrapper xdg_data_dirs_env_var{"XDG_DATA_DIRS", "::::/tmp/goober3:/tmp/goober4/with spaces:::"}; - EnvVarWrapper xdg_data_home_env_var{"XDG_DATA_HOME", "::::/tmp/goober3:/tmp/goober4/with spaces:::"}; - FrameworkEnvironment env{}; + FrameworkEnvironment env{FrameworkSettings{} + .set_home_env_var("/home/fake_home") + .set_xdg_config_dirs_env_var(":/tmp/goober:::::/tmp/goober/::::") + .set_xdg_config_home_env_var(":/tmp/goober:::::/tmp/goober2/::::") + .set_xdg_data_dirs_env_var("::::/tmp/goober3:/tmp/goober4/with spaces:::") + .set_xdg_data_home_env_var("::::/tmp/goober3:/tmp/goober4/with spaces:::")}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); InstWrapper inst{env.vulkan_functions}; @@ -232,9 +243,7 @@ TEST(EnvVarICDOverrideSetup, XDGContainsJsonFile) { // Set up a layer path that includes default and user-specified locations, // so that the test app can find them. Include some badly specified elements as well. // Need to redirect the 'home' directory - EnvVarWrapper xdg_config_dirs_env_var{"XDG_CONFIG_DIRS", "bad_file.json"}; - - FrameworkEnvironment env{}; + FrameworkEnvironment env{FrameworkSettings{}.set_xdg_config_dirs_env_var("bad_file.json")}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); InstWrapper inst{env.vulkan_functions}; @@ -249,24 +258,27 @@ TEST(EnvVarICDOverrideSetup, TestOnlyAddDriverEnvVar) { env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::add_env_var)); env.get_test_icd(0).physical_devices.emplace_back("pd0"); - InstWrapper inst1{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst1.create_info, env.debug_log); - inst1.CheckCreate(); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); std::array phys_devs_array; uint32_t phys_dev_count = 1; - ASSERT_EQ(inst1->vkEnumeratePhysicalDevices(inst1.inst, &phys_dev_count, phys_devs_array.data()), VK_SUCCESS); + ASSERT_EQ(inst->vkEnumeratePhysicalDevices(inst.inst, &phys_dev_count, phys_devs_array.data()), VK_SUCCESS); ASSERT_EQ(phys_dev_count, 1U); +} - env.platform_shim->set_elevated_privilege(true); +// Test VK_ADD_DRIVER_FILES environment variable with elelvated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyAddDriverEnvVarRunningWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_EXPORT_NONE).set_discovery_type(ManifestDiscoveryType::add_env_var)); + env.get_test_icd(0).physical_devices.emplace_back("pd0"); - InstWrapper inst2{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst2.create_info, env.debug_log); - inst2.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(VK_ERROR_INCOMPATIBLE_DRIVER); EXPECT_TRUE(env.debug_log.find("vkCreateInstance: Found no drivers!")); - - env.platform_shim->set_elevated_privilege(false); } // Test Both VK_DRIVER_FILES and VK_ADD_DRIVER_FILES environment variable @@ -295,7 +307,7 @@ TEST(EnvVarICDOverrideSetup, TestBothDriverEnvVars) { TEST(EnvVarICDOverrideSetup, TestOnlyLayerEnvVar) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); - env.platform_shim->redirect_path("/tmp/carol", env.get_folder(ManifestLocation::explicit_layer_env_var).location()); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::explicit_layer_env_var); const char* layer_name = "TestLayer"; env.add_explicit_layer( @@ -312,35 +324,51 @@ TEST(EnvVarICDOverrideSetup, TestOnlyLayerEnvVar) { std::string vk_layer_path = ":/tmp/carol::::/:"; vk_layer_path += (HOME / "/ with spaces/:::::/tandy:"); EnvVarWrapper layer_path_env_var{"VK_LAYER_PATH", vk_layer_path}; - InstWrapper inst1{env.vulkan_functions}; - inst1.create_info.add_layer(layer_name); - FillDebugUtilsCreateDetails(inst1.create_info, env.debug_log); - inst1.CheckCreate(); + InstWrapper inst{env.vulkan_functions}; + inst.create_info.add_layer(layer_name); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); // look for VK_LAYER_PATHS EXPECT_TRUE(env.debug_log.find("/tmp/carol")); EXPECT_TRUE(env.debug_log.find("/tandy")); EXPECT_TRUE(env.debug_log.find((HOME / "/ with spaces/"))); +} +// Test VK_LAYER_PATH environment variable with elevated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyLayerEnvVarRunningWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::explicit_layer_env_var); - env.debug_log.clear(); + const char* layer_name = "TestLayer"; + env.add_explicit_layer( + TestLayerDetails(ManifestLayer{}.add_layer( + ManifestLayer::LayerDescription{}.set_name(layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), + "test_layer.json") + .set_discovery_type(ManifestDiscoveryType::env_var)); - env.platform_shim->set_elevated_privilege(true); + // Now set up a layer path that includes default and user-specified locations, + // so that the test app can find them. Include some badly specified elements as well. + // Need to redirect the 'home' directory + std::filesystem::path HOME = "/home/fake_home"; + EnvVarWrapper home_env_var{"HOME", HOME}; + std::string vk_layer_path = ":/tmp/carol::::/:"; + vk_layer_path += (HOME / "/ with spaces/:::::/tandy:"); + EnvVarWrapper layer_path_env_var{"VK_LAYER_PATH", vk_layer_path}; - InstWrapper inst2{env.vulkan_functions}; - inst2.create_info.add_layer(layer_name); - FillDebugUtilsCreateDetails(inst2.create_info, env.debug_log); - inst2.CheckCreate(VK_ERROR_LAYER_NOT_PRESENT); + InstWrapper inst{env.vulkan_functions}; + inst.create_info.add_layer(layer_name); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(VK_ERROR_LAYER_NOT_PRESENT); EXPECT_FALSE(env.debug_log.find("/tmp/carol")); - - env.platform_shim->set_elevated_privilege(false); } // Test VK_ADD_LAYER_PATH environment variable TEST(EnvVarICDOverrideSetup, TestOnlyAddLayerEnvVar) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); - env.platform_shim->redirect_path("/tmp/carol", env.get_folder(ManifestLocation::explicit_layer_add_env_var).location()); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::explicit_layer_add_env_var); const char* layer_name = "TestLayer"; env.add_explicit_layer( @@ -358,35 +386,52 @@ TEST(EnvVarICDOverrideSetup, TestOnlyAddLayerEnvVar) { vk_layer_path += (HOME / "/ with spaces/:::::/tandy:"); EnvVarWrapper add_layer_path_env_var{"VK_ADD_LAYER_PATH", vk_layer_path}; - InstWrapper inst1{env.vulkan_functions}; - inst1.create_info.add_layer(layer_name); - FillDebugUtilsCreateDetails(inst1.create_info, env.debug_log); - inst1.CheckCreate(); + InstWrapper inst{env.vulkan_functions}; + inst.create_info.add_layer(layer_name); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); // look for VK_ADD_LAYER_PATHS EXPECT_TRUE(env.debug_log.find("/tmp/carol")); EXPECT_TRUE(env.debug_log.find("/tandy")); EXPECT_TRUE(env.debug_log.find((HOME / "/ with spaces/"))); +} - env.debug_log.clear(); +// Test VK_ADD_LAYER_PATH environment variable with elevated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyAddLayerEnvVarRunningWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::explicit_layer_add_env_var); - env.platform_shim->set_elevated_privilege(true); + const char* layer_name = "TestLayer"; + env.add_explicit_layer( + TestLayerDetails(ManifestLayer{}.add_layer( + ManifestLayer::LayerDescription{}.set_name(layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), + "test_layer.json") + .set_discovery_type(ManifestDiscoveryType::add_env_var)); - InstWrapper inst2{env.vulkan_functions}; - inst2.create_info.add_layer(layer_name); - FillDebugUtilsCreateDetails(inst2.create_info, env.debug_log); - inst2.CheckCreate(VK_ERROR_LAYER_NOT_PRESENT); + // Set up a layer path that includes default and user-specified locations, + // so that the test app can find them. Include some badly specified elements as well. + // Need to redirect the 'home' directory + std::filesystem::path HOME = "/home/fake_home"; + EnvVarWrapper home_env_var{"HOME", HOME}; + std::string vk_layer_path = ":/tmp/carol::::/:"; + vk_layer_path += (HOME / "/ with spaces/:::::/tandy:"); + EnvVarWrapper add_layer_path_env_var{"VK_ADD_LAYER_PATH", vk_layer_path}; - EXPECT_FALSE(env.debug_log.find("/tmp/carol")); + InstWrapper inst{env.vulkan_functions}; + inst.create_info.add_layer(layer_name); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(VK_ERROR_LAYER_NOT_PRESENT); - env.platform_shim->set_elevated_privilege(false); + EXPECT_FALSE(env.debug_log.find("/tmp/carol")); } // Test VK_IMPLICIT_LAYER_PATH environment variable TEST(EnvVarICDOverrideSetup, TestOnlyImplicitLayerEnvVar) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); - env.platform_shim->redirect_path("/tmp/carol", env.get_folder(ManifestLocation::implicit_layer_env_var).location()); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::implicit_layer_env_var); const char* layer_name = "TestLayer"; env.add_implicit_layer(TestLayerDetails(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} @@ -404,39 +449,57 @@ TEST(EnvVarICDOverrideSetup, TestOnlyImplicitLayerEnvVar) { std::string vk_implicit_layer_path = ":/tmp/carol::::/:"; vk_implicit_layer_path += (HOME / "/ with spaces/:::::/tandy:"); EnvVarWrapper implicit_layer_path_env_var{"VK_IMPLICIT_LAYER_PATH", vk_implicit_layer_path}; - InstWrapper inst1{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst1.create_info, env.debug_log); - inst1.CheckCreate(); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); - auto active_layers1 = inst1.GetActiveLayers(inst1.GetPhysDev(), 1); - ASSERT_TRUE(string_eq(active_layers1.at(0).layerName, layer_name)); + auto active_layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); + ASSERT_TRUE(string_eq(active_layers.at(0).layerName, layer_name)); // look for VK_IMPLICIT_LAYER_PATHS EXPECT_TRUE(env.debug_log.find("/tmp/carol")); EXPECT_TRUE(env.debug_log.find("/tandy")); EXPECT_TRUE(env.debug_log.find((HOME / "/ with spaces/"))); +} - env.debug_log.clear(); +// Test VK_IMPLICIT_LAYER_PATH environment variable run with elevated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyImplicitLayerEnvVarRunningWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::implicit_layer_env_var); + + const char* layer_name = "TestLayer"; + env.add_implicit_layer(TestLayerDetails(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} + .set_name(layer_name) + .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) + .set_disable_environment("DISABLE_ENV")), + "test_layer.json") + .set_discovery_type(ManifestDiscoveryType::env_var)); - env.platform_shim->set_elevated_privilege(true); + // Now set up a layer path that includes default and user-specified locations, + // so that the test app can find them. Include some badly specified elements as well. + // Need to redirect the 'home' directory + std::filesystem::path HOME = "/home/fake_home"; + EnvVarWrapper home_env_var{"HOME", HOME}; + std::string vk_implicit_layer_path = ":/tmp/carol::::/:"; + vk_implicit_layer_path += (HOME / "/ with spaces/:::::/tandy:"); + EnvVarWrapper implicit_layer_path_env_var{"VK_IMPLICIT_LAYER_PATH", vk_implicit_layer_path}; - InstWrapper inst2{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst2.create_info, env.debug_log); - inst2.CheckCreate(); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); - auto active_layers2 = inst2.GetActiveLayers(inst2.GetPhysDev(), 0); - ASSERT_TRUE(active_layers2.empty()); + auto active_layers = inst.GetActiveLayers(inst.GetPhysDev(), 0); + ASSERT_TRUE(active_layers.empty()); EXPECT_FALSE(env.debug_log.find("/tmp/carol")); - - env.platform_shim->set_elevated_privilege(false); } // Test VK_ADD_IMPLICIT_LAYER_PATH environment variable TEST(EnvVarICDOverrideSetup, TestOnlyAddImplicitLayerEnvVar) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); - env.platform_shim->redirect_path("/tmp/carol", env.get_folder(ManifestLocation::implicit_layer_add_env_var).location()); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::implicit_layer_add_env_var); const char* layer_name = "TestLayer"; env.add_implicit_layer(TestLayerDetails(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} @@ -453,7 +516,7 @@ TEST(EnvVarICDOverrideSetup, TestOnlyAddImplicitLayerEnvVar) { EnvVarWrapper home_env_var{"HOME", HOME}; std::string vk_add_implicit_layer_path = ":/tmp/carol::::/:"; vk_add_implicit_layer_path += (HOME / "/ with spaces/:::::/tandy:"); - EnvVarWrapper add_implicit_layer_path_env_var{"VK_ADD_LAYER_PATH", vk_add_implicit_layer_path}; + EnvVarWrapper add_implicit_layer_path_env_var{"VK_ADD_IMPLICIT_LAYER_PATH", vk_add_implicit_layer_path}; InstWrapper inst1{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst1.create_info, env.debug_log); @@ -466,21 +529,39 @@ TEST(EnvVarICDOverrideSetup, TestOnlyAddImplicitLayerEnvVar) { EXPECT_TRUE(env.debug_log.find("/tmp/carol")); EXPECT_TRUE(env.debug_log.find("/tandy")); EXPECT_TRUE(env.debug_log.find((HOME / "/ with spaces/"))); +} - env.debug_log.clear(); +// Test VK_ADD_IMPLICIT_LAYER_PATH environment variable running with elevated privileges +TEST(EnvVarICDOverrideSetup, TestOnlyAddImplicitLayerEnvVarWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device("physical_device_0"); + env.file_system_manager.add_path_redirect("/tmp/carol", ManifestLocation::implicit_layer_add_env_var); - env.platform_shim->set_elevated_privilege(true); + const char* layer_name = "TestLayer"; + env.add_implicit_layer(TestLayerDetails(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} + .set_name(layer_name) + .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) + .set_disable_environment("DISABLE_ENV")), + "test_layer.json") + .set_discovery_type(ManifestDiscoveryType::add_env_var)); - InstWrapper inst2{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst2.create_info, env.debug_log); - inst2.CheckCreate(); + // Set up a layer path that includes default and user-specified locations, + // so that the test app can find them. Include some badly specified elements as well. + // Need to redirect the 'home' directory + std::filesystem::path HOME = "/home/fake_home"; + EnvVarWrapper home_env_var{"HOME", HOME}; + std::string vk_add_implicit_layer_path = ":/tmp/carol::::/:"; + vk_add_implicit_layer_path += (HOME / "/ with spaces/:::::/tandy:"); + EnvVarWrapper add_implicit_layer_path_env_var{"VK_ADD_IMPLICIT_LAYER_PATH", vk_add_implicit_layer_path}; - auto active_layers2 = inst2.GetActiveLayers(inst2.GetPhysDev(), 0); - ASSERT_TRUE(active_layers2.empty()); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); - EXPECT_FALSE(env.debug_log.find("/tmp/carol")); + auto active_layers = inst.GetActiveLayers(inst.GetPhysDev(), 0); + ASSERT_TRUE(active_layers.empty()); - env.platform_shim->set_elevated_privilege(false); + EXPECT_FALSE(env.debug_log.find("/tmp/carol")); } #endif diff --git a/tests/loader_layer_tests.cpp b/tests/loader_layer_tests.cpp index 0fae6ce96..3babc5961 100644 --- a/tests/loader_layer_tests.cpp +++ b/tests/loader_layer_tests.cpp @@ -27,6 +27,9 @@ #include "test_environment.h" +#include "util/test_defines.h" +#include "util/get_executable_path.h" + void CheckLogForLayerString(FrameworkEnvironment& env, const char* implicit_layer_name, bool check_for_enable) { { InstWrapper inst{env.vulkan_functions}; @@ -968,7 +971,8 @@ TEST(ImplicitLayers, DuplicateLayers) { .set_description("actually_layer_1") .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) .set_disable_environment("if_you_can")), - "regular_layer_1.json")); + "regular_layer_1.json") + .set_discovery_type(ManifestDiscoveryType::add_env_var)); auto& layer1 = env.get_test_layer(0); layer1.set_description("actually_layer_1"); layer1.set_make_spurious_log_in_create_instance("actually_layer_1"); @@ -979,17 +983,10 @@ TEST(ImplicitLayers, DuplicateLayers) { .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) .set_disable_environment("if_you_can")), "regular_layer_1.json") - // use override folder as just a folder and manually add it to the implicit layer search paths - .set_discovery_type(ManifestDiscoveryType::override_folder)); + .set_discovery_type(ManifestDiscoveryType::generic)); auto& layer2 = env.get_test_layer(1); layer2.set_description("actually_layer_2"); layer2.set_make_spurious_log_in_create_instance("actually_layer_2"); -#if defined(WIN32) - env.platform_shim->add_manifest(ManifestCategory::implicit_layer, env.get_folder(ManifestLocation::override_layer).location()); -#elif COMMON_UNIX_PLATFORMS - env.platform_shim->redirect_path(std::filesystem::path(USER_LOCAL_SHARE_DIR "/vulkan/implicit_layer.d"), - env.get_folder(ManifestLocation::override_layer).location()); -#endif auto layer_props = env.GetLayerProperties(2); ASSERT_TRUE(string_eq(same_layer_name_1, layer_props[0].layerName)); @@ -1926,7 +1923,7 @@ TEST(OverrideMetaLayer, AppKeysDoesNotContainCurrentApplication) { } } -TEST(OverrideMetaLayer, RunningWithElevatedPrivilegesFromSecureLocation) { +TEST(OverrideMetaLayer, RunningWithRegularPrivilegesFromSecureLocation) { FrameworkEnvironment env; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); @@ -1950,38 +1947,58 @@ TEST(OverrideMetaLayer, RunningWithElevatedPrivilegesFromSecureLocation) { .add_override_path(override_layer_folder.location())), "meta_test_layer.json"}); - { // try with no elevated privileges - auto layer_props = env.GetLayerProperties(2); - EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); + // try with no elevated privileges + auto layer_props = env.GetLayerProperties(2); + EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); - InstWrapper inst{env.vulkan_functions}; - inst.create_info.set_api_version(1, 1, 0); - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); - auto active_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 2); - EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); - env.debug_log.clear(); - } + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(1, 1, 0); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); + ASSERT_TRUE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); + auto active_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 2); + EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); +} - env.platform_shim->set_elevated_privilege(true); +TEST(OverrideMetaLayer, RunningWithElevatedPrivilegesFromSecureLocation) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); - { // try with elevated privileges - auto layer_props = env.GetLayerProperties(2); - EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); + auto& override_layer_folder = env.get_folder(ManifestLocation::override_layer); - InstWrapper inst{env.vulkan_functions}; - inst.create_info.set_api_version(1, 1, 0); - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); - auto active_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 2); - EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, active_layer_props)); - } + const char* regular_layer_name = "VK_LAYER_TestLayer_1"; + override_layer_folder.write_manifest("regular_test_layer.json", + ManifestLayer{} + .add_layer(ManifestLayer::LayerDescription{} + .set_name(regular_layer_name) + .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) + .set_api_version(VK_MAKE_API_VERSION(0, 1, 1, 0))) + .get_manifest_str()); + auto override_folder_location = override_layer_folder.location().string(); + env.add_implicit_layer(TestLayerDetails{ + ManifestLayer{}.set_file_format_version({1, 2, 0}).add_layer(ManifestLayer::LayerDescription{} + .set_name(lunarg_meta_layer_name) + .set_api_version(VK_MAKE_API_VERSION(0, 1, 1, 0)) + .add_component_layer(regular_layer_name) + .set_disable_environment("DisableMeIfYouCan") + .add_override_path(override_layer_folder.location())), + "meta_test_layer.json"}); + + // try with elevated privileges + auto layer_props = env.GetLayerProperties(2); + EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); + + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(1, 1, 0); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); + ASSERT_TRUE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); + auto active_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 2); + EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, active_layer_props)); } // Override layer should not be found and thus not loaded when running with elevated privileges -TEST(OverrideMetaLayer, RunningWithElevatedPrivilegesFromUnsecureLocation) { +TEST(OverrideMetaLayer, RunningWithRegularPrivilegesFromUnsecureLocation) { FrameworkEnvironment env; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); @@ -2005,32 +2022,51 @@ TEST(OverrideMetaLayer, RunningWithElevatedPrivilegesFromUnsecureLocation) { "meta_test_layer.json"} .set_discovery_type(ManifestDiscoveryType::unsecured_generic)); - { // try with no elevated privileges - auto layer_props = env.GetLayerProperties(2); - EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); + auto layer_props = env.GetLayerProperties(2); + EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, layer_props)); - InstWrapper inst{env.vulkan_functions}; - inst.create_info.set_api_version(1, 1, 0); - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); - env.debug_log.clear(); - auto active_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 2); - EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, active_layer_props)); - } + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(1, 1, 0); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); + ASSERT_TRUE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); + env.debug_log.clear(); + auto active_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 2); + EXPECT_TRUE(check_permutation({regular_layer_name, lunarg_meta_layer_name}, active_layer_props)); +} - env.platform_shim->set_elevated_privilege(true); +TEST(OverrideMetaLayer, RunningWithElevatedPrivilegesFromUnsecureLocation) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); - { // try with no elevated privileges - ASSERT_NO_FATAL_FAILURE(env.GetLayerProperties(0)); + auto& override_layer_folder = env.get_folder(ManifestLocation::override_layer); - InstWrapper inst{env.vulkan_functions}; - inst.create_info.set_api_version(1, 1, 0); - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); - ASSERT_FALSE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); - ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); - } + const char* regular_layer_name = "VK_LAYER_TestLayer_1"; + override_layer_folder.write_manifest("regular_test_layer.json", + ManifestLayer{} + .add_layer(ManifestLayer::LayerDescription{} + .set_name(regular_layer_name) + .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) + .set_api_version(VK_MAKE_API_VERSION(0, 1, 1, 0))) + .get_manifest_str()); + env.add_implicit_layer(TestLayerDetails{ + ManifestLayer{}.set_file_format_version({1, 2, 0}).add_layer(ManifestLayer::LayerDescription{} + .set_name(lunarg_meta_layer_name) + .set_api_version(VK_MAKE_API_VERSION(0, 1, 1, 0)) + .add_component_layer(regular_layer_name) + .set_disable_environment("DisableMeIfYouCan") + .add_override_path(override_layer_folder.location())), + "meta_test_layer.json"} + .set_discovery_type(ManifestDiscoveryType::unsecured_generic)); + + ASSERT_NO_FATAL_FAILURE(env.GetLayerProperties(0)); + + InstWrapper inst{env.vulkan_functions}; + inst.create_info.set_api_version(1, 1, 0); + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); + ASSERT_FALSE(env.debug_log.find(std::string("Insert instance layer \"") + regular_layer_name)); + ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); } // Makes sure explicit layers can't override pre-instance functions even if enabled by the override layer @@ -2825,6 +2861,16 @@ TEST(ExplicitLayers, CorrectOrderOfApplicationEnabledLayers) { } } +// Helpers +bool contains(std::vector const& vec, const char* name) { + return std::any_of(std::begin(vec), std::end(vec), + [name](VkExtensionProperties const& elem) { return string_eq(name, elem.extensionName); }); +} +bool contains(std::vector const& vec, const char* name) { + return std::any_of(std::begin(vec), std::end(vec), + [name](VkLayerProperties const& elem) { return string_eq(name, elem.layerName); }); +} + TEST(LayerExtensions, ImplicitNoAdditionalInstanceExtension) { FrameworkEnvironment env; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); @@ -5380,34 +5426,31 @@ TEST(TestLayers, AllowFilterWithImplicitLayer) { auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); ASSERT_TRUE(string_eq(layers.at(0).layerName, layer_name)); } + env.loader_settings.add_app_specific_setting(AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration( + LoaderSettingsLayerConfiguration{} + .set_name(layer_name) + .set_control("on") + .set_path(env.get_shimmed_layer_manifest_path(0)) + .set_treat_as_implicit_manifest(true))); + env.update_loader_settings(env.loader_settings); { - env.loader_settings.add_app_specific_setting(AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration( - LoaderSettingsLayerConfiguration{} - .set_name(layer_name) - .set_control("on") - .set_path(env.get_shimmed_layer_manifest_path(0)) - .set_treat_as_implicit_manifest(true))); - env.update_loader_settings(env.loader_settings); - InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); ASSERT_TRUE(string_eq(layers.at(0).layerName, layer_name)); } + env.loader_settings.app_specific_settings.at(0).layer_configurations.at(0).set_control("off"); + env.update_loader_settings(env.loader_settings); { - env.loader_settings.app_specific_settings.at(0).layer_configurations.at(0).set_control("off"); - env.update_loader_settings(env.loader_settings); - InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); } + env.loader_settings.app_specific_settings.at(0).layer_configurations.at(0).set_control("auto"); + env.update_loader_settings(env.loader_settings); { - env.loader_settings.app_specific_settings.at(0).layer_configurations.at(0).set_control("auto"); - env.update_loader_settings(env.loader_settings); - InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -5417,7 +5460,7 @@ TEST(TestLayers, AllowFilterWithImplicitLayer) { env.remove_loader_settings(); - // Set the disable_environment variable + // Set the layer specific disable_environment variable - layer should never load if this is set EnvVarWrapper set_disable_env_var{disable_env_var, "1"}; { @@ -5441,7 +5484,6 @@ TEST(TestLayers, AllowFilterWithImplicitLayer) { InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); - // layer's disable_environment takes priority ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); } { diff --git a/tests/loader_regression_tests.cpp b/tests/loader_regression_tests.cpp index c2d652f6c..60b108812 100644 --- a/tests/loader_regression_tests.cpp +++ b/tests/loader_regression_tests.cpp @@ -3872,7 +3872,7 @@ TEST(DuplicateRegistryEntries, Layers) { auto null_path = env.get_folder(ManifestLocation::null).location() / "test_layer.json"; - env.platform_shim->add_manifest(ManifestCategory::explicit_layer, null_path); + env.platform_shim->add_manifest_to_registry(ManifestCategory::explicit_layer, null_path); const char* layer_name = "TestLayer"; env.add_explicit_layer( @@ -3890,7 +3890,7 @@ TEST(DuplicateRegistryEntries, Layers) { TEST(DuplicateRegistryEntries, Drivers) { FrameworkEnvironment env{}; auto null_path = env.get_folder(ManifestLocation::null).location() / "test_icd_0.json"; - env.platform_shim->add_manifest(ManifestCategory::icd, null_path); + env.platform_shim->add_manifest_to_registry(ManifestCategory::icd, null_path); env.add_icd(TestICDDetails{TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA}.set_discovery_type(ManifestDiscoveryType::null_dir)) .add_physical_device("physical_device_0") @@ -3908,8 +3908,6 @@ TEST(DuplicateRegistryEntries, Drivers) { TEST(LibraryLoading, SystemLocations) { FrameworkEnvironment env{}; - EnvVarWrapper ld_library_path("LD_LIBRARY_PATH", env.get_folder(ManifestLocation::driver).location().string()); - ld_library_path.add_to_list(env.get_folder(ManifestLocation::explicit_layer).location()); auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2).set_library_path_type(LibraryPathType::default_search_paths)) .add_physical_device({}); @@ -3941,16 +3939,13 @@ TEST(LibraryLoading, SystemLocations) { } #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) -// Check that valid symlinks do not cause the loader to crash when directly in an XDG env-var -TEST(ManifestDiscovery, ValidSymlinkInXDGEnvVar) { - FrameworkEnvironment env{FrameworkSettings{}.set_enable_default_search_paths(false)}; +// Check that valid symlinks do not cause the loader to crash when found in an unsecure location +TEST(ManifestDiscovery, ValidSymlinkInUnsecureLocation) { + FrameworkEnvironment env; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA).set_discovery_type(ManifestDiscoveryType::override_folder)) .add_physical_device({}); - auto symlink_path = - env.get_folder(ManifestLocation::driver_env_var).add_symlink(env.get_icd_manifest_path(0), "symlink_to_driver.json"); - - EnvVarWrapper xdg_config_dirs_env_var{"XDG_CONFIG_DIRS", symlink_path}; + env.add_symlink(ManifestLocation::unsecured_driver, env.get_icd_manifest_path(0), "symlink_to_driver.json"); InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); @@ -3959,29 +3954,23 @@ TEST(ManifestDiscovery, ValidSymlinkInXDGEnvVar) { // Check that valid symlinks do not cause the loader to crash TEST(ManifestDiscovery, ValidSymlink) { - FrameworkEnvironment env{FrameworkSettings{}.set_enable_default_search_paths(false)}; + FrameworkEnvironment env{FrameworkSettings{}}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA).set_discovery_type(ManifestDiscoveryType::override_folder)) .add_physical_device({}); - auto symlink_path = - env.get_folder(ManifestLocation::driver_env_var).add_symlink(env.get_icd_manifest_path(0), "symlink_to_driver.json"); - - env.platform_shim->set_fake_path(ManifestCategory::icd, env.get_folder(ManifestLocation::driver_env_var).location()); + env.add_symlink(ManifestLocation::driver, env.get_icd_manifest_path(0), "symlink_to_driver.json"); InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); inst.CheckCreate(); } -// Check that invalid symlinks do not cause the loader to crash when directly in an XDG env-var -TEST(ManifestDiscovery, InvalidSymlinkXDGEnvVar) { - FrameworkEnvironment env{FrameworkSettings{}.set_enable_default_search_paths(false)}; - - auto symlink_path = - env.get_folder(ManifestLocation::driver) - .add_symlink(env.get_folder(ManifestLocation::driver).location() / "nothing_here.json", "symlink_to_nothing.json"); +// Check that invalid symlinks do not cause the loader to crash when found in an unsecure location +TEST(ManifestDiscovery, InvalidSymlinkInUnsecureLocation) { + FrameworkEnvironment env; - EnvVarWrapper xdg_config_dirs_env_var{symlink_path}; + env.add_symlink(ManifestLocation::unsecured_driver, env.get_folder(ManifestLocation::driver).location() / "nothing_here.json", + "symlink_to_nothing.json"); InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); @@ -3990,13 +3979,10 @@ TEST(ManifestDiscovery, InvalidSymlinkXDGEnvVar) { // Check that invalid symlinks do not cause the loader to crash TEST(ManifestDiscovery, InvalidSymlink) { - FrameworkEnvironment env{FrameworkSettings{}.set_enable_default_search_paths(false)}; - - auto symlink_path = - env.get_folder(ManifestLocation::driver_env_var) - .add_symlink(env.get_folder(ManifestLocation::driver).location() / "nothing_here.json", "symlink_to_nothing.json"); + FrameworkEnvironment env; - env.platform_shim->set_fake_path(ManifestCategory::icd, env.get_folder(ManifestLocation::driver_env_var).location()); + env.add_symlink(ManifestLocation::driver, env.get_folder(ManifestLocation::driver).location() / "nothing_here.json", + "symlink_to_nothing.json"); InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); @@ -4220,7 +4206,9 @@ TEST(InvalidManifest, ICD) { for (size_t i = 0; i < invalid_jsons.size(); i++) { auto file_name = std::string("invalid_driver_") + std::to_string(i) + ".json"; std::filesystem::path new_path = env.get_folder(ManifestLocation::driver).write_manifest(file_name, invalid_jsons[i]); - env.platform_shim->add_manifest(ManifestCategory::icd, new_path); +#if defined(WIN32) + env.platform_shim->add_manifest_to_registry(ManifestCategory::icd, new_path); +#endif } InstWrapper inst{env.vulkan_functions}; @@ -4245,7 +4233,9 @@ TEST(InvalidManifest, Layer) { auto file_name = std::string("invalid_implicit_layer_") + std::to_string(i) + ".json"; std::filesystem::path new_path = env.get_folder(ManifestLocation::implicit_layer).write_manifest(file_name, invalid_jsons[i]); - env.platform_shim->add_manifest(ManifestCategory::implicit_layer, new_path); +#if defined(WIN32) + env.platform_shim->add_manifest_to_registry(ManifestCategory::implicit_layer, new_path); +#endif } InstWrapper inst{env.vulkan_functions}; diff --git a/tests/loader_settings_tests.cpp b/tests/loader_settings_tests.cpp index adc2ae995..8c1fcb90d 100644 --- a/tests/loader_settings_tests.cpp +++ b/tests/loader_settings_tests.cpp @@ -29,31 +29,33 @@ #include -std::string get_settings_location_log_message([[maybe_unused]] FrameworkEnvironment const& env, - [[maybe_unused]] bool use_secure = false) { +#include "util/get_executable_path.h" +#include "util/json_writer.h" +#include "util/test_defines.h" + +std::string get_settings_location_log_message([[maybe_unused]] FrameworkEnvironment const& env, bool use_secure = true) { std::string s = "Using layer configurations found in loader settings from "; #if defined(WIN32) - return s + (env.get_folder(ManifestLocation::settings_location).location() / "vk_loader_settings.json").string(); -#elif COMMON_UNIX_PLATFORMS - if (use_secure) - return s + "/etc/vulkan/loader_settings.d/vk_loader_settings.json"; - else - return s + "/home/fake_home/.local/share/vulkan/loader_settings.d/vk_loader_settings.json"; + ManifestLocation settings_location = use_secure ? ManifestLocation::settings_location : ManifestLocation::unsecured_settings; + return s + (env.get_folder(settings_location).location() / "vk_loader_settings.json").string(); +#elif TESTING_COMMON_UNIX_PLATFORMS + return s + (use_secure ? env.secure_manifest_base_location : env.unsecure_manifest_base_location) + + "/vulkan/loader_settings.d/vk_loader_settings.json"; #endif } +std::string get_unsecure_settings_location_log_message(FrameworkEnvironment const& env) { + return get_settings_location_log_message(env, false); +} -std::string get_settings_not_in_use_log_message([[maybe_unused]] FrameworkEnvironment const& env, - [[maybe_unused]] bool use_secure = false) { +std::string get_settings_not_in_use_log_message([[maybe_unused]] FrameworkEnvironment const& env, bool use_secure) { std::string s = "vk_loader_settings.json file found at \""; #if defined(WIN32) - s += (env.get_folder(ManifestLocation::settings_location).location() / "vk_loader_settings.json").string(); -#elif COMMON_UNIX_PLATFORMS - if (use_secure) - s += "/etc/vulkan/loader_settings.d/vk_loader_settings.json"; - else - s += "/home/fake_home/.local/share/vulkan/loader_settings.d/vk_loader_settings.json"; + ManifestLocation settings_location = use_secure ? ManifestLocation::settings_location : ManifestLocation::unsecured_settings; + return s + (env.get_folder(settings_location).location() / "vk_loader_settings.json").string(); +#elif TESTING_COMMON_UNIX_PLATFORMS + return s + (use_secure ? env.secure_manifest_base_location : env.unsecure_manifest_base_location) + + "/vulkan/loader_settings.d/vk_loader_settings.json\" but did not contain any valid settings."; #endif - return s + "\" but did not contain any valid settings."; } enum class LayerType { exp, @@ -124,39 +126,56 @@ TEST(SettingsFile, SettingsInUnsecuredLocation) { ManifestLayer::LayerDescription{}.set_name(regular_layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), "regular_test_layer.json"} .set_discovery_type(ManifestDiscoveryType::override_folder)); - env.update_loader_settings(env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( - AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration(LoaderSettingsLayerConfiguration{} - .set_name(regular_layer_name) - .set_path(env.get_layer_manifest_path()) - .set_control("on")))); - { - auto layer_props = env.GetLayerProperties(1); - EXPECT_TRUE(string_eq(layer_props.at(0).layerName, regular_layer_name)); + env.update_loader_settings( + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration(LoaderSettingsLayerConfiguration{} + .set_name(regular_layer_name) + .set_path(env.get_layer_manifest_path()) + .set_control("on"))), + false); - InstWrapper inst{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); + auto layer_props = env.GetLayerProperties(1); + EXPECT_TRUE(string_eq(layer_props.at(0).layerName, regular_layer_name)); - ASSERT_TRUE(env.debug_log.find(get_settings_location_log_message(env))); - env.debug_log.clear(); - auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); - ASSERT_TRUE(string_eq(layers.at(0).layerName, regular_layer_name)); - } - env.platform_shim->set_elevated_privilege(true); - { - ASSERT_NO_FATAL_FAILURE(env.GetLayerProperties(0)); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); - InstWrapper inst{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); + ASSERT_TRUE(env.debug_log.find(get_unsecure_settings_location_log_message(env))); + env.debug_log.clear(); + auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); + ASSERT_TRUE(string_eq(layers.at(0).layerName, regular_layer_name)); +} - ASSERT_FALSE(env.debug_log.find(get_settings_location_log_message(env))); - ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); - } +TEST(SettingsFile, SettingsInUnsecuredLocationRunningWithElevatedPrivileges) { + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; + env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); + const char* regular_layer_name = "VK_LAYER_TestLayer_0"; + env.add_explicit_layer(TestLayerDetails{ + ManifestLayer{}.add_layer( + ManifestLayer::LayerDescription{}.set_name(regular_layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), + "regular_test_layer.json"} + .set_discovery_type(ManifestDiscoveryType::override_folder)); + env.update_loader_settings( + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration(LoaderSettingsLayerConfiguration{} + .set_name(regular_layer_name) + .set_path(env.get_layer_manifest_path()) + .set_control("on"))), + false); + + ASSERT_NO_FATAL_FAILURE(env.GetLayerProperties(0)); + + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); + + ASSERT_FALSE(env.debug_log.find(get_unsecure_settings_location_log_message(env))); + ASSERT_NO_FATAL_FAILURE(inst.GetActiveLayers(inst.GetPhysDev(), 0)); } TEST(SettingsFile, SettingsInSecuredLocation) { - FrameworkEnvironment env{FrameworkSettings{}.set_secure_loader_settings(true)}; + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); const char* regular_layer_name = "VK_LAYER_TestLayer_0"; @@ -170,33 +189,18 @@ TEST(SettingsFile, SettingsInSecuredLocation) { .set_name(regular_layer_name) .set_path(env.get_layer_manifest_path()) .set_control("on")))); - { - auto layer_props = env.GetLayerProperties(1); - EXPECT_TRUE(string_eq(layer_props.at(0).layerName, regular_layer_name)); - - InstWrapper inst{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(get_settings_location_log_message(env, true))); - env.debug_log.clear(); - auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); - ASSERT_TRUE(string_eq(layers.at(0).layerName, regular_layer_name)); - } - env.platform_shim->set_elevated_privilege(true); - { - auto layer_props = env.GetLayerProperties(1); - EXPECT_TRUE(string_eq(layer_props.at(0).layerName, regular_layer_name)); + auto layer_props = env.GetLayerProperties(1); + EXPECT_TRUE(string_eq(layer_props.at(0).layerName, regular_layer_name)); - InstWrapper inst{env.vulkan_functions}; - FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); - inst.CheckCreate(); + InstWrapper inst{env.vulkan_functions}; + FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); + inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(get_settings_location_log_message(env, true))); - env.debug_log.clear(); - auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); - ASSERT_TRUE(string_eq(layers.at(0).layerName, regular_layer_name)); - } + ASSERT_TRUE(env.debug_log.find(get_settings_location_log_message(env))); + env.debug_log.clear(); + auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); + ASSERT_TRUE(string_eq(layers.at(0).layerName, regular_layer_name)); } // Make sure settings file can have multiple sets of settings @@ -505,7 +509,7 @@ TEST(SettingsFile, LayerListIsEmpty) { writer.EndArray(); writer.EndObject(); writer.EndObject(); - env.write_settings_file(writer.output); + env.write_settings_file(writer.output, true); auto layer_props = env.GetLayerProperties(1); ASSERT_TRUE(string_eq(layer_props.at(0).layerName, implicit_layer_name)); @@ -513,7 +517,7 @@ TEST(SettingsFile, LayerListIsEmpty) { InstWrapper inst{env.vulkan_functions}; FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(get_settings_not_in_use_log_message(env))); + ASSERT_TRUE(env.debug_log.find(get_settings_not_in_use_log_message(env, true))); auto actice_layer_props = inst.GetActiveLayers(inst.GetPhysDev(), 1); ASSERT_TRUE(string_eq(actice_layer_props.at(0).layerName, implicit_layer_name)); } @@ -552,7 +556,7 @@ TEST(SettingsFile, InvalidSettingsFile) { ASSERT_TRUE(fuzzer_output_json_file.is_open()); std::stringstream fuzzer_output_json; fuzzer_output_json << fuzzer_output_json_file.rdbuf(); - env.write_settings_file(fuzzer_output_json.str()); + env.write_settings_file(fuzzer_output_json.str(), true); check_integrity(); } @@ -563,7 +567,7 @@ TEST(SettingsFile, InvalidSettingsFile) { writer.StartObject(); writer.AddKeyedString("file_format_version", "0.0.0"); writer.EndObject(); - env.write_settings_file(writer.output); + env.write_settings_file(writer.output, true); check_integrity(); } @@ -577,7 +581,7 @@ TEST(SettingsFile, InvalidSettingsFile) { writer.StartKeyedObject("settings"); writer.EndObject(); writer.EndObject(); - env.write_settings_file(writer.output); + env.write_settings_file(writer.output, true); check_integrity(); } @@ -594,7 +598,7 @@ TEST(SettingsFile, InvalidSettingsFile) { writer.EndObject(); } writer.EndObject(); - env.write_settings_file(writer.output); + env.write_settings_file(writer.output, true); check_integrity(); } @@ -1773,8 +1777,8 @@ TEST(SettingsFile, EnvVarsWork_VK_LOADER_LAYERS_DISABLE) { TEST(SettingsFile, MultipleKeysInRegistryInUnsecureLocation) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); - env.platform_shim->add_unsecured_manifest(ManifestCategory::settings, "jank_path"); - env.platform_shim->add_unsecured_manifest(ManifestCategory::settings, "jank_path2"); + env.platform_shim->add_unsecured_manifest_to_registry(ManifestCategory::settings, "jank_path"); + env.platform_shim->add_unsecured_manifest_to_registry(ManifestCategory::settings, "jank_path2"); const char* regular_layer_name = "VK_LAYER_TestLayer_0"; env.add_explicit_layer(TestLayerDetails{ @@ -1782,11 +1786,13 @@ TEST(SettingsFile, MultipleKeysInRegistryInUnsecureLocation) { ManifestLayer::LayerDescription{}.set_name(regular_layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), "regular_test_layer.json"} .set_discovery_type(ManifestDiscoveryType::override_folder)); - env.update_loader_settings(env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( - AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration(LoaderSettingsLayerConfiguration{} - .set_name(regular_layer_name) - .set_path(env.get_layer_manifest_path()) - .set_control("on")))); + env.update_loader_settings( + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + AppSpecificSettings{}.add_stderr_log_filter("all").add_layer_configuration(LoaderSettingsLayerConfiguration{} + .set_name(regular_layer_name) + .set_path(env.get_layer_manifest_path()) + .set_control("on"))), + false); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); auto layer_props = env.GetLayerProperties(1); @@ -1796,16 +1802,16 @@ TEST(SettingsFile, MultipleKeysInRegistryInUnsecureLocation) { FillDebugUtilsCreateDetails(inst.create_info, env.debug_log); inst.CheckCreate(); - ASSERT_TRUE(env.debug_log.find(get_settings_location_log_message(env))); + ASSERT_TRUE(env.debug_log.find(get_unsecure_settings_location_log_message(env))); auto layers = inst.GetActiveLayers(inst.GetPhysDev(), 1); ASSERT_TRUE(string_eq(layers.at(0).layerName, regular_layer_name)); } TEST(SettingsFile, MultipleKeysInRegistryInSecureLocation) { - FrameworkEnvironment env{FrameworkSettings{}.set_secure_loader_settings(true)}; + FrameworkEnvironment env{FrameworkSettings{}.set_run_as_if_with_elevated_privleges(true)}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).add_physical_device({}); - env.platform_shim->add_manifest(ManifestCategory::settings, "jank_path"); - env.platform_shim->add_manifest(ManifestCategory::settings, "jank_path2"); + env.platform_shim->add_manifest_to_registry(ManifestCategory::settings, "jank_path"); + env.platform_shim->add_manifest_to_registry(ManifestCategory::settings, "jank_path2"); const char* regular_layer_name = "VK_LAYER_TestLayer_0"; env.add_explicit_layer(TestLayerDetails{ @@ -1821,7 +1827,6 @@ TEST(SettingsFile, MultipleKeysInRegistryInSecureLocation) { env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); // Make sure it works if the settings file is in the HKEY_LOCAL_MACHINE - env.platform_shim->set_elevated_privilege(true); env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)); { auto layer_props = env.GetLayerProperties(1); @@ -2571,14 +2576,14 @@ TEST(SettingsFile, StderrLog_NoOutput) { ManifestLayer{}.add_layer( ManifestLayer::LayerDescription{}.set_name(explicit_layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), "explicit_test_layer1.json"}); - env.update_loader_settings(env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( AppSpecificSettings{} .add_layer_configuration(LoaderSettingsLayerConfiguration{} .set_name(explicit_layer_name) .set_path(env.get_shimmed_layer_manifest_path()) .set_control("auto")) .add_layer_configuration( - LoaderSettingsLayerConfiguration{}.set_name("VK_LAYER_missing").set_path("/road/to/nowhere").set_control("auto")))); + LoaderSettingsLayerConfiguration{}.set_name("VK_LAYER_missing").set_path("/road/to/nowhere").set_control("auto"))); env.loader_settings.app_specific_settings.at(0).stderr_log = {""}; env.update_loader_settings(env.loader_settings); @@ -2662,14 +2667,14 @@ TEST(SettingsFile, NoStderr_log_but_VK_LOADER_DEBUG) { ManifestLayer::LayerDescription{}.set_name(explicit_layer_name).set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2)), "explicit_test_layer1.json"}); - env.update_loader_settings(env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( + env.loader_settings.set_file_format_version({1, 0, 0}).add_app_specific_setting( AppSpecificSettings{} .add_layer_configuration(LoaderSettingsLayerConfiguration{} .set_name(explicit_layer_name) .set_path(env.get_shimmed_layer_manifest_path()) .set_control("auto")) .add_layer_configuration( - LoaderSettingsLayerConfiguration{}.set_name("VK_LAYER_missing").set_path("/road/to/nowhere").set_control("auto")))); + LoaderSettingsLayerConfiguration{}.set_name("VK_LAYER_missing").set_path("/road/to/nowhere").set_control("auto"))); env.loader_settings.app_specific_settings.at(0).stderr_log = {}; env.update_loader_settings(env.loader_settings); @@ -3176,7 +3181,7 @@ TEST(SettingsFile, InvalidAdditionalDriversField) { writer.EndArray(); writer.EndObject(); writer.EndObject(); - env.write_settings_file(writer.output); + env.write_settings_file(writer.output, true); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); @@ -3377,7 +3382,6 @@ TEST(SettingsFile, DriverConfigurationsAndAdditionalDrivers) { LoaderSettingsDeviceConfiguration{}.set_deviceUUID(uuids[1])); env.update_loader_settings(env.loader_settings); - env.update_loader_settings(env.loader_settings); InstWrapper inst{env.vulkan_functions}; inst.create_info.set_api_version(VK_API_VERSION_1_1); inst.CheckCreate(); diff --git a/tests/loader_testing_main.cpp b/tests/loader_testing_main.cpp index 018bea434..263c1e36f 100644 --- a/tests/loader_testing_main.cpp +++ b/tests/loader_testing_main.cpp @@ -27,6 +27,8 @@ #include "test_environment.h" +#include "util/test_defines.h" + // Makes any failed assertion throw, allowing for graceful cleanup of resources instead of hard aborts class ThrowListener : public testing::EmptyTestEventListener { void OnTestPartResult(const testing::TestPartResult& result) override { @@ -70,14 +72,16 @@ int main(int argc, char** argv) { EnvVarWrapper vk_loader_layers_disable_env_var{"VK_LOADER_LAYERS_DISABLE"}; EnvVarWrapper vk_loader_debug_env_var{"VK_LOADER_DEBUG"}; EnvVarWrapper vk_loader_disable_inst_ext_filter_env_var{"VK_LOADER_DISABLE_INST_EXT_FILTER"}; + EnvVarWrapper vk_loader_disable_select_env_var{"VK_LOADER_DISABLE_SELECT"}; -#if COMMON_UNIX_PLATFORMS + // even though apple shouldn't have XDG env-vars set, the loader looks for them so we have to clear them +#if TESTING_COMMON_UNIX_PLATFORMS // Set only one of the 4 XDG variables to /etc, let everything else be empty - EnvVarWrapper xdg_config_home_env_var{"XDG_CONFIG_HOME", ETC_DIR}; + EnvVarWrapper xdg_config_home_env_var{"XDG_CONFIG_HOME"}; EnvVarWrapper xdg_config_dirs_env_var{"XDG_CONFIG_DIRS"}; EnvVarWrapper xdg_data_home_env_var{"XDG_DATA_HOME"}; EnvVarWrapper xdg_data_dirs_env_var{"XDG_DATA_DIRS"}; - EnvVarWrapper home_env_var{"HOME", HOME_DIR}; + EnvVarWrapper home_env_var{"HOME", "/home/test_home_directory"}; #endif ::testing::InitGoogleTest(&argc, argv); ::testing::UnitTest::GetInstance()->listeners().Append(new ThrowListener); diff --git a/tests/loader_unknown_ext_tests.cpp b/tests/loader_unknown_ext_tests.cpp index 782275682..162ceabd3 100644 --- a/tests/loader_unknown_ext_tests.cpp +++ b/tests/loader_unknown_ext_tests.cpp @@ -27,6 +27,8 @@ #include "test_environment.h" +#include "framework/util/dispatchable_handle.h" + enum class TestConfig { add_layer_implementation, add_layer_interception, diff --git a/tests/loader_version_tests.cpp b/tests/loader_version_tests.cpp index c13c7f60a..e097ff3ce 100644 --- a/tests/loader_version_tests.cpp +++ b/tests/loader_version_tests.cpp @@ -338,9 +338,9 @@ TEST(MultipleICDConfig, Basic) { env.get_test_icd(1).physical_devices.at(0).properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; env.get_test_icd(2).physical_devices.at(0).properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_CPU; - copy_string_to_char_array("dev0", env.get_test_icd(0).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); - copy_string_to_char_array("dev1", env.get_test_icd(1).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); - copy_string_to_char_array("dev2", env.get_test_icd(2).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); + std::string("dev0").copy(env.get_test_icd(0).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); + std::string("dev1").copy(env.get_test_icd(1).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); + std::string("dev2").copy(env.get_test_icd(2).physical_devices.at(0).properties.deviceName, VK_MAX_EXTENSION_NAME_SIZE); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); From e5d61f274e75a897c7b8657e41702962a00417fc Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Fri, 25 Jul 2025 15:43:36 -0500 Subject: [PATCH 4/4] loader settings unix search path follows driver/layer searching The searching for the loader_settings.json file relied on faulty assumptions about which paths would be used, notably ignoring XDG env-vars, as well as SYSCONFDIR, EXTRASYSCONFDIR, and the fallback xdg paths. --- loader/loader.c | 2 +- loader/settings.c | 125 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 106 insertions(+), 21 deletions(-) diff --git a/loader/loader.c b/loader/loader.c index 85e3223af..6102a525d 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -3270,7 +3270,7 @@ void copy_data_file_info(const char *cur_path, const char *relative_path, size_t char *cur_write = *output_path; while (cur_path[start] != '\0') { - while (cur_path[start] == PATH_SEPARATOR) { + while (cur_path[start] == PATH_SEPARATOR && cur_path[start] != '\0') { start++; } stop = start; diff --git a/loader/settings.c b/loader/settings.c index a28c34fae..cc6a60c24 100644 --- a/loader/settings.c +++ b/loader/settings.c @@ -387,46 +387,131 @@ VkResult parse_device_configurations(const struct loader_instance* inst, cJSON* return res; } +#if COMMON_UNIX_PLATFORMS +// Given a base and suffix path, determine if a file at that location exists, and if it is return success. +// Since base may contain multiple paths seperated by PATH_SEPARATOR, we must extract each segment and check segment + suffix +// individually VkResult check_if_settings_path_exists(const struct loader_instance* inst, const char* base, const char* suffix, char** settings_file_path) { if (NULL == base || NULL == suffix) { return VK_ERROR_INITIALIZATION_FAILED; } + size_t base_len = strlen(base); size_t suffix_len = strlen(suffix); - size_t path_len = base_len + suffix_len + 1; - *settings_file_path = loader_instance_heap_calloc(inst, path_len, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND); - if (NULL == *settings_file_path) { - return VK_ERROR_OUT_OF_HOST_MEMORY; - } - loader_strncpy(*settings_file_path, path_len, base, base_len); - loader_strncat(*settings_file_path, path_len, suffix, suffix_len); - if (!loader_platform_file_exists(*settings_file_path)) { + uint32_t start = 0; + uint32_t stop = 0; + while (base[start] != '\0' && start < base_len && stop < base_len) { + start = stop; + stop = start + 1; + while (base[stop] != PATH_SEPARATOR && base[stop] != '\0' && stop < base_len) { + stop++; + } + + size_t segment_len = (stop - start); + if (segment_len <= 1) { + // segment is *just* a PATH_SEPARATOR, skip it + continue; + } + size_t path_len = segment_len + suffix_len + 1; + *settings_file_path = loader_instance_heap_calloc(inst, path_len, VK_SYSTEM_ALLOCATION_SCOPE_COMMAND); + if (NULL == *settings_file_path) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + loader_strncpy(*settings_file_path, path_len, base + start, segment_len); + loader_strncat(*settings_file_path, path_len, suffix, suffix_len); + + if (loader_platform_file_exists(*settings_file_path)) { + return VK_SUCCESS; + } loader_instance_heap_free(inst, *settings_file_path); *settings_file_path = NULL; - return VK_ERROR_INITIALIZATION_FAILED; } - return VK_SUCCESS; + + return VK_ERROR_INITIALIZATION_FAILED; } + +// Follow the logic of read_data_files_in_search_paths but only look for "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME VkResult get_unix_settings_path(const struct loader_instance* inst, char** settings_file_path) { - VkResult res = - check_if_settings_path_exists(inst, loader_secure_getenv("HOME", inst), - "/.local/share/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, settings_file_path); + // First, get XDG env-vars we use. Don't need to worry about free'ing because on linux getenv is non-allocating + char* xdg_config_home = loader_secure_getenv("XDG_CONFIG_HOME", inst); + char* xdg_config_dirs = loader_secure_getenv("XDG_CONFIG_DIRS", inst); + char* xdg_data_home = loader_secure_getenv("XDG_DATA_HOME", inst); + char* xdg_data_dirs = loader_secure_getenv("XDG_DATA_DIRS", inst); + + // Use fallback directories for xdg_config_dirs and xdg_data_dirs if they are NULL. +#if !defined(__Fuchsia__) && !defined(__QNX__) + if (NULL == xdg_config_dirs || '\0' == xdg_config_dirs[0]) { + xdg_config_dirs = FALLBACK_CONFIG_DIRS; + } +#endif + +#if !defined(__Fuchsia__) && !defined(__QNX__) + if (NULL == xdg_data_dirs || '\0' == xdg_data_dirs[0]) { + xdg_data_dirs = FALLBACK_DATA_DIRS; + } +#endif + + VkResult res = check_if_settings_path_exists(inst, xdg_config_home, "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); + if (res == VK_SUCCESS) { + return res; + } + + res = check_if_settings_path_exists(inst, xdg_data_home, "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); + if (res == VK_SUCCESS) { + return res; + } + + // Check home if either xdg_config_home or xdg_data_home wasn't set + char* home = loader_secure_getenv("HOME", inst); + if (home != NULL) { + if (NULL == xdg_config_home || '\0' == xdg_config_home[0]) { + res = check_if_settings_path_exists(inst, home, "/.config/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); + if (res == VK_SUCCESS) { + return res; + } + } + if (NULL == xdg_data_home || '\0' == xdg_data_home[0]) { + res = check_if_settings_path_exists(inst, home, "/.local/share/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); + if (res == VK_SUCCESS) { + return res; + } + } + } + + res = check_if_settings_path_exists(inst, xdg_config_dirs, "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); if (res == VK_SUCCESS) { return res; } - // If HOME isn't set, fallback to XDG_DATA_HOME - res = check_if_settings_path_exists(inst, loader_secure_getenv("XDG_DATA_HOME", inst), - "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, settings_file_path); + + res = check_if_settings_path_exists(inst, SYSCONFDIR, "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); + if (res == VK_SUCCESS) { + return res; + } +#if defined(EXTRASYSCONFDIR) + + res = check_if_settings_path_exists(inst, EXTRASYSCONFDIR, "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); if (res == VK_SUCCESS) { return res; } - // if XDG_DATA_HOME isn't set, fallback to /etc. - // note that the settings_fil_path_suffix stays the same since its the same layout as for XDG_DATA_HOME - return check_if_settings_path_exists(inst, "/etc", "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, - settings_file_path); +#endif + res = check_if_settings_path_exists(inst, xdg_data_dirs, "/vulkan/loader_settings.d/" VK_LOADER_SETTINGS_FILENAME, + settings_file_path); + if (res == VK_SUCCESS) { + return res; + } + + return VK_ERROR_INITIALIZATION_FAILED; } +#endif bool check_if_layer_configurations_are_equal(loader_settings_layer_configuration* a, loader_settings_layer_configuration* b) { if (!a->name || !b->name || 0 != strcmp(a->name, b->name)) {