Add intermediate dawn_wire command handler to dump command traces

This adds an argument to Dawn tests to use an intermediate
command handler which dumps command traces. In the near term, this will
be useful to generate a seed corpus for fuzzing. In the future, we may
be able to use the layer to produce reproducible traces of real
applications.

Bug: dawn:295
Change-Id: Ie36d10f4b46f4b16a3ad3ea34961fd38ba8041aa
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/14241
Commit-Queue: Austin Eng <enga@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/src/tests/DawnTest.cpp b/src/tests/DawnTest.cpp
index 8df1cd8..4d9a09e 100644
--- a/src/tests/DawnTest.cpp
+++ b/src/tests/DawnTest.cpp
@@ -19,6 +19,7 @@
 #include "common/Log.h"
 #include "common/Math.h"
 #include "common/Platform.h"
+#include "common/SystemUtils.h"
 #include "dawn/dawn_proc.h"
 #include "dawn_native/DawnNative.h"
 #include "dawn_wire/WireClient.h"
@@ -28,6 +29,7 @@
 #include "utils/WGPUHelpers.h"
 
 #include <algorithm>
+#include <fstream>
 #include <iomanip>
 #include <sstream>
 #include <unordered_map>
@@ -158,6 +160,19 @@
             continue;
         }
 
+        constexpr const char kWireTraceDirArg[] = "--wire-trace-dir=";
+        if (strstr(argv[i], kWireTraceDirArg) == argv[i]) {
+            const char* wireTraceDir = argv[i] + strlen(kWireTraceDirArg);
+            if (wireTraceDir[0] != '\0') {
+                const char* sep = GetPathSeparator();
+                mWireTraceDir = wireTraceDir;
+                if (mWireTraceDir.back() != *sep) {
+                    mWireTraceDir += sep;
+                }
+            }
+            continue;
+        }
+
         if (strcmp("-h", argv[i]) == 0 || strcmp("--help", argv[i]) == 0) {
             dawn::InfoLog()
                 << "\n\nUsage: " << argv[0]
@@ -264,6 +279,13 @@
     return mVendorIdFilter;
 }
 
+const char* DawnTestEnvironment::GetWireTraceDir() const {
+    if (mWireTraceDir.length() == 0) {
+        return nullptr;
+    }
+    return mWireTraceDir.c_str();
+}
+
 void DawnTestEnvironment::DiscoverOpenGLAdapter() {
 #ifdef DAWN_ENABLE_BACKEND_OPENGL
     if (!glfwInit()) {
@@ -285,6 +307,23 @@
 #endif  // DAWN_ENABLE_BACKEND_OPENGL
 }
 
+class WireServerTraceLayer : public dawn_wire::CommandHandler {
+  public:
+    WireServerTraceLayer(const char* file, dawn_wire::WireServer* server)
+        : dawn_wire::CommandHandler(), mServer(server) {
+        mFile.open(file, std::ios_base::app | std::ios_base::binary | std::ios_base::trunc);
+    }
+
+    const volatile char* HandleCommands(const volatile char* commands, size_t size) override {
+        mFile.write(const_cast<const char*>(commands), size);
+        return mServer->HandleCommands(commands, size);
+    }
+
+  private:
+    dawn_wire::WireServer* mServer;
+    std::ofstream mFile;
+};
+
 // Implementation of DawnTest
 
 DawnTestBase::DawnTestBase(const DawnTestParam& param) : mParam(param) {
@@ -497,6 +536,21 @@
         mWireServer.reset(new dawn_wire::WireServer(serverDesc));
         mC2sBuf->SetHandler(mWireServer.get());
 
+        if (gTestEnv->GetWireTraceDir() != nullptr) {
+            std::string file =
+                std::string(
+                    ::testing::UnitTest::GetInstance()->current_test_info()->test_suite_name()) +
+                "_" + ::testing::UnitTest::GetInstance()->current_test_info()->name();
+            // Replace slashes in gtest names with underscores so everything is in one directory.
+            std::replace(file.begin(), file.end(), '/', '_');
+
+            std::string fullPath = gTestEnv->GetWireTraceDir() + file;
+
+            mWireServerTraceLayer.reset(
+                new WireServerTraceLayer(fullPath.c_str(), mWireServer.get()));
+            mC2sBuf->SetHandler(mWireServerTraceLayer.get());
+        }
+
         dawn_wire::WireClientDescriptor clientDesc = {};
         clientDesc.serializer = mC2sBuf.get();
 
diff --git a/src/tests/DawnTest.h b/src/tests/DawnTest.h
index 3f23d4e..d7e88a6 100644
--- a/src/tests/DawnTest.h
+++ b/src/tests/DawnTest.h
@@ -117,6 +117,7 @@
 }  // namespace detail
 
 namespace dawn_wire {
+    class CommandHandler;
     class WireClient;
     class WireServer;
 }  // namespace dawn_wire
@@ -140,6 +141,7 @@
     dawn_native::Instance* GetInstance() const;
     bool HasVendorIdFilter() const;
     uint32_t GetVendorIdFilter() const;
+    const char* GetWireTraceDir() const;
 
   protected:
     std::unique_ptr<dawn_native::Instance> mInstance;
@@ -154,6 +156,7 @@
     bool mBeginCaptureOnStartup = false;
     bool mHasVendorIdFilter = false;
     uint32_t mVendorIdFilter = 0;
+    std::string mWireTraceDir;
 };
 
 class DawnTestBase {
@@ -242,6 +245,8 @@
     std::unique_ptr<utils::TerribleCommandBuffer> mC2sBuf;
     std::unique_ptr<utils::TerribleCommandBuffer> mS2cBuf;
 
+    std::unique_ptr<dawn_wire::CommandHandler> mWireServerTraceLayer;
+
     // Tracking for validation errors
     static void OnDeviceError(WGPUErrorType type, const char* message, void* userdata);
     bool mExpectError = false;