[tint][ir] Refactor IRToProgramTest class

Move it to a header so it can be used by other *_test.cc files.

Have the results returned instead of using gtest checks in the method.
Create a EXPECT_WGSL macro so that test failures have a localized file
and line in the output. Makes finding the broken tests much easier, and
makes fix-tests work with these tests.

Change-Id: I3e78328e933700032b4cd4f50bcd81e572e8f2eb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/139203
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/ir/to_program_test.cc b/src/tint/ir/to_program_test.cc
index 8cfae5c..b0a9c73 100644
--- a/src/tint/ir/to_program_test.cc
+++ b/src/tint/ir/to_program_test.cc
@@ -12,65 +12,60 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <sstream>
 #include <string>
 
 #include "src/tint/ir/disassembler.h"
-#include "src/tint/ir/ir_test_helper.h"
 #include "src/tint/ir/to_program.h"
+#include "src/tint/ir/to_program_test.h"
 #include "src/tint/utils/string.h"
 #include "src/tint/writer/wgsl/generator.h"
 
-#if !TINT_BUILD_WGSL_WRITER
-#error "to_program_test.cc requires both the WGSL writer to be enabled"
-#endif
-
-namespace tint::ir {
-namespace {
+namespace tint::ir::test {
 
 using namespace tint::number_suffixes;        // NOLINT
 using namespace tint::builtin::fluent_types;  // NOLINT
 
-class IRToProgramTest : public IRTestHelper {
-  public:
-    void Test(std::string_view expected_wgsl) {
-        tint::ir::Disassembler d{mod};
-        auto disassembly = d.Disassemble();
+IRToProgramTest::Result IRToProgramTest::Run() {
+    Result result;
 
-        auto output_program = ToProgram(mod);
-        if (!output_program.IsValid()) {
-            FAIL() << output_program.Diagnostics().str() << std::endl  //
-                   << "IR:" << std::endl                               //
-                   << disassembly << std::endl                         //
-                   << "AST:" << std::endl                              //
-                   << Program::printer(&output_program) << std::endl;
-        }
+    tint::ir::Disassembler d{mod};
+    result.ir = d.Disassemble();
 
-        ASSERT_TRUE(output_program.IsValid()) << output_program.Diagnostics().str();
-
-        auto output = writer::wgsl::Generate(&output_program, {});
-        ASSERT_TRUE(output.success) << output.error;
-
-        auto expected = std::string(utils::TrimSpace(expected_wgsl));
-        if (!expected.empty()) {
-            expected = "\n" + expected + "\n";
-        }
-        auto got = std::string(utils::TrimSpace(output.wgsl));
-        if (!got.empty()) {
-            got = "\n" + got + "\n";
-        }
-        EXPECT_EQ(expected, got) << "IR:" << std::endl << disassembly;
+    auto output_program = ToProgram(mod);
+    if (!output_program.IsValid()) {
+        result.err = output_program.Diagnostics().str();
+        result.ast = Program::printer(&output_program);
+        return result;
     }
-};
+
+    auto output = writer::wgsl::Generate(&output_program, {});
+    if (!output.success) {
+        std::stringstream ss;
+        ss << "wgsl::Generate() errored: " << output.error;
+        result.err = ss.str();
+        return result;
+    }
+
+    result.wgsl = std::string(utils::TrimSpace(output.wgsl));
+    if (!result.wgsl.empty()) {
+        result.wgsl = "\n" + result.wgsl + "\n";
+    }
+
+    return result;
+}
+
+namespace {
 
 TEST_F(IRToProgramTest, EmptyModule) {
-    Test("");
+    EXPECT_WGSL("");
 }
 
 TEST_F(IRToProgramTest, SingleFunction_Empty) {
     auto* fn = b.Function("f", ty.void_());
     mod.functions.Push(fn);
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
 }
 )");
@@ -82,7 +77,7 @@
 
     fn->Block()->Append(b.Return(fn));
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
 }
 )");
@@ -94,7 +89,7 @@
 
     fn->Block()->Append(b.Return(fn, 42_i));
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() -> i32 {
   return 42i;
 }
@@ -112,7 +107,7 @@
 
     fn->Block()->Append(b.Return(fn, i));
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(i : i32, u : u32) -> i32 {
   return i;
 }
@@ -131,7 +126,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.Negation(ty.i32(), i)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(i : i32) -> i32 {
   return -(i);
 }
@@ -147,7 +142,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.Complement(ty.u32(), i)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(i : u32) -> u32 {
   return ~(i);
 }
@@ -163,7 +158,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.Not(ty.bool_(), i)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(b : bool) -> bool {
   return !(b);
 }
@@ -184,7 +179,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.Add(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> i32 {
   return (a + b);
 }
@@ -202,7 +197,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.Subtract(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> i32 {
   return (a - b);
 }
@@ -220,7 +215,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.Multiply(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> i32 {
   return (a * b);
 }
@@ -238,7 +233,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.Divide(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> i32 {
   return (a / b);
 }
@@ -256,7 +251,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.Modulo(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> i32 {
   return (a % b);
 }
@@ -274,7 +269,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.And(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> i32 {
   return (a & b);
 }
@@ -292,7 +287,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.Or(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> i32 {
   return (a | b);
 }
@@ -310,7 +305,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.Xor(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> i32 {
   return (a ^ b);
 }
@@ -328,7 +323,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.Equal(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> bool {
   return (a == b);
 }
@@ -346,7 +341,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.NotEqual(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> bool {
   return (a != b);
 }
@@ -364,7 +359,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.LessThan(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> bool {
   return (a < b);
 }
@@ -382,7 +377,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.GreaterThan(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> bool {
   return (a > b);
 }
@@ -400,7 +395,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.LessThanEqual(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> bool {
   return (a <= b);
 }
@@ -418,7 +413,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.GreaterThanEqual(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : i32) -> bool {
   return (a >= b);
 }
@@ -436,7 +431,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.ShiftLeft(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : u32) -> i32 {
   return (a << b);
 }
@@ -454,7 +449,7 @@
 
     b.With(fn->Block(), [&] { b.Return(fn, b.ShiftRight(ty.i32(), pa, pb)); });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : i32, b : u32) -> i32 {
   return (a >> b);
 }
@@ -482,7 +477,7 @@
         b.Return(fn, if_->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : bool, b : bool) -> bool {
   return (a && b);
 }
@@ -514,7 +509,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : bool, b : bool, c : bool) -> bool {
   return ((a && b) && c);
 }
@@ -545,7 +540,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : bool, b : bool, c : bool) -> bool {
   return (a && (b && c));
 }
@@ -571,7 +566,7 @@
         b.Return(fn, if_->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : bool, b : bool) -> bool {
   let l = (a && b);
   return l;
@@ -605,7 +600,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : bool, b : bool, c : bool) -> bool {
   let l = ((a && b) && c);
   return l;
@@ -639,7 +634,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : bool, b : bool, c : bool) -> bool {
   let l = (a && (b && c));
   return l;
@@ -668,7 +663,7 @@
         b.Return(fn, if_->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() -> bool {
   return true;
 }
@@ -713,7 +708,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() -> bool {
   return true;
 }
@@ -762,7 +757,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() -> bool {
   return true;
 }
@@ -799,7 +794,7 @@
         b.Return(fn, if_->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : bool, b : bool) -> bool {
   return (a || b);
 }
@@ -831,7 +826,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : bool, b : bool, c : bool) -> bool {
   return ((a || b) || c);
 }
@@ -863,7 +858,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : bool, b : bool, c : bool) -> bool {
   return (a || (b || c));
 }
@@ -889,7 +884,7 @@
         b.Return(fn, if_->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : bool, b : bool) -> bool {
   let l = (a || b);
   return l;
@@ -923,7 +918,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : bool, b : bool, c : bool) -> bool {
   let l = ((a || b) || c);
   return l;
@@ -957,7 +952,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(a : bool, b : bool, c : bool) -> bool {
   let l = (a || (b || c));
   return l;
@@ -986,7 +981,7 @@
         b.Return(fn, if_->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() -> bool {
   return true;
 }
@@ -1031,7 +1026,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() -> bool {
   return true;
 }
@@ -1080,7 +1075,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() -> bool {
   return true;
 }
@@ -1138,7 +1133,7 @@
         b.Return(fn, if2->Result(0));
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn b() -> bool {
   return true;
 }
@@ -1167,7 +1162,7 @@
         mod.SetName(v, "v");
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var v : i32;
   v = (v + 1i);
@@ -1185,7 +1180,7 @@
         mod.SetName(v, "v");
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var v : i32;
   v = (v - 1i);
@@ -1203,7 +1198,7 @@
         mod.SetName(v, "v");
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var v : i32;
   v = (v + 8i);
@@ -1221,7 +1216,7 @@
         mod.SetName(v, "v");
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var v : i32;
   v = (v - 8i);
@@ -1239,7 +1234,7 @@
         mod.SetName(v, "v");
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var v : i32;
   v = (v * 8i);
@@ -1257,7 +1252,7 @@
         mod.SetName(v, "v");
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var v : i32;
   v = (v / 8i);
@@ -1275,7 +1270,7 @@
         mod.SetName(v, "v");
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var v : i32;
   v = (v ^ 8i);
@@ -1299,7 +1294,7 @@
         mod.SetName(v, "v");
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(i : u32) -> u32 {
   let v = ~(i);
   return v;
@@ -1320,7 +1315,7 @@
         mod.SetName(v, "v");
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(i : i32) -> i32 {
   let v = (i * 2i);
   return (v + v);
@@ -1340,7 +1335,7 @@
         mod.SetName(i, "i");
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var i : i32;
 }
@@ -1357,7 +1352,7 @@
         mod.SetName(i, "i");
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var i : i32 = 42i;
 }
@@ -1385,7 +1380,7 @@
         mod.SetName(vc, "c");
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var a : i32 = 42i;
   var b : i32 = a;
@@ -1415,7 +1410,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() {
 }
 
@@ -1439,7 +1434,7 @@
         b.With(if_->True(), [&] { b.Return(fn); });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(cond : bool) {
   if (cond) {
     return;
@@ -1461,7 +1456,7 @@
         b.Return(fn, 10_i);
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() -> i32 {
   var cond : bool = true;
   if (cond) {
@@ -1497,7 +1492,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() {
 }
 
@@ -1527,7 +1522,7 @@
         b.With(if_->False(), [&] { b.Return(fn, 2.0_f); });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() -> f32 {
   var cond : bool = true;
   if (cond) {
@@ -1563,7 +1558,7 @@
         b.Return(fn, 2_u);
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() {
 }
 
@@ -1616,7 +1611,7 @@
         b.Call(ty.void_(), fn_c);
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() {
 }
 
@@ -1683,7 +1678,7 @@
             });
         });
     });
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn x(i : i32) -> bool {
   return true;
 }
@@ -1724,7 +1719,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() {
 }
 
@@ -1777,7 +1772,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() {
 }
 
@@ -1830,7 +1825,7 @@
         b.Return(fn);
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() {
 }
 
@@ -1900,7 +1895,7 @@
             b.ExitSwitch(s1);
         });
     });
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a() {
 }
 
@@ -1959,7 +1954,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   for(var i : i32 = 0i; (i < 5i); i = (i + 1i)) {
   }
@@ -1987,7 +1982,7 @@
         b.With(loop->Continuing(), [&] { b.Store(i, b.Add(ty.i32(), b.Load(i), 1_i)); });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var i : i32 = 0i;
   for(; (i < 5i); i = (i + 1i)) {
@@ -2016,7 +2011,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   for(var i : i32 = 0i; (i < 5i); ) {
   }
@@ -2059,7 +2054,7 @@
         b.Return(fn, 3_i);
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a(v : i32) -> bool {
   return (v == 1i);
 }
@@ -2110,7 +2105,7 @@
         b.Return(fn, 3_i);
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a(v : i32) -> bool {
   return (v == 1i);
 }
@@ -2161,7 +2156,7 @@
         b.Return(fn, 3_i);
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn a(v : i32) -> bool {
   return (v == 1i);
 }
@@ -2209,7 +2204,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn n(v : i32) -> i32 {
   return (v + 1i);
 }
@@ -2238,7 +2233,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   while(true) {
   }
@@ -2263,7 +2258,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(cond : bool) {
   while(cond) {
   }
@@ -2286,7 +2281,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   while(true) {
     break;
@@ -2315,7 +2310,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(cond : bool) {
   while(true) {
     if (cond) {
@@ -2346,7 +2341,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(cond : bool) {
   while(true) {
     if (cond) {
@@ -2370,7 +2365,7 @@
         b.With(loop->Body(), [&] { b.ExitLoop(loop); });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   loop {
     break;
@@ -2394,7 +2389,7 @@
             b.With(if_->True(), [&] { b.ExitLoop(loop); });
         });
     });
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(cond : bool) {
   loop {
     if (cond) {
@@ -2421,7 +2416,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f(cond : bool) {
   loop {
     if (cond) {
@@ -2451,7 +2446,7 @@
         b.With(loop->Continuing(), [&] { b.Store(cond, true); });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var cond : bool = false;
   loop {
@@ -2492,7 +2487,7 @@
         });
     });
 
-    Test(R"(
+    EXPECT_WGSL(R"(
 fn f() {
   var b : i32 = 1i;
   loop {
@@ -2510,4 +2505,4 @@
 }
 
 }  // namespace
-}  // namespace tint::ir
+}  // namespace tint::ir::test
diff --git a/src/tint/ir/to_program_test.h b/src/tint/ir/to_program_test.h
new file mode 100644
index 0000000..ccf13ce
--- /dev/null
+++ b/src/tint/ir/to_program_test.h
@@ -0,0 +1,61 @@
+// Copyright 2023 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_TINT_IR_TO_PROGRAM_TEST_H_
+#define SRC_TINT_IR_TO_PROGRAM_TEST_H_
+
+#include <string>
+
+#include "src/tint/ir/ir_test_helper.h"
+
+#if !TINT_BUILD_WGSL_WRITER
+#error "to_program_test.h requires the WGSL writer to be enabled"
+#endif
+
+namespace tint::ir::test {
+
+/// Class used for IR to Program tests
+class IRToProgramTest : public IRTestHelper {
+  public:
+    /// The result of Run()
+    struct Result {
+        /// The resulting WGSL
+        std::string wgsl;
+        /// The resulting AST
+        std::string ast;
+        /// The resulting IR
+        std::string ir;
+        /// The resulting error
+        std::string err;
+    };
+    /// @returns the WGSL generated from the IR
+    Result Run();
+};
+
+#define EXPECT_WGSL(expected_wgsl)                                                     \
+    do {                                                                               \
+        if (auto got = Run(); got.err.empty()) {                                       \
+            auto expected = std::string(utils::TrimSpace(expected_wgsl));              \
+            if (!expected.empty()) {                                                   \
+                expected = "\n" + expected + "\n";                                     \
+            }                                                                          \
+            EXPECT_EQ(expected, got.wgsl) << "IR: " << got.ir;                         \
+        } else {                                                                       \
+            FAIL() << got.err << std::endl << "IR: " << got.ir << "AST:" << std::endl; \
+        }                                                                              \
+    } while (false)
+
+}  // namespace tint::ir::test
+
+#endif  // SRC_TINT_IR_TO_PROGRAM_TEST_H_