[dawn][emscripten] Add bare-bone unit testing for Emscripten bindings.

Change-Id: Id950fa83fc3c36e0433e516f96a6c3acbecc1862
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/211235
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a2578b7..bcb91ba1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -77,12 +77,14 @@
 set(USE_X11 OFF)
 set(USE_WINDOWS_UI OFF)
 set(BUILD_SAMPLES OFF)
+set(BUILD_TESTS OFF)
 set(TARGET_MACOS OFF)
 if (CMAKE_SYSTEM_NAME MATCHES "Emscripten")
   # Only the samples are supported for Emscripten at the moment.
   # TODO(crbug.com/42240181): Make dawn_end2end_tests work too.
   set(ENABLE_EMSCRIPTEN ON)
   set(BUILD_SAMPLES ON)
+  set(BUILD_TESTS ON)
   set(ENABLE_NULL OFF)
 elseif (WIN32)
   set(ENABLE_D3D11 ON)
@@ -174,6 +176,7 @@
 option(DAWN_TARGET_MACOS "Manually link Apple core frameworks" ${TARGET_MACOS})
 
 option(DAWN_BUILD_SAMPLES "Enables building Dawn's samples" ${BUILD_SAMPLES})
+option(DAWN_BUILD_TESTS "Enables building Dawn's tests" ${BUILD_TESTS})
 option(DAWN_BUILD_NODE_BINDINGS "Enables building Dawn's NodeJS bindings" OFF)
 option(DAWN_ENABLE_SWIFTSHADER "Enables building Swiftshader as part of the build and Vulkan adapter discovery" OFF)
 option(DAWN_BUILD_BENCHMARKS "Build Dawn benchmarks" OFF)
@@ -293,6 +296,7 @@
 set_if_not_defined(DAWN_PROTOBUF_DIR "${DAWN_THIRD_PARTY_DIR}/protobuf" "Directory in which to find protobuf")
 set_if_not_defined(DAWN_LPM_DIR "${DAWN_THIRD_PARTY_DIR}/libprotobuf-mutator/src" "Directory in which to find libprotobuf")
 set_if_not_defined(DAWN_EMDAWNWEBGPU_DIR "${DAWN_THIRD_PARTY_DIR}/emdawnwebgpu" "Directory in which to find Dawn specific Emscripten bindings")
+set_if_not_defined(DAWN_GOOGLETEST_DIR "${DAWN_THIRD_PARTY_DIR}/googletest" "Directory in which to find googletest")
 
 set_if_not_defined(DAWN_SPIRV_TOOLS_DIR "${DAWN_THIRD_PARTY_DIR}/spirv-tools/src" "Directory in which to find SPIRV-Tools")
 set_if_not_defined(DAWN_SPIRV_HEADERS_DIR "${DAWN_THIRD_PARTY_DIR}/spirv-headers/src" "Directory in which to find SPIRV-Headers")
diff --git a/src/dawn/samples/CMakeLists.txt b/src/dawn/samples/CMakeLists.txt
index 8eee09c..ff4d9d9 100644
--- a/src/dawn/samples/CMakeLists.txt
+++ b/src/dawn/samples/CMakeLists.txt
@@ -78,6 +78,10 @@
     if (${DAWN_ENABLE_EMSCRIPTEN})
         set_target_properties(${arg_NAME} PROPERTIES
             SUFFIX ".html")
+        target_link_options(${arg_NAME} PUBLIC
+            # We need JSPI for Future implementation.
+            "-sJSPI"
+        )
     endif()
     target_link_libraries(${arg_NAME} PUBLIC dawn::dawn_sample_utils)
     common_compile_options(${arg_NAME})
diff --git a/src/emdawnwebgpu/CMakeLists.txt b/src/emdawnwebgpu/CMakeLists.txt
index 68d40fa..3f67954 100644
--- a/src/emdawnwebgpu/CMakeLists.txt
+++ b/src/emdawnwebgpu/CMakeLists.txt
@@ -194,8 +194,6 @@
     target_link_options(emdawnwebgpu_config INTERFACE
         # We are using Dawn-generated bindings, not built-in ones
         "-sUSE_WEBGPU=0"
-        # We need Asyncify for Future implementation.
-        "-sASYNCIFY=1"
         # The JS libraries needed for bindings
         "--js-library=${EM_BUILD_GEN_DIR}/library_webgpu_enum_tables.js"
         "--js-library=${EM_BUILD_GEN_DIR}/library_webgpu_generated_struct_info.js"
@@ -220,6 +218,7 @@
         ENABLE_EMSCRIPTEN
         HEADER_ONLY
         HEADERS
+            "${EM_BUILD_GEN_DIR}/include/dawn/webgpu_cpp_print.h"
             "${EM_BUILD_GEN_DIR}/include/webgpu/webgpu_cpp.h"
             "${EM_BUILD_GEN_DIR}/include/webgpu/webgpu_cpp_chained_struct.h"
             "${DAWN_INCLUDE_DIR}/webgpu/webgpu_enum_class_bitmasks.h"
@@ -227,4 +226,26 @@
             emdawnwebgpu_c
     )
 
+    if (${DAWN_BUILD_TESTS})
+        set(emdawnwebgpu_test_sources
+            "tests/FuturesTests.cpp"
+        )
+        add_executable(emdawnwebgpu_tests ${emdawnwebgpu_test_sources})
+        set_target_properties(emdawnwebgpu_tests PROPERTIES
+            SUFFIX ".html")
+        target_link_libraries(
+            emdawnwebgpu_tests
+            PUBLIC
+                dawn::dawn_wgpu_utils
+                emdawnwebgpu_cpp
+                gmock_main
+        )
+        target_link_options(emdawnwebgpu_tests PUBLIC
+            # We need ASYNCIFY for Future implementation. Note that for
+            # some reason, at the moment, these tests do not work with
+            # JSPI.
+            "-sASYNCIFY=1"
+        )
+    endif()
+
 endif()
diff --git a/src/emdawnwebgpu/tests/FuturesTests.cpp b/src/emdawnwebgpu/tests/FuturesTests.cpp
new file mode 100644
index 0000000..7a47573
--- /dev/null
+++ b/src/emdawnwebgpu/tests/FuturesTests.cpp
@@ -0,0 +1,366 @@
+// Copyright 2024 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 <dawn/webgpu_cpp_print.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <webgpu/webgpu_cpp.h>
+
+#include <string>
+#include <utility>
+
+namespace {
+
+using testing::_;
+using testing::HasSubstr;
+
+class InstanceLevelTests : public testing::Test {
+  public:
+    void SetUp() override { instance = wgpu::CreateInstance(); }
+
+  protected:
+    wgpu::Adapter RequestAdapter(const wgpu::RequestAdapterOptions* adapterOptions = nullptr) {
+        wgpu::RequestAdapterStatus status;
+        wgpu::Adapter result = nullptr;
+        EXPECT_EQ(instance.WaitAny(
+                      instance.RequestAdapter(
+                          adapterOptions, wgpu::CallbackMode::AllowSpontaneous,
+                          [&status, &result](wgpu::RequestAdapterStatus s, wgpu::Adapter adapter,
+                                             wgpu::StringView message) {
+                              status = s;
+                              result = std::move(adapter);
+                          }),
+                      UINT64_MAX),
+                  wgpu::WaitStatus::Success);
+        EXPECT_EQ(status, wgpu::RequestAdapterStatus::Success);
+        return result;
+    }
+
+    wgpu::Instance instance;
+};
+
+TEST_F(InstanceLevelTests, RequestAdapter) {
+    EXPECT_NE(RequestAdapter(), nullptr);
+}
+
+class AdapterLevelTests : public InstanceLevelTests {
+  public:
+    void SetUp() override {
+        InstanceLevelTests::SetUp();
+        adapter = RequestAdapter();
+    }
+
+  protected:
+    wgpu::Device RequestDevice(const wgpu::DeviceDescriptor* descriptor = nullptr) {
+        wgpu::RequestDeviceStatus status;
+        wgpu::Device result = nullptr;
+        EXPECT_EQ(
+            instance.WaitAny(adapter.RequestDevice(
+                                 descriptor, wgpu::CallbackMode::AllowSpontaneous,
+                                 [&status, &result](wgpu::RequestDeviceStatus s,
+                                                    wgpu::Device device, wgpu::StringView message) {
+                                     status = s;
+                                     result = std::move(device);
+                                 }),
+                             UINT64_MAX),
+            wgpu::WaitStatus::Success);
+        EXPECT_EQ(status, wgpu::RequestDeviceStatus::Success);
+        return result;
+    }
+
+    wgpu::Adapter adapter;
+};
+
+TEST_F(AdapterLevelTests, RequestDevice) {
+    EXPECT_NE(RequestDevice(), nullptr);
+}
+
+TEST_F(AdapterLevelTests, RequestDeviceThenDestroy) {
+    wgpu::Device device = nullptr;
+    wgpu::DeviceLostReason reason = wgpu::DeviceLostReason::Unknown;
+
+    wgpu::DeviceDescriptor descriptor = {};
+    descriptor.SetDeviceLostCallback(
+        wgpu::CallbackMode::AllowSpontaneous,
+        [&device, &reason](const wgpu::Device& d, wgpu::DeviceLostReason r, wgpu::StringView) {
+            reason = r;
+            EXPECT_EQ(device.Get(), d.Get());
+        });
+    device = RequestDevice(&descriptor);
+
+    auto deviceLostFuture = device.GetLostFuture();
+    device.Destroy();
+    ASSERT_EQ(instance.WaitAny(deviceLostFuture, UINT64_MAX), wgpu::WaitStatus::Success);
+    EXPECT_EQ(reason, wgpu::DeviceLostReason::Destroyed);
+}
+
+TEST_F(AdapterLevelTests, RequestDeviceThenDrop) {
+    wgpu::DeviceLostReason reason = wgpu::DeviceLostReason::Unknown;
+
+    wgpu::DeviceDescriptor descriptor = {};
+    descriptor.SetDeviceLostCallback(
+        wgpu::CallbackMode::AllowSpontaneous,
+        [&reason](const wgpu::Device&, wgpu::DeviceLostReason r, wgpu::StringView) { reason = r; });
+    wgpu::Device device = RequestDevice(&descriptor);
+
+    auto deviceLostFuture = device.GetLostFuture();
+    device = nullptr;
+    ASSERT_EQ(instance.WaitAny(deviceLostFuture, UINT64_MAX), wgpu::WaitStatus::Success);
+    EXPECT_EQ(reason, wgpu::DeviceLostReason::Destroyed);
+}
+
+class DeviceLevelTests : public AdapterLevelTests {
+  public:
+    void SetUp() override {
+        AdapterLevelTests::SetUp();
+
+        wgpu::DeviceDescriptor descriptor = {};
+        descriptor.SetDeviceLostCallback(
+            wgpu::CallbackMode::AllowSpontaneous,
+            [](const wgpu::Device&, wgpu::DeviceLostReason reason, wgpu::StringView) {
+                EXPECT_EQ(reason, wgpu::DeviceLostReason::Destroyed);
+            });
+        descriptor.SetUncapturedErrorCallback(
+            [](const wgpu::Device& d, wgpu::ErrorType t, wgpu::StringView m,
+               DeviceLevelTests* self) { self->uncapturedErrorCb.Call(d, t, m); },
+            this);
+        device = RequestDevice(&descriptor);
+    }
+
+    void TearDown() override {
+        // For teardown, we explicitly wait for the device lost so that we can ensure that errors
+        // have been flushed.
+        auto deviceLostFuture = device.GetLostFuture();
+        device = nullptr;
+        EXPECT_EQ(instance.WaitAny(deviceLostFuture, UINT64_MAX), wgpu::WaitStatus::Success);
+    }
+
+  protected:
+    wgpu::ShaderModule CreateShaderModule(const char* source) {
+        wgpu::ShaderSourceWGSL wgsl;
+        wgsl.code = source;
+        wgpu::ShaderModuleDescriptor desc;
+        desc.nextInChain = &wgsl;
+        return device.CreateShaderModule(&desc);
+    }
+
+    wgpu::Device device;
+
+    // Mock callback used for uncaptured errors so that test writers can add expectations on this
+    // callback which will enforce the expectations at teardown of the test.
+    testing::StrictMock<
+        testing::MockFunction<void(const wgpu::Device&, wgpu::ErrorType, wgpu::StringView)>>
+        uncapturedErrorCb;
+};
+
+TEST_F(DeviceLevelTests, ValidationError) {
+    EXPECT_CALL(uncapturedErrorCb, Call(_, wgpu::ErrorType::Validation, _)).Times(1);
+
+    wgpu::BufferDescriptor desc = {};
+    desc.size = 1024;
+    desc.usage = static_cast<wgpu::BufferUsage>(UINT64_MAX);
+    wgpu::Buffer buffer = device.CreateBuffer(&desc);
+}
+
+TEST_F(DeviceLevelTests, PopErrorScope) {
+    device.PushErrorScope(wgpu::ErrorFilter::Validation);
+
+    wgpu::BufferDescriptor desc = {};
+    desc.size = 1024;
+    desc.usage = static_cast<wgpu::BufferUsage>(UINT64_MAX);
+    wgpu::Buffer buffer = device.CreateBuffer(&desc);
+
+    wgpu::PopErrorScopeStatus status;
+    wgpu::ErrorType type;
+    EXPECT_EQ(instance.WaitAny(
+                  device.PopErrorScope(wgpu::CallbackMode::AllowSpontaneous,
+                                       [&status, &type](wgpu::PopErrorScopeStatus s,
+                                                        wgpu::ErrorType t, wgpu::StringView) {
+                                           status = s;
+                                           type = t;
+                                       }),
+                  UINT64_MAX),
+              wgpu::WaitStatus::Success);
+    EXPECT_EQ(status, wgpu::PopErrorScopeStatus::Success);
+    EXPECT_EQ(type, wgpu::ErrorType::Validation);
+}
+
+TEST_F(DeviceLevelTests, BufferMapAndWorkDone) {
+    static constexpr uint32_t kData = 100u;
+    size_t kSize = sizeof(uint32_t);
+
+    wgpu::Buffer src;
+    wgpu::Buffer dst;
+    {
+        wgpu::BufferDescriptor desc;
+        desc.label = "src";
+        desc.size = kSize;
+        desc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite;
+        src = device.CreateBuffer(&desc);
+    }
+    {
+        wgpu::BufferDescriptor desc;
+        desc.label = "dst";
+        desc.size = kSize;
+        desc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
+        dst = device.CreateBuffer(&desc);
+    }
+
+    // Map the writable buffer and write to it.
+    wgpu::MapAsyncStatus writeStatus = wgpu::MapAsyncStatus::Unknown;
+    EXPECT_EQ(instance.WaitAny(
+                  src.MapAsync(wgpu::MapMode::Write, 0, kSize, wgpu::CallbackMode::AllowSpontaneous,
+                               [&writeStatus](wgpu::MapAsyncStatus status, wgpu::StringView) {
+                                   writeStatus = status;
+                               }),
+                  UINT64_MAX),
+              wgpu::WaitStatus::Success);
+    ASSERT_EQ(writeStatus, wgpu::MapAsyncStatus::Success);
+    auto writeData = static_cast<uint32_t*>(src.GetMappedRange());
+    ASSERT_NE(writeData, nullptr);
+    *writeData = kData;
+    src.Unmap();
+
+    // Copy the buffer to the readable one, and wait for the copy to complete. Note that the wait
+    // for the copy is not strictly necessary since the map async call following it will already
+    // wait for it, but we do it explicitly here to test the additional entry point.
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    encoder.CopyBufferToBuffer(src, 0, dst, 0, kSize);
+    wgpu::CommandBuffer commands = encoder.Finish();
+    wgpu::Queue queue = device.GetQueue();
+    queue.Submit(1, &commands);
+
+    wgpu::QueueWorkDoneStatus copyStatus;
+    EXPECT_EQ(
+        instance.WaitAny(queue.OnSubmittedWorkDone(wgpu::CallbackMode::AllowSpontaneous,
+                                                   [&copyStatus](wgpu::QueueWorkDoneStatus status) {
+                                                       copyStatus = status;
+                                                   }),
+                         UINT64_MAX),
+        wgpu::WaitStatus::Success);
+    ASSERT_EQ(copyStatus, wgpu::QueueWorkDoneStatus::Success);
+
+    // Map the readable buffer and verify the contents.
+    wgpu::MapAsyncStatus readStatus = wgpu::MapAsyncStatus::Unknown;
+    EXPECT_EQ(instance.WaitAny(
+                  dst.MapAsync(wgpu::MapMode::Read, 0, kSize, wgpu::CallbackMode::AllowSpontaneous,
+                               [&readStatus](wgpu::MapAsyncStatus status, wgpu::StringView) {
+                                   readStatus = status;
+                               }),
+                  UINT64_MAX),
+              wgpu::WaitStatus::Success);
+    ASSERT_EQ(readStatus, wgpu::MapAsyncStatus::Success);
+    auto readData = static_cast<const uint32_t*>(dst.GetConstMappedRange());
+    ASSERT_NE(readData, nullptr);
+    EXPECT_EQ(*readData, kData);
+    dst.Unmap();
+}
+
+TEST_F(DeviceLevelTests, CreateComputePipelineAsync) {
+    wgpu::ComputePipelineDescriptor desc;
+    desc.compute.module = CreateShaderModule(R"(
+        @compute @workgroup_size(1) fn main() {}
+    )");
+
+    wgpu::CreatePipelineAsyncStatus status = wgpu::CreatePipelineAsyncStatus::Unknown;
+    wgpu::ComputePipeline pipeline = nullptr;
+    EXPECT_EQ(instance.WaitAny(device.CreateComputePipelineAsync(
+                                   &desc, wgpu::CallbackMode::AllowSpontaneous,
+                                   [&status, &pipeline](wgpu::CreatePipelineAsyncStatus s,
+                                                        wgpu::ComputePipeline p, wgpu::StringView) {
+                                       status = s;
+                                       pipeline = std::move(p);
+                                   }),
+                               UINT64_MAX),
+              wgpu::WaitStatus::Success);
+    EXPECT_EQ(status, wgpu::CreatePipelineAsyncStatus::Success);
+    EXPECT_NE(pipeline, nullptr);
+}
+
+TEST_F(DeviceLevelTests, CreateRenderPipelineAsync) {
+    wgpu::RenderPipelineDescriptor desc;
+    desc.vertex.module = CreateShaderModule(R"(
+        @vertex fn main() -> @builtin(position) vec4f {
+            return vec4f(0.0, 0.0, 0.0, 1.0);
+        }
+    )");
+
+    wgpu::FragmentState frag;
+    frag.module = CreateShaderModule(R"(
+        @fragment fn main() -> @location(0) vec4f {
+            return vec4f(0.0, 1.0, 0.0, 1.0);
+        }
+    )");
+    wgpu::ColorTargetState target;
+    target.format = wgpu::TextureFormat::RGBA8Unorm;
+    frag.targetCount = 1;
+    frag.targets = &target;
+    desc.fragment = &frag;
+
+    wgpu::CreatePipelineAsyncStatus status = wgpu::CreatePipelineAsyncStatus::Unknown;
+    wgpu::RenderPipeline pipeline = nullptr;
+    EXPECT_EQ(instance.WaitAny(device.CreateRenderPipelineAsync(
+                                   &desc, wgpu::CallbackMode::AllowSpontaneous,
+                                   [&status, &pipeline](wgpu::CreatePipelineAsyncStatus s,
+                                                        wgpu::RenderPipeline p, wgpu::StringView) {
+                                       status = s;
+                                       pipeline = std::move(p);
+                                   }),
+                               UINT64_MAX),
+              wgpu::WaitStatus::Success);
+    EXPECT_EQ(status, wgpu::CreatePipelineAsyncStatus::Success);
+    EXPECT_NE(pipeline, nullptr);
+}
+
+TEST_F(DeviceLevelTests, GetCompilationInfo) {
+    wgpu::ShaderModule shader = CreateShaderModule(R"(
+        @fragment fn main(@location(0) x : f32) {
+            return;
+            return;
+        }
+    )");
+
+    wgpu::CompilationMessageType messageType;
+    std::string message;
+    EXPECT_EQ(instance.WaitAny(shader.GetCompilationInfo(
+                                   wgpu::CallbackMode::AllowSpontaneous,
+                                   [&message, &messageType](wgpu::CompilationInfoRequestStatus s,
+                                                            const wgpu::CompilationInfo* info) {
+                                       ASSERT_EQ(s, wgpu::CompilationInfoRequestStatus::Success);
+                                       ASSERT_NE(info, nullptr);
+                                       ASSERT_EQ(info->messageCount, 1);
+
+                                       message = info->messages[0].message;
+                                       messageType = info->messages[0].type;
+                                   }),
+                               UINT64_MAX),
+              wgpu::WaitStatus::Success);
+    EXPECT_EQ(messageType, wgpu::CompilationMessageType::Warning);
+    EXPECT_THAT(message, HasSubstr("unreachable"));
+}
+
+}  // namespace
diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt
index 05c7bb4..1bc7f06 100644
--- a/third_party/CMakeLists.txt
+++ b/third_party/CMakeLists.txt
@@ -33,7 +33,7 @@
 # a parent project that does not want to use depot_tools.
 if (DAWN_FETCH_DEPENDENCIES)
     set(EXTRA_FETCH_ARGS)
-    if (NOT TARGET gmock AND ${TINT_BUILD_TESTS})
+    if (NOT TARGET gmock AND (DAWN_BUILD_TESTS OR TINT_BUILD_TESTS))
         list(APPEND EXTRA_FETCH_ARGS --use-test-deps)
     endif()
 
@@ -70,6 +70,15 @@
     add_subdirectory(${DAWN_ABSEIL_DIR} "${CMAKE_CURRENT_BINARY_DIR}/abseil")
 endif()
 
+if ((DAWN_BUILD_TESTS OR TINT_BUILD_TESTS) AND NOT TARGET gmock)
+    set(gtest_force_shared_crt ON CACHE BOOL "Controls whether a shared run-time library should be used even when Google Test is built as static library" FORCE)
+    if (${DAWN_ENABLE_EMSCRIPTEN})
+        # For Emscripten builds, we need to disable pthreads.
+        set(gtest_disable_pthreads ON)
+    endif()
+    add_subdirectory(${DAWN_GOOGLETEST_DIR} "${CMAKE_CURRENT_BINARY_DIR}/googletest" EXCLUDE_FROM_ALL)
+endif()
+
 ################################################################################
 # End of Emscripten enabled third party directories
 ################################################################################
@@ -161,11 +170,6 @@
     add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/google_benchmark/src EXCLUDE_FROM_ALL)
 endif()
 
-if (TINT_BUILD_TESTS AND NOT TARGET gmock)
-    set(gtest_force_shared_crt ON CACHE BOOL "Controls whether a shared run-time library should be used even when Google Test is built as static library" FORCE)
-    add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/googletest EXCLUDE_FROM_ALL)
-endif()
-
 # Populates 'targets' with list of all targets under 'dir'
 # Use 'get_all_targets_recursive' instead of this macro
 macro(do_get_all_targets_recursive targets dir)
diff --git a/third_party/emdawnwebgpu/library_webgpu.js b/third_party/emdawnwebgpu/library_webgpu.js
index 991f4e8..9e1d3fc 100644
--- a/third_party/emdawnwebgpu/library_webgpu.js
+++ b/third_party/emdawnwebgpu/library_webgpu.js
@@ -576,12 +576,14 @@
     obj.label = UTF8ToString(data, length);
   },
 
-#if ASYNCIFY
   // Returns a FutureID that was resolved, or kNullFutureId if timed out.
+#if ASYNCIFY
   emwgpuWaitAny__async: true,
+#endif
   emwgpuWaitAny__i53abi: false,
   emwgpuWaitAny__sig: 'jppp',
   emwgpuWaitAny: (futurePtr, futureCount, timeoutNSPtr) => {
+#if ASYNCIFY
     var promises = WebGPU.Internals.waitAnyPromisesList;
     if (timeoutNSPtr) {
       var timeoutMS = {{{ gpu.makeGetU64('timeoutNSPtr', 0) }}} / 1000000;
@@ -608,8 +610,10 @@
     delete WebGPU.Internals.futures[result];
     WebGPU.Internals.waitAnyPromisesList.length = 0;
     return result;
-  },
+#else
+    assert(false);
 #endif
+  },
 
   emwgpuGetPreferredFormat__sig: 'i',
   emwgpuGetPreferredFormat: () => {