/* Copyright (c) 2020-2022 The Khronos Group Inc.
 * Copyright (c) 2020-2022 Valve Corporation
 * Copyright (c) 2020-2022 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.
 */

//   Instanceless tests
// Tests of validation of vkCreateInstance and vkDestroyInstance via the pNext debug callback.
//
// This set of test should ideally be as complete as possible. Most of the VUs are Implicit (i.e. automatically generated), but any
// of the parameters could expose a bug or inadequacy in the Loader or the debug extension.
//
// Note: testing pCreateInfo pointer, the sType of a debug struct, the debug callback pointer, the ppEnabledLayerNames pointer, and
//       the ppEnabledExtensionNames would require extenally enabled debug layers, so this is currently not performed.
//
// TODO: VkDebugReportCallbackCreateInfoEXT::flags and VkDebugUtilsMessengerCreateInfoEXT various Flags could theoretically be
//       tested if the debug extensions are made robust enough

#include <memory>
#include <vector>

#include "cast_utils.h"
#include "layer_validation_tests.h"

static VkInstance dummy_instance;

TEST_F(VkLayerTest, InstanceExtensionDependencies) {
    TEST_DESCRIPTION("Test enabling instance extension without dependencies met.");

    if (!InstanceExtensionSupported(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME)) {
        printf("%s Did not find required instance extension %s.\n", kSkipPrefix, VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME);
        return;
    }
    ASSERT_TRUE(InstanceExtensionSupported(VK_KHR_SURFACE_EXTENSION_NAME));  // Driver should always provide dependencies

    Monitor().SetDesiredFailureMsg(kErrorBit, "VUID-vkCreateInstance-ppEnabledExtensionNames-01388");
    instance_extensions_.push_back(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME);
    const auto ici = GetInstanceCreateInfo();
    vk::CreateInstance(&ici, nullptr, &dummy_instance);
    Monitor().VerifyFound();
}

TEST_F(VkLayerTest, InstanceBadStype) {
    TEST_DESCRIPTION("Test creating instance with bad sType.");

    auto ici = GetInstanceCreateInfo();

    Monitor().SetDesiredFailureMsg(kErrorBit, "VUID-VkInstanceCreateInfo-sType-sType");
    ici.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    vk::CreateInstance(&ici, nullptr, &dummy_instance);
    Monitor().VerifyFound();
}

TEST_F(VkLayerTest, InstanceDuplicatePnextStype) {
    TEST_DESCRIPTION("Test creating instance with duplicate sType in the pNext chain.");

    if (!InstanceExtensionSupported(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME)) {
        printf("%s Did not find required instance extension %s.\n", kSkipPrefix, VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME);
        return;
    }
    instance_extensions_.push_back(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME);

    auto ici = GetInstanceCreateInfo();

    Monitor().SetDesiredFailureMsg(kErrorBit, "VUID-VkInstanceCreateInfo-sType-unique");
    const VkValidationFeaturesEXT duplicate_pnext = {VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT, ici.pNext};
    const VkValidationFeaturesEXT first_pnext = {VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT, &duplicate_pnext};
    ici.pNext = &first_pnext;
    vk::CreateInstance(&ici, nullptr, &dummy_instance);
    Monitor().VerifyFound();
}

TEST_F(VkLayerTest, InstanceAppInfoBadStype) {
    TEST_DESCRIPTION("Test creating instance with invalid sType in VkApplicationInfo.");

    auto ici = GetInstanceCreateInfo();

    VkApplicationInfo bad_app_info = {};
    if (ici.pApplicationInfo) bad_app_info = *ici.pApplicationInfo;
    bad_app_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    ici.pApplicationInfo = &bad_app_info;

    Monitor().SetDesiredFailureMsg(kErrorBit, "VUID-VkApplicationInfo-sType-sType");
    vk::CreateInstance(&ici, nullptr, &dummy_instance);
    Monitor().VerifyFound();
}

TEST_F(VkLayerTest, InstanceValidationFeaturesBadFlags) {
    TEST_DESCRIPTION("Test creating instance with invalid flags in VkValidationFeaturesEXT.");

    if (!InstanceExtensionSupported(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME)) {
        printf("%s Did not find required instance extension %s.\n", kSkipPrefix, VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME);
        return;
    }
    instance_extensions_.push_back(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME);

    auto ici = GetInstanceCreateInfo();

    // the test framework should not be using VkValidationFeatureEnableEXT itself
    for (auto traversable_pnext = reinterpret_cast<const VkBaseInStructure*>(ici.pNext); traversable_pnext;
         traversable_pnext = traversable_pnext->pNext) {
        ASSERT_NE(traversable_pnext->sType, VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT);
    }

    VkValidationFeaturesEXT validation_features_template = {};
    validation_features_template.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT;
    validation_features_template.pNext = ici.pNext;

    {
        const VkValidationFeatureEnableEXT bad_enable = (VkValidationFeatureEnableEXT)0x42;
        VkValidationFeaturesEXT validation_features = validation_features_template;
        validation_features.enabledValidationFeatureCount = 1;
        validation_features.pEnabledValidationFeatures = &bad_enable;
        ici.pNext = &validation_features;

        // VUID-VkValidationFeaturesEXT-pEnabledValidationFeatures-parameter
        Monitor().SetDesiredFailureMsg(kErrorBit, "UNASSIGNED-GeneralParameterError-UnrecognizedValue");
        vk::CreateInstance(&ici, nullptr, &dummy_instance);
        Monitor().VerifyFound();
    }

    {
        const VkValidationFeatureDisableEXT bad_disable = (VkValidationFeatureDisableEXT)0x42;
        VkValidationFeaturesEXT validation_features = validation_features_template;
        validation_features.disabledValidationFeatureCount = 1;
        validation_features.pDisabledValidationFeatures = &bad_disable;
        ici.pNext = &validation_features;

        // VUID-VkValidationFeaturesEXT-pEnabledValidationFeatures-parameter
        Monitor().SetDesiredFailureMsg(kErrorBit, "UNASSIGNED-GeneralParameterError-UnrecognizedValue");
        vk::CreateInstance(&ici, nullptr, &dummy_instance);
        Monitor().VerifyFound();
    }
}

TEST_F(VkLayerTest, InstanceBadValidationFlags) {
    TEST_DESCRIPTION("Test creating instance with invalid VkValidationFlagsEXT.");

    if (!InstanceExtensionSupported(VK_EXT_VALIDATION_FLAGS_EXTENSION_NAME)) {
        printf("%s Did not find required instance extension %s.\n", kSkipPrefix, VK_EXT_VALIDATION_FLAGS_EXTENSION_NAME);
        return;
    }
    instance_extensions_.push_back(VK_EXT_VALIDATION_FLAGS_EXTENSION_NAME);

    auto ici = GetInstanceCreateInfo();

    // the test framework should not be using VkValidationFlagsEXT itself
    for (auto traversable_pnext = reinterpret_cast<const VkBaseInStructure*>(ici.pNext); traversable_pnext;
         traversable_pnext = traversable_pnext->pNext) {
        ASSERT_NE(traversable_pnext->sType, VK_STRUCTURE_TYPE_VALIDATION_FLAGS_EXT);
    }

    VkValidationFlagsEXT validation_flags_template = {};
    validation_flags_template.sType = VK_STRUCTURE_TYPE_VALIDATION_FLAGS_EXT;
    validation_flags_template.pNext = ici.pNext;

    {
        const VkValidationCheckEXT bad_disable = (VkValidationCheckEXT)0x42;
        VkValidationFlagsEXT validation_flags = validation_flags_template;
        validation_flags.disabledValidationCheckCount = 1;
        validation_flags.pDisabledValidationChecks = &bad_disable;
        ici.pNext = &validation_flags;

        // VUID-VkValidationFlagsEXT-pDisabledValidationChecks-parameter
        Monitor().SetDesiredFailureMsg(kErrorBit, "UNASSIGNED-GeneralParameterError-UnrecognizedValue");
        vk::CreateInstance(&ici, nullptr, &dummy_instance);
        Monitor().VerifyFound();
    }

    {
        VkValidationFlagsEXT validation_flags = validation_flags_template;
        validation_flags.disabledValidationCheckCount = 0;
        ici.pNext = &validation_flags;

        // VUID-VkValidationFlagsEXT-disabledValidationCheckCount-arraylength
        Monitor().SetDesiredFailureMsg(kErrorBit, "parameter disabledValidationCheckCount must be greater than 0");
        vk::CreateInstance(&ici, nullptr, &dummy_instance);
        Monitor().VerifyFound();
    }
}

void* VKAPI_PTR DummyAlloc(void*, size_t size, size_t alignment, VkSystemAllocationScope) {
    size_t space = size + alignment - 1;
    void* mem_ptr = std::malloc(space);
    return std::align(alignment, size, mem_ptr, space);
}
void VKAPI_PTR DummyFree(void*, void* pMemory) {
    // just leak it
}
void* VKAPI_PTR DummyRealloc(void* pUserData, void* pOriginal, size_t size, size_t alignment,
                             VkSystemAllocationScope allocationScope) {
    DummyFree(pUserData, pOriginal);
    return DummyAlloc(pUserData, size, alignment, allocationScope);
}
void VKAPI_PTR DummyInfoAlloc(void*, size_t, VkInternalAllocationType, VkSystemAllocationScope) {}
void VKAPI_PTR DummyInfoFree(void*, size_t, VkInternalAllocationType, VkSystemAllocationScope) {}

TEST_F(VkLayerTest, DestroyInstanceAllocationCallbacksCompatibility) {
    TEST_DESCRIPTION("Test vkDestroyInstance with incompatible allocation callbacks.");

    const auto ici = GetInstanceCreateInfo();
    const VkAllocationCallbacks alloc_callbacks = {nullptr,    &DummyAlloc,     &DummyRealloc,
                                                   &DummyFree, &DummyInfoAlloc, &DummyInfoFree};

    {
        VkInstance instance;
        ASSERT_VK_SUCCESS(vk::CreateInstance(&ici, nullptr, &instance));

        Monitor().SetDesiredFailureMsg(kErrorBit, "VUID-vkDestroyInstance-instance-00631");
        vk::DestroyInstance(instance, &alloc_callbacks);
        Monitor().VerifyFound();
    }
}

TEST_F(VkLayerTest, DestroyInstanceHandleLeak) {
    TEST_DESCRIPTION("Test vkDestroyInstance while leaking a VkDevice object.");
    ASSERT_NO_FATAL_FAILURE(InitFramework());
    if (!IsPlatform(kMockICD)) {
        // This test leaks a device (on purpose) and should not be run on a real driver
        GTEST_SKIP() << "This test only runs on the mock ICD";
    }
    const auto ici = GetInstanceCreateInfo();

    VkInstance instance;
    ASSERT_VK_SUCCESS(vk::CreateInstance(&ici, nullptr, &instance));
    uint32_t physical_device_count = 1;
    VkPhysicalDevice physical_device;
    const VkResult err = vk::EnumeratePhysicalDevices(instance, &physical_device_count, &physical_device);
    ASSERT_TRUE(err == VK_SUCCESS || err == VK_INCOMPLETE) << vk_result_string(err);
    ASSERT_EQ(physical_device_count, 1);

    float dqci_priorities[] = {1.0};
    VkDeviceQueueCreateInfo dqci = LvlInitStruct<VkDeviceQueueCreateInfo>();
    dqci.queueFamilyIndex = 0;
    dqci.queueCount = 1;
    dqci.pQueuePriorities = dqci_priorities;

    VkDeviceCreateInfo dci = LvlInitStruct<VkDeviceCreateInfo>();
    dci.queueCreateInfoCount = 1;
    dci.pQueueCreateInfos = &dqci;

    VkDevice leaked_device;
    ASSERT_VK_SUCCESS(vk::CreateDevice(physical_device, &dci, nullptr, &leaked_device));

    // VUID-vkDestroyInstance-instance-00629
    Monitor().SetDesiredFailureMsg(kErrorBit, "UNASSIGNED-ObjectTracker-ObjectLeak");
    vk::DestroyInstance(instance, nullptr);
    Monitor().VerifyFound();
}
