// Copyright 2021 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "dawn/native/DawnNative.h"
#include "src/dawn/native/ChainUtils.h"
#include "src/dawn/native/dawn_platform.h"

namespace dawn::native {
namespace {

using ::testing::HasSubstr;

// Empty chain on roots that have and don't have valid extensions should not fail validation and all
// values should be nullptr.
TEST(ChainUtilsTests, ValidateAndUnpackEmpty) {
    {
        // TextureViewDescriptor has at least 1 valid chain extension..
        TextureViewDescriptor desc;
        auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
        EXPECT_TRUE(unpacked.Empty());
    }
    {
        // InstanceDescriptor has at least 1 valid chain extension.
        InstanceDescriptor desc;
        auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
        EXPECT_TRUE(unpacked.Empty());
    }
    {
        // SharedTextureMemoryProperties has at least 1 valid chain extension.
        SharedTextureMemoryProperties properties;
        auto unpacked = ValidateAndUnpack(&properties).AcquireSuccess();
        EXPECT_TRUE(unpacked.Empty());
    }
    {
        // SharedFenceExportInfo has at least 1 valid chain extension.
        SharedFenceExportInfo properties;
        auto unpacked = ValidateAndUnpack(&properties).AcquireSuccess();
        EXPECT_TRUE(unpacked.Empty());
    }
}

// Invalid chain extensions cause an error.
TEST(ChainUtilsTests, ValidateAndUnpackUnexpected) {
    {
        // TextureViewDescriptor (as of when this test was written) does not have any valid chains
        // in the JSON nor via additional extensions.
        TextureViewDescriptor desc;
        ChainedStruct chain;
        desc.nextInChain = &chain;
        EXPECT_THAT(ValidateAndUnpack(&desc).AcquireError()->GetFormattedMessage(),
                    HasSubstr("Unexpected"));
    }
    {
        // InstanceDescriptor has at least 1 valid chain extension.
        InstanceDescriptor desc;
        ChainedStruct chain;
        desc.nextInChain = &chain;
        EXPECT_THAT(ValidateAndUnpack(&desc).AcquireError()->GetFormattedMessage(),
                    HasSubstr("Unexpected"));
    }
}

// Inject invalid chain extensions cause an error.
TEST(ChainUtilsTests, ValidateAndUnpackInjected) {
    {
        // TextureViewDescriptor (as of when this test was written) does not have any valid chains
        // in the JSON nor via additional extensions.
        TextureViewDescriptor desc;
        DawnInjectedInvalidSType chain;
        chain.invalidSType = wgpu::SType::ShaderSourceWGSL;
        desc.nextInChain = &chain;
        EXPECT_THAT(ValidateAndUnpack(&desc).AcquireError()->GetFormattedMessage(),
                    HasSubstr("ShaderSourceWGSL"));
    }
    {
        // InstanceDescriptor has at least 1 valid chain extension.
        InstanceDescriptor desc;
        DawnInjectedInvalidSType chain;
        chain.invalidSType = wgpu::SType::ShaderSourceWGSL;
        desc.nextInChain = &chain;
        EXPECT_THAT(ValidateAndUnpack(&desc).AcquireError()->GetFormattedMessage(),
                    HasSubstr("ShaderSourceWGSL"));
    }
}

// Nominal unpacking valid descriptors should return the expected descriptors in the unpacked type.
TEST(ChainUtilsTests, ValidateAndUnpack) {
    // DawnTogglesDescriptor is a valid extension for InstanceDescriptor.
    InstanceDescriptor desc;
    DawnTogglesDescriptor chain;
    desc.nextInChain = &chain;
    auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
    auto ext = unpacked.Get<DawnTogglesDescriptor>();
    EXPECT_EQ(ext, &chain);

    // For ChainedStructs, the resulting pointer from Get should be a const type.
    static_assert(std::is_const_v<std::remove_reference_t<decltype(*ext)>>);
}

// Nominal unpacking valid descriptors should return the expected descriptors in the unpacked type.
TEST(ChainUtilsTests, ValidateAndUnpackOut) {
    // DawnAdapterPropertiesPowerPreference is a valid extension for AdapterInfo.
    AdapterInfo info;
    DawnAdapterPropertiesPowerPreference chain;
    info.nextInChain = &chain;
    auto unpacked = ValidateAndUnpack(&info).AcquireSuccess();
    auto ext = unpacked.Get<DawnAdapterPropertiesPowerPreference>();
    EXPECT_EQ(ext, &chain);

    // For ChainedStructOuts, the resulting pointer from Get should not be a const type.
    static_assert(!std::is_const_v<std::remove_reference_t<decltype(*ext)>>);
}

// Duplicate valid extensions cause an error.
TEST(ChainUtilsTests, ValidateAndUnpackDuplicate) {
    // DawnTogglesDescriptor is a valid extension for InstanceDescriptor.
    InstanceDescriptor desc;
    DawnTogglesDescriptor chain1;
    DawnTogglesDescriptor chain2;
    desc.nextInChain = &chain1;
    chain1.nextInChain = &chain2;
    EXPECT_THAT(ValidateAndUnpack(&desc).AcquireError()->GetFormattedMessage(),
                HasSubstr("Duplicate"));
}

// Duplicate valid extensions cause an error.
TEST(ChainUtilsTests, ValidateAndUnpackOutDuplicate) {
    // DawnAdapterPropertiesPowerPreference is a valid extension for AdapterInfo.
    AdapterInfo info;
    DawnAdapterPropertiesPowerPreference chain1;
    DawnAdapterPropertiesPowerPreference chain2;
    info.nextInChain = &chain1;
    chain1.nextInChain = &chain2;
    EXPECT_THAT(ValidateAndUnpack(&info).AcquireError()->GetFormattedMessage(),
                HasSubstr("Duplicate"));
}

// Additional extensions added via template specialization and not specified in the JSON unpack
// properly.
TEST(ChainUtilsTests, ValidateAndUnpackAdditionalExtensions) {
    // DawnInstanceDescriptor is an extension on InstanceDescriptor added in ChainUtilsImpl.inl.
    InstanceDescriptor desc;
    DawnInstanceDescriptor chain;
    desc.nextInChain = &chain;
    auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
    EXPECT_EQ(unpacked.Get<DawnInstanceDescriptor>(), &chain);
}

// Duplicate additional extensions added via template specialization should cause an error.
TEST(ChainUtilsTests, ValidateAndUnpackDuplicateAdditionalExtensions) {
    // DawnInstanceDescriptor is an extension on InstanceDescriptor added in ChainUtilsImpl.inl.
    InstanceDescriptor desc;
    DawnInstanceDescriptor chain1;
    DawnInstanceDescriptor chain2;
    desc.nextInChain = &chain1;
    chain1.nextInChain = &chain2;
    EXPECT_THAT(ValidateAndUnpack(&desc).AcquireError()->GetFormattedMessage(),
                HasSubstr("Duplicate"));
}

using B1 = Branch<ShaderSourceWGSL>;
using B2 = Branch<ShaderSourceSPIRV>;
using B2Ext = Branch<ShaderSourceSPIRV, DawnShaderModuleSPIRVOptionsDescriptor>;

// Validates exacly 1 branch and ensures that there are no other extensions.
TEST(ChainUtilsTests, ValidateBranchesOneValidBranch) {
    ShaderModuleDescriptor desc;
    // Either allowed branches should validate successfully and return the expected enum.
    {
        ShaderSourceWGSL chain;
        desc.nextInChain = &chain;
        auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
        EXPECT_EQ((unpacked.ValidateBranches<B1, B2>().AcquireSuccess()),
                  wgpu::SType::ShaderSourceWGSL);
    }
    {
        ShaderSourceSPIRV chain;
        desc.nextInChain = &chain;
        auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
        EXPECT_EQ((unpacked.ValidateBranches<B1, B2>().AcquireSuccess()),
                  wgpu::SType::ShaderSourceSPIRV);

        // Extensions are optional so validation should still pass when the extension is not
        // provided.
        EXPECT_EQ((unpacked.ValidateBranches<B1, B2Ext>().AcquireSuccess()),
                  wgpu::SType::ShaderSourceSPIRV);
    }
}

// An allowed chain that is not one of the branches causes an error.
TEST(ChainUtilsTests, ValidateBranchesInvalidBranch) {
    ShaderModuleDescriptor desc;
    DawnShaderModuleSPIRVOptionsDescriptor chain;
    desc.nextInChain = &chain;
    auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
    EXPECT_NE((unpacked.ValidateBranches<B1, B2>().AcquireError()), nullptr);
    EXPECT_NE((unpacked.ValidateBranches<B1, B2Ext>().AcquireError()), nullptr);
}

// Additional chains should cause an error when branches don't allow extensions.
TEST(ChainUtilsTests, ValidateBranchesInvalidExtension) {
    ShaderModuleDescriptor desc;
    {
        ShaderSourceWGSL chain1;
        DawnShaderModuleSPIRVOptionsDescriptor chain2;
        desc.nextInChain = &chain1;
        chain1.nextInChain = &chain2;
        auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
        EXPECT_NE((unpacked.ValidateBranches<B1, B2>().AcquireError()), nullptr);
        EXPECT_NE((unpacked.ValidateBranches<B1, B2Ext>().AcquireError()), nullptr);
    }
    {
        ShaderSourceSPIRV chain1;
        DawnShaderModuleSPIRVOptionsDescriptor chain2;
        desc.nextInChain = &chain1;
        chain1.nextInChain = &chain2;
        auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
        EXPECT_NE((unpacked.ValidateBranches<B1, B2>().AcquireError()), nullptr);
    }
}

// Branches that allow extensions pass successfully.
TEST(ChainUtilsTests, ValidateBranchesAllowedExtensions) {
    ShaderModuleDescriptor desc;
    ShaderSourceSPIRV chain1;
    DawnShaderModuleSPIRVOptionsDescriptor chain2;
    desc.nextInChain = &chain1;
    chain1.nextInChain = &chain2;
    auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
    EXPECT_EQ((unpacked.ValidateBranches<B1, B2Ext>().AcquireSuccess()),
              wgpu::SType::ShaderSourceSPIRV);
}

// Unrealistic branching for ChainedStructOut testing. Note that this setup does not make sense.
using BOut1 = Branch<SharedFenceVkSemaphoreOpaqueFDExportInfo>;
using BOut2 = Branch<SharedFenceSyncFDExportInfo>;
using BOut2Ext = Branch<SharedFenceSyncFDExportInfo, SharedFenceVkSemaphoreZirconHandleExportInfo>;

// Validates exacly 1 branch and ensures that there are no other extensions.
TEST(ChainUtilsTests, ValidateBranchesOneValidBranchOut) {
    SharedFenceExportInfo info;
    // Either allowed branches should validate successfully and return the expected enum.
    {
        SharedFenceVkSemaphoreOpaqueFDExportInfo chain;
        info.nextInChain = &chain;
        auto unpacked = ValidateAndUnpack(&info).AcquireSuccess();
        EXPECT_EQ((unpacked.ValidateBranches<BOut1, BOut2>().AcquireSuccess()),
                  wgpu::SType::SharedFenceVkSemaphoreOpaqueFDExportInfo);
    }
    {
        SharedFenceSyncFDExportInfo chain;
        info.nextInChain = &chain;
        auto unpacked = ValidateAndUnpack(&info).AcquireSuccess();
        EXPECT_EQ((unpacked.ValidateBranches<BOut1, BOut2>().AcquireSuccess()),
                  wgpu::SType::SharedFenceSyncFDExportInfo);

        // Extensions are optional so validation should still pass when the extension is not
        // provided.
        EXPECT_EQ((unpacked.ValidateBranches<BOut1, BOut2Ext>().AcquireSuccess()),
                  wgpu::SType::SharedFenceSyncFDExportInfo);
    }
}

// An allowed chain that is not one of the branches causes an error.
TEST(ChainUtilsTests, ValidateBranchesInvalidBranchOut) {
    SharedFenceExportInfo info;
    SharedFenceDXGISharedHandleExportInfo chain;
    info.nextInChain = &chain;
    auto unpacked = ValidateAndUnpack(&info).AcquireSuccess();
    EXPECT_NE((unpacked.ValidateBranches<BOut1, BOut2>().AcquireError()), nullptr);
    EXPECT_NE((unpacked.ValidateBranches<BOut1, BOut2Ext>().AcquireError()), nullptr);
}

// Additional chains should cause an error when branches don't allow extensions.
TEST(ChainUtilsTests, ValidateBranchesInvalidExtensionOut) {
    SharedFenceExportInfo info;
    {
        SharedFenceVkSemaphoreOpaqueFDExportInfo chain1;
        SharedFenceVkSemaphoreZirconHandleExportInfo chain2;
        info.nextInChain = &chain1;
        chain1.nextInChain = &chain2;
        auto unpacked = ValidateAndUnpack(&info).AcquireSuccess();
        EXPECT_NE((unpacked.ValidateBranches<BOut1, BOut2>().AcquireError()), nullptr);
        EXPECT_NE((unpacked.ValidateBranches<BOut1, BOut2Ext>().AcquireError()), nullptr);
    }
    {
        SharedFenceSyncFDExportInfo chain1;
        SharedFenceVkSemaphoreZirconHandleExportInfo chain2;
        info.nextInChain = &chain1;
        chain1.nextInChain = &chain2;
        auto unpacked = ValidateAndUnpack(&info).AcquireSuccess();
        EXPECT_NE((unpacked.ValidateBranches<BOut1, BOut2>().AcquireError()), nullptr);
    }
}

// Branches that allow extensions pass successfully.
TEST(ChainUtilsTests, ValidateBranchesAllowedExtensionsOut) {
    SharedFenceExportInfo info;
    SharedFenceSyncFDExportInfo chain1;
    SharedFenceVkSemaphoreZirconHandleExportInfo chain2;
    info.nextInChain = &chain1;
    chain1.nextInChain = &chain2;
    auto unpacked = ValidateAndUnpack(&info).AcquireSuccess();
    EXPECT_EQ((unpacked.ValidateBranches<BOut1, BOut2Ext>().AcquireSuccess()),
              wgpu::SType::SharedFenceSyncFDExportInfo);
}

// Valid subsets should pass successfully, while invalid ones should error.
TEST(ChainUtilsTests, ValidateSubset) {
    DeviceDescriptor desc;
    DawnTogglesDescriptor chain1;
    DawnCacheDeviceDescriptor chain2;

    // With none set, subset for anything should work.
    {
        auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
        EXPECT_TRUE(unpacked.ValidateSubset<>().IsSuccess());
        EXPECT_TRUE(unpacked.ValidateSubset<DawnTogglesDescriptor>().IsSuccess());
        EXPECT_TRUE(unpacked.ValidateSubset<DawnCacheDeviceDescriptor>().IsSuccess());
        EXPECT_TRUE((unpacked.ValidateSubset<DawnTogglesDescriptor, DawnCacheDeviceDescriptor>()
                         .IsSuccess()));
    }
    // With one set, subset with that allow that one should pass. Otherwise it should fail.
    {
        desc.nextInChain = &chain1;
        auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
        EXPECT_NE(unpacked.ValidateSubset<>().AcquireError(), nullptr);
        EXPECT_TRUE(unpacked.ValidateSubset<DawnTogglesDescriptor>().IsSuccess());
        EXPECT_NE(unpacked.ValidateSubset<DawnCacheDeviceDescriptor>().AcquireError(), nullptr);
        EXPECT_TRUE((unpacked.ValidateSubset<DawnTogglesDescriptor, DawnCacheDeviceDescriptor>()
                         .IsSuccess()));
    }
    // With both set, single subsets should all fail.
    {
        chain1.nextInChain = &chain2;
        auto unpacked = ValidateAndUnpack(&desc).AcquireSuccess();
        EXPECT_NE(unpacked.ValidateSubset<>().AcquireError(), nullptr);
        EXPECT_NE(unpacked.ValidateSubset<DawnTogglesDescriptor>().AcquireError(), nullptr);
        EXPECT_NE(unpacked.ValidateSubset<DawnCacheDeviceDescriptor>().AcquireError(), nullptr);
        EXPECT_TRUE((unpacked.ValidateSubset<DawnTogglesDescriptor, DawnCacheDeviceDescriptor>()
                         .IsSuccess()));
    }
}

// Valid subsets should pass successfully, while invalid ones should error.
TEST(ChainUtilsTests, ValidateSubsetOut) {
    SharedFenceExportInfo info;
    SharedFenceVkSemaphoreOpaqueFDExportInfo chain1;
    SharedFenceVkSemaphoreZirconHandleExportInfo chain2;

    // With none set, subset for anything should work.
    {
        auto unpacked = ValidateAndUnpack(&info).AcquireSuccess();
        EXPECT_TRUE(unpacked.ValidateSubset<>().IsSuccess());
        EXPECT_TRUE(
            unpacked.ValidateSubset<SharedFenceVkSemaphoreOpaqueFDExportInfo>().IsSuccess());
        EXPECT_TRUE(
            unpacked.ValidateSubset<SharedFenceVkSemaphoreZirconHandleExportInfo>().IsSuccess());
        EXPECT_TRUE((unpacked
                         .ValidateSubset<SharedFenceVkSemaphoreOpaqueFDExportInfo,
                                         SharedFenceVkSemaphoreZirconHandleExportInfo>()
                         .IsSuccess()));
    }
    // With one set, subset with that allow that one should pass. Otherwise it should fail.
    {
        info.nextInChain = &chain1;
        auto unpacked = ValidateAndUnpack(&info).AcquireSuccess();
        EXPECT_NE(unpacked.ValidateSubset<>().AcquireError(), nullptr);
        EXPECT_TRUE(
            unpacked.ValidateSubset<SharedFenceVkSemaphoreOpaqueFDExportInfo>().IsSuccess());
        EXPECT_NE(
            unpacked.ValidateSubset<SharedFenceVkSemaphoreZirconHandleExportInfo>().AcquireError(),
            nullptr);
        EXPECT_TRUE((unpacked
                         .ValidateSubset<SharedFenceVkSemaphoreOpaqueFDExportInfo,
                                         SharedFenceVkSemaphoreZirconHandleExportInfo>()
                         .IsSuccess()));
    }
    // With both set, single subsets should all fail.
    {
        chain1.nextInChain = &chain2;
        auto unpacked = ValidateAndUnpack(&info).AcquireSuccess();
        EXPECT_NE(unpacked.ValidateSubset<>().AcquireError(), nullptr);
        EXPECT_NE(
            unpacked.ValidateSubset<SharedFenceVkSemaphoreOpaqueFDExportInfo>().AcquireError(),
            nullptr);
        EXPECT_NE(
            unpacked.ValidateSubset<SharedFenceVkSemaphoreZirconHandleExportInfo>().AcquireError(),
            nullptr);
        EXPECT_TRUE((unpacked
                         .ValidateSubset<SharedFenceVkSemaphoreOpaqueFDExportInfo,
                                         SharedFenceVkSemaphoreZirconHandleExportInfo>()
                         .IsSuccess()));
    }
}

}  // anonymous namespace
}  // namespace dawn::native
