Add tint_unittest flags for enabling DXC validation

HLSL validation will be disabled by default, and is now opt-in with
--validate-hlsl and/or with a --dxc-path

Change-Id: Ia98d7b1fbba50168bbe85c7982b73598100016d7
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/42024
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/test_main.cc b/src/test_main.cc
index 7a8b394..30e2645 100644
--- a/src/test_main.cc
+++ b/src/test_main.cc
@@ -15,6 +15,8 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "src/debug.h"
+#include "src/utils/command.h"
+#include "src/writer/hlsl/test_helper.h"
 
 namespace {
 
@@ -22,12 +24,75 @@
   FAIL() << diagnostics.str();
 }
 
+struct Flags {
+  bool validate_hlsl = false;
+  std::string dxc_path;
+
+  bool parse(int argc, char** argv) {
+    bool errored = false;
+    for (int i = 1; i < argc && !errored; i++) {
+      auto match = [&](std::string name) { return name == argv[i]; };
+
+      auto parse_value = [&](std::string name, std::string& value) {
+        if (!match(name)) {
+          return false;
+        }
+        if (i + 1 >= argc) {
+          std::cout << "Expected value for flag " << name << "" << std::endl;
+          errored = true;
+          return false;
+        }
+        i++;
+        value = argv[i];
+        return true;
+      };
+
+      if (match("--validate-hlsl") || parse_value("--dxc-path", dxc_path)) {
+        validate_hlsl = true;
+      } else {
+        std::cout << "Unknown flag '" << argv[i] << "'" << std::endl;
+        return false;
+      }
+    }
+    return true;
+  }
+};
+
 }  // namespace
 
 // Entry point for tint unit tests
 int main(int argc, char** argv) {
   testing::InitGoogleMock(&argc, argv);
 
+  Flags flags;
+  if (!flags.parse(argc, argv)) {
+    return -1;
+  }
+
+#if TINT_BUILD_HLSL_WRITER
+  // This must be kept alive for the duration of RUN_ALL_TESTS() as the c_str()
+  // is passed into tint::writer::hlsl::EnableHLSLValidation(), which does not
+  // make a copy. This is to work around Chromium's strict rules on globals
+  // having no constructors / destructors.
+  std::string dxc_path;
+  if (flags.validate_hlsl) {
+    auto dxc = flags.dxc_path.empty() ? tint::utils::Command::LookPath("dxc")
+                                      : tint::utils::Command(flags.dxc_path);
+
+    if (!dxc.Found()) {
+      std::cout << "DXC executable not found" << std::endl;
+      return -1;
+    }
+
+    std::cout << "HLSL validation with DXC enabled" << std::endl;
+
+    dxc_path = dxc.Path();
+    tint::writer::hlsl::EnableHLSLValidation(dxc_path.c_str());
+  } else {
+    std::cout << "HLSL validation with DXC is not enabled" << std::endl;
+  }
+#endif  // TINT_BUILD_HLSL_WRITER
+
   tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
 
   auto res = RUN_ALL_TESTS();
diff --git a/src/utils/command.h b/src/utils/command.h
index c978480..213c7dc 100644
--- a/src/utils/command.h
+++ b/src/utils/command.h
@@ -52,6 +52,9 @@
   /// constructor
   bool Found() const;
 
+  /// @returns the path of the command
+  const std::string& Path() const { return path_; }
+
   /// Invokes the command with the given argument strings, blocking until the
   /// process has returned.
   /// @param args the string arguments to pass to the process
diff --git a/src/writer/hlsl/test_helper.cc b/src/writer/hlsl/test_helper.cc
index 12f6828..9a7b453 100644
--- a/src/writer/hlsl/test_helper.cc
+++ b/src/writer/hlsl/test_helper.cc
@@ -21,12 +21,28 @@
 namespace writer {
 namespace hlsl {
 
+namespace {
+
+const char* dxc_path = nullptr;
+
+}  // namespace
+
+void EnableHLSLValidation(const char* dxc) {
+  dxc_path = dxc;
+}
+
 CompileResult Compile(Program* program, GeneratorImpl* generator) {
   CompileResult result;
 
-  auto dxc = utils::Command::LookPath("dxc");
+  if (!dxc_path) {
+    result.status = CompileResult::Status::kVerificationNotEnabled;
+    return result;
+  }
+
+  auto dxc = utils::Command(dxc_path);
   if (!dxc.Found()) {
-    result.status = CompileResult::Status::kDXCNotFound;
+    result.output = "DXC not found at '" + std::string(dxc_path) + "'";
+    result.status = CompileResult::Status::kFailed;
     return result;
   }
 
diff --git a/src/writer/hlsl/test_helper.h b/src/writer/hlsl/test_helper.h
index a92e2c1..2b169f7 100644
--- a/src/writer/hlsl/test_helper.h
+++ b/src/writer/hlsl/test_helper.h
@@ -31,10 +31,15 @@
 namespace writer {
 namespace hlsl {
 
+/// EnableHLSLValidation enables verification of HLSL shaders by running DXC and
+/// checking no errors are reported.
+/// @param dxc_path the path to the DXC executable
+void EnableHLSLValidation(const char* dxc_path);
+
 /// The return structure of Compile()
 struct CompileResult {
   /// Status is an enumerator of status codes from Compile()
-  enum class Status { kSuccess, kFailed, kDXCNotFound };
+  enum class Status { kSuccess, kFailed, kVerificationNotEnabled };
   /// The resulting status of the compile
   Status status;
   /// Output of DXC.