diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fbf50afe11..d1bcece777 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -632,6 +632,8 @@ if(${TINT_BUILD_TESTS}) inspector/inspector_test.cc inspector/test_inspector_builder.cc inspector/test_inspector_builder.h + inspector/test_inspector_runner.cc + inspector/test_inspector_runner.h intrinsic_table_test.cc program_test.cc resolver/assignment_validation_test.cc diff --git a/src/inspector/inspector.cc b/src/inspector/inspector.cc index d02f5bfe6b..de1f65e361 100644 --- a/src/inspector/inspector.cc +++ b/src/inspector/inspector.cc @@ -44,6 +44,7 @@ #include "src/sem/vector_type.h" #include "src/sem/void_type.h" #include "src/utils/math.h" +#include "src/utils/unique_vector.h" namespace tint { namespace inspector { @@ -394,8 +395,6 @@ std::vector Inspector::GetSamplerResourceBindings( return {}; } - GenerateSamplerTargets(); - std::vector result; auto* func_sem = program_->Sem().Get(func); @@ -420,8 +419,6 @@ std::vector Inspector::GetComparisonSamplerResourceBindings( return {}; } - GenerateSamplerTargets(); - std::vector result; auto* func_sem = program_->Sem().Get(func); @@ -809,24 +806,133 @@ void Inspector::GenerateSamplerTargets() { continue; } - auto* s = c->params()[sampler_index]; - auto* sampler = sem.Get(s)->Variable(); - sem::BindingPoint sampler_binding_point = { - sampler->Declaration()->binding_point().group->value(), - sampler->Declaration()->binding_point().binding->value()}; - auto* t = c->params()[texture_index]; - auto* texture = sem.Get(t)->Variable(); - sem::BindingPoint texture_binding_point = { - texture->Declaration()->binding_point().group->value(), - texture->Declaration()->binding_point().binding->value()}; + auto* s = c->params()[sampler_index]; - for (auto entry_point : entry_points) { - const auto& ep_name = program_->Symbols().NameFor(entry_point); - (*sampler_targets_)[ep_name].add( - {sampler_binding_point, texture_binding_point}); + GetOriginatingResources( + std::array{t, s}, + [&](std::array globals) { + auto* texture = globals[0]; + sem::BindingPoint texture_binding_point = { + texture->Declaration()->binding_point().group->value(), + texture->Declaration()->binding_point().binding->value()}; + + auto* sampler = globals[1]; + sem::BindingPoint sampler_binding_point = { + sampler->Declaration()->binding_point().group->value(), + sampler->Declaration()->binding_point().binding->value()}; + + for (auto entry_point : entry_points) { + const auto& ep_name = program_->Symbols().NameFor(entry_point); + (*sampler_targets_)[ep_name].add( + {sampler_binding_point, texture_binding_point}); + } + }); + } +} + +template +void Inspector::GetOriginatingResources( + std::array exprs, + F&& callback) { + if (!program_->IsValid()) { + TINT_ICE(Inspector, diagnostics_) + << "attempting to get originating resources in invalid program"; + return; + } + + auto& sem = program_->Sem(); + + std::array globals{}; + std::array parameters{}; + UniqueVector callsites; + + for (size_t i = 0; i < N; i++) { + auto*& expr = exprs[i]; + // Resolve each of the expressions + while (true) { + if (auto* user = sem.Get(expr)) { + auto* var = user->Variable(); + + if (auto* global = tint::As(var)) { + // Found the global resource declaration. + globals[i] = global; + break; // Done with this expression. + } + + if (auto* local = tint::As(var)) { + // Chase the variable + expr = local->Declaration()->constructor(); + if (!expr) { + TINT_ICE(Inspector, diagnostics_) + << "resource variable had no initializer"; + return; + } + continue; // Continue chasing the expression in this function + } + + if (auto* param = tint::As(var)) { + // Gather each of the callers of this function + auto* func = tint::As(param->Owner()); + if (func->CallSites().empty()) { + // One or more of the expressions is a parameter, but this function + // is not called. Ignore. + return; + } + for (auto* call_expr : func->CallSites()) { + callsites.add(call_expr); + } + // Need to evaluate each function call with the group of + // expressions, so move on to the next expression. + parameters[i] = param; + break; + } + + TINT_ICE(Inspector, diagnostics_) + << "unexpected variable type " << var->TypeInfo().name; + } + + if (auto* unary = tint::As(expr)) { + switch (unary->op()) { + case ast::UnaryOp::kAddressOf: + case ast::UnaryOp::kIndirection: + // `*` and `&` are the only valid unary ops for a resource type, + // and must be balanced in order for the program to have passed + // validation. Just skip past these. + expr = unary->expr(); + continue; + default: { + TINT_ICE(Inspector, diagnostics_) + << "unexpected unary op on resource: " << unary->op(); + return; + } + } + } + + TINT_ICE(Inspector, diagnostics_) + << "cannot resolve originating resource with expression type " + << expr->TypeInfo().name; + return; } } + + if (callsites.size()) { + for (auto* call_expr : callsites) { + // Make a copy of the expressions for this callsite + std::array call_exprs = exprs; + // Patch all the parameter expressions with their argument + for (size_t i = 0; i < N; i++) { + if (auto* param = parameters[i]) { + call_exprs[i] = call_expr->params()[param->Index()]; + } + } + // Now call GetOriginatingResources() with from the callsite + GetOriginatingResources(call_exprs, callback); + } + } else { + // All the expressions resolved to globals + callback(globals); + } } } // namespace inspector diff --git a/src/inspector/inspector.h b/src/inspector/inspector.h index 613bd0bcae..ce8ce63669 100644 --- a/src/inspector/inspector.h +++ b/src/inspector/inspector.h @@ -210,6 +210,21 @@ class Inspector { /// Constructes |sampler_targets_| if it hasn't already been instantiated. void GenerateSamplerTargets(); + + /// For a N-uple of expressions, resolve to the appropriate global resources + /// and call 'cb'. + /// 'cb' may be called multiple times. + /// Assumes that not being able to resolve the resources is an error, so will + /// invoke TINT_ICE when that occurs. + /// @tparam N number of expressions in the n-uple + /// @tparam F type of the callback provided. + /// @param exprs N-uple of expressions to resolve. + /// @param cb is a callback function with the signature: + /// `void(std::array)`, which is invoked + /// whenever a set of expressions are resolved to globals. + template + void GetOriginatingResources(std::array exprs, + F&& cb); }; } // namespace inspector diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc index 91d120a0e9..9e43df505b 100644 --- a/src/inspector/inspector_test.cc +++ b/src/inspector/inspector_test.cc @@ -20,6 +20,7 @@ #include "src/ast/struct_block_decoration.h" #include "src/ast/workgroup_decoration.h" #include "src/inspector/test_inspector_builder.h" +#include "src/inspector/test_inspector_runner.h" #include "src/program_builder.h" #include "src/sem/depth_texture_type.h" #include "src/sem/external_texture_type.h" @@ -34,8 +35,15 @@ namespace { // All the tests that descend from InspectorBuilder are expected to define their // test state via building up the AST through InspectorBuilder and then generate -// the program with ::Build(). -// The returned Inspector from ::Build() can then be used to test expecations. +// the program with ::Build. +// The returned Inspector from ::Build can then be used to test expecations. +// +// All the tests that descend from InspectorRunner are expected to define their +// test state via a WGSL shader, which will be parsed to generate a Program and +// Inspector in ::Initialize. +// The returned Inspector from ::Initialize can then be used to test +// expecations. + class InspectorGetEntryPointTest : public InspectorBuilder, public testing::Test {}; @@ -135,12 +143,16 @@ class InspectorGetStorageTextureResourceBindingsTestWithParam class InspectorGetExternalTextureResourceBindingsTest : public InspectorBuilder, public testing::Test {}; -class InspectorGetSamplerTextureUsesTest : public InspectorBuilder, +class InspectorGetSamplerTextureUsesTest : public InspectorRunner, public testing::Test {}; class InspectorGetWorkgroupStorageSizeTest : public InspectorBuilder, public testing::Test {}; +// This is a catch all for shaders that have demonstrated regressions/crashes in +// the wild. +class InspectorRegressionTest : public InspectorRunner, public testing::Test {}; + TEST_F(InspectorGetEntryPointTest, NoFunctions) { Inspector& inspector = Build(); @@ -2353,34 +2365,31 @@ TEST_F(InspectorGetExternalTextureResourceBindingsTest, Simple) { } TEST_F(InspectorGetSamplerTextureUsesTest, None) { - MakeEmptyBodyFunction("ep_func", ast::DecorationList{ - Stage(ast::PipelineStage::kFragment), - }); + std::string shader = R"( +[[stage(fragment)]] +fn main() { +})"; - Inspector& inspector = Build(); - - auto result = inspector.GetSamplerTextureUses("ep_func"); + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); ASSERT_FALSE(inspector.has_error()) << inspector.error(); ASSERT_EQ(0u, result.size()); } TEST_F(InspectorGetSamplerTextureUsesTest, Simple) { - auto* sampled_texture_type = - ty.sampled_texture(ast::TextureDimension::k1d, ty.f32()); - AddResource("foo_texture", sampled_texture_type, 0, 10); - AddSampler("foo_sampler", 0, 1); - AddGlobalVariable("foo_coords", ty.f32()); + std::string shader = R"( +[[group(0), binding(1)]] var mySampler: sampler; +[[group(0), binding(2)]] var myTexture: texture_2d; - MakeSamplerReferenceBodyFunction("ep_func", "foo_texture", "foo_sampler", - "foo_coords", ty.f32(), - ast::DecorationList{ - Stage(ast::PipelineStage::kFragment), - }); +[[stage(fragment)]] +fn main([[location(0)]] fragUV: vec2, + [[location(1)]] fragPosition: vec4) -> [[location(0)]] vec4 { + return textureSample(myTexture, mySampler, fragUV) * fragPosition; +})"; - Inspector& inspector = Build(); - - auto result = inspector.GetSamplerTextureUses("ep_func"); + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); ASSERT_FALSE(inspector.has_error()) << inspector.error(); ASSERT_EQ(1u, result.size()); @@ -2388,58 +2397,233 @@ TEST_F(InspectorGetSamplerTextureUsesTest, Simple) { EXPECT_EQ(0u, result[0].sampler_binding_point.group); EXPECT_EQ(1u, result[0].sampler_binding_point.binding); EXPECT_EQ(0u, result[0].texture_binding_point.group); - EXPECT_EQ(10u, result[0].texture_binding_point.binding); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, UnknownEntryPoint) { + std::string shader = R"( +[[group(0), binding(1)]] var mySampler: sampler; +[[group(0), binding(2)]] var myTexture: texture_2d; + +[[stage(fragment)]] +fn main([[location(0)]] fragUV: vec2, + [[location(1)]] fragPosition: vec4) -> [[location(0)]] vec4 { + return textureSample(myTexture, mySampler, fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("foo"); + ASSERT_TRUE(inspector.has_error()) << inspector.error(); } TEST_F(InspectorGetSamplerTextureUsesTest, MultipleCalls) { - auto* sampled_texture_type = - ty.sampled_texture(ast::TextureDimension::k1d, ty.f32()); - AddResource("foo_texture", sampled_texture_type, 0, 10); - AddSampler("foo_sampler", 0, 1); - AddGlobalVariable("foo_coords", ty.f32()); + std::string shader = R"( +[[group(0), binding(1)]] var mySampler: sampler; +[[group(0), binding(2)]] var myTexture: texture_2d; - MakeSamplerReferenceBodyFunction("ep_func", "foo_texture", "foo_sampler", - "foo_coords", ty.f32(), - ast::DecorationList{ - Stage(ast::PipelineStage::kFragment), - }); +[[stage(fragment)]] +fn main([[location(0)]] fragUV: vec2, + [[location(1)]] fragPosition: vec4) -> [[location(0)]] vec4 { + return textureSample(myTexture, mySampler, fragUV) * fragPosition; +})"; - Inspector& inspector = Build(); - - auto result_0 = inspector.GetSamplerTextureUses("ep_func"); + Inspector& inspector = Initialize(shader); + auto result_0 = inspector.GetSamplerTextureUses("main"); ASSERT_FALSE(inspector.has_error()) << inspector.error(); - auto result_1 = inspector.GetSamplerTextureUses("ep_func"); + auto result_1 = inspector.GetSamplerTextureUses("main"); ASSERT_FALSE(inspector.has_error()) << inspector.error(); EXPECT_EQ(result_0, result_1); } -TEST_F(InspectorGetSamplerTextureUsesTest, InFunction) { - auto* sampled_texture_type = - ty.sampled_texture(ast::TextureDimension::k1d, ty.f32()); - AddResource("foo_texture", sampled_texture_type, 0, 0); - AddSampler("foo_sampler", 0, 1); - AddGlobalVariable("foo_coords", ty.f32()); +TEST_F(InspectorGetSamplerTextureUsesTest, BothIndirect) { + std::string shader = R"( +[[group(0), binding(1)]] var mySampler: sampler; +[[group(0), binding(2)]] var myTexture: texture_2d; - MakeSamplerReferenceBodyFunction("foo_func", "foo_texture", "foo_sampler", - "foo_coords", ty.f32(), {}); +fn doSample(t: texture_2d, s: sampler, uv: vec2) -> vec4 { + return textureSample(t, s, uv); +} - MakeCallerBodyFunction("ep_func", {"foo_func"}, - ast::DecorationList{ - Stage(ast::PipelineStage::kFragment), - }); +[[stage(fragment)]] +fn main([[location(0)]] fragUV: vec2, + [[location(1)]] fragPosition: vec4) -> [[location(0)]] vec4 { + return doSample(myTexture, mySampler, fragUV) * fragPosition; +})"; - Inspector& inspector = Build(); - - auto result = inspector.GetSamplerTextureUses("ep_func"); + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); ASSERT_FALSE(inspector.has_error()) << inspector.error(); ASSERT_EQ(1u, result.size()); + EXPECT_EQ(0u, result[0].sampler_binding_point.group); EXPECT_EQ(1u, result[0].sampler_binding_point.binding); EXPECT_EQ(0u, result[0].texture_binding_point.group); - EXPECT_EQ(0u, result[0].texture_binding_point.binding); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, SamplerIndirect) { + std::string shader = R"( +[[group(0), binding(1)]] var mySampler: sampler; +[[group(0), binding(2)]] var myTexture: texture_2d; + +fn doSample(s: sampler, uv: vec2) -> vec4 { + return textureSample(myTexture, s, uv); +} + +[[stage(fragment)]] +fn main([[location(0)]] fragUV: vec2, + [[location(1)]] fragPosition: vec4) -> [[location(0)]] vec4 { + return doSample(mySampler, fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, TextureIndirect) { + std::string shader = R"( +[[group(0), binding(1)]] var mySampler: sampler; +[[group(0), binding(2)]] var myTexture: texture_2d; + +fn doSample(t: texture_2d, uv: vec2) -> vec4 { + return textureSample(t, mySampler, uv); +} + +[[stage(fragment)]] +fn main([[location(0)]] fragUV: vec2, + [[location(1)]] fragPosition: vec4) -> [[location(0)]] vec4 { + return doSample(myTexture, fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, NeitherIndirect) { + std::string shader = R"( +[[group(0), binding(1)]] var mySampler: sampler; +[[group(0), binding(2)]] var myTexture: texture_2d; + +fn doSample(uv: vec2) -> vec4 { + return textureSample(myTexture, mySampler, uv); +} + +[[stage(fragment)]] +fn main([[location(0)]] fragUV: vec2, + [[location(1)]] fragPosition: vec4) -> [[location(0)]] vec4 { + return doSample(fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); +} + +TEST_F(InspectorGetSamplerTextureUsesTest, Complex) { + std::string shader = R"( +[[group(0), binding(1)]] var mySampler: sampler; +[[group(0), binding(2)]] var myTexture: texture_2d; + + +fn doSample(t: texture_2d, s: sampler, uv: vec2) -> vec4 { + return textureSample(t, s, uv); +} + +fn X(t: texture_2d, s: sampler, uv: vec2) -> vec4 { + return doSample(t, s, uv); +} + +fn Y(t: texture_2d, s: sampler, uv: vec2) -> vec4 { + return doSample(t, s, uv); +} + +fn Z(t: texture_2d, s: sampler, uv: vec2) -> vec4 { + return X(t, s, uv) + Y(t, s, uv); +} + +[[stage(fragment)]] +fn via_call([[location(0)]] fragUV: vec2, + [[location(1)]] fragPosition: vec4) -> [[location(0)]] vec4 { + return Z(myTexture, mySampler, fragUV) * fragPosition; +} + +[[stage(fragment)]] +fn via_ptr([[location(0)]] fragUV: vec2, + [[location(1)]] fragPosition: vec4) -> [[location(0)]] vec4 { + let t = &myTexture; + let s = &mySampler; + return textureSample(*t, *s, fragUV) + fragPosition; +} + +[[stage(fragment)]] +fn direct([[location(0)]] fragUV: vec2, + [[location(1)]] fragPosition: vec4) -> [[location(0)]] vec4 { + return textureSample(myTexture, mySampler, fragUV) + fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + + { + auto result = inspector.GetSamplerTextureUses("via_call"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); + } + + { + auto result = inspector.GetSamplerTextureUses("via_ptr"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); + } + + { + auto result = inspector.GetSamplerTextureUses("direct"); + ASSERT_FALSE(inspector.has_error()) << inspector.error(); + + ASSERT_EQ(1u, result.size()); + + EXPECT_EQ(0u, result[0].sampler_binding_point.group); + EXPECT_EQ(1u, result[0].sampler_binding_point.binding); + EXPECT_EQ(0u, result[0].texture_binding_point.group); + EXPECT_EQ(2u, result[0].texture_binding_point.binding); + } } TEST_F(InspectorGetWorkgroupStorageSizeTest, Empty) { @@ -2529,6 +2713,27 @@ TEST_F(InspectorGetWorkgroupStorageSizeTest, StructAlignment) { EXPECT_EQ(1024u, inspector.GetWorkgroupStorageSize("ep_func")); } +// 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; + +fn doSample(t: texture_2d, s: sampler, uv: vec2) -> vec4 { + return textureSample(t, s, uv); +} + +[[stage(fragment)]] +fn main([[location(0)]] fragUV: vec2, + [[location(1)]] fragPosition: vec4) -> [[location(0)]] vec4 { + return doSample(myTexture, mySampler, fragUV) * fragPosition; +})"; + + Inspector& inspector = Initialize(shader); + auto result = inspector.GetSamplerTextureUses("main"); +} + } // namespace } // namespace inspector } // namespace tint diff --git a/src/inspector/test_inspector_runner.cc b/src/inspector/test_inspector_runner.cc new file mode 100644 index 0000000000..b7c6c7af96 --- /dev/null +++ b/src/inspector/test_inspector_runner.cc @@ -0,0 +1,39 @@ +// Copyright 2021 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/inspector/test_inspector_runner.h" + +namespace tint { +namespace inspector { + +InspectorRunner::InspectorRunner() = default; +InspectorRunner::~InspectorRunner() = default; + +Inspector& InspectorRunner::Initialize(std::string shader) { + if (inspector_) { + return *inspector_; + } + + file_ = std::make_unique("test", shader); + program_ = std::make_unique(reader::wgsl::Parse(file_.get())); + [&]() { + ASSERT_TRUE(program_->IsValid()) + << diag::Formatter().format(program_->Diagnostics()); + }(); + inspector_ = std::make_unique(program_.get()); + return *inspector_; +} + +} // namespace inspector +} // namespace tint diff --git a/src/inspector/test_inspector_runner.h b/src/inspector/test_inspector_runner.h new file mode 100644 index 0000000000..d4054424da --- /dev/null +++ b/src/inspector/test_inspector_runner.h @@ -0,0 +1,51 @@ +// Copyright 2021 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SRC_INSPECTOR_TEST_INSPECTOR_RUNNER_H_ +#define SRC_INSPECTOR_TEST_INSPECTOR_RUNNER_H_ + +#include +#include + +#include "gtest/gtest.h" +#include "tint/tint.h" + +namespace tint { +namespace inspector { + +/// Utility class for running shaders in inspector tests +class InspectorRunner { + public: + InspectorRunner(); + virtual ~InspectorRunner(); + + /// Create a Program with Inspector from the provided WGSL shader. + /// Should only be called once per test. + /// @param shader a WGSL shader + /// @returns a reference to the Inspector for the built Program. + Inspector& Initialize(std::string shader); + + protected: + /// File created from input shader and used to create Program. + std::unique_ptr file_; + /// Program created by this runner. + std::unique_ptr program_; + /// Inspector for |program_| + std::unique_ptr inspector_; +}; + +} // namespace inspector +} // namespace tint + +#endif // SRC_INSPECTOR_TEST_INSPECTOR_RUNNER_H_ diff --git a/test/BUILD.gn b/test/BUILD.gn index 67d2d39c49..7c8738c595 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -224,6 +224,8 @@ tint_unittests_source_set("tint_unittests_core_src") { "../src/inspector/inspector_test.cc", "../src/inspector/test_inspector_builder.cc", "../src/inspector/test_inspector_builder.h", + "../src/inspector/test_inspector_runner.cc", + "../src/inspector/test_inspector_runner.h", "../src/intrinsic_table_test.cc", "../src/program_builder_test.cc", "../src/program_test.cc",