// Copyright 2015 The Shaderc Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "shaderc/shaderc.h"

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

#include <memory>
#include <thread>
#include <unordered_map>

#include "common_shaders_for_test.h"
#include "spirv/unified1/spirv.hpp"

namespace {

using testing::Each;
using testing::HasSubstr;
using testing::Not;

TEST(Init, MultipleCalls) {
  shaderc_compiler_t compiler1, compiler2, compiler3;
  EXPECT_NE(nullptr, compiler1 = shaderc_compiler_initialize());
  EXPECT_NE(nullptr, compiler2 = shaderc_compiler_initialize());
  EXPECT_NE(nullptr, compiler3 = shaderc_compiler_initialize());
  shaderc_compiler_release(compiler1);
  shaderc_compiler_release(compiler2);
  shaderc_compiler_release(compiler3);
}

#ifndef SHADERC_DISABLE_THREADED_TESTS
TEST(Init, MultipleThreadsCalling) {
  shaderc_compiler_t compiler1, compiler2, compiler3;
  std::thread t1([&compiler1]() { compiler1 = shaderc_compiler_initialize(); });
  std::thread t2([&compiler2]() { compiler2 = shaderc_compiler_initialize(); });
  std::thread t3([&compiler3]() { compiler3 = shaderc_compiler_initialize(); });
  t1.join();
  t2.join();
  t3.join();
  EXPECT_NE(nullptr, compiler1);
  EXPECT_NE(nullptr, compiler2);
  EXPECT_NE(nullptr, compiler3);
  shaderc_compiler_release(compiler1);
  shaderc_compiler_release(compiler2);
  shaderc_compiler_release(compiler3);
}
#endif

TEST(Init, SPVVersion) {
  unsigned int version = 0;
  unsigned int revision = 0;
  shaderc_get_spv_version(&version, &revision);
  EXPECT_EQ(spv::Version, version);
  EXPECT_EQ(spv::Revision, revision);
}

// Determines the kind of output required from the compiler.
enum class OutputType {
  SpirvBinary,
  SpirvAssemblyText,
  PreprocessedText,
};

// Generate a compilation result object with the given compile,
// shader source, shader kind, input file name, entry point name, options,
// and for the specified output type.  The entry point name is only significant
// for HLSL compilation.
shaderc_compilation_result_t MakeCompilationResult(
    const shaderc_compiler_t compiler, const std::string& shader,
    shaderc_shader_kind kind, const char* input_file_name,
    const char* entry_point_name, const shaderc_compile_options_t options,
    OutputType output_type) {
  switch (output_type) {
    case OutputType::SpirvBinary:
      return shaderc_compile_into_spv(compiler, shader.c_str(), shader.size(),
                                      kind, input_file_name, entry_point_name,
                                      options);
      break;
    case OutputType::SpirvAssemblyText:
      return shaderc_compile_into_spv_assembly(
          compiler, shader.c_str(), shader.size(), kind, input_file_name,
          entry_point_name, options);
      break;
    case OutputType::PreprocessedText:
      return shaderc_compile_into_preprocessed_text(
          compiler, shader.c_str(), shader.size(), kind, input_file_name,
          entry_point_name, options);
      break;
  }
  // We shouldn't reach here.  But some compilers might not know that.
  // Be a little defensive and produce something.
  return shaderc_compile_into_spv(compiler, shader.c_str(), shader.size(), kind,
                                  input_file_name, entry_point_name, options);
}

// RAII class for shaderc_compilation_result. Used for shader compilation.
class Compilation {
 public:
  // Compiles shader and keeps the result.
  Compilation(const shaderc_compiler_t compiler, const std::string& shader,
              shaderc_shader_kind kind, const char* input_file_name,
              const char* entry_point_name,
              const shaderc_compile_options_t options = nullptr,
              OutputType output_type = OutputType::SpirvBinary)
      : compiled_result_(
            MakeCompilationResult(compiler, shader, kind, input_file_name,
                                  entry_point_name, options, output_type)) {}

  ~Compilation() { shaderc_result_release(compiled_result_); }

  shaderc_compilation_result_t result() const { return compiled_result_; }

 private:
  shaderc_compilation_result_t compiled_result_;
};

// RAII class for shaderc_compilation_result. Used for shader assembling.
class Assembling {
 public:
  // Assembles shader and keeps the result.
  Assembling(const shaderc_compiler_t compiler, const std::string& assembly,
             const shaderc_compile_options_t options = nullptr)
      : compiled_result_(shaderc_assemble_into_spv(compiler, assembly.data(),
                                                   assembly.size(), options)) {}

  ~Assembling() { shaderc_result_release(compiled_result_); }

  shaderc_compilation_result_t result() const { return compiled_result_; }

 private:
  shaderc_compilation_result_t compiled_result_;
};

struct CleanupOptions {
  void operator()(shaderc_compile_options_t options) const {
    shaderc_compile_options_release(options);
  }
};

typedef std::unique_ptr<shaderc_compile_options, CleanupOptions>
    compile_options_ptr;

// RAII class for shaderc_compiler_t
class Compiler {
 public:
  Compiler() { compiler = shaderc_compiler_initialize(); }
  ~Compiler() { shaderc_compiler_release(compiler); }
  shaderc_compiler_t get_compiler_handle() { return compiler; }

 private:
  shaderc_compiler_t compiler;
};

// RAII class for shader_compiler_options_t
class Options {
 public:
  Options() : options_(shaderc_compile_options_initialize()) {}
  ~Options() { shaderc_compile_options_release(options_); }
  shaderc_compile_options_t get() { return options_; }

 private:
  shaderc_compile_options_t options_;
};

// Helper function to check if the compilation result indicates a successful
// compilation.
bool CompilationResultIsSuccess(const shaderc_compilation_result_t result) {
  return shaderc_result_get_compilation_status(result) ==
         shaderc_compilation_status_success;
}

// Returns true if the given result contains a SPIR-V module that contains
// at least the number of bytes of the header and the correct magic number.
bool ResultContainsValidSpv(shaderc_compilation_result_t result) {
  if (!CompilationResultIsSuccess(result)) return false;
  size_t length = shaderc_result_get_length(result);
  if (length < 20) return false;
  const uint32_t* bytes = static_cast<const uint32_t*>(
      static_cast<const void*>(shaderc_result_get_bytes(result)));
  return bytes[0] == spv::MagicNumber;
}

// Compiles a shader and returns true if the result is valid SPIR-V.
bool CompilesToValidSpv(Compiler& compiler, const std::string& shader,
                        shaderc_shader_kind kind,
                        const shaderc_compile_options_t options = nullptr) {
  const Compilation comp(compiler.get_compiler_handle(), shader, kind, "shader",
                         "main", options, OutputType::SpirvBinary);
  return ResultContainsValidSpv(comp.result());
}

// A testing class to test the compilation of a string with or without options.
// This class wraps the initailization of compiler and compiler options and
// groups the result checking methods. Subclass tests can access the compiler
// object and compiler option object to set their properties. Input file names
// are set to "shader".
class CompileStringTest : public testing::Test {
 protected:
  // Compiles a shader and returns true on success, false on failure.
  bool CompilationSuccess(const std::string& shader, shaderc_shader_kind kind,
                          shaderc_compile_options_t options = nullptr,
                          OutputType output_type = OutputType::SpirvBinary) {
    return CompilationResultIsSuccess(
        Compilation(compiler_.get_compiler_handle(), shader, kind, "shader",
                    "main", options, output_type)
            .result());
  }

  // Compiles a shader, expects compilation success, and returns the warning
  // messages.
  const std::string CompilationWarnings(
      const std::string& shader, shaderc_shader_kind kind,
      const shaderc_compile_options_t options = nullptr,
      OutputType output_type = OutputType::SpirvBinary) {
    const Compilation comp(compiler_.get_compiler_handle(), shader, kind,
                           "shader", "main", options, output_type);
    EXPECT_TRUE(CompilationResultIsSuccess(comp.result())) << kind << '\n'
                                                           << shader;
    return shaderc_result_get_error_message(comp.result());
  }

  // Compiles a shader, expects compilation failure, and returns the messages.
  const std::string CompilationErrors(
      const std::string& shader, shaderc_shader_kind kind,
      const shaderc_compile_options_t options = nullptr,
      OutputType output_type = OutputType::SpirvBinary,
      const char* source_name = "shader") {
    const Compilation comp(compiler_.get_compiler_handle(), shader, kind,
                           source_name, "main", options, output_type);
    EXPECT_FALSE(CompilationResultIsSuccess(comp.result())) << kind << '\n'
                                                            << shader;
    EXPECT_EQ(0u, shaderc_result_get_length(comp.result()));
    return shaderc_result_get_error_message(comp.result());
  }

  // Compiles a shader and returns the messages.
  const std::string CompilationMessages(
      const std::string& shader, shaderc_shader_kind kind,
      const shaderc_compile_options_t options = nullptr,
      OutputType output_type = OutputType::SpirvBinary) {
    const Compilation comp(compiler_.get_compiler_handle(), shader, kind,
                           "shader", "main", options, output_type);
    return shaderc_result_get_error_message(comp.result());
  }

  // Compiles a shader, expects compilation success, and returns the output
  // bytes.
  const std::string CompilationOutput(
      const std::string& shader, shaderc_shader_kind kind,
      const shaderc_compile_options_t options = nullptr,
      OutputType output_type = OutputType::SpirvBinary) {
    const Compilation comp(compiler_.get_compiler_handle(), shader, kind,
                           "shader", "main", options, output_type);
    EXPECT_TRUE(CompilationResultIsSuccess(comp.result()))
        << "shader kind: " << kind << "\nerror message: "
        << shaderc_result_get_error_message(comp.result())
        << "\nshader source code: \n"
        << shader;
    // Use string(const char* s, size_t n) constructor instead of
    // string(const char* s) to make sure the string has complete binary data.
    // string(const char* s) assumes a null-terminated C-string, which will cut
    // the binary data when it sees a '\0' byte.
    return std::string(shaderc_result_get_bytes(comp.result()),
                       shaderc_result_get_length(comp.result()));
  }

  Compiler compiler_;
  compile_options_ptr options_;

 public:
  CompileStringTest() : options_(shaderc_compile_options_initialize()) {}
};

// A testing class to test the assembling of a string.
// This class wraps the initailization of compiler and groups the result
// checking methods. Subclass tests can access the compiler object to set their
// properties.
class AssembleStringTest : public testing::Test {
 public:
  AssembleStringTest() : options_(shaderc_compile_options_initialize()) {}
  ~AssembleStringTest() { shaderc_compile_options_release(options_); }

 protected:
  // Assembles the given assembly and returns true on success.
  bool AssemblingSuccess(const std::string& assembly) {
    return CompilationResultIsSuccess(
        Assembling(compiler_.get_compiler_handle(), assembly, options_)
            .result());
  }

  bool AssemblingValid(const std::string& assembly) {
    const auto assembling =
        Assembling(compiler_.get_compiler_handle(), assembly);
    return ResultContainsValidSpv(assembling.result());
  }

  Compiler compiler_;
  shaderc_compile_options_t options_;
};

// Name holders so that we have test cases being grouped with only one real
// compilation class.
using CompileStringWithOptionsTest = CompileStringTest;
using CompileKindsTest = CompileStringTest;

TEST_F(CompileStringTest, EmptyString) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_FALSE(CompilationSuccess("", shaderc_glsl_vertex_shader));
  EXPECT_FALSE(CompilationSuccess("", shaderc_glsl_fragment_shader));
}

TEST_F(AssembleStringTest, EmptyString) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_TRUE(AssemblingSuccess(""));
}

TEST_F(CompileStringTest, GarbageString) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_FALSE(CompilationSuccess("jfalkds", shaderc_glsl_vertex_shader));
  EXPECT_FALSE(CompilationSuccess("jfalkds", shaderc_glsl_fragment_shader));
}

TEST_F(AssembleStringTest, GarbageString) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  auto assembling = Assembling(compiler_.get_compiler_handle(), "jfalkds");
  EXPECT_FALSE(CompilationResultIsSuccess(assembling.result()));
  EXPECT_EQ(1u, shaderc_result_get_num_errors(assembling.result()));
  EXPECT_EQ(0u, shaderc_result_get_num_warnings(assembling.result()));
}

// TODO(antiagainst): right now there is no assembling difference for all the
// target environments exposed by shaderc. So the following is just testing the
// target environment is accepted.
TEST_F(AssembleStringTest, AcceptTargetEnv) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  shaderc_compile_options_set_target_env(options_, shaderc_target_env_opengl,
                                         /* version = */ 0);
  EXPECT_TRUE(AssemblingSuccess("OpCapability Shader"));
}

TEST_F(CompileStringTest, ReallyLongShader) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  std::string minimal_shader = "";
  minimal_shader += "#version 140\n";
  minimal_shader += "void foo(){}";
  minimal_shader.append(1024 * 1024 * 8, ' ');  // 8MB of spaces.
  minimal_shader += "void main(){}";
  EXPECT_TRUE(CompilesToValidSpv(compiler_, minimal_shader,
                                 shaderc_glsl_vertex_shader));
  EXPECT_TRUE(CompilesToValidSpv(compiler_, minimal_shader,
                                 shaderc_glsl_fragment_shader));
}

TEST_F(CompileStringTest, MinimalShader) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kMinimalShader,
                                 shaderc_glsl_vertex_shader));
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kMinimalShader,
                                 shaderc_glsl_fragment_shader));
}

TEST_F(AssembleStringTest, MinimalShader) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_TRUE(AssemblingValid(kMinimalShaderAssembly));
}

TEST_F(CompileStringTest, WorksWithCompileOptions) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kMinimalShader,
                                 shaderc_glsl_vertex_shader, options_.get()));
}

TEST_F(CompileStringTest, GetNumErrors) {
  Compilation comp(compiler_.get_compiler_handle(), kTwoErrorsShader,
                   shaderc_glsl_vertex_shader, "shader", "main");
  // Expects compilation failure and two errors.
  EXPECT_FALSE(CompilationResultIsSuccess(comp.result()));
  EXPECT_EQ(2u, shaderc_result_get_num_errors(comp.result()));
  // Expects the number of warnings to be zero.
  EXPECT_EQ(0u, shaderc_result_get_num_warnings(comp.result()));
}

TEST_F(CompileStringTest, GetNumWarnings) {
  Compilation comp(compiler_.get_compiler_handle(), kTwoWarningsShader,
                   shaderc_glsl_vertex_shader, "shader", "main");
  // Expects compilation success with two warnings.
  EXPECT_TRUE(CompilationResultIsSuccess(comp.result()));
  EXPECT_EQ(2u, shaderc_result_get_num_warnings(comp.result()));
  // Expects the number of errors to be zero.
  EXPECT_EQ(0u, shaderc_result_get_num_errors(comp.result()));
}

TEST_F(CompileStringTest, ZeroErrorsZeroWarnings) {
  Compilation comp(compiler_.get_compiler_handle(), kMinimalShader,
                   shaderc_glsl_vertex_shader, "shader", "main");
  // Expects compilation success with zero warnings.
  EXPECT_TRUE(CompilationResultIsSuccess(comp.result()));
  EXPECT_EQ(0u, shaderc_result_get_num_warnings(comp.result()));
  // Expects the number of errors to be zero.
  EXPECT_EQ(0u, shaderc_result_get_num_errors(comp.result()));
}

TEST_F(CompileStringTest, ErrorTypeUnknownShaderStage) {
  // The shader kind/stage can not be determined, the error type field should
  // indicate the error type is shaderc_shader_kind_error.
  Compilation comp(compiler_.get_compiler_handle(), kMinimalShader,
                   shaderc_glsl_infer_from_source, "shader", "main");
  EXPECT_EQ(shaderc_compilation_status_invalid_stage,
            shaderc_result_get_compilation_status(comp.result()));
}
TEST_F(CompileStringTest, ErrorTypeCompilationError) {
  // The shader kind is valid, the result object's error type field should
  // indicate this compilaion fails due to compilation errors.
  Compilation comp(compiler_.get_compiler_handle(), kTwoErrorsShader,
                   shaderc_glsl_vertex_shader, "shader", "main");
  EXPECT_EQ(shaderc_compilation_status_compilation_error,
            shaderc_result_get_compilation_status(comp.result()));
}

TEST_F(CompileStringWithOptionsTest, CloneCompilerOptions) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  compile_options_ptr options_(shaderc_compile_options_initialize());
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kMinimalShader,
                                 shaderc_glsl_vertex_shader, options_.get()));
  compile_options_ptr cloned_options(
      shaderc_compile_options_clone(options_.get()));
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kMinimalShader,
                                 shaderc_glsl_vertex_shader,
                                 cloned_options.get()));
}

TEST_F(CompileStringWithOptionsTest, MacroCompileOptions) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  shaderc_compile_options_add_macro_definition(options_.get(), "E", 1u, "main",
                                               4u);
  const std::string kMinimalExpandedShader = "#version 140\nvoid E(){}";
  const std::string kMinimalDoubleExpandedShader = "#version 140\nF E(){}";
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kMinimalExpandedShader,
                                 shaderc_glsl_vertex_shader, options_.get()));
  compile_options_ptr cloned_options(
      shaderc_compile_options_clone(options_.get()));
  // The simplest should still compile with the cloned options.
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kMinimalExpandedShader,
                                 shaderc_glsl_vertex_shader,
                                 cloned_options.get()));
  EXPECT_FALSE(CompilesToValidSpv(compiler_, kMinimalDoubleExpandedShader,
                                  shaderc_glsl_vertex_shader,
                                  cloned_options.get()));

  shaderc_compile_options_add_macro_definition(cloned_options.get(), "F", 1u,
                                               "void", 4u);
  // This should still not work with the original options.
  EXPECT_FALSE(CompilesToValidSpv(compiler_, kMinimalDoubleExpandedShader,
                                  shaderc_glsl_vertex_shader, options_.get()));
  // This should work with the cloned options that have the additional
  // parameter.
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kMinimalDoubleExpandedShader,
                                 shaderc_glsl_vertex_shader,
                                 cloned_options.get()));
}

TEST_F(CompileStringWithOptionsTest, MacroCompileOptionsNotNullTerminated) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  shaderc_compile_options_add_macro_definition(options_.get(), "EFGH", 1u,
                                               "mainnnnnn", 4u);
  const std::string kMinimalExpandedShader = "#version 140\nvoid E(){}";
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kMinimalExpandedShader,
                                 shaderc_glsl_vertex_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest, ValuelessMacroCompileOptionsZeroLength) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  shaderc_compile_options_add_macro_definition(options_.get(), "E", 1u,
                                               "somthing", 0u);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kValuelessPredefinitionShader,
                                 shaderc_glsl_vertex_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest, ValuelessMacroCompileOptionsNullPointer) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  shaderc_compile_options_add_macro_definition(options_.get(), "E", 1u, nullptr,
                                               100u);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kValuelessPredefinitionShader,
                                 shaderc_glsl_vertex_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest, DisassemblyOption) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  // This should work with both the glslang assembly format and the
  // SPIR-V tools assembly format.
  const std::string disassembly_text =
      CompilationOutput(kMinimalShader, shaderc_glsl_vertex_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("Capability Shader"));
  EXPECT_THAT(disassembly_text, HasSubstr("MemoryModel"));
}

TEST_F(CompileStringWithOptionsTest, DisassembleMinimalShader) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  const std::string disassembly_text =
      CompilationOutput(kMinimalShader, shaderc_glsl_vertex_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  for (const auto& substring : kMinimalShaderDisassemblySubstrings) {
    EXPECT_THAT(disassembly_text, HasSubstr(substring));
  }
}

TEST_F(CompileStringWithOptionsTest, ForcedVersionProfileCorrectStd) {
  // Forces the version and profile to 450core, which fixes the missing
  // #version.
  shaderc_compile_options_set_forced_version_profile(options_.get(), 450,
                                                     shaderc_profile_core);
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kCoreVertShaderWithoutVersion,
                                 shaderc_glsl_vertex_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       ForcedVersionProfileCorrectStdClonedOptions) {
  // Forces the version and profile to 450core, which fixes the missing
  // #version.
  shaderc_compile_options_set_forced_version_profile(options_.get(), 450,
                                                     shaderc_profile_core);
  // This mode should be carried to any clone of the original options object.
  compile_options_ptr cloned_options(
      shaderc_compile_options_clone(options_.get()));
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kCoreVertShaderWithoutVersion,
                                 shaderc_glsl_vertex_shader,
                                 cloned_options.get()));
}

TEST_F(CompileStringWithOptionsTest, ForcedVersionProfileInvalidModule) {
  // Forces the version and profile to 310es, while the source module is invalid
  // for this version of GLSL. Compilation should fail.
  shaderc_compile_options_set_forced_version_profile(options_.get(), 310,
                                                     shaderc_profile_es);
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_THAT(CompilationErrors(kCoreVertShaderWithoutVersion,
                                shaderc_glsl_vertex_shader, options_.get()),
              HasSubstr("error: 'gl_ClipDistance' : undeclared identifier\n"));
}

TEST_F(CompileStringWithOptionsTest, ForcedVersionProfileConflictingStd) {
  // Forces the version and profile to 450core, which is in conflict with the
  // #version in shader.
  shaderc_compile_options_set_forced_version_profile(options_.get(), 450,
                                                     shaderc_profile_core);
  const std::string kVertexShader =
      std::string("#version 310 es\n") + kCoreVertShaderWithoutVersion;
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_THAT(CompilationWarnings(kVertexShader, shaderc_glsl_vertex_shader,
                                  options_.get()),
              HasSubstr("warning: (version, profile) forced to be (450, core), "
                        "while in source code it is (310, es)\n"));
}

TEST_F(CompileStringWithOptionsTest, ForcedVersionProfileUnknownVersionStd) {
  // Forces the version and profile to 4242core, which is an unknown version.
  shaderc_compile_options_set_forced_version_profile(
      options_.get(), 4242 /*unknown version*/, shaderc_profile_core);
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  // Warning message should complain about the unknown version.
  //
  // Also, Glslang errors out on unkown versions, due to commit:
  // https://github.com/KhronosGroup/glslang/commit/9353f1afab8d1c2b1811c6acd807675128eaabc5
  const auto errs = CompilationErrors(
      kMinimalShader, shaderc_glsl_vertex_shader, options_.get());
  EXPECT_THAT(
      errs, HasSubstr("warning: (version, profile) forced to be (4242, core), "
                      "while in source code it is (140, none)\n"));
  EXPECT_THAT(errs, HasSubstr("error: version not supported\n"));
}

TEST_F(CompileStringWithOptionsTest, ForcedVersionProfileVersionsBefore150) {
  // Versions before 150 do not allow a profile token, shaderc_profile_none
  // should be passed down as the profile parameter.
  shaderc_compile_options_set_forced_version_profile(options_.get(), 140,
                                                     shaderc_profile_none);
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_TRUE(CompilationSuccess(kMinimalShaderWithoutVersion,
                                 shaderc_glsl_vertex_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest, ForcedVersionProfileRedundantProfileStd) {
  // Forces the version and profile to 100core. But versions before 150 do not
  // allow a profile token, compilation should fail.
  shaderc_compile_options_set_forced_version_profile(options_.get(), 100,
                                                     shaderc_profile_core);
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_THAT(CompilationErrors(kMinimalShader, shaderc_glsl_vertex_shader,
                                options_.get()),
              HasSubstr("error: #version: versions before 150 do not allow a "
                        "profile token\n"));
}

TEST_F(CompileStringWithOptionsTest, GenerateDebugInfoBinary) {
  shaderc_compile_options_set_generate_debug_info(options_.get());
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  const std::string binary_output = CompilationOutput(
      kMinimalDebugInfoShader, shaderc_glsl_vertex_shader, options_.get());
  // The binary output should contain the name of the vector (debug_info_sample)
  // null-terminated, as well as the whole original source.
  std::string vector_name("debug_info_sample");
  vector_name.resize(vector_name.size() + 1);
  EXPECT_THAT(binary_output, HasSubstr(vector_name));
  EXPECT_THAT(binary_output, HasSubstr(kMinimalDebugInfoShader));
}

TEST_F(CompileStringWithOptionsTest, GenerateDebugInfoBinaryClonedOptions) {
  shaderc_compile_options_set_generate_debug_info(options_.get());
  compile_options_ptr cloned_options(
      shaderc_compile_options_clone(options_.get()));
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  const std::string binary_output =
      CompilationOutput(kMinimalDebugInfoShader, shaderc_glsl_vertex_shader,
                        cloned_options.get());
  // The binary output should contain the name of the vector (debug_info_sample)
  // null-terminated, as well as the whole original source.
  std::string vector_name("debug_info_sample");
  vector_name.resize(vector_name.size() + 1);
  EXPECT_THAT(binary_output, HasSubstr(vector_name));
  EXPECT_THAT(binary_output, HasSubstr(kMinimalDebugInfoShader));
}

TEST_F(CompileStringWithOptionsTest, GenerateDebugInfoDisassembly) {
  shaderc_compile_options_set_generate_debug_info(options_.get());
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  // Generate assembly text we can compare its output as a string.
  // The disassembly output should contain the name of the vector:
  // debug_info_sample.
  EXPECT_THAT(
      CompilationOutput(kMinimalDebugInfoShader, shaderc_glsl_vertex_shader,
                        options_.get(), OutputType::SpirvAssemblyText),
      HasSubstr("debug_info_sample"));
}

TEST_F(CompileStringWithOptionsTest, CompileAndOptimizeWithLevelZero) {
  shaderc_compile_options_set_optimization_level(
      options_.get(), shaderc_optimization_level_zero);
  const std::string disassembly_text =
      CompilationOutput(kMinimalShader, shaderc_glsl_vertex_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  for (const auto& substring : kMinimalShaderDisassemblySubstrings) {
    EXPECT_THAT(disassembly_text, HasSubstr(substring));
  }
  // Check that we still have debug instructions.
  EXPECT_THAT(disassembly_text, HasSubstr("OpName"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpSource"));
}

TEST_F(CompileStringWithOptionsTest, CompileAndOptimizeWithLevelPerformance) {
  shaderc_compile_options_set_optimization_level(
      options_.get(), shaderc_optimization_level_performance);
  const std::string disassembly_text =
      CompilationOutput(kGlslMultipleFnShader, shaderc_glsl_fragment_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  // Check that we do not have function calls anymore.
  EXPECT_THAT(disassembly_text, Not(HasSubstr("OpFunctionCall")));
}

TEST_F(CompileStringWithOptionsTest, CompileAndOptimizeWithLevelSize) {
  shaderc_compile_options_set_optimization_level(
      options_.get(), shaderc_optimization_level_size);
  const std::string disassembly_text =
      CompilationOutput(kMinimalShader, shaderc_glsl_vertex_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  for (const auto& substring : kMinimalShaderDisassemblySubstrings) {
    EXPECT_THAT(disassembly_text, HasSubstr(substring));
  }
  // Check that we do not have debug instructions.
  EXPECT_THAT(disassembly_text, Not(HasSubstr("OpName")));
  EXPECT_THAT(disassembly_text, Not(HasSubstr("OpSource")));
}

TEST_F(CompileStringWithOptionsTest, CompileAndOptimizeForVulkan10Failure) {
  shaderc_compile_options_set_source_language(options_.get(),
                                              shaderc_source_language_hlsl);
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_0);
  shaderc_compile_options_set_optimization_level(
      options_.get(), shaderc_optimization_level_performance);

  EXPECT_FALSE(CompilesToValidSpv(compiler_, kHlslWaveActiveSumeComputeShader,
                                  shaderc_compute_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest, CompileAndOptimizeForVulkan11Success) {
  shaderc_compile_options_set_source_language(options_.get(),
                                              shaderc_source_language_hlsl);
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_1);
  shaderc_compile_options_set_optimization_level(
      options_.get(), shaderc_optimization_level_performance);

  const std::string disassembly_text = CompilationOutput(
      kHlslWaveActiveSumeComputeShader, shaderc_compute_shader, options_.get(),
      OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpGroupNonUniformIAdd"));
}

TEST_F(CompileStringWithOptionsTest, FollowingOptLevelOverridesPreviousOne) {
  shaderc_compile_options_set_optimization_level(
      options_.get(), shaderc_optimization_level_size);
  // Optimization level settings overridden by
  shaderc_compile_options_set_optimization_level(
      options_.get(), shaderc_optimization_level_zero);
  const std::string disassembly_text =
      CompilationOutput(kMinimalShader, shaderc_glsl_vertex_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  for (const auto& substring : kMinimalShaderDisassemblySubstrings) {
    EXPECT_THAT(disassembly_text, HasSubstr(substring));
  }
  // Check that we still have debug instructions.
  EXPECT_THAT(disassembly_text, HasSubstr("OpName"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpSource"));
}

TEST_F(CompileStringWithOptionsTest,
       GenerateDebugInfoOverridesOptimizationLevel) {
  shaderc_compile_options_set_optimization_level(
      options_.get(), shaderc_optimization_level_size);
  // Optimization level settings overridden by
  shaderc_compile_options_set_generate_debug_info(options_.get());
  const std::string disassembly_text =
      CompilationOutput(kMinimalShader, shaderc_glsl_vertex_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  for (const auto& substring : kMinimalShaderDebugInfoDisassemblySubstrings) {
    EXPECT_THAT(disassembly_text, HasSubstr(substring));
  }
  // Check that we still have debug instructions.
  EXPECT_THAT(disassembly_text, HasSubstr("OpName"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpSource"));
}

TEST_F(CompileStringWithOptionsTest,
       GenerateDebugInfoProhibitsOptimizationLevel) {
  // Setting generate debug info first also works.
  shaderc_compile_options_set_generate_debug_info(options_.get());
  shaderc_compile_options_set_optimization_level(
      options_.get(), shaderc_optimization_level_size);
  const std::string disassembly_text =
      CompilationOutput(kMinimalShader, shaderc_glsl_vertex_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  for (const auto& substring : kMinimalShaderDebugInfoDisassemblySubstrings) {
    EXPECT_THAT(disassembly_text, HasSubstr(substring));
  }
  // Check that we still have debug instructions.
  EXPECT_THAT(disassembly_text, HasSubstr("OpName"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpSource"));
}

TEST_F(CompileStringWithOptionsTest, PreprocessingOnlyOption) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  const std::string kMinimalShaderWithMacro =
      "#version 150\n"
      "#define E main\n"
      "void E(){}\n";
  const std::string preprocessed_text =
      CompilationOutput(kMinimalShaderWithMacro, shaderc_glsl_vertex_shader,
                        options_.get(), OutputType::PreprocessedText);
  EXPECT_THAT(preprocessed_text, HasSubstr("void main() { }"));

  const std::string kMinimalShaderWithMacroCloneOption =
      "#version 150\n"
      "#define E_CLONE_OPTION main\n"
      "void E_CLONE_OPTION(){}\n";
  compile_options_ptr cloned_options(
      shaderc_compile_options_clone(options_.get()));
  const std::string preprocessed_text_cloned_options = CompilationOutput(
      kMinimalShaderWithMacroCloneOption, shaderc_glsl_vertex_shader,
      options_.get(), OutputType::PreprocessedText);
  EXPECT_THAT(preprocessed_text_cloned_options, HasSubstr("void main() { }"));
}

// A shader kind test cases needs: 1) A shader text with or without #pragma
// annotation, 2) shader_kind.
struct ShaderKindTestCase {
  const char* shader_;
  shaderc_shader_kind shader_kind_;
};

// Test the shader kind deduction process. If the shader kind is one of the
// forced ones, the compiler will just try to compile the source code in that
// specified shader kind. If the shader kind is shaderc_glsl_deduce_from_pragma,
// the compiler will determine the shader kind from #pragma annotation in the
// source code and emit error if none such annotation is found. When the shader
// kind is one of the default ones, the compiler will fall back to use the
// specified shader kind if and only if #pragma annoation is not found.

// Valid shader kind settings should generate valid SPIR-V code.
using ValidShaderKind = testing::TestWithParam<ShaderKindTestCase>;

TEST_P(ValidShaderKind, ValidSpvCode) {
  const ShaderKindTestCase& test_case = GetParam();
  Compiler compiler;
  EXPECT_TRUE(
      CompilesToValidSpv(compiler, test_case.shader_, test_case.shader_kind_));
}

INSTANTIATE_TEST_SUITE_P(
    CompileStringTest, ValidShaderKind,
    testing::ValuesIn(std::vector<ShaderKindTestCase>{
        // Valid default shader kinds.
        {kEmpty310ESShader, shaderc_glsl_default_vertex_shader},
        {kEmpty310ESShader, shaderc_glsl_default_fragment_shader},
        {kEmpty310ESShader, shaderc_glsl_default_compute_shader},
        {kGeometryOnlyShader, shaderc_glsl_default_geometry_shader},
        {kTessControlOnlyShader, shaderc_glsl_default_tess_control_shader},
        {kTessEvaluationOnlyShader,
         shaderc_glsl_default_tess_evaluation_shader},
        {kNVMeshShader, shaderc_glsl_default_mesh_shader},
        {kNVTaskShader, shaderc_glsl_default_task_shader},

        // #pragma annotation overrides default shader kinds.
        {kVertexOnlyShaderWithPragma, shaderc_glsl_default_compute_shader},
        {kFragmentOnlyShaderWithPragma, shaderc_glsl_default_vertex_shader},
        {kTessControlOnlyShaderWithPragma,
         shaderc_glsl_default_fragment_shader},
        {kTessEvaluationOnlyShaderWithPragma,
         shaderc_glsl_default_tess_control_shader},
        {kGeometryOnlyShaderWithPragma,
         shaderc_glsl_default_tess_evaluation_shader},
        {kComputeOnlyShaderWithPragma, shaderc_glsl_default_geometry_shader},
        {kNVMeshShaderWithPragma, shaderc_glsl_default_geometry_shader},
        {kNVTaskShaderWithPragma, shaderc_glsl_default_geometry_shader},

        // Infer from source
        {kVertexOnlyShaderWithPragma, shaderc_glsl_infer_from_source},
        {kNVMeshShaderWithPragma, shaderc_glsl_infer_from_source},
        {kNVTaskShaderWithPragma, shaderc_glsl_infer_from_source},

        // Specified non-default shader kind overrides #pragma annotation.
        {kVertexOnlyShaderWithInvalidPragma, shaderc_glsl_vertex_shader},
    }));

using InvalidShaderKind = testing::TestWithParam<ShaderKindTestCase>;

// Invalid shader kind settings should generate errors.
TEST_P(InvalidShaderKind, CompilationShouldFail) {
  const ShaderKindTestCase& test_case = GetParam();
  Compiler compiler;
  EXPECT_FALSE(
      CompilesToValidSpv(compiler, test_case.shader_, test_case.shader_kind_));
}

INSTANTIATE_TEST_SUITE_P(
    CompileStringTest, InvalidShaderKind,
    testing::ValuesIn(std::vector<ShaderKindTestCase>{
        // Invalid default shader kind.
        {kVertexOnlyShader, shaderc_glsl_default_fragment_shader},
        // Sets to deduce shader kind from #pragma, but #pragma is defined in
        // the source code.
        {kVertexOnlyShader, shaderc_glsl_infer_from_source},
        // Invalid #pragma cause errors, even though default shader kind is set
        // to valid shader kind.
        {kVertexOnlyShaderWithInvalidPragma,
         shaderc_glsl_default_vertex_shader},
    }));

// To test file inclusion, use an unordered_map as a fake file system to store
// fake files to be included. The unordered_map represents a filesystem by
// mapping filename (or path) string to the contents of that file as a string.
using FakeFS = std::unordered_map<std::string, std::string>;

// An includer test case needs: 1) A fake file system which is actually an
// unordered_map, so that we can resolve the content given a string. A valid
// fake file system must have one entry with key:'root' to specify the start
// shader file for compilation. 2) An string that we expect to see in the
// compilation output.
class IncluderTestCase {
 public:
  IncluderTestCase(FakeFS fake_fs, std::string expected_substring)
      : fake_fs_(fake_fs), expected_substring_(expected_substring) {
    assert(fake_fs_.find("root") != fake_fs_.end() &&
           "Valid fake file system needs a 'root' file\n");
  }

  const FakeFS& fake_fs() const { return fake_fs_; }
  const std::string& expected_substring() const { return expected_substring_; }

 private:
  FakeFS fake_fs_;
  std::string expected_substring_;
};

// A mock class that simulate an includer. C API needs two function pointers
// each for get including data and release the data. This class defined two
// static functions, which wrap their matching member functions, to be passed to
// libshaderc C API.
class TestIncluder {
 public:
  explicit TestIncluder(const FakeFS& fake_fs)
      : fake_fs_(fake_fs), responses_({}) {}

  // Get path and content from the fake file system.
  shaderc_include_result* GetInclude(const char* filename) {
    responses_.emplace_back(shaderc_include_result{
        filename, strlen(filename), fake_fs_.at(std::string(filename)).c_str(),
        fake_fs_.at(std::string(filename)).size()});
    return &responses_.back();
  }

  // Response data is owned as private property, no need to release explicitly.
  void ReleaseInclude(shaderc_include_result*) {}

  // Wrapper for the corresponding member function.
  static shaderc_include_result* GetIncluderResponseWrapper(
      void* user_data, const char* filename, int, const char* includer,
      size_t include_depth) {
    return static_cast<TestIncluder*>(user_data)->GetInclude(filename);
  }

  // Wrapper for the corresponding member function.
  static void ReleaseIncluderResponseWrapper(void* user_data,
                                             shaderc_include_result* data) {
    return static_cast<TestIncluder*>(user_data)->ReleaseInclude(data);
  }

 private:
  // Includer response data is stored as private property.
  const FakeFS& fake_fs_;
  std::vector<shaderc_include_result> responses_;
};

using IncluderTests = testing::TestWithParam<IncluderTestCase>;

// Parameterized tests for includer.
TEST_P(IncluderTests, SetIncluderCallbacks) {
  const IncluderTestCase& test_case = GetParam();
  const FakeFS& fs = test_case.fake_fs();
  const std::string& shader = fs.at("root");
  TestIncluder includer(fs);
  Compiler compiler;
  compile_options_ptr options(shaderc_compile_options_initialize());
  shaderc_compile_options_set_include_callbacks(
      options.get(), TestIncluder::GetIncluderResponseWrapper,
      TestIncluder::ReleaseIncluderResponseWrapper, &includer);

  const Compilation comp(compiler.get_compiler_handle(), shader,
                         shaderc_glsl_vertex_shader, "shader", "main",
                         options.get(), OutputType::PreprocessedText);
  // Checks the existence of the expected string.
  EXPECT_THAT(shaderc_result_get_bytes(comp.result()),
              HasSubstr(test_case.expected_substring()));
}

TEST_P(IncluderTests, SetIncluderCallbacksClonedOptions) {
  const IncluderTestCase& test_case = GetParam();
  const FakeFS& fs = test_case.fake_fs();
  const std::string& shader = fs.at("root");
  TestIncluder includer(fs);
  Compiler compiler;
  compile_options_ptr options(shaderc_compile_options_initialize());
  shaderc_compile_options_set_include_callbacks(
      options.get(), TestIncluder::GetIncluderResponseWrapper,
      TestIncluder::ReleaseIncluderResponseWrapper, &includer);

  // Cloned options should have all the settings.
  compile_options_ptr cloned_options(
      shaderc_compile_options_clone(options.get()));

  const Compilation comp(compiler.get_compiler_handle(), shader,
                         shaderc_glsl_vertex_shader, "shader", "main",
                         cloned_options.get(), OutputType::PreprocessedText);
  // Checks the existence of the expected string.
  EXPECT_THAT(shaderc_result_get_bytes(comp.result()),
              HasSubstr(test_case.expected_substring()));
}

INSTANTIATE_TEST_SUITE_P(CompileStringTest, IncluderTests,
                         testing::ValuesIn(std::vector<IncluderTestCase>{
                             IncluderTestCase(
                                 // Fake file system.
                                 {
                                     {"root",
                                      "#version 150\n"
                                      "void foo() {}\n"
                                      "#include \"path/to/file_1\"\n"},
                                     {"path/to/file_1", "content of file_1\n"},
                                 },
                                 // Expected output.
                                 "#line 0 \"path/to/file_1\"\n"
                                 " content of file_1\n"
                                 "#line 3"),
                             IncluderTestCase(
                                 // Fake file system.
                                 {{"root",
                                   "#version 150\n"
                                   "void foo() {}\n"
                                   "#include \"path/to/file_1\"\n"},
                                  {"path/to/file_1",
                                   "#include \"path/to/file_2\"\n"
                                   "content of file_1\n"},
                                  {"path/to/file_2", "content of file_2\n"}},
                                 // Expected output.
                                 "#line 0 \"path/to/file_1\"\n"
                                 "#line 0 \"path/to/file_2\"\n"
                                 " content of file_2\n"
                                 "#line 1 \"path/to/file_1\"\n"
                                 " content of file_1\n"
                                 "#line 3"),

                         }));

TEST_F(CompileStringWithOptionsTest, WarningsOnLine) {
  // Some versions of Glslang will return an error, some will return just
  // warnings.
  EXPECT_THAT(
      CompilationMessages(kDeprecatedAttributeShader,
                          shaderc_glsl_vertex_shader, options_.get()),
      HasSubstr(":2: warning: attribute deprecated in version 130; may be "
                "removed in future release\n"));
}

TEST_F(CompileStringWithOptionsTest, WarningsOnLineAsErrors) {
  shaderc_compile_options_set_warnings_as_errors(options_.get());
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_THAT(
      CompilationErrors(kDeprecatedAttributeShader, shaderc_glsl_vertex_shader,
                        options_.get()),
      HasSubstr(":2: error: attribute deprecated in version 130; may be "
                "removed in future release\n"));
}

TEST_F(CompileStringWithOptionsTest, SuppressWarningsOnLine) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  shaderc_compile_options_set_suppress_warnings(options_.get());
  EXPECT_THAT(
      CompilationMessages(kDeprecatedAttributeShader,
                          shaderc_glsl_vertex_shader, options_.get()),
      Not(HasSubstr(":2: warning: attribute deprecated in version 130; may be "
                    "removed in future release\n")));
}

TEST_F(CompileStringWithOptionsTest, GlobalWarnings) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  shaderc_compile_options_set_forced_version_profile(options_.get(), 400,
                                                     shaderc_profile_core);
  EXPECT_THAT(CompilationWarnings(kMinimalUnknownVersionShader,
                                  shaderc_glsl_vertex_shader, options_.get()),
              HasSubstr("(version, profile) forced to be (400, core),"
                        " while in source code it is (550, none)\n"));
}

TEST_F(CompileStringWithOptionsTest, GlobalWarningsAsErrors) {
  shaderc_compile_options_set_warnings_as_errors(options_.get());
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  shaderc_compile_options_set_forced_version_profile(options_.get(), 400,
                                                     shaderc_profile_core);
  EXPECT_THAT(CompilationErrors(kMinimalUnknownVersionShader,
                                shaderc_glsl_vertex_shader, options_.get()),
              HasSubstr("(version, profile) forced to be (400, core),"
                        " while in source code it is (550, none)\n"));
}

TEST_F(CompileStringWithOptionsTest, SuppressGlobalWarnings) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  shaderc_compile_options_set_suppress_warnings(options_.get());
  shaderc_compile_options_set_forced_version_profile(options_.get(), 400,
                                                     shaderc_profile_core);
  EXPECT_EQ("",
            CompilationWarnings(kMinimalUnknownVersionShader,
                                shaderc_glsl_vertex_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       SuppressWarningsModeFirstOverridesWarningsAsErrorsMode) {
  // Sets suppress-warnings mode first, then sets warnings-as-errors mode.
  // suppress-warnings mode should override warnings-as-errors mode.
  shaderc_compile_options_set_suppress_warnings(options_.get());
  shaderc_compile_options_set_warnings_as_errors(options_.get());
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());

  // Warnings on particular lines should be inhibited.
  Compilation comp_line(compiler_.get_compiler_handle(),
                        kDeprecatedAttributeShader, shaderc_glsl_vertex_shader,
                        "shader", "main", options_.get());
  EXPECT_EQ(0u, shaderc_result_get_num_warnings(comp_line.result()));

  // Global warnings should be inhibited.
  Compilation comp_global(
      compiler_.get_compiler_handle(), kMinimalUnknownVersionShader,
      shaderc_glsl_vertex_shader, "shader", "main", options_.get());
  EXPECT_EQ(0u, shaderc_result_get_num_warnings(comp_global.result()));
}

TEST_F(CompileStringWithOptionsTest,
       SuppressWarningsModeSecondOverridesWarningsAsErrorsMode) {
  // Sets suppress-warnings mode first, then sets warnings-as-errors mode.
  // suppress-warnings mode should override warnings-as-errors mode.
  shaderc_compile_options_set_warnings_as_errors(options_.get());
  shaderc_compile_options_set_suppress_warnings(options_.get());
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());

  // Warnings on particular lines should be inhibited.
  Compilation comp_line(compiler_.get_compiler_handle(),
                        kDeprecatedAttributeShader, shaderc_glsl_vertex_shader,
                        "shader", "main", options_.get());
  EXPECT_EQ(0u, shaderc_result_get_num_warnings(comp_line.result()));

  // Global warnings should be inhibited.
  Compilation comp_global(
      compiler_.get_compiler_handle(), kMinimalUnknownVersionShader,
      shaderc_glsl_vertex_shader, "shader", "main", options_.get());
  EXPECT_EQ(0u, shaderc_result_get_num_warnings(comp_global.result()));
}

TEST_F(CompileStringWithOptionsTest, IfDefCompileOption) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  shaderc_compile_options_add_macro_definition(options_.get(), "E", 1u, nullptr,
                                               0u);
  const std::string kMinimalExpandedShader =
      "#version 140\n"
      "#ifdef E\n"
      "void main(){}\n"
      "#else\n"
      "#error\n"
      "#endif";
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kMinimalExpandedShader,
                                 shaderc_glsl_vertex_shader, options_.get()));
}

TEST_F(
    CompileStringWithOptionsTest,
    TargetEnvRespectedWhenCompilingOpenGLCompatibilityShaderToBinaryAndAlwaysFails) {
  // Glslang does not support generating SPIR-V for compatibility profile
  // shaders.

  EXPECT_FALSE(CompilesToValidSpv(compiler_, kOpenGLCompatibilityFragmentShader,
                                  shaderc_glsl_fragment_shader,
                                  options_.get()));

  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_opengl_compat, 0);
  EXPECT_FALSE(CompilesToValidSpv(compiler_, kOpenGLCompatibilityFragmentShader,
                                  shaderc_glsl_fragment_shader,
                                  options_.get()));

  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_opengl, 0);
  EXPECT_FALSE(CompilesToValidSpv(compiler_, kOpenGLCompatibilityFragmentShader,
                                  shaderc_glsl_fragment_shader,
                                  options_.get()));

  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan, 0);
  EXPECT_FALSE(CompilesToValidSpv(compiler_, kOpenGLCompatibilityFragmentShader,
                                  shaderc_glsl_fragment_shader,
                                  options_.get()));
}

TEST_F(CompileStringWithOptionsTest, CompilingFailsWhenTargetingOpenGLCompat) {
  // Confirm that kOpenGLVertexShader fails when targeting OpenGL
  // compatibility profile.

  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_opengl_compat, 0);
  EXPECT_FALSE(CompilesToValidSpv(compiler_, kOpenGLVertexShader,
                                  shaderc_glsl_vertex_shader, options_.get()));
  const std::string errors = CompilationErrors(
      kOpenGLVertexShader, shaderc_glsl_vertex_shader, options_.get());
  EXPECT_EQ(errors, "error: OpenGL compatibility profile is not supported");
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingOpenGLCoreShaderToBinary) {
  // Confirm that kOpenGLVertexShader compiles when targeting OpenGL core
  // profile.

  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_opengl, 0);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kOpenGLVertexShader,
                                 shaderc_glsl_vertex_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_0ShaderToVulkan1_0Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_0);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderComputeBarrier,
                                 shaderc_glsl_compute_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_0ShaderToVulkan1_1Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_1);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderComputeBarrier,
                                 shaderc_glsl_compute_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_1ShaderToVulkan1_0Fails) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_0);
  EXPECT_FALSE(CompilesToValidSpv(compiler_, kGlslShaderComputeSubgroupBarrier,
                                  shaderc_glsl_compute_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_1ShaderToVulkan1_1Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_1);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderComputeSubgroupBarrier,
                                 shaderc_glsl_compute_shader, options_.get()));
}

// Simple Vulkan 1.2 tests
TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_0ShaderToVulkan1_2Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_2);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderComputeBarrier,
                                 shaderc_glsl_compute_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_1ShaderToVulkan1_2Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_2);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderComputeSubgroupBarrier,
                                 shaderc_glsl_compute_shader, options_.get()));
}

// Simple Vulkan 1.3 tests
TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_0ShaderToVulkan1_3Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_3);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderComputeBarrier,
                                 shaderc_glsl_compute_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_1ShaderToVulkan1_3Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_3);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderComputeSubgroupBarrier,
                                 shaderc_glsl_compute_shader, options_.get()));
}

// Simple Vulkan 1.4 tests
TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_0ShaderToVulkan1_4Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_4);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderComputeBarrier,
                                 shaderc_glsl_compute_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_1ShaderToVulkan1_4Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_4);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderComputeSubgroupBarrier,
                                 shaderc_glsl_compute_shader, options_.get()));
}

// task shader
TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_0TaskShaderToVulkan1_0Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_0);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderTaskBarrier,
                                 shaderc_glsl_task_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_0TaskShaderToVulkan1_1Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_1);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderTaskBarrier,
                                 shaderc_glsl_task_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_1TaskShaderToVulkan1_0Fails) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_0);
  EXPECT_FALSE(CompilesToValidSpv(compiler_, kGlslShaderTaskSubgroupBarrier,
                                  shaderc_glsl_task_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_1TaskShaderToVulkan1_1Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_1);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderTaskSubgroupBarrier,
                                 shaderc_glsl_task_shader, options_.get()));
}

// mesh shader
TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_0MeshShaderToVulkan1_0Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_0);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderMeshBarrier,
                                 shaderc_glsl_mesh_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_0MeshShaderToVulkan1_1Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_1);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderMeshBarrier,
                                 shaderc_glsl_mesh_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_1MeshShaderToVulkan1_0Fails) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_0);
  EXPECT_FALSE(CompilesToValidSpv(compiler_, kGlslShaderMeshSubgroupBarrier,
                                  shaderc_glsl_mesh_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       TargetEnvRespectedWhenCompilingVulkan1_1MeshShaderToVulkan1_1Succeeds) {
  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan,
                                         shaderc_env_version_vulkan_1_1);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kGlslShaderMeshSubgroupBarrier,
                                 shaderc_glsl_mesh_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest,
       DISABLED_TargetEnvIgnoredWhenPreprocessing) {
  // This test is disabled since some versions of glslang may refuse to compile
  // very old shaders to SPIR-V with OpenGL target. Re-enable and rewrite this
  // test once we have a differential set of environments to test.
  const auto output_type = OutputType::PreprocessedText;

  EXPECT_TRUE(CompilationSuccess(kOpenGLCompatibilityFragmentShader,
                                 shaderc_glsl_fragment_shader, options_.get(),
                                 output_type));

  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_opengl_compat, 0);
  EXPECT_TRUE(CompilationSuccess(kOpenGLCompatibilityFragmentShader,
                                 shaderc_glsl_fragment_shader, options_.get(),
                                 output_type));

  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_opengl, 0);
  EXPECT_TRUE(CompilationSuccess(kOpenGLCompatibilityFragmentShader,
                                 shaderc_glsl_fragment_shader, options_.get(),
                                 output_type));

  shaderc_compile_options_set_target_env(options_.get(),
                                         shaderc_target_env_vulkan, 0);
  EXPECT_TRUE(CompilationSuccess(kOpenGLCompatibilityFragmentShader,
                                 shaderc_glsl_fragment_shader, options_.get(),
                                 output_type));
}

TEST_F(CompileStringTest, ShaderKindRespected) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  const std::string kVertexShader =
      "#version 140\nvoid main(){ gl_Position = vec4(0);}";
  EXPECT_TRUE(CompilationSuccess(kVertexShader, shaderc_glsl_vertex_shader));
  EXPECT_FALSE(CompilationSuccess(kVertexShader, shaderc_glsl_fragment_shader));
}

TEST_F(CompileStringTest, ErrorsReported) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  EXPECT_THAT(CompilationErrors("int f(){return wrongname;}",
                                shaderc_glsl_vertex_shader),
              HasSubstr("wrongname"));
}

#ifndef SHADERC_DISABLE_THREADED_TESTS
TEST_F(CompileStringTest, MultipleThreadsCalling) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  bool results[10];
  std::vector<std::thread> threads;
  for (auto& r : results) {
    threads.emplace_back([&r, this]() {
      r = CompilationSuccess("#version 140\nvoid main(){}",
                             shaderc_glsl_vertex_shader);
    });
  }
  for (auto& t : threads) {
    t.join();
  }
  EXPECT_THAT(results, Each(true));
}
#endif

TEST_F(CompileKindsTest, Vertex) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  const std::string kVertexShader =
      "#version 140\nvoid main(){ gl_Position = vec4(0);}";
  EXPECT_TRUE(CompilationSuccess(kVertexShader, shaderc_glsl_vertex_shader));
}

TEST_F(CompileKindsTest, Fragment) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  const std::string kFragShader =
      "#version 140\nvoid main(){ gl_FragColor = vec4(0);}";
  EXPECT_TRUE(CompilationSuccess(kFragShader, shaderc_glsl_fragment_shader));
}

TEST_F(CompileKindsTest, Compute) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  const std::string kCompShader =
      R"(#version 310 es
       void main() {}
  )";
  EXPECT_TRUE(CompilationSuccess(kCompShader, shaderc_glsl_compute_shader));
}

TEST_F(CompileKindsTest, Geometry) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  const std::string kGeoShader =

      R"(#version 310 es
       #extension GL_OES_geometry_shader : enable
       layout(points) in;
       layout(points, max_vertices=1) out;
       void main() {
         gl_Position = vec4(1.0);
         EmitVertex();
         EndPrimitive();
       }
  )";
  EXPECT_TRUE(CompilationSuccess(kGeoShader, shaderc_glsl_geometry_shader));
}

TEST_F(CompileKindsTest, TessControl) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  const std::string kTessControlShader =
      R"(#version 310 es
       #extension GL_OES_tessellation_shader : enable
       layout(vertices=1) out;
       void main() {}
  )";
  EXPECT_TRUE(
      CompilationSuccess(kTessControlShader, shaderc_glsl_tess_control_shader));
}

TEST_F(CompileKindsTest, TessEvaluation) {
  ASSERT_NE(nullptr, compiler_.get_compiler_handle());
  const std::string kTessEvaluationShader =
      R"(#version 310 es
       #extension GL_OES_tessellation_shader : enable
       layout(triangles, equal_spacing, ccw) in;
       void main() {
         gl_Position = vec4(gl_TessCoord, 1.0);
       }
  )";
  EXPECT_TRUE(CompilationSuccess(kTessEvaluationShader,
                                 shaderc_glsl_tess_evaluation_shader));
}

// A test case for ParseVersionProfileTest needs: 1) the input string, 2)
// expected parsing results, including 'succeed' flag, version value, and
// profile enum.
struct ParseVersionProfileTestCase {
  ParseVersionProfileTestCase(
      const std::string& input_string, bool expected_succeed,
      int expected_version = 0,
      shaderc_profile expected_profile = shaderc_profile_none)
      : input_string_(input_string),
        expected_succeed_(expected_succeed),
        expected_version_(expected_version),
        expected_profile_(expected_profile) {}
  std::string input_string_;
  bool expected_succeed_;
  int expected_version_;
  shaderc_profile expected_profile_;
};

// Test for a helper function to parse version and profile from string.
using ParseVersionProfileTest =
    testing::TestWithParam<ParseVersionProfileTestCase>;

TEST_P(ParseVersionProfileTest, FromNullTerminatedString) {
  const ParseVersionProfileTestCase& test_case = GetParam();
  int version;
  shaderc_profile profile;
  bool succeed = shaderc_parse_version_profile(test_case.input_string_.c_str(),
                                               &version, &profile);
  EXPECT_EQ(test_case.expected_succeed_, succeed);
  // check the return version and profile only when the parsing succeeds.
  if (succeed) {
    EXPECT_EQ(test_case.expected_version_, version);
    EXPECT_EQ(test_case.expected_profile_, profile);
  }
}

INSTANTIATE_TEST_SUITE_P(
    HelperMethods, ParseVersionProfileTest,
    testing::ValuesIn(std::vector<ParseVersionProfileTestCase>{
        // Valid version profiles
        ParseVersionProfileTestCase("450core", true, 450, shaderc_profile_core),
        ParseVersionProfileTestCase("450compatibility", true, 450,
                                    shaderc_profile_compatibility),
        ParseVersionProfileTestCase("310es", true, 310, shaderc_profile_es),
        ParseVersionProfileTestCase("100", true, 100, shaderc_profile_none),

        // Invalid version profiles, the expected_version and expected_profile
        // doesn't matter as they won't be checked if the tests pass correctly.
        ParseVersionProfileTestCase("totally_wrong", false),
        ParseVersionProfileTestCase("111core", false),
        ParseVersionProfileTestCase("450wrongprofile", false),
        ParseVersionProfileTestCase("", false),
    }));

TEST_F(CompileStringTest, NullSourceNameFailsCompilingToBinary) {
  EXPECT_THAT(CompilationErrors(kEmpty310ESShader, shaderc_glsl_vertex_shader,
                                nullptr, OutputType::SpirvBinary, nullptr),
              HasSubstr("Input file name string was null."));
}

TEST_F(CompileStringTest, NullSourceNameFailsCompilingToAssemblyText) {
  EXPECT_THAT(
      CompilationErrors(kEmpty310ESShader, shaderc_glsl_vertex_shader, nullptr,
                        OutputType::SpirvAssemblyText, nullptr),
      HasSubstr("Input file name string was null."));
}

TEST_F(CompileStringTest, NullSourceNameFailsCompilingToPreprocessedText) {
  EXPECT_THAT(CompilationErrors(kEmpty310ESShader, shaderc_glsl_vertex_shader,
                                nullptr, OutputType::PreprocessedText, nullptr),
              HasSubstr("Input file name string was null."));
}

const char kGlslVertexShader[] =
    "#version 140\nvoid main(){ gl_Position = vec4(0);}";

const char kHlslVertexShader[] =
    "float4 EntryPoint(uint index : SV_VERTEXID) : SV_POSITION\n"
    "{ return float4(1.0, 2.0, 3.0, 4.0); }";

TEST_F(CompileStringTest, LangGlslOnGlslVertexSucceeds) {
  shaderc_compile_options_set_source_language(options_.get(),
                                              shaderc_source_language_glsl);
  EXPECT_TRUE(CompilationSuccess(kGlslVertexShader, shaderc_glsl_vertex_shader,
                                 options_.get()));
}

TEST_F(CompileStringTest, LangGlslOnHlslVertexFails) {
  shaderc_compile_options_set_source_language(options_.get(),
                                              shaderc_source_language_glsl);
  EXPECT_FALSE(CompilationSuccess(kHlslVertexShader, shaderc_glsl_vertex_shader,
                                  options_.get()));
}

TEST_F(CompileStringTest, LangHlslOnGlslVertexFails) {
  shaderc_compile_options_set_source_language(options_.get(),
                                              shaderc_source_language_hlsl);
  EXPECT_FALSE(CompilationSuccess(kGlslVertexShader, shaderc_glsl_vertex_shader,
                                  options_.get()));
}

TEST_F(CompileStringTest, LangHlslOnHlslVertexSucceeds) {
  shaderc_compile_options_set_source_language(options_.get(),
                                              shaderc_source_language_hlsl);
  EXPECT_TRUE(CompilationSuccess(kHlslVertexShader, shaderc_glsl_vertex_shader,
                                 options_.get()));
}

TEST(EntryPointTest,
     LangGlslOnHlslVertexSucceedsButAssumesEntryPointNameIsMain) {
  Compiler compiler;
  Options options;
  auto compilation =
      Compilation(compiler.get_compiler_handle(), kGlslVertexShader,
                  shaderc_glsl_vertex_shader, "shader", "blah blah blah",
                  options.get(), OutputType::SpirvAssemblyText);

  EXPECT_THAT(shaderc_result_get_bytes(compilation.result()),
              HasSubstr("OpEntryPoint Vertex %main \"main\""))
      << std::string(shaderc_result_get_bytes(compilation.result()));
}

TEST(EntryPointTest, LangHlslOnHlslVertexSucceedsWithGivenEntryPointName) {
  Compiler compiler;
  Options options;
  shaderc_compile_options_set_source_language(options.get(),
                                              shaderc_source_language_hlsl);
  auto compilation =
      Compilation(compiler.get_compiler_handle(), kHlslVertexShader,
                  shaderc_glsl_vertex_shader, "shader", "EntryPoint",
                  options.get(), OutputType::SpirvAssemblyText);

  EXPECT_THAT(shaderc_result_get_bytes(compilation.result()),
              HasSubstr("OpEntryPoint Vertex %EntryPoint \"EntryPoint\""))
      << std::string(shaderc_result_get_bytes(compilation.result()));
}

// Returns a fragment shader accessing a texture with the given
// offset.
std::string ShaderWithTexOffset(int offset) {
  std::ostringstream oss;
  oss << "#version 450\n"
         "layout (binding=0) uniform sampler1D tex;\n"
         "void main() { vec4 x = textureOffset(tex, 1.0, "
      << offset << "); }\n";
  return oss.str();
}

// Ensure compilation is sensitive to limit setting.  Sample just
// two particular limits.
TEST_F(CompileStringTest, LimitsTexelOffsetDefault) {
  EXPECT_FALSE(CompilationSuccess(ShaderWithTexOffset(-9).c_str(),
                                  shaderc_glsl_fragment_shader,
                                  options_.get()));
  EXPECT_TRUE(CompilationSuccess(ShaderWithTexOffset(-8).c_str(),
                                 shaderc_glsl_fragment_shader, options_.get()));
  EXPECT_TRUE(CompilationSuccess(ShaderWithTexOffset(7).c_str(),
                                 shaderc_glsl_fragment_shader, options_.get()));
  EXPECT_FALSE(CompilationSuccess(ShaderWithTexOffset(8).c_str(),
                                  shaderc_glsl_fragment_shader,
                                  options_.get()));
}

TEST_F(CompileStringTest, LimitsTexelOffsetLowerMinimum) {
  shaderc_compile_options_set_limit(
      options_.get(), shaderc_limit_min_program_texel_offset, -99);
  EXPECT_FALSE(CompilationSuccess(ShaderWithTexOffset(-100).c_str(),
                                  shaderc_glsl_fragment_shader,
                                  options_.get()));
  EXPECT_TRUE(CompilationSuccess(ShaderWithTexOffset(-99).c_str(),
                                 shaderc_glsl_fragment_shader, options_.get()));
}

TEST_F(CompileStringTest, LimitsTexelOffsetHigherMaximum) {
  shaderc_compile_options_set_limit(options_.get(),
                                    shaderc_limit_max_program_texel_offset, 10);
  EXPECT_TRUE(CompilationSuccess(ShaderWithTexOffset(10).c_str(),
                                 shaderc_glsl_fragment_shader, options_.get()));
  EXPECT_FALSE(CompilationSuccess(ShaderWithTexOffset(11).c_str(),
                                  shaderc_glsl_fragment_shader,
                                  options_.get()));
}

TEST_F(CompileStringWithOptionsTest, UniformsWithoutBindingsFailCompilation) {
  const std::string errors =
      CompilationErrors(kShaderWithUniformsWithoutBindings,
                        shaderc_glsl_vertex_shader, options_.get());
  EXPECT_THAT(errors,
              HasSubstr("sampler/texture/image requires layout(binding=X)"));
}

TEST_F(CompileStringWithOptionsTest,
       UniformsWithoutBindingsOptionSetAutoBindingsAssignsBindings) {
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  const std::string disassembly_text = CompilationOutput(
      kShaderWithUniformsWithoutBindings, shaderc_glsl_vertex_shader,
      options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_tex Binding 0"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_sam Binding 1"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_img Binding 2"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_imbuf Binding 3"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_ubo Binding 4"));
}

TEST_F(CompileStringWithOptionsTest, AutoBindUniformsOptionsSurvivesCloning) {
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  compile_options_ptr cloned_options(
      shaderc_compile_options_clone(options_.get()));
  const std::string disassembly_text = CompilationOutput(
      kShaderWithUniformsWithoutBindings, shaderc_glsl_vertex_shader,
      cloned_options.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_tex Binding 0"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_sam Binding 1"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_img Binding 2"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_imbuf Binding 3"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_ubo Binding 4"));
}

TEST_F(CompileStringWithOptionsTest,
       SetBindingBaseForTextureAdjustsTextureBindingsOnly) {
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  shaderc_compile_options_set_binding_base(options_.get(),
                                           shaderc_uniform_kind_texture, 44);
  const std::string disassembly_text = CompilationOutput(
      kShaderWithUniformsWithoutBindings, shaderc_glsl_vertex_shader,
      options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_tex Binding 44"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_sam Binding 0"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_img Binding 1"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_imbuf Binding 2"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_ubo Binding 3"));
}

TEST_F(CompileStringWithOptionsTest,
       SetBindingBaseForSamplerAdjustsSamplerBindingsOnly) {
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  shaderc_compile_options_set_binding_base(options_.get(),
                                           shaderc_uniform_kind_sampler, 44);
  const std::string disassembly_text = CompilationOutput(
      kShaderWithUniformsWithoutBindings, shaderc_glsl_vertex_shader,
      options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_tex Binding 0"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_sam Binding 44"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_img Binding 1"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_imbuf Binding 2"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_ubo Binding 3"));
}

TEST_F(CompileStringWithOptionsTest,
       SetBindingBaseForImageAdjustsImageBindingsOnly) {
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  shaderc_compile_options_set_binding_base(options_.get(),
                                           shaderc_uniform_kind_image, 44);
  const std::string disassembly_text = CompilationOutput(
      kShaderWithUniformsWithoutBindings, shaderc_glsl_vertex_shader,
      options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_tex Binding 0"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_sam Binding 1"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_img Binding 44"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_imbuf Binding 45"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_ubo Binding 2"));
}

TEST_F(CompileStringWithOptionsTest,
       SetBindingBaseForBufferAdjustsBufferBindingsOnly) {
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  shaderc_compile_options_set_binding_base(options_.get(),
                                           shaderc_uniform_kind_buffer, 44);
  const std::string disassembly_text = CompilationOutput(
      kShaderWithUniformsWithoutBindings, shaderc_glsl_vertex_shader,
      options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_tex Binding 0"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_sam Binding 1"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_img Binding 2"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_imbuf Binding 3"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_ubo Binding 44"));
}

TEST_F(CompileStringWithOptionsTest, SetBindingBaseSurvivesCloning) {
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  shaderc_compile_options_set_binding_base(options_.get(),
                                           shaderc_uniform_kind_texture, 40);
  shaderc_compile_options_set_binding_base(options_.get(),
                                           shaderc_uniform_kind_sampler, 50);
  shaderc_compile_options_set_binding_base(options_.get(),
                                           shaderc_uniform_kind_image, 60);
  shaderc_compile_options_set_binding_base(options_.get(),
                                           shaderc_uniform_kind_buffer, 70);
  compile_options_ptr cloned_options(
      shaderc_compile_options_clone(options_.get()));
  const std::string disassembly_text = CompilationOutput(
      kShaderWithUniformsWithoutBindings, shaderc_glsl_vertex_shader,
      cloned_options.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_tex Binding 40"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_sam Binding 50"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_img Binding 60"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_imbuf Binding 61"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_ubo Binding 70"));
}

TEST(Compiler, IncludeWithoutOptionsReturnsValidError) {
  auto compiler = shaderc_compiler_initialize();
  const char source[] = "#version 450\n#include \"no where\"";
  auto result = shaderc_compile_into_spv(compiler, source, strlen(source),
                                         shaderc_glsl_vertex_shader, "file",
                                         "main", nullptr);
  EXPECT_EQ(shaderc_compilation_status_compilation_error,
            shaderc_result_get_compilation_status(result));
  EXPECT_THAT(shaderc_result_get_error_message(result),
              HasSubstr("error: '#include' : #error unexpected include "
                        "directive for header name: no where"));

  shaderc_result_release(result);
  shaderc_compiler_release(compiler);
}

TEST_F(
    CompileStringWithOptionsTest,
    SetBindingBaseForTextureForVertexAdjustsTextureBindingsOnlyCompilingAsVertex) {
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  shaderc_compile_options_set_binding_base_for_stage(
      options_.get(), shaderc_vertex_shader, shaderc_uniform_kind_texture, 100);
  const std::string disassembly_text = CompilationOutput(
      kShaderWithUniformsWithoutBindings, shaderc_vertex_shader, options_.get(),
      OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_tex Binding 100"))
      << disassembly_text;
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_sam Binding 0"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_img Binding 1"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_imbuf Binding 2"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_ubo Binding 3"));
}

TEST_F(CompileStringWithOptionsTest,
       SetBindingBaseForTextureForVertexIgnoredWhenCompilingAsFragment) {
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  // This is ignored since we're compiling as a different stage.
  shaderc_compile_options_set_binding_base_for_stage(
      options_.get(), shaderc_vertex_shader, shaderc_uniform_kind_texture, 100);
  const std::string disassembly_text = CompilationOutput(
      kShaderWithUniformsWithoutBindings, shaderc_fragment_shader,
      options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_tex Binding 0"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_sam Binding 1"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_img Binding 2"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_imbuf Binding 3"));
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorate %my_ubo Binding 4"));
}

TEST_F(CompileStringWithOptionsTest, GlslDefaultPackingUsed) {
  const std::string disassembly_text =
      CompilationOutput(kGlslShaderWeirdPacking, shaderc_vertex_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpMemberDecorate %B 1 Offset 16"));
}

TEST_F(CompileStringWithOptionsTest, HlslOffsetsOptionDisableRespected) {
  shaderc_compile_options_set_hlsl_offsets(options_.get(), false);
  const std::string disassembly_text =
      CompilationOutput(kGlslShaderWeirdPacking, shaderc_vertex_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpMemberDecorate %B 1 Offset 16"));
}

TEST_F(CompileStringWithOptionsTest, HlslOffsetsOptionEnableRespected) {
  shaderc_compile_options_set_hlsl_offsets(options_.get(), true);
  const std::string disassembly_text =
      CompilationOutput(kGlslShaderWeirdPacking, shaderc_vertex_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpMemberDecorate %B 1 Offset 4"));
}

TEST_F(CompileStringWithOptionsTest, HlslFunctionality1OffByDefault) {
  shaderc_compile_options_set_source_language(options_.get(),
                                              shaderc_source_language_hlsl);
  // The counter should automatically get a binding.
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  const std::string disassembly_text =
      CompilationOutput(kHlslShaderWithCounterBuffer, shaderc_fragment_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, Not(HasSubstr("OpDecorateString")))
      << disassembly_text;
}

TEST_F(CompileStringWithOptionsTest, HlslFunctionality1Respected) {
  shaderc_compile_options_set_source_language(options_.get(),
                                              shaderc_source_language_hlsl);
  shaderc_compile_options_set_hlsl_functionality1(options_.get(), true);
  // The counter should automatically get a binding.
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  const std::string disassembly_text =
      CompilationOutput(kHlslShaderWithCounterBuffer, shaderc_fragment_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorateString"));
}

TEST_F(CompileStringWithOptionsTest, HlslFunctionality1SurvivesCloning) {
  shaderc_compile_options_set_source_language(options_.get(),
                                              shaderc_source_language_hlsl);
  shaderc_compile_options_set_hlsl_functionality1(options_.get(), true);
  // The counter should automatically get a binding.
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  compile_options_ptr cloned_options(
      shaderc_compile_options_clone(options_.get()));
  const std::string disassembly_text =
      CompilationOutput(kHlslShaderWithCounterBuffer, shaderc_fragment_shader,
                        cloned_options.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpDecorateString"));
}

TEST_F(CompileStringWithOptionsTest, HlslFlexibleMemoryLayoutAllowed) {
  shaderc_compile_options_set_source_language(options_.get(),
                                              shaderc_source_language_hlsl);
  shaderc_compile_options_set_optimization_level(
      options_.get(), shaderc_optimization_level_performance);
  // There is no way to set the counter's binding, so set it automatically.
  // See https://github.com/KhronosGroup/glslang/issues/1616
  shaderc_compile_options_set_auto_bind_uniforms(options_.get(), true);
  EXPECT_TRUE(CompilesToValidSpv(compiler_, kHlslMemLayoutResourceSelect,
                                 shaderc_fragment_shader, options_.get()));
}

TEST_F(CompileStringWithOptionsTest, ClampMapsToFClampByDefault) {
  const std::string disassembly_text =
      CompilationOutput(kGlslShaderWithClamp, shaderc_fragment_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpExtInst %v4float %1 FClamp"));
}

TEST_F(CompileStringWithOptionsTest, ClampMapsToNClampWithNanClamp) {
  shaderc_compile_options_set_nan_clamp(options_.get(), true);
  const std::string disassembly_text =
      CompilationOutput(kGlslShaderWithClamp, shaderc_fragment_shader,
                        options_.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpExtInst %v4float %1 NClamp"));
}

TEST_F(CompileStringWithOptionsTest, NanClampSurvivesCloning) {
  shaderc_compile_options_set_nan_clamp(options_.get(), true);
  compile_options_ptr cloned_options(
      shaderc_compile_options_clone(options_.get()));
  const std::string disassembly_text =
      CompilationOutput(kGlslShaderWithClamp, shaderc_fragment_shader,
                        cloned_options.get(), OutputType::SpirvAssemblyText);
  EXPECT_THAT(disassembly_text, HasSubstr("OpExtInst %v4float %1 NClamp"));
}

}  // anonymous namespace
