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

#include "src/tint/lang/wgsl/inspector/inspector.h"

#include <algorithm>
#include <memory>
#include <unordered_set>

#include "gmock/gmock.h"
#include "src/tint/lang/core/fluent_types.h"  // IWYU pragma: export
#include "src/tint/lang/wgsl/inspector/entry_point.h"
#include "src/tint/lang/wgsl/reader/reader.h"

using namespace tint::core::number_suffixes;  // NOLINT
using namespace tint::core::fluent_types;     // NOLINT

namespace tint::inspector {
namespace {

/// Utility class for building programs in inspector tests
class TestHelper {
  public:
    /// Create a Program with Inspector from the provided WGSL shader.
    /// Should only be called once per test and cannot be used with Build.
    /// @param shader a WGSL shader
    /// @returns a reference to the Inspector for the built Program.
    Inspector& Initialize(std::string shader) {
        if (inspector_) {
            return *inspector_;
        }

        wgsl::reader::Options options;
        options.allowed_features = wgsl::AllowedFeatures::Everything();
        file_ = std::make_unique<Source::File>("test", shader);
        program_ = std::make_unique<Program>(wgsl::reader::Parse(file_.get(), options));
        if (!program_->IsValid()) {
            ADD_FAILURE() << program_->Diagnostics();
        }
        inspector_ = std::make_unique<Inspector>(*program_);
        return *inspector_;
    }

  protected:
    /// File created from input shader and used to create Program.
    std::unique_ptr<Source::File> file_;
    /// Program created by this runner.
    std::unique_ptr<Program> program_;
    /// Inspector for |program_|
    std::unique_ptr<Inspector> inspector_;
};

class InspectorTest : public TestHelper, public testing::Test {};

template <typename T>
class InspectorTestWithParam : public TestHelper, public testing::TestWithParam<T> {};

using InspectorGetEntryPointTest = InspectorTest;
using InspectorOverridesTest = InspectorTest;
using InspectorGetConstantNameToIdMapTest = InspectorTest;
using InspectorGetResourceBindingsTest = InspectorTest;
using InspectorGetResourceTableInfoTest = InspectorTest;
using InspectorGetUsedExtensionNamesTest = InspectorTest;
using InspectorGetBlendSrcTest = InspectorTest;
using InspectorSubgroupMatrixTest = InspectorTest;
using InspectorTextureTest = InspectorTest;

// This is a catch all for shaders that have demonstrated regressions/crashes in the wild.
using InspectorRegressionTest = InspectorTest;

TEST_F(InspectorGetEntryPointTest, NoFunctions) {
    auto* src = R"(
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    EXPECT_EQ(0u, result.size());
}

TEST_F(InspectorGetEntryPointTest, NoEntryPoints) {
    auto* src = R"(
fn foo() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    EXPECT_EQ(0u, result.size());
}

TEST_F(InspectorGetEntryPointTest, OneEntryPoint) {
    auto* src = R"(
@fragment fn foo() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ("foo", result[0].name);
    EXPECT_EQ(PipelineStage::kFragment, result[0].stage);
}

TEST_F(InspectorGetEntryPointTest, MultipleEntryPoints) {
    auto* src = R"(
@fragment fn foo() {}
@compute @workgroup_size(1i) fn bar() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(2u, result.size());
    EXPECT_EQ("foo", result[0].name);
    EXPECT_EQ(PipelineStage::kFragment, result[0].stage);
    EXPECT_EQ("bar", result[1].name);
    EXPECT_EQ(PipelineStage::kCompute, result[1].stage);
}

TEST_F(InspectorGetEntryPointTest, MixFunctionsAndEntryPoints) {
    auto* src = R"(
fn func() {}

@compute @workgroup_size(1i)
fn foo() { func(); }

@fragment fn bar() { func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    EXPECT_FALSE(inspector.has_error());

    ASSERT_EQ(2u, result.size());
    EXPECT_EQ("foo", result[0].name);
    EXPECT_EQ(PipelineStage::kCompute, result[0].stage);
    EXPECT_EQ("bar", result[1].name);
    EXPECT_EQ(PipelineStage::kFragment, result[1].stage);
}

TEST_F(InspectorGetEntryPointTest, DefaultWorkgroupSize) {
    auto* src = R"(
@compute @workgroup_size(8i, 2i, 1i)  fn foo() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    auto workgroup_size = result[0].workgroup_size;
    ASSERT_TRUE(workgroup_size.has_value());
    EXPECT_EQ(8u, workgroup_size->x);
    EXPECT_EQ(2u, workgroup_size->y);
    EXPECT_EQ(1u, workgroup_size->z);
}

// Test that immediate_data_size is zero if there are no immediate data.
TEST_F(InspectorGetEntryPointTest, ImmediateDataSizeNone) {
    auto* src = R"(
@fragment fn foo() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(0u, result[0].immediate_data_size);
}

// Test that immediate_data_size is 4 (bytes) if there is a single F32 immediate data.
TEST_F(InspectorGetEntryPointTest, ImmediateDataSizeOneWord) {
    auto* src = R"(
var<immediate> pc: f32;

@fragment fn foo() { _ = pc; }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(4u, result[0].immediate_data_size);
}

// Test that immediate_data_size is 12 (bytes) if there is a struct containing one
// each of i32, f32 and u32.
TEST_F(InspectorGetEntryPointTest, ImmediateDataSizeThreeWords) {
    auto* src = R"(
struct S {
  a: i32,
  b: f32,
  c: u32,
}
var<immediate> pc : S;

@fragment fn foo() { _ = pc; }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(12u, result[0].immediate_data_size);
}

// Test that immediate_data_size is 4 (bytes) if there are two immediate data,
// one used by the entry point containing an f32, and one unused by the entry
// point containing a struct of size 12 bytes.
TEST_F(InspectorGetEntryPointTest, ImmediateDataSizeTwoConstants) {
    auto* src = R"(
struct S {
  a: i32,
  b: f32,
  c: u32,
}
var<immediate> unused : S;
var<immediate> pc: f32;

@fragment fn foo() { _ = pc; }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());

    // Check that the result only includes the single f32 immediate data.
    EXPECT_EQ(4u, result[0].immediate_data_size);
}

TEST_F(InspectorGetEntryPointTest, NonDefaultWorkgroupSize) {
    auto* src = R"(
@compute @workgroup_size(8i, 2i, 1i)
fn foo() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    auto workgroup_size = result[0].workgroup_size;
    ASSERT_TRUE(workgroup_size.has_value());
    EXPECT_EQ(8u, workgroup_size->x);
    EXPECT_EQ(2u, workgroup_size->y);
    EXPECT_EQ(1u, workgroup_size->z);
}

TEST_F(InspectorGetEntryPointTest, NoInOutVariables) {
    auto* src = R"(
fn func() {}
@fragment fn foo() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(0u, result[0].input_variables.size());
    EXPECT_EQ(0u, result[0].output_variables.size());
}

std::string GetType(ComponentType component, CompositionType composition) {
    std::string comp;
    switch (component) {
        case ComponentType::kF32:
            comp = "f32";
            break;
        case ComponentType::kI32:
            comp = "i32";
            break;
        case ComponentType::kU32:
            comp = "u32";
            break;
        case ComponentType::kF16:
            comp = "f16";
            break;
        case ComponentType::kUnknown:
            TINT_UNREACHABLE();
    }

    uint32_t n;
    switch (composition) {
        case CompositionType::kScalar:
            return comp;
        case CompositionType::kVec2:
            n = 2;
            break;
        case CompositionType::kVec3:
            n = 3;
            break;
        case CompositionType::kVec4:
            n = 4;
            break;
        default:
            TINT_UNREACHABLE();
    }
    return std::string("vec") + std::to_string(n) + "<" + comp + ">";
}

typedef std::tuple<inspector::ComponentType, inspector::CompositionType>
    InspectorGetEntryPointComponentAndCompositionTestParams;
using InspectorGetEntryPointComponentAndCompositionTest =
    InspectorTestWithParam<InspectorGetEntryPointComponentAndCompositionTestParams>;

TEST_P(InspectorGetEntryPointComponentAndCompositionTest, Test) {
    ComponentType component;
    CompositionType composition;
    std::tie(component, composition) = GetParam();

    std::string src = "";
    if (component == ComponentType::kF16) {
        src += "enable f16;\n";
    }

    auto tint_type = GetType(component, composition);
    src += R"(
@fragment
fn foo(@location(0u) @interpolate(flat) in_var: )" +
           tint_type + ") -> @location(0) " + tint_type + R"( {
  return in_var;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());

    ASSERT_EQ(1u, result[0].input_variables.size());
    EXPECT_EQ("in_var", result[0].input_variables[0].name);
    EXPECT_EQ("in_var", result[0].input_variables[0].variable_name);
    EXPECT_EQ(0u, result[0].input_variables[0].attributes.location);
    EXPECT_EQ(component, result[0].input_variables[0].component_type);

    ASSERT_EQ(1u, result[0].output_variables.size());
    EXPECT_EQ("<retval>", result[0].output_variables[0].name);
    EXPECT_EQ("", result[0].output_variables[0].variable_name);
    EXPECT_EQ(0u, result[0].output_variables[0].attributes.location);
    EXPECT_EQ(component, result[0].output_variables[0].component_type);
}
INSTANTIATE_TEST_SUITE_P(InspectorGetEntryPointTest,
                         InspectorGetEntryPointComponentAndCompositionTest,
                         testing::Combine(testing::Values(ComponentType::kF32,
                                                          ComponentType::kI32,
                                                          ComponentType::kU32,
                                                          ComponentType::kF16),
                                          testing::Values(CompositionType::kScalar,
                                                          CompositionType::kVec2,
                                                          CompositionType::kVec3,
                                                          CompositionType::kVec4)));

TEST_F(InspectorGetEntryPointTest, MultipleInOutVariables) {
    auto* src = R"(
enable chromium_experimental_framebuffer_fetch;

@fragment
fn foo(@location(0u) @interpolate(flat) in_var0: u32, @location(1u) @interpolate(flat) in_var1: u32, @color(2u) in_var4: u32) -> @location(0u) u32 {
  return in_var0;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());

    ASSERT_EQ(3u, result[0].input_variables.size());
    EXPECT_EQ("in_var0", result[0].input_variables[0].name);
    EXPECT_EQ("in_var0", result[0].input_variables[0].variable_name);
    EXPECT_EQ(0u, result[0].input_variables[0].attributes.location);
    EXPECT_EQ(std::nullopt, result[0].input_variables[0].attributes.color);
    EXPECT_EQ(InterpolationType::kFlat, result[0].input_variables[0].interpolation_type);
    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[0].component_type);
    EXPECT_EQ("in_var1", result[0].input_variables[1].name);
    EXPECT_EQ("in_var1", result[0].input_variables[1].variable_name);
    EXPECT_EQ(1u, result[0].input_variables[1].attributes.location);
    EXPECT_EQ(std::nullopt, result[0].input_variables[1].attributes.color);
    EXPECT_EQ(InterpolationType::kFlat, result[0].input_variables[1].interpolation_type);
    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[1].component_type);
    EXPECT_EQ("in_var4", result[0].input_variables[2].name);
    EXPECT_EQ("in_var4", result[0].input_variables[2].variable_name);
    EXPECT_EQ(std::nullopt, result[0].input_variables[2].attributes.location);
    EXPECT_EQ(2u, result[0].input_variables[2].attributes.color);
    EXPECT_EQ(InterpolationType::kPerspective, result[0].input_variables[2].interpolation_type);
    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[2].component_type);

    ASSERT_EQ(1u, result[0].output_variables.size());
    EXPECT_EQ("<retval>", result[0].output_variables[0].name);
    EXPECT_EQ("", result[0].output_variables[0].variable_name);
    EXPECT_EQ(0u, result[0].output_variables[0].attributes.location);
    EXPECT_EQ(std::nullopt, result[0].output_variables[0].attributes.color);
    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
}

TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutVariables) {
    auto* src = R"(
@fragment
fn foo(@location(0u) @interpolate(flat) in_var_foo: u32) -> @location(0) u32 {
  return in_var_foo;
}

@fragment
fn bar(@location(0u) @interpolate(flat) in_var_bar: u32) -> @location(1u) u32 {
  return in_var_bar;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(2u, result.size());

    ASSERT_EQ(1u, result[0].input_variables.size());
    EXPECT_EQ("in_var_foo", result[0].input_variables[0].name);
    EXPECT_EQ("in_var_foo", result[0].input_variables[0].variable_name);
    EXPECT_EQ(0u, result[0].input_variables[0].attributes.location);
    EXPECT_EQ(InterpolationType::kFlat, result[0].input_variables[0].interpolation_type);
    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[0].component_type);

    ASSERT_EQ(1u, result[0].output_variables.size());
    EXPECT_EQ("<retval>", result[0].output_variables[0].name);
    EXPECT_EQ("", result[0].output_variables[0].variable_name);
    EXPECT_EQ(0u, result[0].output_variables[0].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);

    ASSERT_EQ(1u, result[1].input_variables.size());
    EXPECT_EQ("in_var_bar", result[1].input_variables[0].name);
    EXPECT_EQ("in_var_bar", result[1].input_variables[0].variable_name);
    EXPECT_EQ(0u, result[1].input_variables[0].attributes.location);
    EXPECT_EQ(InterpolationType::kFlat, result[1].input_variables[0].interpolation_type);
    EXPECT_EQ(ComponentType::kU32, result[1].input_variables[0].component_type);

    ASSERT_EQ(1u, result[1].output_variables.size());
    EXPECT_EQ("<retval>", result[1].output_variables[0].name);
    EXPECT_EQ("", result[1].output_variables[0].variable_name);
    EXPECT_EQ(1u, result[1].output_variables[0].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[1].output_variables[0].component_type);
}

TEST_F(InspectorGetEntryPointTest, BuiltInsNotStageVariables) {
    auto* src = R"(
@fragment
fn foo(@builtin(sample_index) in_var0: u32, @location(0u) in_var1: f32) -> @builtin(frag_depth) f32 {
  return in_var1;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());

    ASSERT_EQ(1u, result[0].input_variables.size());
    EXPECT_EQ("in_var1", result[0].input_variables[0].name);
    EXPECT_EQ("in_var1", result[0].input_variables[0].variable_name);
    EXPECT_EQ(0u, result[0].input_variables[0].attributes.location);
    EXPECT_EQ(ComponentType::kF32, result[0].input_variables[0].component_type);

    ASSERT_EQ(0u, result[0].output_variables.size());
}

TEST_F(InspectorGetEntryPointTest, InOutStruct) {
    auto* src = R"(
struct Interface {
  @location(0u) @interpolate(flat) a: u32,
  @location(1u) @interpolate(flat) b: u32,
}
@fragment
fn foo(param: Interface) -> Interface {
  return param;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());

    ASSERT_EQ(2u, result[0].input_variables.size());
    EXPECT_EQ("param.a", result[0].input_variables[0].name);
    EXPECT_EQ("a", result[0].input_variables[0].variable_name);
    EXPECT_EQ(0u, result[0].input_variables[0].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[0].component_type);
    EXPECT_EQ("param.b", result[0].input_variables[1].name);
    EXPECT_EQ("b", result[0].input_variables[1].variable_name);
    EXPECT_EQ(1u, result[0].input_variables[1].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[1].component_type);

    ASSERT_EQ(2u, result[0].output_variables.size());
    EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
    EXPECT_EQ("a", result[0].output_variables[0].variable_name);
    EXPECT_EQ(0u, result[0].output_variables[0].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
    EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
    EXPECT_EQ("b", result[0].output_variables[1].variable_name);
    EXPECT_EQ(1u, result[0].output_variables[1].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[1].component_type);
}

TEST_F(InspectorGetEntryPointTest, MultipleEntryPointsInOutSharedStruct) {
    auto* src = R"(
struct Interface {
  @location(0u) @interpolate(flat) a: u32,
  @location(1u) @interpolate(flat) b: u32,
}
@fragment
fn foo() -> Interface {
  return Interface();
}

@fragment
fn bar(param: Interface) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(2u, result.size());

    ASSERT_EQ(0u, result[0].input_variables.size());

    ASSERT_EQ(2u, result[0].output_variables.size());
    EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
    EXPECT_EQ("a", result[0].output_variables[0].variable_name);
    EXPECT_EQ(0u, result[0].output_variables[0].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
    EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
    EXPECT_EQ("b", result[0].output_variables[1].variable_name);
    EXPECT_EQ(1u, result[0].output_variables[1].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[1].component_type);

    ASSERT_EQ(2u, result[1].input_variables.size());
    EXPECT_EQ("param.a", result[1].input_variables[0].name);
    EXPECT_EQ("a", result[1].input_variables[0].variable_name);
    EXPECT_EQ(0u, result[1].input_variables[0].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[1].input_variables[0].component_type);
    EXPECT_EQ("param.b", result[1].input_variables[1].name);
    EXPECT_EQ("b", result[1].input_variables[1].variable_name);
    EXPECT_EQ(1u, result[1].input_variables[1].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[1].input_variables[1].component_type);

    ASSERT_EQ(0u, result[1].output_variables.size());
}

TEST_F(InspectorGetEntryPointTest, MixInOutVariablesAndStruct) {
    auto* src = R"(
struct struct_a {
  @location(0u) @interpolate(flat) a: u32,
  @location(1u) @interpolate(flat) b: u32,
}
struct struct_b {
  @location(2u) @interpolate(flat) a: u32,
}

@fragment
fn foo(param_a: struct_a, param_b: struct_b, @location(3u) param_c: f32, @location(4u) param_d: f32) -> struct_a {
  return param_a;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());

    ASSERT_EQ(5u, result[0].input_variables.size());
    EXPECT_EQ("param_a.a", result[0].input_variables[0].name);
    EXPECT_EQ("a", result[0].input_variables[0].variable_name);
    EXPECT_EQ(0u, result[0].input_variables[0].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[0].component_type);
    EXPECT_EQ("param_a.b", result[0].input_variables[1].name);
    EXPECT_EQ("b", result[0].input_variables[1].variable_name);
    EXPECT_EQ(1u, result[0].input_variables[1].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[1].component_type);
    EXPECT_EQ("param_b.a", result[0].input_variables[2].name);
    EXPECT_EQ("a", result[0].input_variables[2].variable_name);
    EXPECT_EQ(2u, result[0].input_variables[2].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[0].input_variables[2].component_type);
    EXPECT_EQ("param_c", result[0].input_variables[3].name);
    EXPECT_EQ("param_c", result[0].input_variables[3].variable_name);
    EXPECT_EQ(3u, result[0].input_variables[3].attributes.location);
    EXPECT_EQ(ComponentType::kF32, result[0].input_variables[3].component_type);
    EXPECT_EQ("param_d", result[0].input_variables[4].name);
    EXPECT_EQ("param_d", result[0].input_variables[4].variable_name);
    EXPECT_EQ(4u, result[0].input_variables[4].attributes.location);
    EXPECT_EQ(ComponentType::kF32, result[0].input_variables[4].component_type);

    ASSERT_EQ(2u, result[0].output_variables.size());
    EXPECT_EQ("<retval>.a", result[0].output_variables[0].name);
    EXPECT_EQ("a", result[0].output_variables[0].variable_name);
    EXPECT_EQ(0u, result[0].output_variables[0].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[0].component_type);
    EXPECT_EQ("<retval>.b", result[0].output_variables[1].name);
    EXPECT_EQ("b", result[0].output_variables[1].variable_name);
    EXPECT_EQ(1u, result[0].output_variables[1].attributes.location);
    EXPECT_EQ(ComponentType::kU32, result[0].output_variables[1].component_type);
}

TEST_F(InspectorGetEntryPointTest, OverrideUnreferenced) {
    auto* src = R"(
override foo: f32;

@compute @workgroup_size(1i)
fn ep_func() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(0u, result[0].overrides.size());
}

TEST_F(InspectorGetEntryPointTest, OverrideReferencedByEntryPoint) {
    auto* src = R"(
override foo: f32;

@compute @workgroup_size(1i)
fn ep_func() { _ = foo; }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(1u, result[0].overrides.size());
    EXPECT_EQ("foo", result[0].overrides[0].name);
}

TEST_F(InspectorGetEntryPointTest, OverrideReferencedByCallee) {
    auto* src = R"(
override foo: f32;

fn callee_func() { _ = foo; }

@compute @workgroup_size(1i)
fn ep_func() {
  callee_func();
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(1u, result[0].overrides.size());
    EXPECT_EQ("foo", result[0].overrides[0].name);
}

TEST_F(InspectorGetEntryPointTest, OverrideSomeReferenced) {
    auto* src = R"(
@id(1) override foo: f32;
@id(2) override bar: f32;

fn callee_fn() { _ = foo; }

@compute @workgroup_size(1i)
fn ep_func() {
  callee_fn();
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(1u, result[0].overrides.size());
    EXPECT_EQ("foo", result[0].overrides[0].name);
    EXPECT_EQ(1, result[0].overrides[0].id.value);
}

TEST_F(InspectorGetEntryPointTest, OverrideReferencedIndirectly) {
    auto* src = R"(
override foo: f32;
override bar: f32 = 2 * foo;

@compute @workgroup_size(1i)
fn ep_func() { _ = bar; }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(2u, result[0].overrides.size());
    EXPECT_EQ("bar", result[0].overrides[0].name);
    EXPECT_TRUE(result[0].overrides[0].is_initialized);
    EXPECT_EQ("foo", result[0].overrides[1].name);
    EXPECT_FALSE(result[0].overrides[1].is_initialized);
}

TEST_F(InspectorGetEntryPointTest, OverrideReferencedIndirectly_ViaPrivateInitializer) {
    auto* src = R"(
override foo: f32;
var<private> bar: f32 = 2 * foo;

@compute @workgroup_size(1i)
fn ep_func() { _ = bar; }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(1u, result[0].overrides.size());
    EXPECT_EQ("foo", result[0].overrides[0].name);
    EXPECT_FALSE(result[0].overrides[0].is_initialized);
}

TEST_F(InspectorGetEntryPointTest, OverrideReferencedIndirectly_MultipleEntryPoints) {
    auto* src = R"(
override foo1: f32;
override bar1: f32 = 2 * foo1;
@compute @workgroup_size(1i)
fn ep_func1() { _ = bar1; }

override foo2: f32;
override bar2: f32 = 2 * foo2;
@compute @workgroup_size(1i)
fn ep_func2() { _ = bar2; }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(2u, result.size());

    ASSERT_EQ(2u, result[0].overrides.size());
    EXPECT_EQ("bar1", result[0].overrides[0].name);
    EXPECT_TRUE(result[0].overrides[0].is_initialized);
    EXPECT_EQ("foo1", result[0].overrides[1].name);
    EXPECT_FALSE(result[0].overrides[1].is_initialized);

    ASSERT_EQ(2u, result[1].overrides.size());
    EXPECT_EQ("bar2", result[1].overrides[0].name);
    EXPECT_TRUE(result[1].overrides[0].is_initialized);
    EXPECT_EQ("foo2", result[1].overrides[1].name);
    EXPECT_FALSE(result[1].overrides[1].is_initialized);
}

TEST_F(InspectorGetEntryPointTest, OverrideReferencedByAttribute) {
    auto* src = R"(
override wgsize: u32;
@compute @workgroup_size(wgsize)
fn ep_func() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(1u, result[0].overrides.size());
    EXPECT_EQ("wgsize", result[0].overrides[0].name);
    EXPECT_FALSE(result[0].overrides[0].is_initialized);
}

TEST_F(InspectorGetEntryPointTest, OverrideReferencedByAttributeIndirectly) {
    auto* src = R"(
override foo: u32;
override bar: u32 = 2 * foo;
@compute @workgroup_size(2 * bar)
fn ep_func() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(2u, result[0].overrides.size());
    EXPECT_EQ("bar", result[0].overrides[0].name);
    EXPECT_TRUE(result[0].overrides[0].is_initialized);
    EXPECT_EQ("foo", result[0].overrides[1].name);
    EXPECT_FALSE(result[0].overrides[1].is_initialized);
}

TEST_F(InspectorGetEntryPointTest, OverrideReferencedByArraySize) {
    auto* src = R"(
override size: u32;
var<workgroup> v: array<f32, size>;

@compute @workgroup_size(1i)
fn ep() { _ = v[0]; }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(1u, result[0].overrides.size());
    EXPECT_EQ("size", result[0].overrides[0].name);
    EXPECT_FALSE(result[0].overrides[0].is_initialized);
}

TEST_F(InspectorGetEntryPointTest, OverrideReferencedByArraySizeIndirectly) {
    auto* src = R"(
override foo: u32;
override bar: u32 = 2 * foo;
var<workgroup> v: array<f32, 2 * bar>;

@compute @workgroup_size(1i)
fn ep() { _ = v[0]; }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(2u, result[0].overrides.size());
    EXPECT_EQ("bar", result[0].overrides[0].name);
    EXPECT_TRUE(result[0].overrides[0].is_initialized);
    EXPECT_EQ("foo", result[0].overrides[1].name);
    EXPECT_FALSE(result[0].overrides[1].is_initialized);
}

TEST_F(InspectorGetEntryPointTest, OverrideReferencedByArraySizeViaAlias) {
    auto* src = R"(
override foo: u32;
override bar: u32 = foo;
alias MyArray = array<f32, 2 * bar>;

override zoo: u32;
alias MyArrayUnused = array<f32, 2 * zoo>;

var<workgroup> v: MyArray;

@compute @workgroup_size(1i)
fn ep() { _ = v[0]; }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(2u, result[0].overrides.size());
    EXPECT_EQ("bar", result[0].overrides[0].name);
    EXPECT_TRUE(result[0].overrides[0].is_initialized);
    EXPECT_EQ("foo", result[0].overrides[1].name);
    EXPECT_FALSE(result[0].overrides[1].is_initialized);
}

TEST_F(InspectorGetEntryPointTest, OverrideTypes) {
    auto* src = R"(
enable f16;

override bool_var: bool;
override float_var: f32;
override u32_var: u32;
override i32_var: i32;
override f16_var: f16;

fn bool_func() { _ = bool_var; }
fn float_func() { _ = float_var; }
fn u32_func() { _ = u32_var; }
fn i32_func() { _ = i32_var; }
fn f16_func() { _ = f16_var; }

@compute @workgroup_size(1)
fn ep_func() {
  bool_func();
  float_func();
  u32_func();
  i32_func();
  f16_func();
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(5u, result[0].overrides.size());
    EXPECT_EQ("bool_var", result[0].overrides[0].name);
    EXPECT_EQ(inspector::Override::Type::kBool, result[0].overrides[0].type);
    EXPECT_EQ("float_var", result[0].overrides[1].name);
    EXPECT_EQ(inspector::Override::Type::kFloat32, result[0].overrides[1].type);
    EXPECT_EQ("u32_var", result[0].overrides[2].name);
    EXPECT_EQ(inspector::Override::Type::kUint32, result[0].overrides[2].type);
    EXPECT_EQ("i32_var", result[0].overrides[3].name);
    EXPECT_EQ(inspector::Override::Type::kInt32, result[0].overrides[3].type);
    EXPECT_EQ("f16_var", result[0].overrides[4].name);
    EXPECT_EQ(inspector::Override::Type::kFloat16, result[0].overrides[4].type);
}

TEST_F(InspectorGetEntryPointTest, OverrideInitialized) {
    auto* src = R"(
override foo: f32 = 0f;
@compute @workgroup_size(1i)
fn ep_func() { _ = foo; }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(1u, result[0].overrides.size());
    EXPECT_EQ("foo", result[0].overrides[0].name);
    EXPECT_TRUE(result[0].overrides[0].is_initialized);
}

TEST_F(InspectorGetEntryPointTest, OverrideUninitialized) {
    auto* src = R"(
override foo: f32;
@compute @workgroup_size(1i)
fn ep_func() { _ = foo; }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(1u, result[0].overrides.size());
    EXPECT_EQ("foo", result[0].overrides[0].name);

    EXPECT_FALSE(result[0].overrides[0].is_initialized);
}

TEST_F(InspectorGetEntryPointTest, OverrideNumericIDSpecified) {
    auto* src = R"(
override foo_no_id: f32;
@id(1234) override foo_id: f32;

fn no_id_func() { _ = foo_no_id; }
fn id_func() { _ = foo_id; }

@compute @workgroup_size(1i)
fn ep_func() {
  no_id_func();
  id_func();
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(2u, result[0].overrides.size());
    EXPECT_EQ("foo_no_id", result[0].overrides[0].name);
    EXPECT_EQ("foo_id", result[0].overrides[1].name);
    EXPECT_EQ(1234, result[0].overrides[1].id.value);

    EXPECT_FALSE(result[0].overrides[0].is_id_specified);
    EXPECT_TRUE(result[0].overrides[1].is_id_specified);
}

TEST_F(InspectorGetEntryPointTest, NonOverrideSkipped) {
    auto* src = R"(
struct foo_type {
  a: i32,
}
@binding(0) @group(0) var<uniform> foo_ub: foo_type;
fn ub_func() { _ = foo_ub.a; }

@fragment
fn ep_func() {
  ub_func();
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(0u, result[0].overrides.size());
}

TEST_F(InspectorGetEntryPointTest, BuiltinNotReferenced) {
    auto* src = R"(
@fragment
fn ep_func() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_FALSE(result[0].input_sample_mask_used);
    EXPECT_FALSE(result[0].output_sample_mask_used);
    EXPECT_FALSE(result[0].front_facing_used);
    EXPECT_FALSE(result[0].sample_index_used);
    EXPECT_FALSE(result[0].num_workgroups_used);
    EXPECT_FALSE(result[0].frag_depth_used);
    EXPECT_FALSE(result[0].fine_derivative_builtin_used);
    EXPECT_FALSE(result[0].primitive_index_used);
    EXPECT_FALSE(result[0].subgroup_invocation_id_used);
    EXPECT_FALSE(result[0].subgroup_size_used);
    EXPECT_FALSE(result[0].global_invocation_index_used);
}

TEST_F(InspectorGetEntryPointTest, InputSampleMaskSimpleReferenced) {
    auto* src = R"(
@fragment
fn ep_func(@builtin(sample_mask) in_var: u32) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].input_sample_mask_used);
}

TEST_F(InspectorGetEntryPointTest, InputSampleMaskStructReferenced) {
    auto* src = R"(
struct in_struct {
  @builtin(sample_mask) inner_position: u32,
}

@fragment
fn ep_func(in_var: in_struct) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].input_sample_mask_used);
}

TEST_F(InspectorGetEntryPointTest, OutputSampleMaskSimpleReferenced) {
    auto* src = R"(
@fragment
fn ep_func(@builtin(sample_mask) in_var: u32) -> @builtin(sample_mask) u32 {
  return in_var;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].output_sample_mask_used);
}

TEST_F(InspectorGetEntryPointTest, OutputSampleMaskStructReferenced) {
    auto* src = R"(
struct out_struct {
  @builtin(sample_mask) inner_sample_mask: u32,
}

@fragment
fn ep_func() -> out_struct {
  var out_var: out_struct;
  return out_var;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].output_sample_mask_used);
}

TEST_F(InspectorGetEntryPointTest, FrontFacingSimpleReferenced) {
    auto* src = R"(
@fragment
fn ep_func(@builtin(front_facing) in_var: bool) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].front_facing_used);
}

TEST_F(InspectorGetEntryPointTest, FrontFacingStructReferenced) {
    auto* src = R"(
struct in_struct {
  @builtin(front_facing) inner_position: bool,
}
@fragment
fn ep_func(in_var: in_struct) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].front_facing_used);
}

TEST_F(InspectorGetEntryPointTest, PrimitiveIndexSimpleReferenced) {
    auto* src = R"(
enable primitive_index;
@fragment
fn ep_func(@builtin(primitive_index) in_var: u32) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].primitive_index_used);
}

TEST_F(InspectorGetEntryPointTest, PrimitiveIndexStructReferenced) {
    auto* src = R"(
enable primitive_index;
struct in_struct {
  @builtin(primitive_index) inner_position: u32,
}
@fragment
fn ep_func(in_var: in_struct) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].primitive_index_used);
}

TEST_F(InspectorGetEntryPointTest, SubgroupInvocationIdSimpleReferenced) {
    auto* src = R"(
enable subgroups;
@fragment
fn ep_func(@builtin(subgroup_invocation_id) in_var: u32) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].subgroup_invocation_id_used);
}

TEST_F(InspectorGetEntryPointTest, SubgroupInvocationIdStructReferenced) {
    auto* src = R"(
enable subgroups;
struct in_struct {
  @builtin(subgroup_invocation_id) inner_position: u32,
}
@fragment
fn ep_func(in_var: in_struct) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].subgroup_invocation_id_used);
}

TEST_F(InspectorGetEntryPointTest, SubgroupSizeSimpleReferenced) {
    auto* src = R"(
enable subgroups;
@fragment
fn ep_func(@builtin(subgroup_size) in_var: u32) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].subgroup_size_used);
}

TEST_F(InspectorGetEntryPointTest, SubgroupSizeStructReferenced) {
    auto* src = R"(
enable subgroups;
struct in_struct {
  @builtin(subgroup_size) inner_position: u32,
}
@fragment
fn ep_func(in_var: in_struct) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].subgroup_size_used);
}

TEST_F(InspectorGetEntryPointTest, GlobalInvocationIndexSimpleReferenced) {
    auto* src = R"(
@compute @workgroup_size(1,1,1)
fn ep_func(@builtin(global_invocation_index) in_var: u32) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].global_invocation_index_used);
    EXPECT_TRUE(result[0].num_workgroups_used);
}

TEST_F(InspectorGetEntryPointTest, GlobalInvocationIndexStructReferenced) {
    auto* src = R"(
struct in_struct {
  @builtin(global_invocation_index) inner_position: u32,
}
@compute @workgroup_size(1,1,1)
fn ep_func(in_var: in_struct) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].global_invocation_index_used);
    EXPECT_TRUE(result[0].num_workgroups_used);
}

TEST_F(InspectorGetEntryPointTest, WorkgroupIndexSimpleReferenced) {
    auto* src = R"(
@compute @workgroup_size(1,1,1)
fn ep_func(@builtin(workgroup_index) in_var: u32) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].workgroup_index_used);
    EXPECT_TRUE(result[0].num_workgroups_used);
}

TEST_F(InspectorGetEntryPointTest, WorkgroupIndexStructReferenced) {
    auto* src = R"(
struct in_struct {
  @builtin(workgroup_index) inner_position: u32,
}
@compute @workgroup_size(1,1,1)
fn ep_func(in_var: in_struct) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].workgroup_index_used);
    EXPECT_TRUE(result[0].num_workgroups_used);
}

TEST_F(InspectorGetEntryPointTest, SampleIndexSimpleReferenced) {
    auto* src = R"(
@fragment
fn ep_func(@builtin(sample_index) in_var: u32) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].sample_index_used);
}

TEST_F(InspectorGetEntryPointTest, SampleIndexStructReferenced) {
    auto* src = R"(
struct in_struct {
  @builtin(sample_index) inner_position: u32,
}

@fragment
fn ep_func(in_var: in_struct) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].sample_index_used);
}

TEST_F(InspectorGetEntryPointTest, NumWorkgroupsSimpleReferenced) {
    auto* src = R"(
@compute @workgroup_size(1i)
fn ep_func(@builtin(num_workgroups) in_var: vec3u) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].num_workgroups_used);
}

TEST_F(InspectorGetEntryPointTest, NumWorkgroupsStructReferenced) {
    auto* src = R"(
struct in_struct {
  @builtin(num_workgroups) inner_position: vec3u,
}

@compute @workgroup_size(1i)
fn ep_func(in_var: in_struct) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].num_workgroups_used);
}

TEST_F(InspectorGetEntryPointTest, FragDepthSimpleReferenced) {
    auto* src = R"(
@fragment
fn ep_func() -> @builtin(frag_depth) f32 {
  return 0f;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].frag_depth_used);
}

TEST_F(InspectorGetEntryPointTest, FragDepthStructReferenced) {
    auto* src = R"(
struct out_struct {
  @builtin(frag_depth) inner_frag_depth: f32,
}
@fragment
fn ep_func() -> out_struct {
  var out_var: out_struct;
  return out_var;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].frag_depth_used);
}

TEST_F(InspectorGetEntryPointTest, DpdxFineDirectlyUsed) {
    auto* src = R"(
@fragment
fn ep_func(@location(0) i: f32) {
  _ = dpdxFine(i);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].fine_derivative_builtin_used);
}

TEST_F(InspectorGetEntryPointTest, DpdxFineTransientlyUsed) {
    auto* src = R"(
fn foo(i: f32) {
  _ = dpdxFine(i);
}

@fragment
fn ep_func(@location(0) i: f32) {
    foo(i);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].fine_derivative_builtin_used);
}

TEST_F(InspectorGetEntryPointTest, DpdyFineDirectlyUsed) {
    auto* src = R"(
@fragment
fn ep_func(@location(0) i: f32) {
  _ = dpdyFine(i);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].fine_derivative_builtin_used);
}

TEST_F(InspectorGetEntryPointTest, DpdyFineTransientlyUsed) {
    auto* src = R"(
fn foo(i: f32) {
  _ = dpdyFine(i);
}

@fragment
fn ep_func(@location(0) i: f32) {
    foo(i);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].fine_derivative_builtin_used);
}

TEST_F(InspectorGetEntryPointTest, FwidthFineDirectlyUsed) {
    auto* src = R"(
@fragment
fn ep_func(@location(0) i: f32) {
  _ = fwidthFine(i);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].fine_derivative_builtin_used);
}

TEST_F(InspectorGetEntryPointTest, FwidthFineTransientlyUsed) {
    auto* src = R"(
fn foo(i: f32) {
  _ = fwidthFine(i);
}

@fragment
fn ep_func(@location(0) i: f32) {
    foo(i);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].fine_derivative_builtin_used);
}

TEST_F(InspectorGetEntryPointTest, ClipDistancesReferenced) {
    auto* src = R"(
enable clip_distances;

struct out_struct {
  @builtin(clip_distances) inner_clip_distances: array<f32, 8>,
  @builtin(position) inner_position: vec4f,
}

@vertex
fn ep_func() -> out_struct {
  var out_var: out_struct;
  return out_var;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].clip_distances_size.has_value());
    EXPECT_EQ(8u, *result[0].clip_distances_size);
}

TEST_F(InspectorGetEntryPointTest, ClipDistancesNotReferenced) {
    auto* src = R"(
struct out_struct {
  @builtin(position) inner_position: vec4f,
}
@vertex
fn ep_func() -> out_struct {
  var out_var : out_struct;
  return out_var;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    EXPECT_FALSE(result[0].clip_distances_size.has_value());
}

TEST_F(InspectorGetEntryPointTest, ImplicitInterpolate) {
    auto* src = R"(
struct in_struct {
  @location(0) struct_inner: f32,
}
@fragment
fn ep_func(in_var: in_struct) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(1u, result[0].input_variables.size());
    EXPECT_EQ(InterpolationType::kPerspective, result[0].input_variables[0].interpolation_type);
    EXPECT_EQ(InterpolationSampling::kCenter, result[0].input_variables[0].interpolation_sampling);
}

TEST_F(InspectorGetEntryPointTest, PixelLocalMemberDefault) {
    auto* src = R"(
@fragment
fn foo() {}
)";
    Inspector& inspector = Initialize(src);
    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(0u, result[0].pixel_local_members.size());
}

TEST_F(InspectorGetEntryPointTest, PixelLocalMemberTypes) {
    auto* src = R"(
enable chromium_experimental_pixel_local;

struct Ure {
  toto: u32,
  titi: f32,
  tata: i32,
  tonton: u32, // Check having the same type multiple times
}

var<pixel_local> pls: Ure;
@fragment fn foo() {  _ = pls; }
)";
    Inspector& inspector = Initialize(src);
    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(4u, result[0].pixel_local_members.size());
    ASSERT_EQ(PixelLocalMemberType::kU32, result[0].pixel_local_members[0]);
    ASSERT_EQ(PixelLocalMemberType::kF32, result[0].pixel_local_members[1]);
    ASSERT_EQ(PixelLocalMemberType::kI32, result[0].pixel_local_members[2]);
    ASSERT_EQ(PixelLocalMemberType::kU32, result[0].pixel_local_members[3]);
}

struct InspectorGetEntryPointInterpolateTestParams {
    std::string in;
    inspector::InterpolationType out_type;
    inspector::InterpolationSampling out_sampling;
};
using InspectorGetEntryPointInterpolateTest =
    InspectorTestWithParam<InspectorGetEntryPointInterpolateTestParams>;

TEST_P(InspectorGetEntryPointInterpolateTest, Test) {
    auto& params = GetParam();
    auto src = R"(
struct in_struct {
  )" + params.in +
               R"( @location(0) struct_inner: f32,
}
@fragment fn ep_func(in_var: in_struct) {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(1u, result[0].input_variables.size());
    EXPECT_EQ(params.out_type, result[0].input_variables[0].interpolation_type);
    EXPECT_EQ(params.out_sampling, result[0].input_variables[0].interpolation_sampling);
}

INSTANTIATE_TEST_SUITE_P(
    InspectorGetEntryPointTest,
    InspectorGetEntryPointInterpolateTest,
    testing::Values(
        InspectorGetEntryPointInterpolateTestParams{"@interpolate(perspective, center)",
                                                    InterpolationType::kPerspective,
                                                    InterpolationSampling::kCenter},
        InspectorGetEntryPointInterpolateTestParams{"@interpolate(perspective, centroid)",
                                                    InterpolationType::kPerspective,
                                                    InterpolationSampling::kCentroid},
        InspectorGetEntryPointInterpolateTestParams{"@interpolate(perspective, sample)",
                                                    InterpolationType::kPerspective,
                                                    InterpolationSampling::kSample},
        InspectorGetEntryPointInterpolateTestParams{"@interpolate(perspective)",
                                                    InterpolationType::kPerspective,
                                                    InterpolationSampling::kCenter},
        InspectorGetEntryPointInterpolateTestParams{"@interpolate(linear, center)",
                                                    InterpolationType::kLinear,
                                                    InterpolationSampling::kCenter},
        InspectorGetEntryPointInterpolateTestParams{"@interpolate(linear, centroid)",
                                                    InterpolationType::kLinear,
                                                    InterpolationSampling::kCentroid},
        InspectorGetEntryPointInterpolateTestParams{"@interpolate(linear, sample)",
                                                    InterpolationType::kLinear,
                                                    InterpolationSampling::kSample},
        InspectorGetEntryPointInterpolateTestParams{
            "@interpolate(linear)", InterpolationType::kLinear, InterpolationSampling::kCenter},
        InspectorGetEntryPointInterpolateTestParams{"@interpolate(flat)", InterpolationType::kFlat,
                                                    InterpolationSampling::kFirst},
        InspectorGetEntryPointInterpolateTestParams{
            "@interpolate(flat, first)", InterpolationType::kFlat, InterpolationSampling::kFirst},
        InspectorGetEntryPointInterpolateTestParams{"@interpolate(flat, either)",
                                                    InterpolationType::kFlat,
                                                    InterpolationSampling::kEither}));

TEST_F(InspectorOverridesTest, NoOverrides) {
    auto* src = R"(
@compute @workgroup_size(1i)
fn ep_func() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.Overrides();
    EXPECT_TRUE(result.empty());
}

TEST_F(InspectorOverridesTest, Multiple) {
    auto* src = R"(
@id(1) override foo: f32;
@id(2) override bar: f32;

fn callee_func() { _ = foo; }
@compute @workgroup_size(1i) fn ep_func() {
  callee_func();
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.Overrides();
    ASSERT_EQ(2u, result.size());

    auto& ep = result[0];
    EXPECT_EQ(ep.name, "foo");
    EXPECT_EQ(ep.id.value, 1);
    EXPECT_FALSE(ep.is_initialized);
    EXPECT_TRUE(ep.is_id_specified);

    ep = result[1];
    EXPECT_EQ(ep.name, "bar");
    EXPECT_EQ(ep.id.value, 2);
    EXPECT_FALSE(ep.is_initialized);
    EXPECT_TRUE(ep.is_id_specified);
}

TEST_F(InspectorGetEntryPointTest, HasTextureFromGetResource) {
    std::string shader = R"(
enable chromium_experimental_resource_table;

const kHouseTexture = 2u;

@fragment fn fs() {
    let td = getResource<texture_depth_2d>(kHouseTexture);
    _ = textureLoad(td, vec2(0), 0);
}
    )";
    Inspector& inspector = Initialize(shader);
    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    // Resource table information doesn't set the flag.
    EXPECT_FALSE(inspector.GetEntryPoint("lfs").has_texture_load_with_depth_texture);
}

TEST_F(InspectorGetEntryPointTest, HasTextureLoadWithDepthTexture) {
    std::string shader = R"(
        @group(0) @binding(0) var td : texture_depth_2d;
        @group(0) @binding(1) var tdm : texture_depth_multisampled_2d;
        @group(0) @binding(2) var t : texture_2d<f32>;
        @group(0) @binding(3) var s : sampler;

        @compute @workgroup_size(1) fn load_texture_depth() {
            _ = textureLoad(td, vec2(0), 0);
        }
        @compute @workgroup_size(1) fn load_texture_depth_multisample() {
            _ = textureLoad(td, vec2(0), 0);
        }
        @compute @workgroup_size(1) fn load_texture_2d() {
            _ = textureLoad(t, vec2(0), 0);
        }
        @fragment fn sample_texture_depth() -> @location(0) u32 {
            _ = textureSample(td, s, vec2(0));
            return 0;
        }
        fn load_texture_depth_arg(tex : texture_depth_2d) {
            _ = textureLoad(tex, vec2(0), 0);
        }
        @compute @workgroup_size(1) fn load_texture_depth_in_function() {
            load_texture_depth_arg(td);
        }
    )";
    Inspector& inspector = Initialize(shader);
    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    EXPECT_TRUE(inspector.GetEntryPoint("load_texture_depth").has_texture_load_with_depth_texture);
    EXPECT_TRUE(inspector.GetEntryPoint("load_texture_depth_multisample")
                    .has_texture_load_with_depth_texture);
    EXPECT_FALSE(inspector.GetEntryPoint("load_texture_2d").has_texture_load_with_depth_texture);
    EXPECT_FALSE(
        inspector.GetEntryPoint("sample_texture_depth").has_texture_load_with_depth_texture);
    EXPECT_TRUE(inspector.GetEntryPoint("load_texture_depth_in_function")
                    .has_texture_load_with_depth_texture);
}

TEST_F(InspectorGetEntryPointTest, HasTextureLoadWithDepthTextureThroughLet) {
    std::string shader = R"(
        @group(0) @binding(0) var td : texture_depth_2d;
        @group(0) @binding(1) var tdm : texture_depth_multisampled_2d;
        @group(0) @binding(2) var t : texture_2d<f32>;
        @group(0) @binding(3) var s : sampler;

        @compute @workgroup_size(1) fn load_texture_depth() {
            let a = td;
            _ = textureLoad(a, vec2(0), 0);
        }
        @compute @workgroup_size(1) fn load_texture_depth_multisample() {
            let a = td;
            _ = textureLoad(a, vec2(0), 0);
        }
        @compute @workgroup_size(1) fn load_texture_2d() {
            let a = t;
            _ = textureLoad(a, vec2(0), 0);
        }
        @fragment fn sample_texture_depth() -> @location(0) u32 {
            let a = td;
            let b = a;
            let c = b;
            _ = textureSample(c, s, vec2(0));
            return 0;
        }
        fn load_texture_depth_arg(tex : texture_depth_2d) {
           let a = tex;
            _ = textureLoad(a, vec2(0), 0);
        }
        @compute @workgroup_size(1) fn load_texture_depth_in_function() {
            let a = td;
            let b = a;
            load_texture_depth_arg(b);
        }
    )";
    Inspector& inspector = Initialize(shader);
    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    EXPECT_TRUE(inspector.GetEntryPoint("load_texture_depth").has_texture_load_with_depth_texture);
    EXPECT_TRUE(inspector.GetEntryPoint("load_texture_depth_multisample")
                    .has_texture_load_with_depth_texture);
    EXPECT_FALSE(inspector.GetEntryPoint("load_texture_2d").has_texture_load_with_depth_texture);
    EXPECT_FALSE(
        inspector.GetEntryPoint("sample_texture_depth").has_texture_load_with_depth_texture);
    EXPECT_TRUE(inspector.GetEntryPoint("load_texture_depth_in_function")
                    .has_texture_load_with_depth_texture);
}

TEST_F(InspectorGetEntryPointTest, HasDepthTextureWithNonComparisonSampler) {
    std::string shader = R"(
        @group(0) @binding(0) var td : texture_depth_2d;
        @group(0) @binding(1) var s : sampler;
        @group(0) @binding(2) var cs : sampler_comparison;

        @fragment fn sample_texture_depth() -> @location(0) u32 {
            _ = textureSample(td, s, vec2(0));
            return 0;
        }
        @fragment fn comparison_sample_texture_depth() -> @location(0) u32 {
            _ = textureSampleCompare(td, cs, vec2(0), 0.5);
            return 0;
        }
        @fragment fn gather_texture_depth() -> @location(0) u32 {
            _ = textureGather(td, s, vec2(0));
            return 0;
        }
        @fragment fn comparison_gather_texture_depth() -> @location(0) u32 {
            _ = textureGatherCompare(td, cs, vec2(0), 0.5);
            return 0;
        }
        @fragment fn sample_level_texture_depth() -> @location(0) u32 {
            _ = textureSampleLevel(td, s, vec2(0), 0);
            return 0;
        }
        @fragment fn comparison_sample_level_texture_depth() -> @location(0) u32 {
            _ = textureSampleCompareLevel(td, cs, vec2(0), 0.5);
            return 0;
        }

        fn sample_texture_depth_arg(tex : texture_depth_2d) {
            _ = textureSample(tex, s, vec2(0));
        }
        @fragment fn sample_texture_depth_in_function() -> @location(0) u32 {
            sample_texture_depth_arg(td);
            return 0;
        }
    )";
    Inspector& inspector = Initialize(shader);
    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    EXPECT_TRUE(inspector.GetEntryPoint("sample_texture_depth")
                    .has_depth_texture_with_non_comparison_sampler);
    EXPECT_FALSE(inspector.GetEntryPoint("comparison_sample_texture_depth")
                     .has_depth_texture_with_non_comparison_sampler);
    EXPECT_TRUE(inspector.GetEntryPoint("gather_texture_depth")
                    .has_depth_texture_with_non_comparison_sampler);
    EXPECT_FALSE(inspector.GetEntryPoint("comparison_gather_texture_depth")
                     .has_depth_texture_with_non_comparison_sampler);
    EXPECT_TRUE(inspector.GetEntryPoint("sample_level_texture_depth")
                    .has_depth_texture_with_non_comparison_sampler);
    EXPECT_FALSE(inspector.GetEntryPoint("comparison_sample_level_texture_depth")
                     .has_depth_texture_with_non_comparison_sampler);
    EXPECT_TRUE(inspector.GetEntryPoint("sample_texture_depth_in_function")
                    .has_depth_texture_with_non_comparison_sampler);
}

TEST_F(InspectorGetConstantNameToIdMapTest, WithAndWithoutIds) {
    auto* src = R"(
@id(1) override v1: f32;
@id(20) override v20: f32;
@id(300) override v300: f32;
override a: f32;
override b: f32;
override c: f32;
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetNamedOverrideIds();
    ASSERT_EQ(6u, result.size());

    ASSERT_TRUE(result.count("v1"));
    EXPECT_EQ(result["v1"].value, 1u);

    ASSERT_TRUE(result.count("v20"));
    EXPECT_EQ(result["v20"].value, 20u);

    ASSERT_TRUE(result.count("v300"));
    EXPECT_EQ(result["v300"].value, 300u);

    ASSERT_TRUE(result.count("a"));
    EXPECT_EQ(result["a"].value, 0);

    ASSERT_TRUE(result.count("b"));
    EXPECT_EQ(result["b"].value, 2);

    ASSERT_TRUE(result.count("c"));
    EXPECT_EQ(result["c"].value, 3);
}

TEST_F(InspectorGetResourceBindingsTest, Empty) {
    auto* src = R"(
@fragment fn ep_func() {}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(0u, result.size());
}

TEST_F(InspectorGetResourceBindingsTest, Simple) {
    auto* src = R"(
struct ub_type {
  a: i32,
}
@group(0) @binding(0) var<uniform> ub_var: ub_type;
fn ub_func() { _ = ub_var.a; }

struct sb_type {
  a: i32,
}
@group(1) @binding(0) var<storage, read_write> sb_var: sb_type;
fn sb_func() { _ = sb_var.a; }

struct rosb_type {
  a: i32,
}
@group(1) @binding(1) var<storage, read> rosb_var: rosb_type;
fn rosb_func() { _ = rosb_var.a; }

@group(2) @binding(0) var s_texture : texture_1d<f32>;
@group(3) @binding(0) var s_var: sampler;
var<private> s_coords: f32;
fn s_func() {
  let sampler_result = textureSample(s_texture, s_var, s_coords);
}

@group(3) @binding(1) var cs_texture : texture_depth_2d;
@group(3) @binding(2) var cs_var: sampler_comparison;
var<private> cs_coords: vec2f;
var<private> cs_depth: f32;
fn cs_func() {
  let sampler_result = textureSampleCompare(cs_texture, cs_var, cs_coords, cs_depth);
}

@group(3) @binding(3) var depth_ms_texture : texture_depth_multisampled_2d;
fn depth_ms_func() {
  _ = depth_ms_texture;
}

@group(4) @binding(0) var st_var: texture_storage_2d<r32uint, write>;
fn st_func() { let dim = textureDimensions(st_var); }

@group(4) @binding(1) var ba_s: binding_array<texture_2d<f32>, 3>;
fn ba_s_func() { let dim = textureDimensions(ba_s[2]); }

@fragment
fn ep_func() {
  ub_func();
  sb_func();
  rosb_func();
  s_func();
  cs_func();
  depth_ms_func();
  st_func();
  ba_s_func();
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(10u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_FALSE(result[0].array_size.has_value());

    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[1].resource_type);
    EXPECT_EQ(1u, result[1].bind_group);
    EXPECT_EQ(0u, result[1].binding);
    EXPECT_FALSE(result[1].array_size.has_value());

    EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, result[2].resource_type);
    EXPECT_EQ(1u, result[2].bind_group);
    EXPECT_EQ(1u, result[2].binding);
    EXPECT_FALSE(result[2].array_size.has_value());

    EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture, result[3].resource_type);
    EXPECT_EQ(2u, result[3].bind_group);
    EXPECT_EQ(0u, result[3].binding);
    EXPECT_FALSE(result[3].array_size.has_value());

    EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[4].resource_type);
    EXPECT_EQ(3u, result[4].bind_group);
    EXPECT_EQ(0u, result[4].binding);
    EXPECT_FALSE(result[4].array_size.has_value());

    EXPECT_EQ(ResourceBinding::ResourceType::kDepthTexture, result[5].resource_type);
    EXPECT_EQ(3u, result[5].bind_group);
    EXPECT_EQ(1u, result[5].binding);
    EXPECT_FALSE(result[5].array_size.has_value());

    EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[6].resource_type);
    EXPECT_EQ(ResourceBinding::SamplerType::kComparison, result[6].sampler_type);
    EXPECT_EQ(3u, result[6].bind_group);
    EXPECT_EQ(2u, result[6].binding);
    EXPECT_FALSE(result[6].array_size.has_value());

    EXPECT_EQ(ResourceBinding::ResourceType::kDepthMultisampledTexture, result[7].resource_type);
    EXPECT_EQ(3u, result[7].bind_group);
    EXPECT_EQ(3u, result[7].binding);
    EXPECT_FALSE(result[7].array_size.has_value());

    EXPECT_EQ(ResourceBinding::ResourceType::kWriteOnlyStorageTexture, result[8].resource_type);
    EXPECT_EQ(4u, result[8].bind_group);
    EXPECT_EQ(0u, result[8].binding);
    EXPECT_FALSE(result[8].array_size.has_value());

    EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture, result[9].resource_type);
    EXPECT_EQ(4u, result[9].bind_group);
    EXPECT_EQ(1u, result[9].binding);
    EXPECT_TRUE(result[9].array_size.has_value());
    EXPECT_EQ(3u, result[9].array_size.value());
}

TEST_F(InspectorGetResourceBindingsTest, TextureFilterable) {
    auto* src = R"(
@group(0) @binding(1) var tex2 : texture_2d<f32>;
@group(0) @binding(3) var tex4 : texture_depth_2d;
@group(0) @binding(4) var tex5 : texture_multisampled_2d<f32>;
@group(0) @binding(5) var tex6 : texture_2d<i32>;

@fragment
fn ep_func() {
    _ = tex2;
    _ = tex4;
    _ = tex5;
    _ = tex6;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(4u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(1u, result[0].binding);
    EXPECT_EQ(inspector::ResourceBinding::SampledKind::kUnknownFilterable, result[0].sampled_kind);

    EXPECT_EQ(ResourceBinding::ResourceType::kDepthTexture, result[1].resource_type);
    EXPECT_EQ(0u, result[1].bind_group);
    EXPECT_EQ(3u, result[1].binding);

    EXPECT_EQ(ResourceBinding::ResourceType::kMultisampledTexture, result[2].resource_type);
    EXPECT_EQ(0u, result[2].bind_group);
    EXPECT_EQ(4u, result[2].binding);
    EXPECT_EQ(inspector::ResourceBinding::SampledKind::kUnfilterable, result[2].sampled_kind);

    EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture, result[3].resource_type);
    EXPECT_EQ(0u, result[3].bind_group);
    EXPECT_EQ(5u, result[3].binding);
    EXPECT_EQ(inspector::ResourceBinding::SampledKind::kSInt, result[3].sampled_kind);
}

TEST_F(InspectorGetResourceBindingsTest, SamplerFiltering) {
    auto* src = R"(
@group(0) @binding(0) var samp1 : sampler;
@group(0) @binding(3) var samp4 : sampler_comparison;

@fragment
fn ep_func() {
    _ = samp1;
    _ = samp4;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(2u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(inspector::ResourceBinding::SamplerType::kUnknownFiltering, result[0].sampler_type);

    EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[1].resource_type);
    EXPECT_EQ(0u, result[1].bind_group);
    EXPECT_EQ(3u, result[1].binding);
    EXPECT_EQ(inspector::ResourceBinding::SamplerType::kComparison, result[1].sampler_type);
}

TEST_F(InspectorGetResourceBindingsTest, InputAttachment) {
    auto* src = R"(
enable chromium_internal_input_attachments;

@group(0) @binding(1) @input_attachment_index(3) var input_tex1: input_attachment<f32>;
@group(4) @binding(3) @input_attachment_index(1) var input_tex2: input_attachment<i32>;

fn f1() -> vec4f { return inputAttachmentLoad(input_tex1); }
fn f2() -> vec4i { return inputAttachmentLoad(input_tex2); }

@fragment
fn main() {
  f1();
  f2();
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("main");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(2u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kInputAttachment, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(1u, result[0].binding);
    EXPECT_EQ(3u, result[0].input_attachment_index);
    EXPECT_EQ(inspector::ResourceBinding::SampledKind::kFloat, result[0].sampled_kind);

    EXPECT_EQ(ResourceBinding::ResourceType::kInputAttachment, result[1].resource_type);
    EXPECT_EQ(4u, result[1].bind_group);
    EXPECT_EQ(3u, result[1].binding);
    EXPECT_EQ(1u, result[1].input_attachment_index);
    EXPECT_EQ(inspector::ResourceBinding::SampledKind::kSInt, result[1].sampled_kind);
}

TEST_F(InspectorGetResourceBindingsTest, TexelBuffer) {
    auto* src = R"(

@group(0) @binding(0) var tb_read: texel_buffer<rgba8uint, read>;
@group(2) @binding(1) var tb_rw: texel_buffer<r32sint, read_write>;

fn use_tb() {
  _ = textureDimensions(tb_read);
  textureStore(tb_rw, 0u, vec4<i32>());
}

@fragment
fn main() { use_tb(); }
)";

    Inspector& inspector = Initialize(src);
    auto result = inspector.GetResourceBindings("main");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(2u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyTexelBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(ResourceBinding::TextureDimension::k1d, result[0].dim);
    EXPECT_EQ(ResourceBinding::TexelFormat::kRgba8Uint, result[0].image_format);
    EXPECT_EQ(ResourceBinding::SampledKind::kUInt, result[0].sampled_kind);

    EXPECT_EQ(ResourceBinding::ResourceType::kReadWriteTexelBuffer, result[1].resource_type);
    EXPECT_EQ(2u, result[1].bind_group);
    EXPECT_EQ(1u, result[1].binding);
    EXPECT_EQ(ResourceBinding::TextureDimension::k1d, result[1].dim);
    EXPECT_EQ(ResourceBinding::TexelFormat::kR32Sint, result[1].image_format);
    EXPECT_EQ(ResourceBinding::SampledKind::kSInt, result[1].sampled_kind);
}

TEST_F(InspectorGetResourceBindingsTest, MissingEntryPoint) {
    auto* src = R"()";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_TRUE(inspector.has_error());
    std::string error = inspector.error();
    EXPECT_TRUE(error.find("not found") != std::string::npos);
}

TEST_F(InspectorGetResourceBindingsTest, NonEntryPointFunc) {
    auto* src = R"(
struct foo_type {
  a: i32,
}
@group(0) @binding(0) var<uniform> foo_ub: foo_type;
fn ub_func() { _ = foo_ub.a; }

@fragment fn ep_func() { ub_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ub_func");
    std::string error = inspector.error();
    EXPECT_TRUE(error.find("not an entry point") != std::string::npos);
}

TEST_F(InspectorGetResourceBindingsTest, UniformBuffer_Simple_NonStruct) {
    auto* src = R"(
@group(0) @binding(0) var<uniform> foo_ub: i32;
fn ub_func() { _ = foo_ub; }
@fragment fn ep_func() { ub_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(4u, result[0].size);
    EXPECT_EQ(4u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, UniformBuffer_Simple_Struct) {
    auto* src = R"(
struct foo_type {
  a: i32,
}
@group(0) @binding(0) var<uniform> foo_ub: foo_type;
fn ub_func() { _ = foo_ub.a; }
@fragment fn ep_func() { ub_func(); }
)";
    Inspector& inspector = Initialize(src);
    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(4u, result[0].size);
    EXPECT_EQ(4u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, UniformBuffer_MultipleMembers) {
    auto* src = R"(
struct foo_type {
  a: i32,
  b: u32,
  c: f32,
}
@group(0) @binding(0) var<uniform> foo_ub: foo_type;
fn ub_func() {
  _ = foo_ub.a;
  _ = foo_ub.b;
  _ = foo_ub.c;
}
@fragment fn ep_func() { ub_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(12u, result[0].size);
    EXPECT_EQ(12u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, UniformBuffer_ContainingPadding) {
    auto* src = R"(
struct foo_type {
  a: vec3f,
}
@group(0) @binding(0) var<uniform> foo_ub: foo_type;
fn ub_func() { _ = foo_ub.a; }
@fragment fn ep_func() { ub_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(16u, result[0].size);
    EXPECT_EQ(12u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, UniformBuffer_NonStructVec3) {
    auto* src = R"(
@group(0) @binding(0) var<uniform> foo_ub: vec3f;
fn ub_func() { _ = foo_ub; }
@fragment fn ep_func() { ub_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(12u, result[0].size);
    EXPECT_EQ(12u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, UniformBuffer_Multiple) {
    auto* src = R"(
struct ub_type {
  a: i32,
  b: u32,
  c: f32,
}
@group(0) @binding(0) var<uniform> ub_foo: ub_type;
@group(0) @binding(1) var<uniform> ub_bar: ub_type;
@group(2) @binding(0) var<uniform> ub_baz: ub_type;
fn ub_foo_func() { _ = ub_foo.a; _ = ub_foo.b; _ = ub_foo.c; }
fn ub_bar_func() { _ = ub_bar.a; _ = ub_bar.b; _ = ub_bar.c; }
fn ub_baz_func() { _ = ub_baz.a; _ = ub_baz.b; _ = ub_baz.c; }

@fragment fn ep_func() {
  ub_foo_func();
  ub_bar_func();
  ub_baz_func();
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(3u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(12u, result[0].size);
    EXPECT_EQ(12u, result[0].size_no_padding);

    EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, result[1].resource_type);
    EXPECT_EQ(0u, result[1].bind_group);
    EXPECT_EQ(1u, result[1].binding);
    EXPECT_EQ(12u, result[1].size);
    EXPECT_EQ(12u, result[1].size_no_padding);

    EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, result[2].resource_type);
    EXPECT_EQ(2u, result[2].bind_group);
    EXPECT_EQ(0u, result[2].binding);
    EXPECT_EQ(12u, result[2].size);
    EXPECT_EQ(12u, result[2].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, UniformBuffer_ContainingArray) {
    // Manually create uniform buffer to make sure it had a valid layout (array
    // with elem stride of 16, and that is 16-byte aligned within the struct)
    auto* src = R"(
struct foo_type {
  a: i32,
  @align(16) b: array<vec4i, 4>,
}
@group(0) @binding(0) var<uniform> foo_ub: foo_type;
fn ub_func() { _ = foo_ub.a; }
@fragment fn ep_func() { ub_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kUniformBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(80u, result[0].size);
    EXPECT_EQ(80u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, StorageBuffer_Simple_NonStruct) {
    auto* src = R"(
@group(0) @binding(0) var<storage, read_write> foo_sb: i32;
fn sb_func() { _ = foo_sb; }
@fragment fn ep_func() { sb_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(4u, result[0].size);
    EXPECT_EQ(4u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, StorageBuffer_Simple_Struct) {
    auto* src = R"(
struct foo_type {
  a: i32,
}
@group(0) @binding(0) var<storage, read_write> foo_sb: foo_type;
fn sb_func() { _ = foo_sb.a; }
@fragment fn ep_func() { sb_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(4u, result[0].size);
    EXPECT_EQ(4u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, StorageBuffer_MultipleMembers) {
    auto* src = R"(
struct foo_type {
  a: i32,
  b: u32,
  c: f32
}
@group(0) @binding(0) var<storage, read_write> foo_sb: foo_type;
fn sb_func() { _ = foo_sb.a; _ = foo_sb.b; _ = foo_sb.c; }
@fragment fn ep_func() { sb_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(12u, result[0].size);
    EXPECT_EQ(12u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, StorageBuffer_Multiple) {
    auto* src = R"(
struct sb_type {
  a: i32,
  b: u32,
  c: f32,
}
@group(0) @binding(0) var<storage, read_write> sb_foo: sb_type;
@group(0) @binding(1) var<storage, read_write> sb_bar: sb_type;
@group(2) @binding(0) var<storage, read_write> sb_baz: sb_type;
fn sb_foo_func() { _ = sb_foo.a; _ = sb_foo.b; _ = sb_foo.c; }
fn sb_bar_func() { _ = sb_bar.a; _ = sb_bar.b; _ = sb_bar.c; }
fn sb_baz_func() { _ = sb_baz.a; _ = sb_baz.b; _ = sb_baz.c; }

@fragment
fn ep_func() {
  sb_foo_func();
  sb_bar_func();
  sb_baz_func();
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(3u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(12u, result[0].size);
    EXPECT_EQ(12u, result[0].size_no_padding);

    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[1].resource_type);
    EXPECT_EQ(0u, result[1].bind_group);
    EXPECT_EQ(1u, result[1].binding);
    EXPECT_EQ(12u, result[1].size);
    EXPECT_EQ(12u, result[1].size_no_padding);

    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[2].resource_type);
    EXPECT_EQ(2u, result[2].bind_group);
    EXPECT_EQ(0u, result[2].binding);
    EXPECT_EQ(12u, result[2].size);
    EXPECT_EQ(12u, result[2].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, StorageBuffer_ContainingArray) {
    auto* src = R"(
struct foo_type {
  a: i32,
  b: array<u32, 4>,
}
@group(0) @binding(0) var<storage, read_write> foo_sb: foo_type;
fn sb_func() { _ = foo_sb.a; }
@fragment fn ep_func() { sb_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(20u, result[0].size);
    EXPECT_EQ(20u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, StorageBuffer_ContainingRuntimeArray) {
    auto* src = R"(
struct foo_type {
  a: i32,
  b: array<u32>,
}
@group(0) @binding(0) var<storage, read_write> foo_sb: foo_type;
fn sb_func() { _ = foo_sb.a; }
@fragment fn ep_func() { sb_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(8u, result[0].size);
    EXPECT_EQ(8u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, StorageBuffer_ContainingPadding) {
    auto* src = R"(
struct foo_type {
  a: vec3f,
}
@group(0) @binding(0) var<storage, read_write> foo_sb: foo_type;
fn sb_func() { _ = foo_sb.a; }
@fragment fn ep_func() { sb_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(16u, result[0].size);
    EXPECT_EQ(12u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, StorageBuffer_NonStructVec3) {
    auto* src = R"(
@group(0) @binding(0) var<storage, read_write> foo_ub: vec3f;
fn ub_func() { _ = foo_ub; }
@fragment fn ep_func() { ub_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(12u, result[0].size);
    EXPECT_EQ(12u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, StorageBuffer_ReadOnlySimple) {
    auto* src = R"(
struct foo_type {
  a: i32,
}
@group(0) @binding(0) var<storage, read> foo_sb: foo_type;
fn sb_func() { _ = foo_sb.a; }
@fragment fn ep_func() { sb_func(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(4u, result[0].size);
    EXPECT_EQ(4u, result[0].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, StorageBuffer_MultipleROAndRW) {
    auto* src = R"(
struct sb_type {
  a: i32,
  b: u32,
  c: f32,
}
@group(0) @binding(0) var<storage, read> sb_foo: sb_type;
@group(0) @binding(1) var<storage, read_write> sb_bar: sb_type;
@group(2) @binding(0) var<storage, read> sb_baz: sb_type;
fn sb_foo_func() { _ = sb_foo.a; _ = sb_foo.b; _ = sb_foo.c; }
fn sb_bar_func() { _ = sb_bar.a; _ = sb_bar.b; _ = sb_bar.c; }
fn sb_baz_func() { _ = sb_baz.a; _ = sb_baz.b; _ = sb_baz.c; }
@fragment fn ep_func() {
  sb_foo_func();
  sb_bar_func();
  sb_baz_func();
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(3u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(12u, result[0].size);
    EXPECT_EQ(12u, result[0].size_no_padding);

    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[1].resource_type);
    EXPECT_EQ(0u, result[1].bind_group);
    EXPECT_EQ(1u, result[1].binding);
    EXPECT_EQ(12u, result[1].size);
    EXPECT_EQ(12u, result[1].size_no_padding);

    EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, result[2].resource_type);
    EXPECT_EQ(2u, result[2].bind_group);
    EXPECT_EQ(0u, result[2].binding);
    EXPECT_EQ(12u, result[2].size);
    EXPECT_EQ(12u, result[2].size_no_padding);
}

TEST_F(InspectorGetResourceBindingsTest, Sampler_Simple) {
    auto* src = R"(
@group(0) @binding(0) var foo_sampler: sampler;
@group(0) @binding(1) var foo_texture: texture_1d<f32>;
var<private> foo_coords: f32;
@fragment fn ep() {
  _ = textureSample(foo_texture, foo_sampler, foo_coords);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(2u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[1].resource_type);
    EXPECT_EQ(0u, result[1].bind_group);
    EXPECT_EQ(0u, result[1].binding);
}

TEST_F(InspectorGetResourceBindingsTest, Sampler_InFunction) {
    auto* src = R"(
@group(0) @binding(0) var foo_sampler: sampler;
@group(0) @binding(1) var foo_texture: texture_1d<f32>;
var<private> foo_coords: f32;
@fragment fn ep_func() {
  _ = textureSample(foo_texture, foo_sampler, foo_coords);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep_func");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(2u, result.size());
    EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[1].resource_type);
    EXPECT_EQ(0u, result[1].bind_group);
    EXPECT_EQ(0u, result[1].binding);
}

TEST_F(InspectorGetResourceBindingsTest, Sampler_Comparison) {
    auto* src = R"(
@group(0) @binding(0) var foo_sampler: sampler_comparison;
@group(0) @binding(1) var foo_texture: texture_depth_2d;
var<private> foo_coords: vec2f;
var<private> foo_depth: f32;
@fragment fn ep() {
  _ = textureSampleCompare(foo_texture, foo_sampler, foo_coords, foo_depth);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(2u, result.size());
    EXPECT_EQ(ResourceBinding::ResourceType::kSampler, result[1].resource_type);
    EXPECT_EQ(ResourceBinding::SamplerType::kComparison, result[1].sampler_type);
    EXPECT_EQ(0u, result[1].bind_group);
    EXPECT_EQ(0u, result[1].binding);
}

TEST_F(InspectorGetResourceBindingsTest, UnsizedBuffer_Direct_Vec4f) {
    auto* src = R"(
@group(0) @binding(0) var<storage> buf : buffer;
@fragment fn ep() {
  let p = bufferView<vec4f>(&buf, 16u);
}
)";

    Inspector& inspector = Initialize(src);
    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, result[0].resource_type);
    EXPECT_EQ(32u, result[0].size);
}

TEST_F(InspectorGetResourceBindingsTest, UnsizedBuffer_Direct_Struct) {
    auto* src = R"(
struct S { // size 144
  a : u32,
  b : array<T, 4>,
}
struct T { // size 32 (8 bytes padding)
  x : vec4f,
  y : vec2f,
}
@group(0) @binding(0) var<storage> buf : buffer;
@fragment fn ep() {
  let p = bufferView<S>(&buf, 16u);
}
)";

    Inspector& inspector = Initialize(src);
    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, result[0].resource_type);
    EXPECT_EQ(160u, result[0].size);
}

TEST_F(InspectorGetResourceBindingsTest, UnsizedBuffer_Indirect_ArrayU32_Size) {
    auto* src = R"(
@group(0) @binding(0) var<storage> buf : buffer;
var<private> offset = 0u; // hides from constant eval
fn foo(p : ptr<storage, buffer>) {
  let q = bufferArrayView<array<u32>>(p, offset, 128u);
}
fn bar(p : ptr<storage, buffer>) {
  foo(p);
}
@fragment
fn ep() {
  bar(&buf);
}
)";

    Inspector& inspector = Initialize(src);
    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, result[0].resource_type);
    EXPECT_EQ(128u, result[0].size);
}

TEST_F(InspectorGetResourceBindingsTest, UnsizedBuffer_Indirect_ArrayU32_Offset) {
    auto* src = R"(
@group(0) @binding(0) var<storage> buf : buffer;
var<private> size = 0u; // hides from constant eval
fn foo(p : ptr<storage, buffer>) {
  let q = bufferArrayView<array<u32>>(p, 32u, size);
}
fn bar() {
  foo(&buf);
}
@fragment
fn ep() {
  bar();
}
)";

    Inspector& inspector = Initialize(src);
    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(ResourceBinding::ResourceType::kReadOnlyStorageBuffer, result[0].resource_type);
    EXPECT_EQ(36u, result[0].size);
}

TEST_F(InspectorGetResourceBindingsTest, UnsizedBuffer_Indirect_RuntimeStruct) {
    auto* src = R"(
struct S {
  t : T,
  arr : array<vec3f>,
}
struct T {
  a : vec4f,
  b : vec4f,
}
@group(0) @binding(0) var<storage, read_write> buf : buffer;
var<private> offset = 0u; // hides from constant eval
var<private> size = 0u; // hides from constant eval
fn foo(p : ptr<storage, buffer, read_write>) {
  // Req size = SizeOf(T) + StrideOf(array<vec3f>) = 48
  let q = bufferArrayView<S>(p, offset, size);
}
@compute @workgroup_size(1)
fn ep() {
  foo(&buf);
}
)";

    Inspector& inspector = Initialize(src);
    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[0].resource_type);
    EXPECT_EQ(48u, result[0].size);
}

TEST_F(InspectorGetResourceBindingsTest, UnsizedBuffer_Indirect_RuntimeStruct_MaxOperation) {
    auto* src = R"(
struct S {
  t : T,
  arr : array<vec3f>,
}
struct T {
  a : vec4f,
  b : vec4f,
}
@group(0) @binding(0) var<storage, read_write> buf : buffer;
var<private> offset = 0u; // hides from constant eval
var<private> size = 0u; // hides from constant eval
fn foo(p : ptr<storage, buffer, read_write>) {
  // Req size = SizeOf(T) + StrideOf(array<vec3f>) = 48
  let q = bufferArrayView<S>(p, offset, size);
}
fn bar(p : ptr<storage, buffer, read_write>) {
  // Req size = SizeOf(T) + StrideOf(array<vec3f>) + offset = 64
  let q = bufferView<S>(p, 16u);
}
@compute @workgroup_size(1)
fn ep() {
  foo(&buf);
  bar(&buf);
}
)";

    Inspector& inspector = Initialize(src);
    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[0].resource_type);
    EXPECT_EQ(64u, result[0].size);
}

TEST_F(InspectorGetResourceBindingsTest, UnsizedBuffer_OnlyThroughEntryPoint) {
    auto* src = R"(
@group(0) @binding(0) var<storage, read_write> buf : buffer;
fn foo(p : ptr<storage, buffer, read_write>) {
  let q = bufferView<vec4f>(p, 16u);
}
@fragment fn unused() {
  foo(&buf);
}
@compute @workgroup_size(1) fn ep() {
  let p = bufferView<vec4f>(&buf, 0);
}
)";

    Inspector& inspector = Initialize(src);
    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(ResourceBinding::ResourceType::kStorageBuffer, result[0].resource_type);
    EXPECT_EQ(16u, result[0].size);
}

std::string CoordsType(core::type::TextureDimension dim, std::string_view name) {
    switch (dim) {
        case core::type::TextureDimension::k1d:
            return std::string(name);
        case core::type::TextureDimension::k2d:
        case core::type::TextureDimension::k2dArray:
            return "vec2<" + std::string(name) + ">";
        case core::type::TextureDimension::k3d:
        case core::type::TextureDimension::kCube:
        case core::type::TextureDimension::kCubeArray:
            return "vec3<" + std::string(name) + ">";
        default:
            break;
    }
    TINT_UNREACHABLE();
}

struct SampledTextureTestParams {
    core::type::TextureDimension type_dim;
    inspector::ResourceBinding::TextureDimension inspector_dim;
    inspector::ResourceBinding::SampledKind sampled_kind;
};
using InspectorGetResourceBindingsTest_WithSampledTextureParams =
    InspectorTestWithParam<SampledTextureTestParams>;
TEST_P(InspectorGetResourceBindingsTest_WithSampledTextureParams, TextureSample) {
    auto& params = GetParam();

    auto src = R"(
@group(0) @binding(0) var foo_texture: texture_)" +
               std::string(ToString(params.type_dim)) +
               R"(<f32>;
@group(0) @binding(1) var foo_sampler: sampler;
var<private> foo_coords: )" +
               CoordsType(params.type_dim, "f32") + R"(;
@fragment fn ep() {
  _ = textureSample(foo_texture, foo_sampler, foo_coords);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(2u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(params.inspector_dim, result[0].dim);
    EXPECT_EQ(params.sampled_kind, result[0].sampled_kind);
}

INSTANTIATE_TEST_SUITE_P(
    InspectorGetResourceBindingsTest,
    InspectorGetResourceBindingsTest_WithSampledTextureParams,
    testing::Values(
        SampledTextureTestParams{core::type::TextureDimension::k1d,
                                 inspector::ResourceBinding::TextureDimension::k1d,
                                 inspector::ResourceBinding::SampledKind::kUnknownFilterable},
        SampledTextureTestParams{core::type::TextureDimension::k2d,
                                 inspector::ResourceBinding::TextureDimension::k2d,
                                 inspector::ResourceBinding::SampledKind::kUnknownFilterable},
        SampledTextureTestParams{core::type::TextureDimension::k3d,
                                 inspector::ResourceBinding::TextureDimension::k3d,
                                 inspector::ResourceBinding::SampledKind::kUnknownFilterable},
        SampledTextureTestParams{core::type::TextureDimension::kCube,
                                 inspector::ResourceBinding::TextureDimension::kCube,
                                 inspector::ResourceBinding::SampledKind::kUnknownFilterable}));

using ArraySampledTextureTestParams = SampledTextureTestParams;
using InspectorGetResourceBindingsTest_WithArraySampledTextureParams =
    InspectorTestWithParam<ArraySampledTextureTestParams>;
TEST_P(InspectorGetResourceBindingsTest_WithArraySampledTextureParams, TextureSample) {
    auto& params = GetParam();
    auto src = R"(
@group(0) @binding(0) var foo_texture: texture_)" +
               std::string(ToString(params.type_dim)) +
               R"(<f32>;
@group(0) @binding(1) var foo_sampler: sampler;
var<private> foo_coords: )" +
               CoordsType(params.type_dim, "f32") + R"(;
var<private> foo_array_index: i32;
@fragment fn ep() {
  _ = textureSample(foo_texture, foo_sampler, foo_coords, foo_array_index);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(2u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
    EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind);
}

using MultisampledTextureTestParams = SampledTextureTestParams;
using InspectorGetResourceBindingsTest_WithMultisampledTextureParams =
    InspectorTestWithParam<MultisampledTextureTestParams>;
INSTANTIATE_TEST_SUITE_P(InspectorGetResourceBindingsTest,
                         InspectorGetResourceBindingsTest_WithArraySampledTextureParams,
                         testing::Values(
                             ArraySampledTextureTestParams{
                                 core::type::TextureDimension::k2dArray,
                                 inspector::ResourceBinding::TextureDimension::k2dArray,
                                 inspector::ResourceBinding::SampledKind::kUnknownFilterable},
                             ArraySampledTextureTestParams{
                                 core::type::TextureDimension::kCubeArray,
                                 inspector::ResourceBinding::TextureDimension::kCubeArray,
                                 inspector::ResourceBinding::SampledKind::kUnknownFilterable}));

std::string BaseType(ResourceBinding::SampledKind sampled_kind) {
    switch (sampled_kind) {
        case ResourceBinding::SampledKind::kUnfilterable:
        case ResourceBinding::SampledKind::kFloat:
            return "f32";
        case ResourceBinding::SampledKind::kSInt:
            return "i32";
        case ResourceBinding::SampledKind::kUInt:
            return "u32";
        default:
            TINT_UNREACHABLE();
    }
}

TEST_P(InspectorGetResourceBindingsTest_WithMultisampledTextureParams, TextureLoad) {
    auto& params = GetParam();
    auto src = R"(
@group(0) @binding(0) var foo_texture: texture_multisampled_)" +
               std::string(ToString(params.type_dim)) + "<" + BaseType(params.sampled_kind) + R"(>;
var<private> foo_coords: )" +
               CoordsType(params.type_dim, "i32") + R"(;
var<private> foo_sample_index: i32;
@fragment fn ep() {
  _ = textureLoad(foo_texture, foo_coords, foo_sample_index);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(ResourceBinding::ResourceType::kMultisampledTexture, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(GetParam().inspector_dim, result[0].dim);
    EXPECT_EQ(GetParam().sampled_kind, result[0].sampled_kind);
}

INSTANTIATE_TEST_SUITE_P(
    InspectorGetResourceBindingsTest,
    InspectorGetResourceBindingsTest_WithMultisampledTextureParams,
    testing::Values(
        MultisampledTextureTestParams{core::type::TextureDimension::k2d,
                                      inspector::ResourceBinding::TextureDimension::k2d,
                                      inspector::ResourceBinding::SampledKind::kUnfilterable},
        MultisampledTextureTestParams{core::type::TextureDimension::k2d,
                                      inspector::ResourceBinding::TextureDimension::k2d,
                                      inspector::ResourceBinding::SampledKind::kSInt},
        MultisampledTextureTestParams{core::type::TextureDimension::k2d,
                                      inspector::ResourceBinding::TextureDimension::k2d,
                                      inspector::ResourceBinding::SampledKind::kUInt}));

using DimensionParams = std::tuple<core::type::TextureDimension, ResourceBinding::TextureDimension>;
using TexelFormatParams =
    std::tuple<core::TexelFormat, ResourceBinding::TexelFormat, ResourceBinding::SampledKind>;
using StorageTextureTestParams = std::tuple<DimensionParams, TexelFormatParams, core::Access>;
using InspectorGetResourceBindingsTest_WithStorageTextureParams =
    InspectorTestWithParam<StorageTextureTestParams>;
TEST_P(InspectorGetResourceBindingsTest_WithStorageTextureParams, Simple) {
    DimensionParams dim_params;
    TexelFormatParams format_params;
    core::Access access;
    std::tie(dim_params, format_params, access) = GetParam();

    core::type::TextureDimension dim;
    ResourceBinding::TextureDimension expected_dim;
    std::tie(dim, expected_dim) = dim_params;

    core::TexelFormat format;
    ResourceBinding::TexelFormat expected_format;
    ResourceBinding::SampledKind expected_kind;
    std::tie(format, expected_format, expected_kind) = format_params;

    ResourceBinding::ResourceType expectedResourceType;
    switch (access) {
        case core::Access::kWrite:
            expectedResourceType = ResourceBinding::ResourceType::kWriteOnlyStorageTexture;
            break;
        case core::Access::kRead:
            expectedResourceType = ResourceBinding::ResourceType::kReadOnlyStorageTexture;
            break;
        case core::Access::kReadWrite:
            expectedResourceType = ResourceBinding::ResourceType::kReadWriteStorageTexture;
            break;
        case core::Access::kUndefined:
            ASSERT_TRUE(false);
            break;
    }

    auto src = R"(
@group(0) @binding(0) var st_var: texture_storage_)" +
               std::string(ToString(dim)) + "<" + std::string(ToString(format)) + ", " +
               std::string(ToString(access)) + R"(>;
@fragment fn ep() { _ = textureDimensions(st_var); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(expectedResourceType, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(expected_dim, result[0].dim);
    EXPECT_EQ(expected_format, result[0].image_format);
    EXPECT_EQ(expected_kind, result[0].sampled_kind);
}

INSTANTIATE_TEST_SUITE_P(
    InspectorGetResourceBindingsTest,
    InspectorGetResourceBindingsTest_WithStorageTextureParams,
    testing::Combine(
        testing::Values(std::make_tuple(core::type::TextureDimension::k1d,
                                        ResourceBinding::TextureDimension::k1d),
                        std::make_tuple(core::type::TextureDimension::k2d,
                                        ResourceBinding::TextureDimension::k2d),
                        std::make_tuple(core::type::TextureDimension::k2dArray,
                                        ResourceBinding::TextureDimension::k2dArray),
                        std::make_tuple(core::type::TextureDimension::k3d,
                                        ResourceBinding::TextureDimension::k3d)),
        testing::Values(std::make_tuple(core::TexelFormat::kR32Float,
                                        ResourceBinding::TexelFormat::kR32Float,
                                        ResourceBinding::SampledKind::kFloat),
                        std::make_tuple(core::TexelFormat::kR32Sint,
                                        ResourceBinding::TexelFormat::kR32Sint,
                                        ResourceBinding::SampledKind::kSInt),
                        std::make_tuple(core::TexelFormat::kR32Uint,
                                        ResourceBinding::TexelFormat::kR32Uint,
                                        ResourceBinding::SampledKind::kUInt),
                        std::make_tuple(core::TexelFormat::kRg32Float,
                                        ResourceBinding::TexelFormat::kRg32Float,
                                        ResourceBinding::SampledKind::kFloat),
                        std::make_tuple(core::TexelFormat::kRg32Sint,
                                        ResourceBinding::TexelFormat::kRg32Sint,
                                        ResourceBinding::SampledKind::kSInt),
                        std::make_tuple(core::TexelFormat::kRg32Uint,
                                        ResourceBinding::TexelFormat::kRg32Uint,
                                        ResourceBinding::SampledKind::kUInt),
                        std::make_tuple(core::TexelFormat::kRgba16Float,
                                        ResourceBinding::TexelFormat::kRgba16Float,
                                        ResourceBinding::SampledKind::kFloat),
                        std::make_tuple(core::TexelFormat::kRgba16Sint,
                                        ResourceBinding::TexelFormat::kRgba16Sint,
                                        ResourceBinding::SampledKind::kSInt),
                        std::make_tuple(core::TexelFormat::kRgba16Uint,
                                        ResourceBinding::TexelFormat::kRgba16Uint,
                                        ResourceBinding::SampledKind::kUInt),
                        std::make_tuple(core::TexelFormat::kRgba32Float,
                                        ResourceBinding::TexelFormat::kRgba32Float,
                                        ResourceBinding::SampledKind::kFloat),
                        std::make_tuple(core::TexelFormat::kRgba32Sint,
                                        ResourceBinding::TexelFormat::kRgba32Sint,
                                        ResourceBinding::SampledKind::kSInt),
                        std::make_tuple(core::TexelFormat::kRgba32Uint,
                                        ResourceBinding::TexelFormat::kRgba32Uint,
                                        ResourceBinding::SampledKind::kUInt),
                        std::make_tuple(core::TexelFormat::kRgba8Sint,
                                        ResourceBinding::TexelFormat::kRgba8Sint,
                                        ResourceBinding::SampledKind::kSInt),
                        std::make_tuple(core::TexelFormat::kRgba8Snorm,
                                        ResourceBinding::TexelFormat::kRgba8Snorm,
                                        ResourceBinding::SampledKind::kFloat),
                        std::make_tuple(core::TexelFormat::kRgba8Uint,
                                        ResourceBinding::TexelFormat::kRgba8Uint,
                                        ResourceBinding::SampledKind::kUInt),
                        std::make_tuple(core::TexelFormat::kRgba8Unorm,
                                        ResourceBinding::TexelFormat::kRgba8Unorm,
                                        ResourceBinding::SampledKind::kFloat)),
        testing::Values(core::Access::kRead, core::Access::kWrite, core::Access::kReadWrite)));

struct DepthTextureTestParams {
    core::type::TextureDimension type_dim;
    inspector::ResourceBinding::TextureDimension inspector_dim;
};
using InspectorGetResourceBindingsTest_WithDepthTextureParams =
    InspectorTestWithParam<DepthTextureTestParams>;
TEST_P(InspectorGetResourceBindingsTest_WithDepthTextureParams, TextureDimensions) {
    auto& params = GetParam();
    auto src = R"(
@group(0) @binding(0) var dt: texture_depth_)" +
               std::string(ToString(params.type_dim)) + R"(;
@fragment fn ep() {
  _ = textureDimensions(dt);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    EXPECT_EQ(ResourceBinding::ResourceType::kDepthTexture, result[0].resource_type);
    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(params.inspector_dim, result[0].dim);
}

INSTANTIATE_TEST_SUITE_P(
    InspectorGetResourceBindingsTest,
    InspectorGetResourceBindingsTest_WithDepthTextureParams,
    testing::Values(DepthTextureTestParams{core::type::TextureDimension::k2d,
                                           inspector::ResourceBinding::TextureDimension::k2d},
                    DepthTextureTestParams{core::type::TextureDimension::k2dArray,
                                           inspector::ResourceBinding::TextureDimension::k2dArray},
                    DepthTextureTestParams{core::type::TextureDimension::kCube,
                                           inspector::ResourceBinding::TextureDimension::kCube},
                    DepthTextureTestParams{
                        core::type::TextureDimension::kCubeArray,
                        inspector::ResourceBinding::TextureDimension::kCubeArray}));

TEST_F(InspectorGetResourceBindingsTest, DepthMultisampledTexture_TextureDimensions) {
    auto* src = R"(
@group(0) @binding(0) var tex: texture_depth_multisampled_2d;
@fragment fn ep() {
  _ = textureDimensions(tex);
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    EXPECT_EQ(ResourceBinding::ResourceType::kDepthMultisampledTexture, result[0].resource_type);
    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
    EXPECT_EQ(ResourceBinding::TextureDimension::k2d, result[0].dim);
}

TEST_F(InspectorGetResourceBindingsTest, ExternalTexture) {
    auto* src = R"(
@group(0) @binding(0) var et: texture_external;
@fragment fn ep() { _ = textureDimensions(et); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();
    EXPECT_EQ(ResourceBinding::ResourceType::kExternalTexture, result[0].resource_type);

    ASSERT_EQ(1u, result.size());
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(0u, result[0].binding);
}

TEST_F(InspectorGetResourceBindingsTest, BindingArray_Simple) {
    auto* src = R"(
@group(0) @binding(1) var toto: binding_array<texture_2d<f32>, 5>;
@fragment fn ep() {
  _ = textureDimensions(toto[3]);
}
)";

    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(1u, result[0].binding);
    EXPECT_TRUE(result[0].array_size.has_value());
    EXPECT_EQ(5u, result[0].array_size.value());
}

TEST_F(InspectorGetResourceBindingsTest, BindingArray_InFunction) {
    auto* src = R"(
@group(0) @binding(1) var toto: binding_array<texture_2d<f32>, 5>;
fn f() {
  _ = textureDimensions(toto[3]);
}
@fragment fn ep() {
  f();
}
)";

    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceBindings("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());

    EXPECT_EQ(ResourceBinding::ResourceType::kSampledTexture, result[0].resource_type);
    EXPECT_EQ(0u, result[0].bind_group);
    EXPECT_EQ(1u, result[0].binding);
    EXPECT_TRUE(result[0].array_size.has_value());
    EXPECT_EQ(5u, result[0].array_size.value());
}

TEST_F(InspectorGetResourceTableInfoTest, NoResourceBinding) {
    auto* src = R"(
@group(0) @binding(1) var toto: texture_2d<f32>;
@fragment fn ep() {
  _ = textureDimensions(toto);
}
)";

    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceTableInfo("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    EXPECT_TRUE(result.empty());
}

TEST_F(InspectorGetResourceTableInfoTest, ResourceTable) {
    auto* src = R"(
enable chromium_experimental_resource_table;

@fragment fn ep() {
  _ = hasResource<texture_2d<f32>>(0);
  _ = getResource<texture_3d<i32>>(1);
}
)";

    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceTableInfo("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(2u, result.size());

    std::vector<ResourceType> types(result.begin(), result.end());
    std::sort(types.begin(), types.end());
    EXPECT_EQ(ResourceType::kTexture2d_f32_unknown_filterable, types[0]);
    EXPECT_EQ(ResourceType::kTexture3d_i32, types[1]);
}

TEST_F(InspectorGetResourceTableInfoTest, ResourceTable_Multiple) {
    auto* src = R"(
enable chromium_experimental_resource_table;

@fragment fn ep() {
  _ = textureDimensions(getResource<texture_cube<f32>>(3));
  _ = textureDimensions(getResource<texture_1d<f32>>(3));

  _ = hasResource<texture_2d<f32>>(0);
  _ = getResource<texture_3d<f32>>(1);
}
)";

    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceTableInfo("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(4u, result.size());

    std::vector<ResourceType> types(result.begin(), result.end());
    std::sort(types.begin(), types.end());

    EXPECT_EQ(ResourceType::kTexture1d_f32_unknown_filterable, types[0]);
    EXPECT_EQ(ResourceType::kTexture2d_f32_unknown_filterable, types[1]);
    EXPECT_EQ(ResourceType::kTexture3d_f32_unknown_filterable, types[2]);
    EXPECT_EQ(ResourceType::kTextureCube_f32_unknown_filterable, types[3]);
}

TEST_F(InspectorGetResourceTableInfoTest, ResourceTable_Nested) {
    auto* src = R"(
enable chromium_experimental_resource_table;

fn nested() {
  _ = textureDimensions(getResource<texture_cube<f32>>(3));
  _ = textureDimensions(getResource<texture_1d<f32>>(3));

  _ = hasResource<texture_2d<f32>>(0);
  _ = getResource<texture_3d<f32>>(1);
}

@fragment fn ep() {
  nested();
}
)";

    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceTableInfo("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(4u, result.size());

    std::vector<ResourceType> types(result.begin(), result.end());
    std::sort(types.begin(), types.end());

    EXPECT_EQ(ResourceType::kTexture1d_f32_unknown_filterable, types[0]);
    EXPECT_EQ(ResourceType::kTexture2d_f32_unknown_filterable, types[1]);
    EXPECT_EQ(ResourceType::kTexture3d_f32_unknown_filterable, types[2]);
    EXPECT_EQ(ResourceType::kTextureCube_f32_unknown_filterable, types[3]);
}

TEST_F(InspectorGetResourceTableInfoTest, ResourceTable_Samplers) {
    auto* src = R"(
enable chromium_experimental_resource_table;

@fragment fn ep() {
  _ = hasResource<sampler>(0);
  _ = getResource<sampler_comparison>(1);
}
)";

    Inspector& inspector = Initialize(src);

    auto result = inspector.GetResourceTableInfo("ep");
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(2u, result.size());

    std::vector<ResourceType> types(result.begin(), result.end());
    std::sort(types.begin(), types.end());
    EXPECT_EQ(ResourceType::kSampler, types[0]);
    EXPECT_EQ(ResourceType::kSampler_comparison, types[1]);
}

class InspectorGetSamplerTextureUsesTest : public TestHelper, public testing::Test {
  public:
    using ResultExpectation = std::initializer_list<SamplerTexturePair>;

    size_t SizeOf(const ResultExpectation& expectation) { return expectation.size(); }
    size_t SizeOf(const std::vector<SamplerTexturePair>& result) { return result.size(); }

    // ValidateEqual checks that the expected and actual SamplerTexturePair list contain same pairs
    // and both are deduplicated.
    template <typename T, typename U>
    void ValidateEqual(const T& expected, const U& actual) {
        ASSERT_EQ(SizeOf(expected), SizeOf(actual));
        std::unordered_set<SamplerTexturePair> pairSet;
        // Insert all pairs in the expected into the set.
        for (const auto& pair : expected) {
            // Expectation should be deduplicated, so every insertion should take place.
            EXPECT_TRUE(pairSet.insert(pair).second)
                << "Duplicated SamplerTexturePair found: Sampler: ("
                << pair.sampler_binding_point.group << ", " << pair.sampler_binding_point.binding
                << "), "
                << "Texture: (" << pair.texture_binding_point.group << ", "
                << pair.texture_binding_point.binding << ")";
        }
        // Check that each SamplerTexturePair in the actual is in the set and occurs only once.
        for (const auto& pair : actual) {
            EXPECT_TRUE(pairSet.erase(pair) == 1)
                << "Unexpected SamplerTexturePair: Sampler: (" << pair.sampler_binding_point.group
                << ", " << pair.sampler_binding_point.binding << "), "
                << "Texture: (" << pair.texture_binding_point.group << ", "
                << pair.texture_binding_point.binding << ")";
        }
    }

    constexpr static BindingPoint non_sampler_placeholder{123u, 654u};
};

TEST_F(InspectorGetSamplerTextureUsesTest, None) {
    std::string shader = R"(
@fragment
fn main() {
})";

    ResultExpectation expected = {};

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("main");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("main", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
}

// Regression test for crbug.com/dawn/380433758.
TEST_F(InspectorGetSamplerTextureUsesTest, DiamondSampler) {
    std::string shader = R"(
fn sample(t: texture_2d<f32>, s: sampler) -> vec4f {
  return textureSampleLevel(t, s, vec2f(0), 0);
}

fn useCombos0() -> vec4f {
  return sample(tex0_0, smp0_0);
}

fn useCombos1() -> vec4f {
  return sample(tex1_15, smp0_0);
}

@group(0) @binding(0) var tex0_0: texture_2d<f32>;
@group(0) @binding(1) var tex1_15: texture_2d<f32>;
@group(0) @binding(2) var smp0_0: sampler;

@vertex fn vs() -> @builtin(position) vec4f {
  return useCombos0();
}

@fragment fn fs() -> @location(0) vec4f {
  return vec4f(useCombos1());
})";

    ResultExpectation expected_vs = {
        {/* Sampler */ BindingPoint{0, 2}, /* Texture */ BindingPoint{0, 0}},
    };
    ResultExpectation expected_fs = {
        {/* Sampler */ BindingPoint{0, 2}, /* Texture */ BindingPoint{0, 1}},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("vs");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_vs, result);
    }
    {
        auto result = inspector.GetSamplerTextureUses("fs");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_fs, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("vs", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_vs, result);
    }
    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("fs", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_fs, result);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, DiamondSampler2) {
    std::string shader = R"(
fn sample(t: texture_2d<f32>, s: sampler) -> vec4f {
  return textureSampleLevel(t, s, vec2f(0), 0);
}

fn useCombos0() -> vec4f {
  return sample(tex0_0, smp0_0);
}

fn useCombos1(t: texture_2d<f32>) -> vec4f {
  return sample(t, smp0_0);
}

fn useCombos2() -> vec4f {
  return useCombos1(tex1_15);
}

@group(0) @binding(0) var tex0_0: texture_2d<f32>;
@group(0) @binding(1) var tex1_15: texture_2d<f32>;
@group(0) @binding(2) var smp0_0: sampler;

@vertex fn vs() -> @builtin(position) vec4f {
  return useCombos0();
}

@fragment fn fs() -> @location(0) vec4f {
  return vec4f(useCombos2());
})";

    ResultExpectation expected_vs = {
        {/* Sampler */ BindingPoint{0, 2}, /* Texture */ BindingPoint{0, 0}},
    };
    ResultExpectation expected_fs = {
        {/* Sampler */ BindingPoint{0, 2}, /* Texture */ BindingPoint{0, 1}},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("vs");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_vs, result);
    }
    {
        auto result = inspector.GetSamplerTextureUses("fs");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_fs, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("vs", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_vs, result);
    }
    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("fs", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_fs, result);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, DiamondSampler3) {
    std::string shader = R"(
fn sample(t: texture_2d<f32>, s: sampler) -> vec4f {
  return textureSampleLevel(t, s, vec2f(0), 0);
}

fn useCombos0() -> vec4f {
  return sample(tex0_0, smp0_0);
}

fn useCombos1(t: texture_2d<f32>) -> vec4f {
  return sample(t, smp0_0);
}

fn useCombos2() -> vec4f {
  return useCombos1(tex1_15);
}

@group(0) @binding(0) var tex0_0: texture_2d<f32>;
@group(0) @binding(1) var tex1_15: texture_2d<f32>;
@group(0) @binding(2) var smp0_0: sampler;

@vertex fn vs() -> @builtin(position) vec4f {
  _ = useCombos0();
  return useCombos2();
}

@fragment fn fs() -> @location(0) vec4f {
  return vec4f(useCombos2());
})";

    ResultExpectation expected_vs = {
        {/* Sampler */ BindingPoint{0, 2}, /* Texture */ BindingPoint{0, 0}},
        {/* Sampler */ BindingPoint{0, 2}, /* Texture */ BindingPoint{0, 1}},
    };
    ResultExpectation expected_fs = {
        {/* Sampler */ BindingPoint{0, 2}, /* Texture */ BindingPoint{0, 1}},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("vs");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_vs, result);
    }
    {
        auto result = inspector.GetSamplerTextureUses("fs");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_fs, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("vs", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_vs, result);
    }
    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("fs", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_fs, result);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, Simple) {
    std::string shader = R"(
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>;

@fragment
fn main(@location(0) fragUV: vec2<f32>,
        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
  return textureSample(myTexture, mySampler, fragUV) * fragPosition;
})";

    ResultExpectation expected = {
        {/* Sampler */ BindingPoint{0, 1}, /* Texture */ BindingPoint{0, 2}},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("main");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("main", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, UnknownEntryPoint) {
    std::string shader = R"(
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>;

@fragment
fn main(@location(0) fragUV: vec2<f32>,
        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
  return textureSample(myTexture, mySampler, fragUV) * fragPosition;
})";

    {
        Inspector& inspector = Initialize(shader);
        inspector.GetSamplerTextureUses("foo");
        ASSERT_TRUE(inspector.has_error()) << inspector.error();
    }
    {
        Inspector& inspector = Initialize(shader);
        inspector.GetSamplerAndNonSamplerTextureUses("foo", non_sampler_placeholder);
        ASSERT_TRUE(inspector.has_error()) << inspector.error();
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, MultipleCalls) {
    std::string shader = R"(
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>;

@fragment
fn main(@location(0) fragUV: vec2<f32>,
        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
  return textureSample(myTexture, mySampler, fragUV) * fragPosition;
})";

    Inspector& inspector = Initialize(shader);

    {
        auto result_0 = inspector.GetSamplerTextureUses("main");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        auto result_1 = inspector.GetSamplerTextureUses("main");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(result_0, result_1);
    }

    {
        auto result_0 =
            inspector.GetSamplerAndNonSamplerTextureUses("main", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();
        auto result_1 =
            inspector.GetSamplerAndNonSamplerTextureUses("main", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();
        ValidateEqual(result_0, result_1);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, BothIndirect) {
    std::string shader = R"(
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>;

fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
  return textureSample(t, s, uv);
}

@fragment
fn main(@location(0) fragUV: vec2<f32>,
        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
  return doSample(myTexture, mySampler, fragUV) * fragPosition;
})";

    ResultExpectation expected = {
        {/* Sampler */ BindingPoint{0, 1}, /* Texture */ BindingPoint{0, 2}},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("main");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("main", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, SamplerIndirect) {
    std::string shader = R"(
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>;

fn doSample(s: sampler, uv: vec2<f32>) -> vec4<f32> {
  return textureSample(myTexture, s, uv);
}

@fragment
fn main(@location(0) fragUV: vec2<f32>,
        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
  return doSample(mySampler, fragUV) * fragPosition;
})";

    ResultExpectation expected = {
        {/* Sampler */ BindingPoint{0, 1}, /* Texture */ BindingPoint{0, 2}},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("main");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("main", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, TextureIndirect) {
    std::string shader = R"(
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>;

fn doSample(t: texture_2d<f32>, uv: vec2<f32>) -> vec4<f32> {
  return textureSample(t, mySampler, uv);
}

@fragment
fn main(@location(0) fragUV: vec2<f32>,
        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
  return doSample(myTexture, fragUV) * fragPosition;
})";

    ResultExpectation expected = {
        {/* Sampler */ BindingPoint{0, 1}, /* Texture */ BindingPoint{0, 2}},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("main");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("main", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, TextureFromBindingArray) {
    std::string shader = R"(
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTextures: binding_array<texture_2d<f32>, 3>;

@fragment fn main() {
  _ = textureSample(myTextures[1], mySampler, vec2(0));
})";

    ResultExpectation expected = {
        {/* Sampler */ BindingPoint{0, 1}, /* Texture */ BindingPoint{0, 2}},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("main");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("main", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, TextureFromBindingArrayPassedAsParameter) {
    std::string shader = R"(
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTextures: binding_array<texture_2d<f32>, 3>;

fn f(ts : binding_array<texture_2d<f32>, 3>) {
  _ = textureSample(ts[1], mySampler, vec2(0));
}
@fragment fn main() {
  f(myTextures);
})";

    ResultExpectation expected = {
        {/* Sampler */ BindingPoint{0, 1}, /* Texture */ BindingPoint{0, 2}},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("main");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("main", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, TextureParameterFromBindingArray) {
    std::string shader = R"(
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTextures: binding_array<texture_2d<f32>, 3>;

fn f(t : texture_2d<f32>) {
  _ = textureSample(t, mySampler, vec2(0));
}
@fragment fn main() {
  f(myTextures[1]);
})";

    ResultExpectation expected = {
        {/* Sampler */ BindingPoint{0, 1}, /* Texture */ BindingPoint{0, 2}},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("main");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("main", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, NeitherIndirect) {
    std::string shader = R"(
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>;

fn doSample(uv: vec2<f32>) -> vec4<f32> {
  return textureSample(myTexture, mySampler, uv);
}

@fragment
fn main(@location(0) fragUV: vec2<f32>,
        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
  return doSample(fragUV) * fragPosition;
})";

    ResultExpectation expected = {
        {/* Sampler */ BindingPoint{0, 1}, /* Texture */ BindingPoint{0, 2}},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("main");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("main", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, Complex) {
    std::string shader = R"(
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>;


fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
  return textureSample(t, s, uv);
}

fn X(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
  return doSample(t, s, uv);
}

fn Y(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
  return doSample(t, s, uv);
}

fn Z(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
  return X(t, s, uv) + Y(t, s, uv);
}

@fragment
fn via_call(@location(0) fragUV: vec2<f32>,
        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
  return Z(myTexture, mySampler, fragUV) * fragPosition;
}

@fragment
fn via_ptr(@location(0) fragUV: vec2<f32>,
        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
  return textureSample(myTexture, mySampler, fragUV) + fragPosition;
}

@fragment
fn direct(@location(0) fragUV: vec2<f32>,
        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
  return textureSample(myTexture, mySampler, fragUV) + fragPosition;
})";

    ResultExpectation expected = {
        {/* Sampler */ BindingPoint{0, 1}, /* Texture */ BindingPoint{0, 2}},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("via_call");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
    {
        auto result = inspector.GetSamplerTextureUses("via_ptr");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
    {
        auto result = inspector.GetSamplerTextureUses("direct");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }

    {
        auto result =
            inspector.GetSamplerAndNonSamplerTextureUses("via_call", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
    {
        auto result =
            inspector.GetSamplerAndNonSamplerTextureUses("via_ptr", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
    {
        auto result =
            inspector.GetSamplerAndNonSamplerTextureUses("direct", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected, result);
    }
}

TEST_F(InspectorGetSamplerTextureUsesTest, SamplerAndNonSamplerTexture) {
    std::string shader = R"(
@group(0) @binding(1) var sampler0: sampler;
@group(0) @binding(3) var sampler1: sampler;
@group(0) @binding(2) var texture0: texture_2d<f32>;
@group(2) @binding(1) var texture1: texture_2d<f32>;
// Storage texture should not be included in the result.
@group(2) @binding(3) var texture2: texture_storage_2d<r32float, read_write>;
@group(2) @binding(4) var external0 : texture_external;
@group(2) @binding(5) var external1 : texture_external;
@group(2) @binding(6) var texture_array : binding_array<texture_2d<f32>, 5>;

const loadStoreCoords = vec2<u32>(0u, 0u);

fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
  _ = textureLoad(t, loadStoreCoords, 0u);
  return textureSample(t, s, uv);
}

@fragment
fn main(@location(0) fragUV: vec2<f32>,
        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
  // Usage with a sampler
  _ = textureSample(texture1, sampler0, fragUV);

  // Non-sampler texture usage.
  _ = textureLoad(texture1, loadStoreCoords, 0u);

  // Both sampler and non-sampler usage but inside a function.
  _ = doSample(texture0, sampler0, fragUV);

  // Using texture0 with sampler0 again, should be deduplicated in the result.
  _ = textureSample(texture0, sampler0, fragUV);

  // Storage texture should not be included in the result.
  _ = textureLoad(texture2, loadStoreCoords);
  textureStore(texture2, loadStoreCoords, fragPosition);

  // Usages of texture_external with and without samplers
  _ = textureSampleBaseClampToEdge(external0, sampler0, fragUV);
  _ = textureLoad(external1, vec2(0, 0));

  // Usages of binding_array with and without samplers
  _ = textureSample(texture_array[2], sampler0, fragUV);
  _ = textureLoad(texture_array[1], vec2(0, 0), 0);

  // Another usage with a sampler.
  return textureSample(texture0, sampler1, fragUV) + fragPosition;
}
)";

    constexpr BindingPoint sampler_0 = {0, 1};
    constexpr BindingPoint sampler_1 = {0, 3};
    constexpr BindingPoint texture_0 = {0, 2};
    constexpr BindingPoint texture_1 = {2, 1};
    constexpr BindingPoint external_0 = {2, 4};
    constexpr BindingPoint external_1 = {2, 5};
    constexpr BindingPoint texture_array = {2, 6};
    // Storage texture texture2 should not be included in the result.

    ResultExpectation expected_sampler_only = {
        {/* Sampler */ sampler_0, /* Texture */ texture_1},
        {/* Sampler */ sampler_0, /* Texture */ texture_0},
        {/* Sampler */ sampler_1, /* Texture */ texture_0},
        {/* Sampler */ sampler_0, /* Texture */ external_0},
        {/* Sampler */ sampler_0, /* Texture */ texture_array},
    };
    ResultExpectation expected_sampler_and_non_sampler = {
        {/* Sampler */ sampler_0, /* Texture */ texture_1},
        {/* Sampler */ non_sampler_placeholder, /* Texture */ texture_1},
        {/* Sampler */ non_sampler_placeholder, /* Texture */ texture_0},
        {/* Sampler */ sampler_0, /* Texture */ texture_0},
        {/* Sampler */ sampler_1, /* Texture */ texture_0},
        {/* Sampler */ sampler_0, /* Texture */ external_0},
        {/* Sampler */ non_sampler_placeholder, /* Texture */ external_1},
        {/* Sampler */ sampler_0, /* Texture */ texture_array},
        {/* Sampler */ non_sampler_placeholder, /* Texture */ texture_array},
    };

    Inspector& inspector = Initialize(shader);

    {
        auto result = inspector.GetSamplerTextureUses("main");
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_sampler_only, result);
    }

    {
        auto result = inspector.GetSamplerAndNonSamplerTextureUses("main", non_sampler_placeholder);
        ASSERT_FALSE(inspector.has_error()) << inspector.error();

        ValidateEqual(expected_sampler_and_non_sampler, result);
    }
}

// Test calling GetUsedExtensionNames on a empty shader.
TEST_F(InspectorGetUsedExtensionNamesTest, Empty) {
    std::string shader = "";

    Inspector& inspector = Initialize(shader);

    auto result = inspector.GetUsedExtensionNames();
    EXPECT_EQ(result.size(), 0u);
}

// Test calling GetUsedExtensionNames on a shader with no extension.
TEST_F(InspectorGetUsedExtensionNamesTest, None) {
    std::string shader = R"(
@fragment
fn main() {
})";

    Inspector& inspector = Initialize(shader);

    auto result = inspector.GetUsedExtensionNames();
    EXPECT_EQ(result.size(), 0u);
}

// Test calling GetUsedExtensionNames on a shader with valid extension.
TEST_F(InspectorGetUsedExtensionNamesTest, Simple) {
    std::string shader = R"(
enable f16;

@fragment
fn main() {
})";

    Inspector& inspector = Initialize(shader);

    auto result = inspector.GetUsedExtensionNames();
    EXPECT_EQ(result.size(), 1u);
    EXPECT_EQ(result[0], "f16");
}

// Test calling GetUsedExtensionNames on a shader with a extension enabled for
// multiple times.
TEST_F(InspectorGetUsedExtensionNamesTest, Duplicated) {
    std::string shader = R"(
enable f16;
enable f16;

@fragment
fn main() {
})";

    Inspector& inspector = Initialize(shader);

    auto result = inspector.GetUsedExtensionNames();
    EXPECT_EQ(result.size(), 1u);
    EXPECT_EQ(result[0], "f16");
}

// Crash was occuring in ::GenerateSamplerTargets, when
// ::GetSamplerTextureUses was called.
TEST_F(InspectorRegressionTest, tint967) {
    std::string shader = R"(
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>;

fn doSample(t: texture_2d<f32>, s: sampler, uv: vec2<f32>) -> vec4<f32> {
  return textureSample(t, s, uv);
}

@fragment
fn main(@location(0) fragUV: vec2<f32>,
        @location(1) fragPosition: vec4<f32>) -> @location(0) vec4<f32> {
  return doSample(myTexture, mySampler, fragUV) * fragPosition;
})";

    Inspector& inspector = Initialize(shader);
    inspector.GetSamplerTextureUses("main");
}

TEST_F(InspectorTextureTest, TextureLevelInEP) {
    std::string shader = R"(
@group(2) @binding(3) var myTexture: texture_2d<f32>;

@compute @workgroup_size(1)
fn main() {
  let num = textureNumLevels(myTexture);
})";

    Inspector& inspector = Initialize(shader);
    auto info = inspector.GetTextureQueries("main");

    ASSERT_EQ(1u, info.size());
    EXPECT_EQ(Inspector::TextureQueryType::kTextureNumLevels, info[0].type);
    EXPECT_EQ(2u, info[0].group);
    EXPECT_EQ(3u, info[0].binding);
}

TEST_F(InspectorTextureTest, TextureLevelInBindingArray) {
    std::string shader = R"(
@group(2) @binding(3) var myTextures : binding_array<texture_2d<f32>, 4>;

@compute @workgroup_size(1)
fn main() {
  let num = textureNumLevels(myTextures[2]);
})";

    Inspector& inspector = Initialize(shader);
    auto info = inspector.GetTextureQueries("main");

    ASSERT_EQ(1u, info.size());
    EXPECT_EQ(Inspector::TextureQueryType::kTextureNumLevels, info[0].type);
    EXPECT_EQ(2u, info[0].group);
    EXPECT_EQ(3u, info[0].binding);
}

TEST_F(InspectorTextureTest, TextureLevelInEPNoDups) {
    std::string shader = R"(
@group(0) @binding(0) var myTexture: texture_2d<f32>;

@compute @workgroup_size(1)
fn main() {
  let num1 = textureNumLevels(myTexture);
  let num2 = textureNumLevels(myTexture);
})";

    Inspector& inspector = Initialize(shader);
    auto info = inspector.GetTextureQueries("main");

    ASSERT_EQ(1u, info.size());
}

TEST_F(InspectorTextureTest, TextureLevelInEPMultiple) {
    std::string shader = R"(
@group(2) @binding(3) var tex1: texture_2d<f32>;
@group(1) @binding(2) var tex2: texture_2d<f32>;

@compute @workgroup_size(1)
fn main() {
  let num1 = textureNumLevels(tex1);
  let num2 = textureNumLevels(tex2);
})";

    Inspector& inspector = Initialize(shader);
    auto info = inspector.GetTextureQueries("main");

    ASSERT_EQ(2u, info.size());

    Inspector::LevelSampleInfo info1 = {
        /*type */ Inspector::TextureQueryType::kTextureNumLevels,
        /*group*/ 1,
        /*binding*/ 2,
    };
    Inspector::LevelSampleInfo info2 = {
        /*type */ Inspector::TextureQueryType::kTextureNumLevels,
        /*group*/ 2,
        /*binding*/ 3,
    };
    EXPECT_THAT(info, testing::UnorderedElementsAre(info1, info2));
}

TEST_F(InspectorTextureTest, TextureSamplesInEP) {
    std::string shader = R"(
@group(2) @binding(3) var myTexture: texture_multisampled_2d<f32>;

@compute @workgroup_size(1)
fn main() {
  let num = textureNumSamples(myTexture);
})";

    Inspector& inspector = Initialize(shader);
    auto info = inspector.GetTextureQueries("main");

    ASSERT_EQ(1u, info.size());
    EXPECT_EQ(Inspector::TextureQueryType::kTextureNumSamples, info[0].type);
    EXPECT_EQ(2u, info[0].group);
    EXPECT_EQ(3u, info[0].binding);
}

TEST_F(InspectorTextureTest, TextureSamplesInEPNoDups) {
    std::string shader = R"(
@group(0) @binding(0) var myTexture: texture_multisampled_2d<f32>;

@compute @workgroup_size(1)
fn main() {
  let num1 = textureNumSamples(myTexture);
  let num2 = textureNumSamples(myTexture);
})";

    Inspector& inspector = Initialize(shader);
    auto info = inspector.GetTextureQueries("main");

    ASSERT_EQ(1u, info.size());
}

TEST_F(InspectorTextureTest, TextureSamplesInEPMultiple) {
    std::string shader = R"(
@group(2) @binding(3) var tex1: texture_multisampled_2d<f32>;
@group(1) @binding(2) var tex2: texture_multisampled_2d<f32>;

@compute @workgroup_size(1)
fn main() {
  let num1 = textureNumSamples(tex1);
  let num2 = textureNumSamples(tex2);
})";

    Inspector& inspector = Initialize(shader);
    auto info = inspector.GetTextureQueries("main");

    ASSERT_EQ(2u, info.size());

    Inspector::LevelSampleInfo info1 = {
        /*type */ Inspector::TextureQueryType::kTextureNumSamples,
        /*group*/ 1,
        /*binding*/ 2,
    };
    Inspector::LevelSampleInfo info2 = {
        /*type */ Inspector::TextureQueryType::kTextureNumSamples,
        /*group*/ 2,
        /*binding*/ 3,
    };
    EXPECT_THAT(info, testing::UnorderedElementsAre(info1, info2));
}

TEST_F(InspectorTextureTest, TextureLoadInEP) {
    std::string shader = R"(
@group(2) @binding(3) var tex1: texture_2d<f32>;

@compute @workgroup_size(1)
fn main() {
  let num1 = textureLoad(tex1, vec2(0, 0), 0);
})";

    Inspector& inspector = Initialize(shader);
    auto info = inspector.GetTextureQueries("main");

    ASSERT_EQ(1u, info.size());

    EXPECT_EQ(Inspector::TextureQueryType::kTextureNumLevels, info[0].type);
    EXPECT_EQ(2u, info[0].group);
    EXPECT_EQ(3u, info[0].binding);
}

TEST_F(InspectorTextureTest, TextureLoadInBindingArray) {
    std::string shader = R"(
@group(2) @binding(3) var textures: binding_array<texture_2d<f32>, 4>;

@compute @workgroup_size(1)
fn main() {
  let num1 = textureLoad(textures[1], vec2(0, 0), 0);
})";

    Inspector& inspector = Initialize(shader);
    auto info = inspector.GetTextureQueries("main");

    ASSERT_EQ(1u, info.size());

    EXPECT_EQ(Inspector::TextureQueryType::kTextureNumLevels, info[0].type);
    EXPECT_EQ(2u, info[0].group);
    EXPECT_EQ(3u, info[0].binding);
}

TEST_F(InspectorTextureTest, TextureLoadMultisampledInEP) {
    std::string shader = R"(
@group(2) @binding(3) var tex1: texture_multisampled_2d<f32>;

@compute @workgroup_size(1)
fn main() {
  let num1 = textureLoad(tex1, vec2(0, 0), 0);
})";

    Inspector& inspector = Initialize(shader);
    auto info = inspector.GetTextureQueries("main");

    ASSERT_EQ(1u, info.size());

    EXPECT_EQ(Inspector::TextureQueryType::kTextureNumSamples, info[0].type);
    EXPECT_EQ(2u, info[0].group);
    EXPECT_EQ(3u, info[0].binding);
}

TEST_F(InspectorTextureTest, TextureLoadMultipleInEP) {
    std::string shader = R"(
@group(2) @binding(3) var tex1: texture_2d<f32>;
@group(1) @binding(4) var tex2: texture_multisampled_2d<f32>;
@group(0) @binding(1) var tex3: texture_2d<f32>;
@group(0) @binding(8) var tex4: texture_external;

@compute @workgroup_size(1)
fn main() {
  let num1 = textureLoad(tex1, vec2(0, 0), 0);
  let num2 = textureLoad(tex2, vec2(0, 0), 0);
  let num3 = textureLoad(tex3, vec2(0, 0), 0);
  let num4 = textureLoad(tex4, vec2(0, 0));
})";

    Inspector& inspector = Initialize(shader);
    auto info = inspector.GetTextureQueries("main");

    ASSERT_EQ(3u, info.size());

    Inspector::LevelSampleInfo info1 = {
        /*type */ Inspector::TextureQueryType::kTextureNumLevels,
        /*group*/ 0,
        /*binding*/ 1,
    };
    Inspector::LevelSampleInfo info2 = {
        /*type */ Inspector::TextureQueryType::kTextureNumLevels,
        /*group*/ 2,
        /*binding*/ 3,
    };
    Inspector::LevelSampleInfo info3 = {
        /*type */ Inspector::TextureQueryType::kTextureNumSamples,
        /*group*/ 1,
        /*binding*/ 4,
    };
    EXPECT_THAT(info, testing::UnorderedElementsAre(info1, info2, info3));
}

TEST_F(InspectorTextureTest, TextureInSubfunction) {
    std::string shader = R"(
@group(2) @binding(3) var tex1: texture_2d<f32>;
@group(1) @binding(4) var tex2: texture_multisampled_2d<f32>;
@group(1) @binding(3) var tex3: texture_2d<f32>;

fn b(tx1: texture_2d<f32>, tx2: texture_multisampled_2d<f32>, tx3: texture_2d<f32>, tx4: texture_2d<f32>) {
  let v1 = textureNumLevels(tx1);
  let v2 = textureNumSamples(tx2);
  let v3 = textureLoad(tx3, vec2(0, 0), 0);
  let v4 = textureNumLevels(tx4);
}

fn a(tx1: texture_2d<f32>, tx2: texture_multisampled_2d<f32>, tx3: texture_2d<f32>) {
  b(tx1, tx2, tx3, tx1);
}

@compute @workgroup_size(1)
fn main() {
  a(tex1, tex2, tex3);
})";

    Inspector& inspector = Initialize(shader);
    auto info = inspector.GetTextureQueries("main");

    ASSERT_EQ(3u, info.size());

    Inspector::LevelSampleInfo info1 = {
        /*type */ Inspector::TextureQueryType::kTextureNumLevels,
        /*group*/ 1,
        /*binding*/ 3,
    };
    Inspector::LevelSampleInfo info2 = {
        /*type */ Inspector::TextureQueryType::kTextureNumLevels,
        /*group*/ 2,
        /*binding*/ 3,
    };
    Inspector::LevelSampleInfo info3 = {
        /*type */ Inspector::TextureQueryType::kTextureNumSamples,
        /*group*/ 1,
        /*binding*/ 4,
    };
    EXPECT_THAT(info, testing::UnorderedElementsAre(info1, info2, info3));
}

TEST_F(InspectorTextureTest, TextureMultipleEPs) {
    std::string shader = R"(
@group(0) @binding(0) var<storage, read_write> dstBuf : array<u32>;
@group(0) @binding(1) var tex1 : texture_2d_array<f32>;
@group(0) @binding(4) var tex2 : texture_multisampled_2d<f32>;
@group(1) @binding(3) var tex3 : texture_2d_array<f32>;

@compute @workgroup_size(1, 1, 1) fn main1() {
    dstBuf[0] = textureNumLayers(tex1);
    dstBuf[1] = textureNumLevels(tex1);
    dstBuf[2] = textureNumSamples(tex2);
    dstBuf[3] = textureNumLevels(tex3);
}

@compute @workgroup_size(1, 1, 1) fn main2() {
    dstBuf[0] = textureNumLayers(tex1);
    dstBuf[1] = textureNumLevels(tex1);
    dstBuf[2] = textureNumSamples(tex2);
}
    )";
    Inspector& inspector = Initialize(shader);
    {
        auto info = inspector.GetTextureQueries("main1");
        ASSERT_EQ(3u, info.size());

        Inspector::LevelSampleInfo info1 = {
            /*type */ Inspector::TextureQueryType::kTextureNumLevels,
            /*group*/ 1,
            /*binding*/ 3,
        };
        Inspector::LevelSampleInfo info2 = {
            /*type */ Inspector::TextureQueryType::kTextureNumLevels,
            /*group*/ 0,
            /*binding*/ 1,
        };
        Inspector::LevelSampleInfo info3 = {
            /*type */ Inspector::TextureQueryType::kTextureNumSamples,
            /*group*/ 0,
            /*binding*/ 4,
        };
        EXPECT_THAT(info, testing::UnorderedElementsAre(info1, info2, info3));
    }
    {
        auto info = inspector.GetTextureQueries("main2");
        ASSERT_EQ(2u, info.size());

        Inspector::LevelSampleInfo info1 = {
            /*type */ Inspector::TextureQueryType::kTextureNumLevels,
            /*group*/ 0,
            /*binding*/ 1,
        };
        Inspector::LevelSampleInfo info2 = {
            /*type */ Inspector::TextureQueryType::kTextureNumSamples,
            /*group*/ 0,
            /*binding*/ 4,
        };
        EXPECT_THAT(info, testing::UnorderedElementsAre(info1, info2));
    }
}

TEST_F(InspectorGetBlendSrcTest, Basic) {
    auto* src = R"(
enable dual_source_blending;

struct out_struct {
  @location(0u) @blend_src(0u) output_color: vec4f,
  @location(0u) @blend_src(1u) output_blend: vec4f,
}

@fragment
fn ep_func() -> out_struct {
  var out_var: out_struct;
  return out_var;
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();

    ASSERT_EQ(1u, result.size());
    ASSERT_EQ(2u, result[0].output_variables.size());
    EXPECT_EQ(0u, result[0].output_variables[0].attributes.blend_src);
    EXPECT_EQ(1u, result[0].output_variables[1].attributes.blend_src);
}

TEST_F(InspectorSubgroupMatrixTest, DirectUse) {
    auto* src = R"(
enable chromium_experimental_subgroup_matrix;

@compute @workgroup_size(1) fn foo() {
  _ = subgroup_matrix_result<f32, 8, 8>();
}
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].uses_subgroup_matrix);
}

TEST_F(InspectorSubgroupMatrixTest, IndirectUse) {
    auto* src = R"(
enable chromium_experimental_subgroup_matrix;

fn foo() { _ = subgroup_matrix_result<f32, 8, 8>(); }
@compute @workgroup_size(1) fn main() { foo(); }
)";
    Inspector& inspector = Initialize(src);

    auto result = inspector.GetEntryPoints();
    ASSERT_FALSE(inspector.has_error()) << inspector.error();

    ASSERT_EQ(1u, result.size());
    EXPECT_TRUE(result[0].uses_subgroup_matrix);
}

}  // namespace

static std::ostream& operator<<(std::ostream& out, const Inspector::TextureQueryType& ty) {
    switch (ty) {
        case Inspector::TextureQueryType::kTextureNumLevels:
            out << "textureNumLevels";
            break;
        case Inspector::TextureQueryType::kTextureNumSamples:
            out << "textureNumSamples";
            break;
    }
    return out;
}

using InspectorImmediateBlockTest = InspectorTest;

TEST_F(InspectorImmediateBlockTest, NoImmediateVariable) {
    auto* src = R"(
@compute @workgroup_size(1)
fn main() {}
)";
    Inspector& inspector = Initialize(src);
    auto info = inspector.GetImmediateBlockInfo("main");
    EXPECT_TRUE(info.none());
}

TEST_F(InspectorImmediateBlockTest, UnusedImmediateVariable) {
    auto* src = R"(
requires immediate_address_space;
var<immediate> a : u32;
@compute @workgroup_size(1)
fn main() {}
)";
    Inspector& inspector = Initialize(src);
    auto info = inspector.GetImmediateBlockInfo("main");
    EXPECT_TRUE(info.none());
}

TEST_F(InspectorImmediateBlockTest, SimpleScalar) {
    auto* src = R"(
requires immediate_address_space;
var<immediate> a : u32;
@compute @workgroup_size(1)
fn main() {
  _ = a;
}
)";
    Inspector& inspector = Initialize(src);
    auto info = inspector.GetImmediateBlockInfo("main");
    EXPECT_TRUE(info[0]);
    for (size_t i = 1; i < info.size(); ++i) {
        EXPECT_FALSE(info[i]);
    }
}

TEST_F(InspectorImmediateBlockTest, SimpleVector) {
    auto* src = R"(
requires immediate_address_space;
var<immediate> a : vec4<f32>;
@compute @workgroup_size(1)
fn main() {
  _ = a;
}
)";
    Inspector& inspector = Initialize(src);
    auto info = inspector.GetImmediateBlockInfo("main");
    for (size_t i = 0; i < 4; ++i) {
        EXPECT_TRUE(info[i]);
    }
    for (size_t i = 4; i < info.size(); ++i) {
        EXPECT_FALSE(info[i]);
    }
}

TEST_F(InspectorImmediateBlockTest, MatrixWithPadding) {
    auto* src = R"(
requires immediate_address_space;
var<immediate> a : mat3x3<f32>;
@compute @workgroup_size(1)
fn main() {
  _ = a;
}
)";
    Inspector& inspector = Initialize(src);
    auto info = inspector.GetImmediateBlockInfo("main");
    // mat3x3<f32> has 3 columns of vec3<f32>.
    // vec3<f32> is 12 bytes, aligned to 16.
    // Size is 48 bytes.

    // Col 0: slots 0, 1, 2. Slot 3 is padding.
    EXPECT_TRUE(info[0]);
    EXPECT_TRUE(info[1]);
    EXPECT_TRUE(info[2]);
    EXPECT_FALSE(info[3]);

    // Col 1: slots 4, 5, 6. Slot 7 is padding.
    EXPECT_TRUE(info[4]);
    EXPECT_TRUE(info[5]);
    EXPECT_TRUE(info[6]);
    EXPECT_FALSE(info[7]);

    // Col 2: slots 8, 9, 10. Slot 11 is padding.
    EXPECT_TRUE(info[8]);
    EXPECT_TRUE(info[9]);
    EXPECT_TRUE(info[10]);
    EXPECT_FALSE(info[11]);

    for (size_t i = 12; i < info.size(); ++i) {
        EXPECT_FALSE(info[i]);
    }
}

TEST_F(InspectorImmediateBlockTest, StructWithPadding) {
    auto* src = R"(
requires immediate_address_space;
struct S {
  a : u32,
  b : vec3<f32>,
}
var<immediate> u : S;
@compute @workgroup_size(1)
fn main() {
  _ = u;
}
)";
    Inspector& inspector = Initialize(src);
    auto info = inspector.GetImmediateBlockInfo("main");
    // S alignment is 16 (vec3<f32>).
    // a: offset 0, size 4.
    // padding: 12 bytes.
    // b: offset 16, size 12.
    // padding: 4 bytes (to align struct size to 16).
    // Total size: 32 bytes.

    // a
    EXPECT_TRUE(info[0]);
    // padding
    EXPECT_FALSE(info[1]);
    EXPECT_FALSE(info[2]);
    EXPECT_FALSE(info[3]);
    // b
    EXPECT_TRUE(info[4]);
    EXPECT_TRUE(info[5]);
    EXPECT_TRUE(info[6]);
    // padding
    EXPECT_FALSE(info[7]);

    for (size_t i = 8; i < info.size(); ++i) {
        EXPECT_FALSE(info[i]);
    }
}

TEST_F(InspectorImmediateBlockTest, IndirectUse) {
    auto* src = R"(
requires immediate_address_space;
var<immediate> a : u32;
fn foo() {
  _ = a;
}
@compute @workgroup_size(1)
fn main() {
  foo();
}
)";
    Inspector& inspector = Initialize(src);
    auto info = inspector.GetImmediateBlockInfo("main");
    EXPECT_TRUE(info[0]);
    for (size_t i = 1; i < info.size(); ++i) {
        EXPECT_FALSE(info[i]);
    }
}

TEST_F(InspectorImmediateBlockTest, PartialAccess) {
    auto* src = R"(
requires immediate_address_space;
struct S {
  a : u32,
  b : u32,
}
var<immediate> u : S;
@compute @workgroup_size(1)
fn main() {
  _ = u.b;
}
)";
    Inspector& inspector = Initialize(src);
    auto info = inspector.GetImmediateBlockInfo("main");
    // Even with partial access, we mark all non-padding bytes.
    EXPECT_TRUE(info[0]);
    EXPECT_TRUE(info[1]);
    for (size_t i = 2; i < info.size(); ++i) {
        EXPECT_FALSE(info[i]);
    }
}

TEST_F(InspectorImmediateBlockTest, FunctionParameter) {
    auto* src = R"(
requires immediate_address_space;
var<immediate> a : u32;
fn foo(val : u32) {
  _ = val;
}
@compute @workgroup_size(1)
fn main() {
  foo(a);
}
)";
    Inspector& inspector = Initialize(src);
    auto info = inspector.GetImmediateBlockInfo("main");
    EXPECT_TRUE(info[0]);
    for (size_t i = 1; i < info.size(); ++i) {
        EXPECT_FALSE(info[i]);
    }
}

}  // namespace tint::inspector
