#include "../common/output_stream.h"
#include "spirv_reflect.h"

#include "gtest/gtest.h"

#include <cstdint>
#include <fstream>
#include <iostream>
#include <regex>
#include <sstream>
#include <string>

#if defined(_MSC_VER)
#include <direct.h>
#define posix_chdir(d) _chdir(d)
#else
#include <unistd.h>
#define posix_chdir(d) chdir(d)
#endif

#if defined(SPIRV_REFLECT_HAS_VULKAN_H)
// clang-format off
// Verify that SpvReflect enums match their Vk equivalents where appropriate
#include <vulkan/vulkan.h>
// SpvReflectFormat == VkFormat
static_assert((uint32_t)SPV_REFLECT_FORMAT_UNDEFINED           == (uint32_t)VK_FORMAT_UNDEFINED, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_FORMAT_R32_UINT            == (uint32_t)VK_FORMAT_R32_UINT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_FORMAT_R32_SINT            == (uint32_t)VK_FORMAT_R32_SINT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_FORMAT_R32_SFLOAT          == (uint32_t)VK_FORMAT_R32_SFLOAT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_FORMAT_R32G32_UINT         == (uint32_t)VK_FORMAT_R32G32_UINT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_FORMAT_R32G32_SINT         == (uint32_t)VK_FORMAT_R32G32_SINT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_FORMAT_R32G32_SFLOAT       == (uint32_t)VK_FORMAT_R32G32_SFLOAT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_FORMAT_R32G32B32_UINT      == (uint32_t)VK_FORMAT_R32G32B32_UINT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_FORMAT_R32G32B32_SINT      == (uint32_t)VK_FORMAT_R32G32B32_SINT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_FORMAT_R32G32B32_SFLOAT    == (uint32_t)VK_FORMAT_R32G32B32_SFLOAT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_FORMAT_R32G32B32A32_UINT   == (uint32_t)VK_FORMAT_R32G32B32A32_UINT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_FORMAT_R32G32B32A32_SINT   == (uint32_t)VK_FORMAT_R32G32B32A32_SINT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_FORMAT_R32G32B32A32_SFLOAT == (uint32_t)VK_FORMAT_R32G32B32A32_SFLOAT, "SpvReflect/Vk enum mismatch");
// SpvReflectDescriptorType == VkDescriptorType
static_assert((uint32_t)SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLER                == (uint32_t)VK_DESCRIPTOR_TYPE_SAMPLER, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER == (uint32_t)VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_DESCRIPTOR_TYPE_SAMPLED_IMAGE          == (uint32_t)VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_IMAGE          == (uint32_t)VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER   == (uint32_t)VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER   == (uint32_t)VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER         == (uint32_t)VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER         == (uint32_t)VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC == (uint32_t)VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC == (uint32_t)VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_DESCRIPTOR_TYPE_INPUT_ATTACHMENT       == (uint32_t)VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, "SpvReflect/Vk enum mismatch");
// SpvReflectShaderStageFlagBits == VkShaderStageFlagBits
static_assert((uint32_t)SPV_REFLECT_SHADER_STAGE_VERTEX_BIT                  == (uint32_t)VK_SHADER_STAGE_VERTEX_BIT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_SHADER_STAGE_TESSELLATION_CONTROL_BIT    == (uint32_t)VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_SHADER_STAGE_TESSELLATION_EVALUATION_BIT == (uint32_t)VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_SHADER_STAGE_GEOMETRY_BIT                == (uint32_t)VK_SHADER_STAGE_GEOMETRY_BIT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_SHADER_STAGE_FRAGMENT_BIT                == (uint32_t)VK_SHADER_STAGE_FRAGMENT_BIT, "SpvReflect/Vk enum mismatch");
static_assert((uint32_t)SPV_REFLECT_SHADER_STAGE_COMPUTE_BIT                 == (uint32_t)VK_SHADER_STAGE_COMPUTE_BIT, "SpvReflect/Vk enum mismatch");
#endif  // defined(SPIRV_REFLECT_HAS_VULKAN_H)
// clang-format on

TEST(SpirvReflectTestCase, SourceLanguage) {
  EXPECT_STREQ(spvReflectSourceLanguage(SpvSourceLanguageESSL), "ESSL");
  EXPECT_STREQ(spvReflectSourceLanguage(SpvSourceLanguageGLSL), "GLSL");
  EXPECT_STREQ(spvReflectSourceLanguage(SpvSourceLanguageOpenCL_C), "OpenCL_C");
  EXPECT_STREQ(spvReflectSourceLanguage(SpvSourceLanguageOpenCL_CPP),
               "OpenCL_CPP");
  EXPECT_STREQ(spvReflectSourceLanguage(SpvSourceLanguageHLSL), "HLSL");

  EXPECT_STREQ(spvReflectSourceLanguage(SpvSourceLanguageUnknown), "Unknown");
  // Invalid inputs
  EXPECT_STREQ(spvReflectSourceLanguage(SpvSourceLanguageMax), "");
  EXPECT_STREQ(spvReflectSourceLanguage(
                   static_cast<SpvSourceLanguage>(SpvSourceLanguageMax - 1)),
               "");
}

class SpirvReflectTest : public ::testing::TestWithParam<const char *> {
public:
  // optional: initialize static data to be shared by all tests in this test
  // case. Note that for parameterized tests, the specific parameter value is a
  // non-static member data
  static void SetUpTestCase() {
    FILE *f = fopen("tests/glsl/built_in_format.spv", "r");
    if (!f) {
      posix_chdir("..");
      f = fopen("tests/glsl/built_in_format.spv", "r");
    }
    EXPECT_NE(f, nullptr) << "Couldn't find test shaders!";
    if (f) {
      fclose(f);
    }
  }
  static void TearDownTestCase() {}

protected:
  SpirvReflectTest() {
    // set-up work for each test
  }

  ~SpirvReflectTest() override {
    // clean-up work that doesn't throw exceptions
  }

  // optional: called after constructor & before destructor, respectively.
  // Used if you have initialization steps that can throw exceptions or must
  // otherwise be deferred.
  void SetUp() override {
    // called after constructor before each test
    spirv_path_ = GetParam();
    std::ifstream spirv_file(spirv_path_, std::ios::binary | std::ios::ate);
    std::streampos spirv_file_nbytes = spirv_file.tellg();
    spirv_file.seekg(0);
    spirv_.resize(spirv_file_nbytes);
    spirv_file.read(reinterpret_cast<char *>(spirv_.data()), spirv_.size());

    SpvReflectResult result =
        spvReflectCreateShaderModule(spirv_.size(), spirv_.data(), &module_);
    ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS, result)
        << "spvReflectCreateShaderModule() failed";
  }

  // optional:
  void TearDown() override {
    // called before destructor after all tests
    spvReflectDestroyShaderModule(&module_);
  }

  // members here will be accessible in all tests in this test case
  std::string spirv_path_;
  std::vector<uint8_t> spirv_;
  SpvReflectShaderModule module_;

  // static members will be accessible to all tests in this test case
  static std::string test_shaders_dir;
};

TEST_P(SpirvReflectTest, GetCodeSize) {
  EXPECT_EQ(spvReflectGetCodeSize(&module_), spirv_.size());
}
TEST(SpirvReflectTestCase, GetCodeSize_Errors) {
  // NULL module
  EXPECT_EQ(spvReflectGetCodeSize(nullptr), 0);
}

TEST_P(SpirvReflectTest, GetCode) {
  int code_compare =
      memcmp(spvReflectGetCode(&module_), spirv_.data(), spirv_.size());
  EXPECT_EQ(code_compare, 0);
}
TEST(SpirvReflectTestCase, GetCode_Errors) {
  // NULL module
  EXPECT_EQ(spvReflectGetCode(nullptr), nullptr);
}

TEST_P(SpirvReflectTest, GetDescriptorBinding) {
  uint32_t binding_count = 0;
  SpvReflectResult result;
  result =
      spvReflectEnumerateDescriptorBindings(&module_, &binding_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  std::vector<SpvReflectDescriptorBinding *> bindings(binding_count);
  result = spvReflectEnumerateDescriptorBindings(&module_, &binding_count,
                                                 bindings.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  for (const auto *db : bindings) {
    const SpvReflectDescriptorBinding *also_db =
        spvReflectGetDescriptorBinding(&module_, db->binding, db->set, &result);
    EXPECT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
    EXPECT_EQ(db, also_db);
  }
}
TEST_P(SpirvReflectTest, EnumerateDescriptorBindings_Errors) {
  uint32_t binding_count = 0;
  // NULL module
  EXPECT_EQ(
      spvReflectEnumerateDescriptorBindings(nullptr, &binding_count, nullptr),
      SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // NULL binding count
  EXPECT_EQ(
    spvReflectEnumerateDescriptorBindings(&module_, nullptr, nullptr),
    SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // binding count / module binding count mismatch
  EXPECT_EQ(
    spvReflectEnumerateDescriptorBindings(&module_, &binding_count, nullptr),
    SPV_REFLECT_RESULT_SUCCESS);
  uint32_t bad_binding_count = binding_count+1;
  std::vector<SpvReflectDescriptorBinding*> bindings(bad_binding_count);
  EXPECT_EQ(
    spvReflectEnumerateDescriptorBindings(&module_, &bad_binding_count, bindings.data()),
    SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH);
}
TEST_P(SpirvReflectTest, GetDescriptorBinding_Errors) {
  SpvReflectResult result;
  // NULL module
  EXPECT_EQ(spvReflectGetDescriptorBinding(nullptr, 0, 0, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetDescriptorBinding(nullptr, 0, 0, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // Invalid binding number
  EXPECT_EQ(spvReflectGetDescriptorBinding(&module_, 0xdeadbeef, 0, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetDescriptorBinding(&module_, 0xdeadbeef, 0, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
  // Invalid set number
  EXPECT_EQ(spvReflectGetDescriptorBinding(&module_, 0, 0xdeadbeef, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetDescriptorBinding(&module_, 0, 0xdeadbeef, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
}

TEST_P(SpirvReflectTest, GetDescriptorSet) {
  uint32_t set_count = 0;
  SpvReflectResult result;
  result = spvReflectEnumerateDescriptorSets(&module_, &set_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  std::vector<SpvReflectDescriptorSet *> sets(set_count);
  result = spvReflectEnumerateDescriptorSets(&module_, &set_count, sets.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  for (const auto *ds : sets) {
    const SpvReflectDescriptorSet *also_ds =
        spvReflectGetDescriptorSet(&module_, ds->set, &result);
    EXPECT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
    EXPECT_EQ(ds, also_ds);
  }
}
TEST_P(SpirvReflectTest, EnumerateDescriptorSets_Errors) {
  uint32_t set_count = 0;
  // NULL module
  EXPECT_EQ(
    spvReflectEnumerateDescriptorSets(nullptr, &set_count, nullptr),
    SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // NULL set count
  EXPECT_EQ(
    spvReflectEnumerateDescriptorSets(&module_, nullptr, nullptr),
    SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // set count / module set count mismatch
  EXPECT_EQ(
    spvReflectEnumerateDescriptorSets(&module_, &set_count, nullptr),
    SPV_REFLECT_RESULT_SUCCESS);
  uint32_t bad_set_count = set_count+1;
  std::vector<SpvReflectDescriptorSet*> sets(bad_set_count);
  EXPECT_EQ(
    spvReflectEnumerateDescriptorSets(&module_, &bad_set_count, sets.data()),
    SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH);
}
TEST_P(SpirvReflectTest, GetDescriptorSet_Errors) {
  SpvReflectResult result;
  // NULL module
  EXPECT_EQ(spvReflectGetDescriptorSet(nullptr, 0, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetDescriptorSet(nullptr, 0, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // Invalid set number
  EXPECT_EQ(spvReflectGetDescriptorSet(&module_, 0xdeadbeef, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetDescriptorSet(&module_, 0xdeadbeef, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
}

TEST_P(SpirvReflectTest, GetInputVariableByLocation) {
  uint32_t iv_count = 0;
  SpvReflectResult result;
  result = spvReflectEnumerateInputVariables(&module_, &iv_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  std::vector<SpvReflectInterfaceVariable *> ivars(iv_count);
  result = spvReflectEnumerateInputVariables(&module_, &iv_count, ivars.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  for (const auto *iv : ivars) {
    const SpvReflectInterfaceVariable *also_iv =
        spvReflectGetInputVariableByLocation(&module_, iv->location, &result);
    if (iv->location == UINT32_MAX) {
      // Not all elements have valid locations.
      EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
      EXPECT_EQ(also_iv, nullptr);
    } else {
      EXPECT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
      EXPECT_EQ(iv, also_iv);
    }
  }
}
TEST_P(SpirvReflectTest, EnumerateInputVariables_Errors) {
  uint32_t var_count = 0;
  // NULL module
  EXPECT_EQ(
    spvReflectEnumerateInputVariables(nullptr, &var_count, nullptr),
    SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // NULL var count
  EXPECT_EQ(
    spvReflectEnumerateInputVariables(&module_, nullptr, nullptr),
    SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // var count / module var count mismatch
  EXPECT_EQ(
    spvReflectEnumerateInputVariables(&module_, &var_count, nullptr),
    SPV_REFLECT_RESULT_SUCCESS);
  uint32_t bad_var_count = var_count+1;
  std::vector<SpvReflectInterfaceVariable*> vars(bad_var_count);
  EXPECT_EQ(
    spvReflectEnumerateInputVariables(&module_, &bad_var_count, vars.data()),
    SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH);
}
TEST_P(SpirvReflectTest, GetInputVariableByLocation_Errors) {
  SpvReflectResult result;
    // NULL module
  EXPECT_EQ(spvReflectGetInputVariableByLocation(nullptr, 0, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetInputVariableByLocation(nullptr, 0, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // explicitly invalid location (UINT32_MAX is *always* not found)
  EXPECT_EQ(spvReflectGetInputVariableByLocation(&module_, UINT32_MAX, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetInputVariableByLocation(&module_, UINT32_MAX, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
  // implicitly invalid location (0xdeadbeef is potentially valid)
  EXPECT_EQ(spvReflectGetInputVariableByLocation(&module_, 0xdeadbeef, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetInputVariableByLocation(&module_, 0xdeadbeef, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
}

TEST_P(SpirvReflectTest, GetInputVariableBySemantic) {
  uint32_t iv_count = 0;
  SpvReflectResult result;
  result = spvReflectEnumerateInputVariables(&module_, &iv_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  std::vector<SpvReflectInterfaceVariable *> ivars(iv_count);
  result = spvReflectEnumerateInputVariables(&module_, &iv_count, ivars.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  for (const auto *iv : ivars) {
    const SpvReflectInterfaceVariable *also_iv =
        spvReflectGetInputVariableBySemantic(&module_, iv->semantic, &result);
    if (iv->semantic == nullptr) {
      // Not all elements have valid semantics
      EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
      EXPECT_EQ(also_iv, nullptr);
    } else if (iv->semantic[0] == '\0') {
      // Not all elements have valid semantics
      EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
      EXPECT_EQ(also_iv, nullptr);
    } else {
      EXPECT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
      EXPECT_EQ(iv, also_iv);
    }
  }
}
TEST_P(SpirvReflectTest, GetInputVariableBySemantic_Errors) {
  SpvReflectResult result;
  // NULL module
  EXPECT_EQ(spvReflectGetInputVariableBySemantic(nullptr, nullptr, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetInputVariableBySemantic(nullptr, nullptr, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // NULL semantic
  EXPECT_EQ(spvReflectGetInputVariableBySemantic(&module_, nullptr, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetInputVariableBySemantic(&module_, nullptr, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // empty semantic ("" is explicitly not found)
  EXPECT_EQ(spvReflectGetInputVariableBySemantic(&module_, "", nullptr), nullptr);
  EXPECT_EQ(spvReflectGetInputVariableBySemantic(&module_, "", &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
  // implicitly invalid semantic
  EXPECT_EQ(spvReflectGetInputVariableBySemantic(&module_, "SV_PLAUSIBLE_BUT_INVALID", nullptr), nullptr);
  EXPECT_EQ(spvReflectGetInputVariableBySemantic(&module_, "SV_PLAUSIBLE_BUT_INVALID", &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
}

TEST_P(SpirvReflectTest, GetOutputVariableByLocation) {
  uint32_t ov_count = 0;
  SpvReflectResult result;
  result = spvReflectEnumerateOutputVariables(&module_, &ov_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  std::vector<SpvReflectInterfaceVariable *> ovars(ov_count);
  result =
      spvReflectEnumerateOutputVariables(&module_, &ov_count, ovars.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  for (const auto *ov : ovars) {
    const SpvReflectInterfaceVariable *also_ov =
        spvReflectGetOutputVariableByLocation(&module_, ov->location, &result);
    if (ov->location == UINT32_MAX) {
      // Not all elements have valid locations.
      EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
      EXPECT_EQ(also_ov, nullptr);
    } else {
      EXPECT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
      EXPECT_EQ(ov, also_ov);
    }
  }
}
TEST_P(SpirvReflectTest, EnumerateOutputVariables_Errors) {
  uint32_t var_count = 0;
  // NULL module
  EXPECT_EQ(
    spvReflectEnumerateOutputVariables(nullptr, &var_count, nullptr),
    SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // NULL var count
  EXPECT_EQ(
    spvReflectEnumerateOutputVariables(&module_, nullptr, nullptr),
    SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // var count / module var count mismatch
  EXPECT_EQ(
    spvReflectEnumerateOutputVariables(&module_, &var_count, nullptr),
    SPV_REFLECT_RESULT_SUCCESS);
  uint32_t bad_var_count = var_count+1;
  std::vector<SpvReflectInterfaceVariable*> vars(bad_var_count);
  EXPECT_EQ(
    spvReflectEnumerateOutputVariables(&module_, &bad_var_count, vars.data()),
    SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH);
}
TEST_P(SpirvReflectTest, GetOutputVariableByLocation_Errors) {
  SpvReflectResult result;
  // NULL module
  EXPECT_EQ(spvReflectGetOutputVariableByLocation(nullptr, 0, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetOutputVariableByLocation(nullptr, 0, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // explicitly invalid location (UINT32_MAX is *always* not found)
  EXPECT_EQ(spvReflectGetOutputVariableByLocation(&module_, UINT32_MAX, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetOutputVariableByLocation(&module_, UINT32_MAX, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
  // implicitly invalid location (0xdeadbeef is potentially valid)
  EXPECT_EQ(spvReflectGetOutputVariableByLocation(&module_, 0xdeadbeef, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetOutputVariableByLocation(&module_, 0xdeadbeef, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
}

TEST_P(SpirvReflectTest, GetOutputVariableBySemantic) {
  uint32_t ov_count = 0;
  SpvReflectResult result;
  result = spvReflectEnumerateOutputVariables(&module_, &ov_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  std::vector<SpvReflectInterfaceVariable *> ovars(ov_count);
  result =
      spvReflectEnumerateOutputVariables(&module_, &ov_count, ovars.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  for (const auto *ov : ovars) {
    const SpvReflectInterfaceVariable *also_ov =
        spvReflectGetOutputVariableBySemantic(&module_, ov->semantic, &result);
    if (ov->semantic == nullptr) {
      // Not all elements have valid semantics
      EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
      EXPECT_EQ(also_ov, nullptr);
    } else if (ov->semantic[0] == '\0') {
      // Not all elements have valid semantics
      EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
      EXPECT_EQ(also_ov, nullptr);
    } else {
      EXPECT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
      EXPECT_EQ(ov, also_ov);
    }
  }
}
TEST_P(SpirvReflectTest, GetOutputVariableBySemantic_Errors) {
  SpvReflectResult result;
  // NULL module
  EXPECT_EQ(spvReflectGetOutputVariableBySemantic(nullptr, nullptr, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetOutputVariableBySemantic(nullptr, nullptr, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // NULL semantic
  EXPECT_EQ(spvReflectGetOutputVariableBySemantic(&module_, nullptr, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetOutputVariableBySemantic(&module_, nullptr, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // empty semantic ("" is explicitly not found)
  EXPECT_EQ(spvReflectGetOutputVariableBySemantic(&module_, "", nullptr), nullptr);
  EXPECT_EQ(spvReflectGetOutputVariableBySemantic(&module_, "", &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
  // implicitly invalid semantic
  EXPECT_EQ(spvReflectGetOutputVariableBySemantic(&module_, "SV_PLAUSIBLE_BUT_INVALID", nullptr), nullptr);
  EXPECT_EQ(spvReflectGetOutputVariableBySemantic(&module_, "SV_PLAUSIBLE_BUT_INVALID", &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
}

TEST_P(SpirvReflectTest, GetPushConstantBlock) {
  uint32_t block_count = 0;
  SpvReflectResult result;
  result =
      spvReflectEnumeratePushConstantBlocks(&module_, &block_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  std::vector<SpvReflectBlockVariable *> blocks(block_count);
  result = spvReflectEnumeratePushConstantBlocks(&module_, &block_count,
                                                 blocks.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  for (uint32_t i = 0; i < block_count; ++i) {
    const SpvReflectBlockVariable *b = blocks[i];
    const SpvReflectBlockVariable *also_b =
        spvReflectGetPushConstantBlock(&module_, i, &result);
    EXPECT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
    EXPECT_EQ(b, also_b);
  }
}
TEST_P(SpirvReflectTest, EnumeratePushConstantBlocks_Errors) {
  uint32_t block_count = 0;
  // NULL module
  EXPECT_EQ(
    spvReflectEnumeratePushConstantBlocks(nullptr, &block_count, nullptr),
    SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // NULL var count
  EXPECT_EQ(
    spvReflectEnumeratePushConstantBlocks(&module_, nullptr, nullptr),
    SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // var count / module var count mismatch
  EXPECT_EQ(
    spvReflectEnumeratePushConstantBlocks(&module_, &block_count, nullptr),
    SPV_REFLECT_RESULT_SUCCESS);
  uint32_t bad_block_count = block_count+1;
  std::vector<SpvReflectBlockVariable*> blocks(bad_block_count);
  EXPECT_EQ(
    spvReflectEnumeratePushConstantBlocks(&module_, &bad_block_count, blocks.data()),
    SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH);
}
TEST_P(SpirvReflectTest, GetPushConstantBlock_Errors) {
  uint32_t block_count = 0;
  SpvReflectResult result;
  result =
    spvReflectEnumeratePushConstantBlocks(&module_, &block_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  // NULL module
  EXPECT_EQ(spvReflectGetPushConstantBlock(nullptr, 0, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetPushConstantBlock(nullptr, 0, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // invalid block index
  EXPECT_EQ(spvReflectGetPushConstantBlock(&module_, block_count, nullptr), nullptr);
  EXPECT_EQ(spvReflectGetPushConstantBlock(&module_, block_count, &result), nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
}

TEST_P(SpirvReflectTest, ChangeDescriptorBindingNumber) {
  uint32_t binding_count = 0;
  SpvReflectResult result;
  result =
      spvReflectEnumerateDescriptorBindings(&module_, &binding_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  if (binding_count == 0) {
    return; // can't change binding numbers if there are no bindings!
  }
  std::vector<SpvReflectDescriptorBinding *> bindings(binding_count);
  result = spvReflectEnumerateDescriptorBindings(&module_, &binding_count,
                                                 bindings.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  uint32_t set_count = 0;
  result = spvReflectEnumerateDescriptorSets(&module_, &set_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  EXPECT_GT(set_count, 0U);
  std::vector<SpvReflectDescriptorSet *> sets(set_count);
  result = spvReflectEnumerateDescriptorSets(&module_, &set_count, sets.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  SpvReflectDescriptorBinding *b = bindings[0];
  const uint32_t new_binding_number = 1000;
  const uint32_t set_number = b->set;
  // Make sure no binding exists at the binding number we're about to change to.
  ASSERT_EQ(spvReflectGetDescriptorBinding(&module_, new_binding_number,
                                           set_number, &result),
            nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
  // Modify the binding number (leaving the set number unchanged)
  result = spvReflectChangeDescriptorBindingNumbers(
      &module_, b, new_binding_number, SPV_REFLECT_SET_NUMBER_DONT_CHANGE);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  // We should now be able to retrieve the binding at the new number
  const SpvReflectDescriptorBinding *new_binding =
      spvReflectGetDescriptorBinding(&module_, new_binding_number, set_number,
                                     &result);
  ASSERT_NE(new_binding, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  EXPECT_EQ(new_binding->binding, new_binding_number);
  EXPECT_EQ(new_binding->set, set_number);
  // The set count & sets contents should not have changed, since we didn't
  // change the set number.
  result = spvReflectEnumerateDescriptorSets(&module_, &set_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  EXPECT_EQ(set_count, sets.size());
  std::vector<SpvReflectDescriptorSet *> new_sets(set_count);
  result =
      spvReflectEnumerateDescriptorSets(&module_, &set_count, new_sets.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  EXPECT_EQ(sets, new_sets);

  // TODO: confirm that the modified SPIR-V code is still valid, either by
  // running spirv-val or calling vkCreateShaderModule().
}
TEST_P(SpirvReflectTest, ChangeDescriptorBindingNumbers_Errors) {
  // NULL module
  EXPECT_EQ(spvReflectChangeDescriptorBindingNumbers(nullptr, nullptr, 0, 0), SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // NULL descriptor binding
  EXPECT_EQ(spvReflectChangeDescriptorBindingNumbers(&module_, nullptr, 0, 0), SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
}

TEST_P(SpirvReflectTest, ChangeDescriptorSetNumber) {
  uint32_t binding_count = 0;
  SpvReflectResult result;
  result =
      spvReflectEnumerateDescriptorBindings(&module_, &binding_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  if (binding_count == 0) {
    return; // can't change set numbers if there are no bindings!
  }
  std::vector<SpvReflectDescriptorBinding *> bindings(binding_count);
  result = spvReflectEnumerateDescriptorBindings(&module_, &binding_count,
                                                 bindings.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  uint32_t set_count = 0;
  result = spvReflectEnumerateDescriptorSets(&module_, &set_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  EXPECT_GT(set_count, 0U);
  std::vector<SpvReflectDescriptorSet *> sets(set_count);
  result = spvReflectEnumerateDescriptorSets(&module_, &set_count, sets.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  SpvReflectDescriptorSet *s = sets[0];

  const uint32_t new_set_number = 13;
  const uint32_t set_binding_count = s->binding_count;
  // Make sure no set exists at the binding number we're about to change to.
  ASSERT_EQ(spvReflectGetDescriptorSet(&module_, new_set_number, &result),
            nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
  // Modify the set number
  result = spvReflectChangeDescriptorSetNumber(&module_, s, new_set_number);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  // We should now be able to retrieve the set at the new number
  const SpvReflectDescriptorSet *new_set =
      spvReflectGetDescriptorSet(&module_, new_set_number, &result);
  ASSERT_NE(new_set, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  EXPECT_EQ(new_set->set, new_set_number);
  EXPECT_EQ(new_set->binding_count, set_binding_count);
  // The set count should not have changed, since we didn't
  // change the set number.
  result = spvReflectEnumerateDescriptorSets(&module_, &set_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  EXPECT_EQ(set_count, sets.size());

  // TODO: confirm that the modified SPIR-V code is still valid, either by
  // running spirv-val or calling vkCreateShaderModule().
}
TEST_P(SpirvReflectTest, ChangeDescriptorSetNumber_Errors) {
  // NULL module
  EXPECT_EQ(spvReflectChangeDescriptorSetNumber(nullptr, nullptr, 0), SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // NULL descriptor set
  EXPECT_EQ(spvReflectChangeDescriptorSetNumber(&module_, nullptr, 0), SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
}

TEST_P(SpirvReflectTest, ChangeInputVariableLocation) {
  uint32_t iv_count = 0;
  SpvReflectResult result;
  result = spvReflectEnumerateInputVariables(&module_, &iv_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  if (iv_count == 0) {
    return; // can't change variable locations if there are no variables!
  }
  std::vector<SpvReflectInterfaceVariable *> ivars(iv_count);
  result = spvReflectEnumerateInputVariables(&module_, &iv_count, ivars.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  SpvReflectInterfaceVariable *iv = ivars[0];

  const uint32_t new_location = 37;
  // Make sure no var exists at the location we're about to change to.
  ASSERT_EQ(
      spvReflectGetInputVariableByLocation(&module_, new_location, &result),
      nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
  // Modify the location
  result = spvReflectChangeInputVariableLocation(&module_, iv, new_location);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  // We should now be able to retrieve the variable at its new location
  const SpvReflectInterfaceVariable *new_iv =
      spvReflectGetInputVariableByLocation(&module_, new_location, &result);
  ASSERT_NE(new_iv, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  EXPECT_EQ(new_iv->location, new_location);
  // The variable count should not have changed
  result = spvReflectEnumerateInputVariables(&module_, &iv_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  EXPECT_EQ(iv_count, ivars.size());

  // TODO: confirm that the modified SPIR-V code is still valid, either by
  // running spirv-val or calling vkCreateShaderModule().
}
TEST_P(SpirvReflectTest, ChangeInputVariableLocation_Errors) {
  // NULL module
  EXPECT_EQ(spvReflectChangeInputVariableLocation(nullptr, nullptr, 0), SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // NULL variable
  EXPECT_EQ(spvReflectChangeInputVariableLocation(&module_, nullptr, 0), SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
}

TEST_P(SpirvReflectTest, ChangeOutputVariableLocation) {
  uint32_t ov_count = 0;
  SpvReflectResult result;
  result = spvReflectEnumerateOutputVariables(&module_, &ov_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  if (ov_count == 0) {
    return; // can't change variable locations if there are no variables!
  }
  std::vector<SpvReflectInterfaceVariable *> ovars(ov_count);
  result =
      spvReflectEnumerateOutputVariables(&module_, &ov_count, ovars.data());
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);

  SpvReflectInterfaceVariable *ov = ovars[0];

  const uint32_t new_location = 37;
  // Make sure no var exists at the location we're about to change to.
  ASSERT_EQ(
      spvReflectGetOutputVariableByLocation(&module_, new_location, &result),
      nullptr);
  EXPECT_EQ(result, SPV_REFLECT_RESULT_ERROR_ELEMENT_NOT_FOUND);
  // Modify the location
  result = spvReflectChangeOutputVariableLocation(&module_, ov, new_location);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  // We should now be able to retrieve the variable at its new location
  const SpvReflectInterfaceVariable *new_ov =
      spvReflectGetOutputVariableByLocation(&module_, new_location, &result);
  ASSERT_NE(new_ov, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  EXPECT_EQ(new_ov->location, new_location);
  // The variable count should not have changed
  result = spvReflectEnumerateOutputVariables(&module_, &ov_count, nullptr);
  ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
  EXPECT_EQ(ov_count, ovars.size());

  // TODO: confirm that the modified SPIR-V code is still valid, either by
  // running spirv-val or calling vkCreateShaderModule().
}
TEST_P(SpirvReflectTest, ChangeOutputVariableLocation_Errors) {
  // NULL module
  EXPECT_EQ(spvReflectChangeOutputVariableLocation(nullptr, nullptr, 0), SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
  // NULL variable
  EXPECT_EQ(spvReflectChangeOutputVariableLocation(&module_, nullptr, 0), SPV_REFLECT_RESULT_ERROR_NULL_POINTER);
}

TEST_P(SpirvReflectTest, CheckYamlOutput) {
  const uint32_t yaml_verbosity = 1;
  SpvReflectToYaml yamlizer(module_, yaml_verbosity);
  std::stringstream test_yaml;
  test_yaml << yamlizer;
  std::string test_yaml_str = test_yaml.str();

  std::string golden_yaml_path = spirv_path_ + ".yaml";
  std::ifstream golden_yaml_file(golden_yaml_path);
  ASSERT_TRUE(golden_yaml_file.good());
  std::stringstream golden_yaml;
  golden_yaml << golden_yaml_file.rdbuf();
  golden_yaml_file.close();
  std::string golden_yaml_str = golden_yaml.str();

  // Quick workaround for potential line ending differences, perf be damned!
  test_yaml_str = std::regex_replace(test_yaml_str, std::regex("\r"), "");
  golden_yaml_str = std::regex_replace(golden_yaml_str, std::regex("\r"), "");

  ASSERT_EQ(test_yaml_str.size(), golden_yaml_str.size());
  // TODO: I wish there were a better way to show what changed, but the
  // differences (if any) tend to be pretty large, even for small changes.
  bool yaml_contents_match = (test_yaml_str == golden_yaml_str);
  EXPECT_TRUE(yaml_contents_match)
      << "YAML output mismatch; try regenerating the golden YAML with "
         "\"tests/build_golden_yaml.py\" and see what changed.";
}

namespace {
const std::vector<const char *> all_spirv_paths = {
    // clang-format off
    "../tests/cbuffer_unused/cbuffer_unused_001.spv",
    "../tests/glsl/built_in_format.spv",
    "../tests/glsl/fn_struct_param.spv",
    "../tests/glsl/input_attachment.spv",
    "../tests/glsl/io_vars_vs.spv",
    "../tests/glsl/texel_buffer.spv",
    "../tests/hlsl/append_consume.spv",
    "../tests/hlsl/array_of_structured_buffer.spv",
    "../tests/hlsl/binding_array.spv",
    "../tests/hlsl/binding_types.spv",
    "../tests/hlsl/cbuffer.spv",
    "../tests/hlsl/constantbuffer.spv",
    "../tests/hlsl/constantbuffer_nested_structs.spv",
    "../tests/hlsl/counter_buffers.spv",
    "../tests/hlsl/localsize.spv",
    "../tests/hlsl/semantics.spv",
    "../tests/hlsl/structuredbuffer.spv",
    "../tests/issues/77/hlsl/array_from_ubo.spv",
    "../tests/issues/77/hlsl/array_from_ubo_with_O0.spv",
    "../tests/issues/77/hlsl/rocketz.spv",
    "../tests/multi_entrypoint/multi_entrypoint.spv",
    "../tests/raytrace/rayquery_equal.cs.spv",
    "../tests/raytrace/rayquery_init_ds.spv",
    "../tests/raytrace/rayquery_init_gs.spv",
    "../tests/raytrace/rayquery_init_hs.spv",
    "../tests/raytrace/rayquery_init_ps.spv",
    "../tests/raytrace/rayquery_init_rahit.spv",
    "../tests/raytrace/rayquery_init_rcall.spv",
    "../tests/raytrace/rayquery_init_rchit.spv",
    "../tests/raytrace/rayquery_init_rgen.spv",
    "../tests/raytrace/rayquery_init_rmiss.spv",
    "../tests/raytrace/raytracing.acceleration-structure.spv",
    "../tests/raytrace/raytracing.khr.closesthit.spv",
    "../tests/raytrace/raytracing.nv.acceleration-structure.spv",
    "../tests/raytrace/raytracing.nv.anyhit.spv",
    "../tests/raytrace/raytracing.nv.callable.spv",
    "../tests/raytrace/raytracing.nv.closesthit.spv",
    "../tests/raytrace/raytracing.nv.enum.spv",
    "../tests/raytrace/raytracing.nv.intersection.spv",
    "../tests/raytrace/raytracing.nv.library.spv",
    "../tests/raytrace/raytracing.nv.miss.spv",
    "../tests/raytrace/raytracing.nv.raygen.spv",
    "../tests/spirv15/VertexShader.spv",
     // clang-format on
};
} // namespace
INSTANTIATE_TEST_CASE_P(ForAllShaders, SpirvReflectTest,
                        ::testing::ValuesIn(all_spirv_paths));

class SpirvReflectMultiEntryPointTest : public ::testing::TestWithParam<int> {
protected:
  void SetUp() override {
    // called after constructor before each test
    const std::string spirv_path = "../tests/multi_entrypoint/multi_entrypoint.spv";
    std::ifstream spirv_file(spirv_path, std::ios::binary | std::ios::ate);
    std::streampos spirv_file_nbytes = spirv_file.tellg();
    spirv_file.seekg(0);
    spirv_.resize(spirv_file_nbytes);
    spirv_file.read(reinterpret_cast<char *>(spirv_.data()), spirv_.size());

    SpvReflectResult result =
        spvReflectCreateShaderModule(spirv_.size(), spirv_.data(), &module_);
    ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS, result)
        << "spvReflectCreateShaderModule() failed";
  }

  // optional:
  void TearDown() override {
    // called before destructor after all tests
    spvReflectDestroyShaderModule(&module_);
  }

  const char* eps_[2] = {"entry_vert", "entry_frag"};
  std::vector<uint8_t> spirv_;
  SpvReflectShaderModule module_;
};

TEST_F(SpirvReflectMultiEntryPointTest, GetEntryPoint) {
  ASSERT_EQ(&module_.entry_points[0],
            spvReflectGetEntryPoint(&module_, eps_[0]));
  ASSERT_EQ(&module_.entry_points[1],
            spvReflectGetEntryPoint(&module_, eps_[1]));
  ASSERT_EQ(NULL,
            spvReflectGetEntryPoint(&module_, "entry_tess"));
}

TEST_F(SpirvReflectMultiEntryPointTest, GetDescriptorBindings0) {
  uint32_t binding_count = 0;
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
            spvReflectEnumerateEntryPointDescriptorBindings(
                &module_,
                eps_[0],
                &binding_count,
                NULL));
  ASSERT_EQ(binding_count, 1);
  SpvReflectDescriptorBinding* binding;
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
            spvReflectEnumerateEntryPointDescriptorBindings(
                &module_,
                eps_[0],
                &binding_count,
                &binding));
  ASSERT_EQ(binding->set, 0);
  ASSERT_EQ(binding->binding, 1);
  ASSERT_EQ(strcmp(binding->name, "ubo"), 0);
  ASSERT_EQ(binding->descriptor_type, SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER);

  SpvReflectResult result;
  ASSERT_EQ(binding,
            spvReflectGetEntryPointDescriptorBinding(
                &module_,
                eps_[0],
                binding->binding,
                binding->set,
                &result));
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS, result);
}

TEST_F(SpirvReflectMultiEntryPointTest, GetDescriptorBindings1) {
  uint32_t binding_count = 0;
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
            spvReflectEnumerateEntryPointDescriptorBindings(
                &module_,
                eps_[1],
                &binding_count,
                NULL));
  ASSERT_EQ(binding_count, 2);
  SpvReflectDescriptorBinding* bindings[2];
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
            spvReflectEnumerateEntryPointDescriptorBindings(
                &module_,
                eps_[1],
                &binding_count,
                bindings));
  ASSERT_EQ(bindings[0]->set, 0);
  ASSERT_EQ(bindings[0]->binding, 0);
  ASSERT_EQ(strcmp(bindings[0]->name, "tex"), 0);
  ASSERT_EQ(bindings[0]->descriptor_type, SPV_REFLECT_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);

  ASSERT_EQ(bindings[1]->set, 0);
  ASSERT_EQ(bindings[1]->binding, 1);
  ASSERT_EQ(strcmp(bindings[1]->name, "ubo"), 0);
  ASSERT_EQ(bindings[1]->descriptor_type, SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER);

  for (size_t i = 0; i < 2; ++i) {
    SpvReflectResult result;
    ASSERT_EQ(bindings[i],
              spvReflectGetEntryPointDescriptorBinding(
                  &module_,
                  eps_[1],
                  bindings[i]->binding,
                  bindings[i]->set,
                  &result));
    ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS, result);
  }
}

TEST_F(SpirvReflectMultiEntryPointTest, GetDescriptorBindingsShared) {
  uint32_t vert_count = 1;
  SpvReflectDescriptorBinding* vert_binding;

  uint32_t frag_count = 2;
  SpvReflectDescriptorBinding* frag_binding[2];
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
            spvReflectEnumerateEntryPointDescriptorBindings(
                &module_,
                eps_[0],
                &vert_count,
                &vert_binding));
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
            spvReflectEnumerateEntryPointDescriptorBindings(
                &module_,
                eps_[1],
                &frag_count,
                frag_binding));
  ASSERT_EQ(vert_binding, frag_binding[1]);
}

TEST_F(SpirvReflectMultiEntryPointTest, GetDescriptorSets0) {
  uint32_t set_count;
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
            spvReflectEnumerateEntryPointDescriptorSets(
                &module_,
                eps_[0],
                &set_count,
                NULL));
  ASSERT_EQ(set_count, 1);
  SpvReflectDescriptorSet *set;
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
            spvReflectEnumerateEntryPointDescriptorSets(
                &module_,
                eps_[0],
                &set_count,
                &set));
  ASSERT_EQ(set->set, 0);
  ASSERT_EQ(set->binding_count, 1);
  ASSERT_EQ(set, &module_.entry_points[0].descriptor_sets[0]);

  SpvReflectResult result;
  ASSERT_EQ(set,
            spvReflectGetEntryPointDescriptorSet(
                &module_,
                eps_[0],
                set->set,
                &result));
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS, result);
}

TEST_F(SpirvReflectMultiEntryPointTest, GetDescriptorSets1) {
  uint32_t set_count;
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
            spvReflectEnumerateEntryPointDescriptorSets(
                &module_,
                eps_[1],
                &set_count,
                NULL));
  ASSERT_EQ(set_count, 1);
  SpvReflectDescriptorSet *set;
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
            spvReflectEnumerateEntryPointDescriptorSets(
                &module_,
                eps_[1],
                &set_count,
                &set));
  ASSERT_EQ(set->set, 0);
  ASSERT_EQ(set->binding_count, 2);
  ASSERT_EQ(set, &module_.entry_points[1].descriptor_sets[0]);

  SpvReflectResult result;
  ASSERT_EQ(set,
            spvReflectGetEntryPointDescriptorSet(
                &module_,
                eps_[1],
                set->set,
                &result));
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS, result);
}

TEST_F(SpirvReflectMultiEntryPointTest, GetInputVariables) {
  const uint32_t counts[2] = {2, 1};
  const char* names[2][2] = {{"iUV", "pos"}, {"iUV", NULL}};
  for (size_t i = 0; i < 2; ++i) {
    uint32_t count = 0;
    ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
              spvReflectEnumerateEntryPointInputVariables(
                  &module_,
                  eps_[i],
                  &count,
                  NULL));
    ASSERT_EQ(count, counts[i]);

    // 2 is the max count
    SpvReflectInterfaceVariable* vars[2];
    ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
              spvReflectEnumerateEntryPointInputVariables(
                  &module_,
                  eps_[i],
                  &count,
                  vars));
    for (size_t j = 0; j < counts[i]; ++j) {
      if (vars[j]->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) {
        // built-ins don't have reasonable locations
        continue;
      }
      SpvReflectResult result;
      const SpvReflectInterfaceVariable* var =
          spvReflectGetEntryPointInputVariableByLocation(
              &module_,
              eps_[i],
              vars[j]->location,
              &result);
      ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
      ASSERT_EQ(var, vars[j]);
      ASSERT_EQ(strcmp(var->name, names[i][var->location]), 0);
    }
  }
}

TEST_F(SpirvReflectMultiEntryPointTest, GetOutputVariables) {
  const uint32_t counts[2] = {2, 1};
  // One of the outputs from the first entry point is a builtin so it has no
  // position.
  const char* names[2][1] = {{"oUV"}, {"colour"}};
  for (size_t i = 0; i < 2; ++i) {
    uint32_t count = 0;
    ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
              spvReflectEnumerateEntryPointOutputVariables(
                  &module_,
                  eps_[i],
                  &count,
                  NULL));
    ASSERT_EQ(count, counts[i]);

    // 2 is the max count
    SpvReflectInterfaceVariable* vars[2];
    ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
              spvReflectEnumerateEntryPointOutputVariables(
                  &module_,
                  eps_[i],
                  &count,
                  vars));
    for (size_t j = 0; j < counts[i]; ++j) {
      if (vars[j]->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) {
        // built-ins don't have reasonable locations
        continue;
      }
      SpvReflectResult result;
      const SpvReflectInterfaceVariable* var =
          spvReflectGetEntryPointOutputVariableByLocation(
              &module_,
              eps_[i],
              vars[j]->location,
              &result);
      ASSERT_EQ(result, SPV_REFLECT_RESULT_SUCCESS);
      ASSERT_EQ(var, vars[j]);
      ASSERT_EQ(strcmp(var->name, names[i][var->location]), 0);
    }
  }
}

TEST_F(SpirvReflectMultiEntryPointTest, GetPushConstants) {
  for (size_t i = 0; i < 2; ++i) {
    SpvReflectBlockVariable* var;
    uint32_t count = 0;
    ASSERT_EQ(SPV_REFLECT_RESULT_ERROR_COUNT_MISMATCH,
              spvReflectEnumerateEntryPointPushConstantBlocks(
                  &module_,
                  eps_[i],
                  &count,
                  &var));
    ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
              spvReflectEnumerateEntryPointPushConstantBlocks(
                  &module_,
                  eps_[i],
                  &count,
                  NULL));
    ASSERT_EQ(count, 1);
    ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
              spvReflectEnumerateEntryPointPushConstantBlocks(
                  &module_,
                  eps_[i],
                  &count,
                  &var));
    SpvReflectResult result;
    ASSERT_EQ(var,
              spvReflectGetEntryPointPushConstantBlock(
                  &module_,
                  eps_[i],
                  &result));
    ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS, result);
  }
}

TEST_F(SpirvReflectMultiEntryPointTest, ChangeDescriptorBindingNumber) {
  const SpvReflectDescriptorBinding* binding = spvReflectGetEntryPointDescriptorBinding(
      &module_,
      eps_[0],
      1,
      0,
      NULL);
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
            spvReflectChangeDescriptorBindingNumbers(&module_,
                                                     binding,
                                                     2,
                                                     1));
  // Change descriptor binding numbers doesn't currently resort so it won't
  // invalidate binding, but if that changes this test will need to be fixed.
  ASSERT_EQ(binding->set, 1);
  ASSERT_EQ(binding->binding, 2);

  SpvReflectResult result;
  const SpvReflectDescriptorSet* set0 = spvReflectGetEntryPointDescriptorSet(
      &module_,
      eps_[0],
      1,
      &result);
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS, result);
  ASSERT_EQ(set0->binding_count, 1);
  const SpvReflectDescriptorSet* set1 = spvReflectGetEntryPointDescriptorSet(
      &module_,
      eps_[1],
      1,
      &result);
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS, result);
  ASSERT_EQ(set1->binding_count, 1);

  ASSERT_EQ(set0->bindings[0], set1->bindings[0]);
}

TEST_F(SpirvReflectMultiEntryPointTest, ChangeDescriptorSetNumber) {
  const SpvReflectDescriptorSet* set = spvReflectGetDescriptorSet(
      &module_,
      0,
      NULL);
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS,
            spvReflectChangeDescriptorSetNumber(&module_,
                                                set,
                                                1));
  // Change descriptor binding numbers doesn't currently resort so it won't
  // invalidate binding, but if that changes this test will need to be fixed.
  ASSERT_EQ(set->set, 1);

  SpvReflectResult result;
  const SpvReflectDescriptorSet* set0 = spvReflectGetEntryPointDescriptorSet(
      &module_,
      eps_[0],
      1,
      &result);
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS, result);
  ASSERT_EQ(set0->binding_count, 1);
  const SpvReflectDescriptorSet* set1 = spvReflectGetEntryPointDescriptorSet(
      &module_,
      eps_[1],
      1,
      &result);
  ASSERT_EQ(SPV_REFLECT_RESULT_SUCCESS, result);
  ASSERT_EQ(set1->binding_count, 2);

  ASSERT_EQ(set0->bindings[0], set1->bindings[1]);
  ASSERT_EQ(set0->bindings[0]->set, 1);
}
