diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d25f5ec6..abe79687 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -117,7 +117,8 @@ option(TEST_USE_ADDRESS_SANITIZER "Linux only: Advanced memory checking" OFF) include(GoogleTest) add_subdirectory(framework) -add_executable(test_regression loader_testing_main.cpp loader_regression_tests.cpp loader_version_tests.cpp) +add_executable(test_regression loader_testing_main.cpp loader_regression_tests.cpp + loader_version_tests.cpp loader_alloc_callback_tests.cpp) target_link_libraries(test_regression PRIVATE testing_dependencies) add_executable(test_wsi loader_testing_main.cpp loader_wsi_tests.cpp) diff --git a/tests/framework/test_environment.h b/tests/framework/test_environment.h index 8ec46f69..269a8539 100644 --- a/tests/framework/test_environment.h +++ b/tests/framework/test_environment.h @@ -29,10 +29,9 @@ * The test_environment is what combines the icd, layer, and shim library into a single object that * test fixtures can create and use. Responsible for loading the libraries and establishing the * channels for tests to talk with the icd's and layers. -*/ + */ #pragma once - // Must include gtest first to guard against Xlib colliding due to redefinitions of "None" and "Bool" #ifdef _MSC_VER @@ -55,6 +54,12 @@ #undef Bool #endif +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + // Use the NDK's header on Android #include "gtest/gtest.h" @@ -124,7 +129,6 @@ struct EnvVarICDOverrideShim : FrameworkEnvironment { GetNewTestICDFunc get_new_test_icd; }; - struct SingleICDShim : FrameworkEnvironment { SingleICDShim(TestICDDetails icd_details, DebugMode debug_mode = DebugMode::none); diff --git a/tests/framework/test_util.cpp b/tests/framework/test_util.cpp index 9d399531..de83509b 100644 --- a/tests/framework/test_util.cpp +++ b/tests/framework/test_util.cpp @@ -622,7 +622,7 @@ DeviceCreateInfo& DeviceCreateInfo::add_device_queue(DeviceQueueCreateInfo queue } VkResult CreateInst(InstWrapper& inst, InstanceCreateInfo& inst_info) { - return inst.functions->fp_vkCreateInstance(inst_info.get(), nullptr, &inst.inst); + return inst.functions->fp_vkCreateInstance(inst_info.get(), inst.callbacks, &inst.inst); } VkResult CreatePhysDevs(InstWrapper& inst, uint32_t phys_dev_count, std::vector& physical_devices) { diff --git a/tests/framework/test_util.h b/tests/framework/test_util.h index cf149f3d..3867f089 100644 --- a/tests/framework/test_util.h +++ b/tests/framework/test_util.h @@ -41,7 +41,7 @@ * Instance & Device create info helpers * InstWrapper & DeviceWrapper - for easier test writing * operator == overloads for many vulkan structs - more concise tests -*/ + */ #pragma once // Following items are needed for C++ to work with PRIxLEAST64 @@ -105,7 +105,7 @@ bool remove_env_var(std::string const& name); std::string get_env_var(std::string const& name); #endif -//Windows specific error handling logic +// 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 @@ -253,7 +253,7 @@ struct path { // get C++ style string std::string const& str() const { return contents; } std::string& str() { return contents; } - size_t size() const { return contents.size();}; + size_t size() const { return contents.size(); }; private: std::string contents; @@ -347,7 +347,9 @@ struct LibraryWrapper { } 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(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) { @@ -596,10 +598,11 @@ struct DeviceCreateInfo { }; struct InstWrapper { - InstWrapper(VulkanFunctions& functions) noexcept : functions(&functions) {} - InstWrapper(VulkanFunctions& functions, VkInstance inst) noexcept : functions(&functions), inst(inst) {} + InstWrapper(VulkanFunctions& functions, VkAllocationCallbacks* callbacks = nullptr) noexcept : functions(&functions), callbacks(callbacks) {} + InstWrapper(VulkanFunctions& functions, VkInstance inst, VkAllocationCallbacks* callbacks = nullptr) noexcept + : functions(&functions), inst(inst), callbacks(callbacks) {} ~InstWrapper() { - if (inst != VK_NULL_HANDLE) functions->fp_vkDestroyInstance(inst, nullptr); + if (inst != VK_NULL_HANDLE) functions->fp_vkDestroyInstance(inst, callbacks); } // Immoveable object @@ -614,6 +617,7 @@ struct InstWrapper { VulkanFunctions* functions = nullptr; VkInstance inst = VK_NULL_HANDLE; + VkAllocationCallbacks* callbacks = nullptr; }; VkResult CreateInst(InstWrapper& inst, InstanceCreateInfo& inst_info); diff --git a/tests/loader_alloc_callback_tests.cpp b/tests/loader_alloc_callback_tests.cpp new file mode 100644 index 00000000..2be23b0a --- /dev/null +++ b/tests/loader_alloc_callback_tests.cpp @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2021 The Khronos Group Inc. + * Copyright (c) 2021 Valve Corporation + * Copyright (c) 2021 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_environment.h" + +#include + +struct MemoryTrackerSettings { + MemoryTrackerSettings(bool should_fail_on_allocation, size_t fail_after_allocations) + : should_fail_on_allocation(should_fail_on_allocation), fail_after_allocations(fail_after_allocations) {} + bool should_fail_on_allocation = false; + size_t fail_after_allocations = 0; +}; + +class MemoryTracker { + std::mutex main_mutex; + MemoryTrackerSettings settings; + VkAllocationCallbacks callbacks{}; + // Implementation internals + struct AllocationDetails { + size_t requested_size_bytes; + size_t actual_size_bytes; + VkSystemAllocationScope alloc_scope; + }; + const static size_t UNKNOWN_ALLOCATION = std::numeric_limits::max(); + size_t allocation_count = 0; + std::vector> allocations; + std::vector allocations_aligned; + std::vector allocation_details; + void add_element(std::unique_ptr&& alloc, void* aligned_alloc, AllocationDetails detail) { + allocations.push_back(std::move(alloc)); + allocations_aligned.push_back(aligned_alloc); + allocation_details.push_back(detail); + } + void erase_index(size_t index) { + allocations.erase(std::next(allocations.begin(), index)); + allocations_aligned.erase(std::next(allocations_aligned.begin(), index)); + allocation_details.erase(std::next(allocation_details.begin(), index)); + } + size_t find_element(void* ptr) { + auto it = std::find(allocations_aligned.begin(), allocations_aligned.end(), ptr); + if (it == allocations_aligned.end()) return UNKNOWN_ALLOCATION; + return it - allocations_aligned.begin(); + } + + void* allocate(size_t size, size_t alignment, VkSystemAllocationScope alloc_scope) { + AllocationDetails detail{size, size + (alignment - 1), alloc_scope}; + auto alloc = std::unique_ptr(new char[detail.actual_size_bytes]); + if (!alloc) return nullptr; + uint64_t addr = (uint64_t)alloc.get(); + addr += (alignment - 1); + addr &= ~(alignment - 1); + void* aligned_alloc = (void*)addr; + add_element(std::move(alloc), aligned_alloc, detail); + allocation_count++; + return allocations_aligned.back(); + } + void* reallocate(void* pOriginal, size_t size, size_t alignment, VkSystemAllocationScope alloc_scope) { + if (pOriginal == nullptr) { + return allocate(size, alignment, alloc_scope); + } + size_t index = find_element(pOriginal); + if (index == UNKNOWN_ALLOCATION) return nullptr; + size_t original_size = allocation_details[index].requested_size_bytes; + if (size == 0) { + erase_index(index); + allocation_count--; + return nullptr; + } else if (size < original_size) { + return pOriginal; + } else { + void* new_alloc = allocate(size, alignment, alloc_scope); + if (new_alloc == nullptr) return nullptr; + memcpy(new_alloc, pOriginal, original_size); + erase_index(index); + return new_alloc; + } + } + void free(void* pMemory) { + if (pMemory == nullptr) return; + size_t index = find_element(pMemory); + if (index == UNKNOWN_ALLOCATION) return; + erase_index(index); + assert(allocation_count != 0 && "Cant free when there are no valid allocations"); + allocation_count--; + } + + // Implementation of public functions + void* impl_allocation(size_t size, size_t alignment, VkSystemAllocationScope allocationScope) noexcept { + std::lock_guard lg(main_mutex); + if (settings.should_fail_on_allocation && allocation_count == settings.fail_after_allocations) return nullptr; + void* addr = allocate(size, alignment, allocationScope); + return addr; + } + void* impl_reallocation(void* pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocationScope) noexcept { + std::lock_guard lg(main_mutex); + void* addr = reallocate(pOriginal, size, alignment, allocationScope); + return addr; + } + void impl_free(void* pMemory) noexcept { + std::lock_guard lg(main_mutex); + free(pMemory); + } + void impl_internal_allocation_notification(size_t size, VkInternalAllocationType allocationType, + VkSystemAllocationScope allocationScope) noexcept { + std::lock_guard lg(main_mutex); + // TODO? + } + void impl_internal_free(size_t size, VkInternalAllocationType allocationType, + VkSystemAllocationScope allocationScope) noexcept { + std::lock_guard lg(main_mutex); + // TODO? + } + + public: + MemoryTracker(MemoryTrackerSettings settings) noexcept : settings(settings) { + allocations.reserve(512); + allocations_aligned.reserve(512); + allocation_details.reserve(512); + + callbacks.pUserData = this; + callbacks.pfnAllocation = public_allocation; + callbacks.pfnReallocation = public_reallocation; + callbacks.pfnFree = public_free; + callbacks.pfnInternalAllocation = public_internal_allocation_notification; + callbacks.pfnInternalFree = public_internal_free; + } + MemoryTracker() noexcept : MemoryTracker(MemoryTrackerSettings{false, 0}) {} + + VkAllocationCallbacks* get() noexcept { return &callbacks; } + + bool empty() noexcept { return allocation_count == 0; } + + // Static callbacks + static VKAPI_ATTR void* VKAPI_CALL public_allocation(void* pUserData, size_t size, size_t alignment, + VkSystemAllocationScope allocationScope) noexcept { + return reinterpret_cast(pUserData)->impl_allocation(size, alignment, allocationScope); + } + static VKAPI_ATTR void* VKAPI_CALL public_reallocation(void* pUserData, void* pOriginal, size_t size, size_t alignment, + VkSystemAllocationScope allocationScope) noexcept { + return reinterpret_cast(pUserData)->impl_reallocation(pOriginal, size, alignment, allocationScope); + } + static VKAPI_ATTR void VKAPI_CALL public_free(void* pUserData, void* pMemory) noexcept { + reinterpret_cast(pUserData)->impl_free(pMemory); + } + static VKAPI_ATTR void VKAPI_CALL public_internal_allocation_notification(void* pUserData, size_t size, + VkInternalAllocationType allocationType, + VkSystemAllocationScope allocationScope) noexcept { + reinterpret_cast(pUserData)->impl_internal_allocation_notification(size, allocationType, allocationScope); + } + static VKAPI_ATTR void VKAPI_CALL public_internal_free(void* pUserData, size_t size, VkInternalAllocationType allocationType, + VkSystemAllocationScope allocationScope) noexcept { + reinterpret_cast(pUserData)->impl_internal_free(size, allocationType, allocationScope); + } +}; + +class Allocation : public ::testing::Test { + protected: + virtual void SetUp() { + env = std::unique_ptr(new SingleICDShim(TestICDDetails(TEST_ICD_PATH_VERSION_2, VK_MAKE_VERSION(1, 0, 0)))); + } + + virtual void TearDown() { env.reset(); } + std::unique_ptr env; +}; + +// Test making sure the allocation functions are called to allocate and cleanup everything during +// a CreateInstance/DestroyInstance call pair. +TEST_F(Allocation, Instance) { + MemoryTracker tracker; + { + InstWrapper inst{env->vulkan_functions, tracker.get()}; + InstanceCreateInfo inst_create_info; + ASSERT_EQ(CreateInst(inst, inst_create_info), VK_SUCCESS); + } + ASSERT_TRUE(tracker.empty()); +} + +// Test making sure the allocation functions are called to allocate and cleanup everything during +// a CreateInstance/DestroyInstance call pair with a call to GetInstanceProcAddr. +TEST_F(Allocation, GetInstanceProcAddr) { + MemoryTracker tracker; + { + InstWrapper inst{env->vulkan_functions, tracker.get()}; + InstanceCreateInfo inst_create_info; + ASSERT_EQ(CreateInst(inst, inst_create_info), VK_SUCCESS); + + auto* pfnCreateDevice = inst->fp_vkGetInstanceProcAddr(inst, "vkCreateDevice"); + auto* pfnDestroyDevice = inst->fp_vkGetInstanceProcAddr(inst, "vkDestroyDevice"); + ASSERT_TRUE(pfnCreateDevice != nullptr && pfnDestroyDevice != nullptr); + } + ASSERT_TRUE(tracker.empty()); +} + +// Test making sure the allocation functions are called to allocate and cleanup everything during +// a vkEnumeratePhysicalDevices call pair. +TEST_F(Allocation, EnumeratePhysicalDevices) { + MemoryTracker tracker; + auto& driver = env->get_test_icd(); + driver.physical_devices.emplace_back("physical_device_0"); + { + InstWrapper inst{env->vulkan_functions, tracker.get()}; + InstanceCreateInfo inst_create_info; + ASSERT_EQ(CreateInst(inst, inst_create_info), VK_SUCCESS); + + uint32_t physical_count = 1; + uint32_t returned_physical_count = 0; + ASSERT_EQ(VK_SUCCESS, inst->fp_vkEnumeratePhysicalDevices(inst.inst, &returned_physical_count, nullptr)); + ASSERT_EQ(physical_count, returned_physical_count); + + VkPhysicalDevice physical_device; + ASSERT_EQ(VK_SUCCESS, inst->fp_vkEnumeratePhysicalDevices(inst.inst, &returned_physical_count, &physical_device)); + ASSERT_EQ(physical_count, returned_physical_count); + } + ASSERT_TRUE(tracker.empty()); +} + +// Test making sure the allocation functions are called to allocate and cleanup everything from +// vkCreateInstance, to vkCreateDevicce, and then through their destructors. With special +// allocators used on both the instance and device. +TEST_F(Allocation, InstanceAndDevice) { + MemoryTracker tracker; + auto& driver = env->get_test_icd(); + driver.physical_devices.emplace_back("physical_device_0"); + driver.physical_devices[0].add_queue_family_properties({VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}); + { + InstWrapper inst{env->vulkan_functions, tracker.get()}; + InstanceCreateInfo inst_create_info; + ASSERT_EQ(CreateInst(inst, inst_create_info), VK_SUCCESS); + + uint32_t physical_count = 1; + uint32_t returned_physical_count = 0; + ASSERT_EQ(VK_SUCCESS, inst->fp_vkEnumeratePhysicalDevices(inst.inst, &returned_physical_count, nullptr)); + ASSERT_EQ(physical_count, returned_physical_count); + + VkPhysicalDevice physical_device; + ASSERT_EQ(VK_SUCCESS, inst->fp_vkEnumeratePhysicalDevices(inst.inst, &returned_physical_count, &physical_device)); + ASSERT_EQ(physical_count, returned_physical_count); + + uint32_t family_count = 1; + uint32_t returned_family_count = 0; + env->vulkan_functions.fp_vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &returned_family_count, nullptr); + ASSERT_EQ(returned_family_count, family_count); + + VkQueueFamilyProperties family; + env->vulkan_functions.fp_vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &returned_family_count, &family); + ASSERT_EQ(returned_family_count, family_count); + ASSERT_EQ(family.queueFlags, VK_QUEUE_GRAPHICS_BIT); + ASSERT_EQ(family.queueCount, family_count); + ASSERT_EQ(family.timestampValidBits, 0); + + DeviceCreateInfo dev_create_info; + DeviceQueueCreateInfo queue_info; + queue_info.add_priority(0.0f); + dev_create_info.add_device_queue(queue_info); + + VkDevice device; + ASSERT_EQ(inst->fp_vkCreateDevice(physical_device, dev_create_info.get(), tracker.get(), &device), VK_SUCCESS); + inst->fp_vkDestroyDevice(device, tracker.get()); + } + ASSERT_TRUE(tracker.empty()); +} +// Test making sure the allocation functions are called to allocate and cleanup everything from +// vkCreateInstance, to vkCreateDevicce, and then through their destructors. With special +// allocators used on only the instance and not the device. +TEST_F(Allocation, InstanceButNotDevice) { + MemoryTracker tracker; + { + auto& driver = env->get_test_icd(); + driver.physical_devices.emplace_back("physical_device_0"); + driver.physical_devices[0].add_queue_family_properties({VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}); + + InstWrapper inst{env->vulkan_functions, tracker.get()}; + InstanceCreateInfo inst_create_info; + ASSERT_EQ(CreateInst(inst, inst_create_info), VK_SUCCESS); + + uint32_t physical_count = 1; + uint32_t returned_physical_count = 0; + ASSERT_EQ(VK_SUCCESS, inst->fp_vkEnumeratePhysicalDevices(inst.inst, &returned_physical_count, nullptr)); + ASSERT_EQ(physical_count, returned_physical_count); + + VkPhysicalDevice physical_device; + ASSERT_EQ(VK_SUCCESS, inst->fp_vkEnumeratePhysicalDevices(inst.inst, &returned_physical_count, &physical_device)); + ASSERT_EQ(physical_count, returned_physical_count); + + uint32_t family_count = 1; + uint32_t returned_family_count = 0; + env->vulkan_functions.fp_vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &returned_family_count, nullptr); + ASSERT_EQ(returned_family_count, family_count); + + VkQueueFamilyProperties family; + env->vulkan_functions.fp_vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &returned_family_count, &family); + ASSERT_EQ(returned_family_count, family_count); + ASSERT_EQ(family.queueFlags, VK_QUEUE_GRAPHICS_BIT); + ASSERT_EQ(family.queueCount, family_count); + ASSERT_EQ(family.timestampValidBits, 0); + + DeviceCreateInfo dev_create_info; + DeviceQueueCreateInfo queue_info; + queue_info.add_priority(0.0f); + dev_create_info.add_device_queue(queue_info); + + VkDevice device; + ASSERT_EQ(inst->fp_vkCreateDevice(physical_device, dev_create_info.get(), nullptr, &device), VK_SUCCESS); + inst->fp_vkDestroyDevice(device, nullptr); + } + ASSERT_TRUE(tracker.empty()); +} + +// Test making sure the allocation functions are called to allocate and cleanup everything from +// vkCreateInstance, to vkCreateDevicce, and then through their destructors. With special +// allocators used on only the device and not the instance. +TEST_F(Allocation, DeviceButNotInstance) { + MemoryTracker tracker; + { + auto& driver = env->get_test_icd(); + driver.physical_devices.emplace_back("physical_device_0"); + driver.physical_devices[0].add_queue_family_properties({VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}); + + InstWrapper inst{env->vulkan_functions}; + InstanceCreateInfo inst_create_info; + ASSERT_EQ(CreateInst(inst, inst_create_info), VK_SUCCESS); + + uint32_t physical_count = 1; + uint32_t returned_physical_count = 0; + ASSERT_EQ(VK_SUCCESS, inst->fp_vkEnumeratePhysicalDevices(inst.inst, &returned_physical_count, nullptr)); + ASSERT_EQ(physical_count, returned_physical_count); + + VkPhysicalDevice physical_device; + ASSERT_EQ(VK_SUCCESS, inst->fp_vkEnumeratePhysicalDevices(inst.inst, &returned_physical_count, &physical_device)); + ASSERT_EQ(physical_count, returned_physical_count); + + uint32_t family_count = 1; + uint32_t returned_family_count = 0; + env->vulkan_functions.fp_vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &returned_family_count, nullptr); + ASSERT_EQ(returned_family_count, family_count); + + VkQueueFamilyProperties family; + env->vulkan_functions.fp_vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &returned_family_count, &family); + ASSERT_EQ(returned_family_count, family_count); + ASSERT_EQ(family.queueFlags, VK_QUEUE_GRAPHICS_BIT); + ASSERT_EQ(family.queueCount, family_count); + ASSERT_EQ(family.timestampValidBits, 0); + + DeviceCreateInfo dev_create_info; + DeviceQueueCreateInfo queue_info; + queue_info.add_priority(0.0f); + dev_create_info.add_device_queue(queue_info); + + VkDevice device; + ASSERT_EQ(inst->fp_vkCreateDevice(physical_device, dev_create_info.get(), tracker.get(), &device), VK_SUCCESS); + inst->fp_vkDestroyDevice(device, tracker.get()); + } + ASSERT_TRUE(tracker.empty()); +} + +// Test failure during vkCreateInstance to make sure we don't leak memory if +// one of the out-of-memory conditions trigger. +TEST_F(Allocation, CreateInstanceIntentionalAllocFail) { + size_t fail_index = 0; + VkResult result = VK_ERROR_OUT_OF_HOST_MEMORY; + while (result == VK_ERROR_OUT_OF_HOST_MEMORY && fail_index <= 10000) { + MemoryTracker tracker(MemoryTrackerSettings{true, fail_index}); + + InstanceCreateInfo inst_create_info; + VkInstance instance; + result = env->vulkan_functions.fp_vkCreateInstance(inst_create_info.get(), tracker.get(), &instance); + if (result == VK_SUCCESS) { + env->vulkan_functions.fp_vkDestroyInstance(instance, tracker.get()); + } + ASSERT_TRUE(tracker.empty()); + fail_index++; + } +} + +// Test failure during vkCreateDevice to make sure we don't leak memory if +// one of the out-of-memory conditions trigger. +TEST_F(Allocation, CreateDeviceIntentionalAllocFail) { + auto& driver = env->get_test_icd(); + driver.physical_devices.emplace_back("physical_device_0"); + driver.physical_devices[0].add_queue_family_properties({VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}); + + InstWrapper inst{env->vulkan_functions}; + InstanceCreateInfo inst_create_info; + ASSERT_EQ(CreateInst(inst, inst_create_info), VK_SUCCESS); + + uint32_t physical_count = 1; + uint32_t returned_physical_count = 0; + ASSERT_EQ(VK_SUCCESS, inst->fp_vkEnumeratePhysicalDevices(inst.inst, &returned_physical_count, nullptr)); + ASSERT_EQ(physical_count, returned_physical_count); + + VkPhysicalDevice physical_device; + ASSERT_EQ(VK_SUCCESS, inst->fp_vkEnumeratePhysicalDevices(inst.inst, &returned_physical_count, &physical_device)); + ASSERT_EQ(physical_count, returned_physical_count); + + uint32_t family_count = 1; + uint32_t returned_family_count = 0; + env->vulkan_functions.fp_vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &returned_family_count, nullptr); + ASSERT_EQ(returned_family_count, family_count); + + VkQueueFamilyProperties family; + env->vulkan_functions.fp_vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &returned_family_count, &family); + ASSERT_EQ(returned_family_count, family_count); + ASSERT_EQ(family.queueFlags, VK_QUEUE_GRAPHICS_BIT); + ASSERT_EQ(family.queueCount, family_count); + ASSERT_EQ(family.timestampValidBits, 0); + + size_t fail_index = 0; + VkResult result = VK_ERROR_OUT_OF_HOST_MEMORY; + while (result == VK_ERROR_OUT_OF_HOST_MEMORY) { + MemoryTracker tracker(MemoryTrackerSettings{true, fail_index}); + + DeviceCreateInfo dev_create_info; + DeviceQueueCreateInfo queue_info; + queue_info.add_priority(0.0f); + dev_create_info.add_device_queue(queue_info); + + VkDevice device; + result = inst->fp_vkCreateDevice(physical_device, dev_create_info.get(), tracker.get(), &device); + if (result == VK_SUCCESS || fail_index > 10000) { + inst->fp_vkDestroyDevice(device, tracker.get()); + break; + } + ASSERT_TRUE(tracker.empty()); + fail_index++; + } +} + +// Test failure during vkCreateInstance and vkCreateDevice to make sure we don't +// leak memory if one of the out-of-memory conditions trigger. +TEST_F(Allocation, CreateInstanceDeviceIntentionalAllocFail) { + auto& driver = env->get_test_icd(); + driver.physical_devices.emplace_back("physical_device_0"); + driver.physical_devices[0].add_queue_family_properties({VK_QUEUE_GRAPHICS_BIT, 1, 0, {1, 1, 1}}); + + size_t fail_index = 0; + VkResult result = VK_ERROR_OUT_OF_HOST_MEMORY; + while (result == VK_ERROR_OUT_OF_HOST_MEMORY && fail_index <= 10000) { + MemoryTracker tracker(MemoryTrackerSettings{true, fail_index}); + fail_index++; // applies to the next loop + + InstanceCreateInfo inst_create_info; + VkInstance instance; + result = env->vulkan_functions.fp_vkCreateInstance(inst_create_info.get(), tracker.get(), &instance); + if (result == VK_ERROR_OUT_OF_HOST_MEMORY) { + ASSERT_TRUE(tracker.empty()); + continue; + } + + uint32_t physical_count = 1; + uint32_t returned_physical_count = 0; + result = env->vulkan_functions.fp_vkEnumeratePhysicalDevices(instance, &returned_physical_count, nullptr); + if (result == VK_ERROR_OUT_OF_HOST_MEMORY) { + env->vulkan_functions.fp_vkDestroyInstance(instance, tracker.get()); + ASSERT_TRUE(tracker.empty()); + continue; + } + ASSERT_EQ(physical_count, returned_physical_count); + + VkPhysicalDevice physical_device; + result = env->vulkan_functions.fp_vkEnumeratePhysicalDevices(instance, &returned_physical_count, &physical_device); + if (result == VK_ERROR_OUT_OF_HOST_MEMORY) { + env->vulkan_functions.fp_vkDestroyInstance(instance, tracker.get()); + ASSERT_TRUE(tracker.empty()); + continue; + } + ASSERT_EQ(physical_count, returned_physical_count); + + uint32_t family_count = 1; + uint32_t returned_family_count = 0; + env->vulkan_functions.fp_vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &returned_family_count, nullptr); + ASSERT_EQ(returned_family_count, family_count); + + VkQueueFamilyProperties family; + env->vulkan_functions.fp_vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &returned_family_count, &family); + ASSERT_EQ(returned_family_count, family_count); + ASSERT_EQ(family.queueFlags, VK_QUEUE_GRAPHICS_BIT); + ASSERT_EQ(family.queueCount, family_count); + ASSERT_EQ(family.timestampValidBits, 0); + + DeviceCreateInfo dev_create_info; + DeviceQueueCreateInfo queue_info; + queue_info.add_priority(0.0f); + dev_create_info.add_device_queue(queue_info); + + VkDevice device; + result = env->vulkan_functions.fp_vkCreateDevice(physical_device, dev_create_info.get(), tracker.get(), &device); + if (result == VK_SUCCESS) { + env->vulkan_functions.fp_vkDestroyDevice(device, tracker.get()); + } + env->vulkan_functions.fp_vkDestroyInstance(instance, tracker.get()); + + ASSERT_TRUE(tracker.empty()); + } +}