[WGPUFuture] Adds callback mode testing for Future entry points on wire.

- Adds new WireFuture test infrastructure to allow for wire future
  tests across all callback modes. Moves some common test utilities
  around to accomplish this.
- Updates WireQueue tests to use the new testing infrastructure to test
  different callback modes.
- Adjusts work done status to always return success to make device lost
  appear transparent.

Bug: dawn:1987
Change-Id: Ifaafb30f6c71190a8948baa6edac1320612ccb4d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/154682
Commit-Queue: Loko Kung <lokokung@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index ce1e672..2491bae 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -295,6 +295,7 @@
     "DawnNativeTest.cpp",
     "DawnNativeTest.h",
     "MockCallback.h",
+    "ParamGenerator.h",
     "ToggleParser.cpp",
     "ToggleParser.h",
     "unittests/AsyncTaskTests.cpp",
@@ -408,6 +409,8 @@
     "unittests/wire/WireDisconnectTests.cpp",
     "unittests/wire/WireErrorCallbackTests.cpp",
     "unittests/wire/WireExtensionTests.cpp",
+    "unittests/wire/WireFutureTest.cpp",
+    "unittests/wire/WireFutureTest.h",
     "unittests/wire/WireInjectDeviceTests.cpp",
     "unittests/wire/WireInjectInstanceTests.cpp",
     "unittests/wire/WireInjectSwapChainTests.cpp",
diff --git a/src/dawn/tests/DawnTest.h b/src/dawn/tests/DawnTest.h
index 512c214..7a3d476 100644
--- a/src/dawn/tests/DawnTest.h
+++ b/src/dawn/tests/DawnTest.h
@@ -782,11 +782,6 @@
         DawnTestBase::PrintToStringParamName(#testName));                                    \
     GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(testName)
 
-// Implementation for DAWN_TEST_PARAM_STRUCT to declare/print struct fields.
-#define DAWN_TEST_PARAM_STRUCT_DECL_STRUCT_FIELD(Type) Type DAWN_PP_CONCATENATE(m, Type);
-#define DAWN_TEST_PARAM_STRUCT_PRINT_STRUCT_FIELD(Type) \
-    o << "; " << #Type << "=" << param.DAWN_PP_CONCATENATE(m, Type);
-
 // Usage: DAWN_TEST_PARAM_STRUCT(Foo, TypeA, TypeB, ...)
 // Generate a test param struct called Foo which extends AdapterTestParam and generated
 // struct _Dawn_Foo. _Dawn_Foo has members of types TypeA, TypeB, etc. which are named mTypeA,
@@ -797,29 +792,8 @@
 // Example:
 //   using MyParam = unsigned int;
 //   DAWN_TEST_PARAM_STRUCT(FooParams, MyParam);
-#define DAWN_TEST_PARAM_STRUCT(StructName, ...)                                                    \
-    struct DAWN_PP_CONCATENATE(_Dawn_, StructName) {                                               \
-        DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH)(DAWN_TEST_PARAM_STRUCT_DECL_STRUCT_FIELD,  \
-                                                        __VA_ARGS__))                              \
-    };                                                                                             \
-    inline std::ostream& operator<<(std::ostream& o,                                               \
-                                    const DAWN_PP_CONCATENATE(_Dawn_, StructName) & param) {       \
-        DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH)(DAWN_TEST_PARAM_STRUCT_PRINT_STRUCT_FIELD, \
-                                                        __VA_ARGS__))                              \
-        return o;                                                                                  \
-    }                                                                                              \
-    struct StructName : AdapterTestParam, DAWN_PP_CONCATENATE(_Dawn_, StructName) {                \
-        template <typename... Args>                                                                \
-        StructName(const AdapterTestParam& param, Args&&... args)                                  \
-            : AdapterTestParam(param),                                                             \
-              DAWN_PP_CONCATENATE(_Dawn_, StructName){std::forward<Args>(args)...} {}              \
-    };                                                                                             \
-    inline std::ostream& operator<<(std::ostream& o, const StructName& param) {                    \
-        o << static_cast<const AdapterTestParam&>(param);                                          \
-        o << "; " << static_cast<const DAWN_PP_CONCATENATE(_Dawn_, StructName)&>(param);           \
-        return o;                                                                                  \
-    }                                                                                              \
-    static_assert(true, "require semicolon")
+#define DAWN_TEST_PARAM_STRUCT(StructName, ...) \
+    DAWN_TEST_PARAM_STRUCT_BASE(AdapterTestParam, StructName, __VA_ARGS__)
 
 namespace detail {
 // Helper functions used for DAWN_INSTANTIATE_TEST
@@ -900,5 +874,20 @@
 };
 
 }  // namespace detail
+
+template <typename Param, typename... Params>
+auto MakeParamGenerator(std::vector<BackendTestConfig>&& first,
+                        std::initializer_list<Params>&&... params) {
+    return ParamGenerator<Param, AdapterTestParam, Params...>(
+        ::dawn::detail::GetAvailableAdapterTestParamsForBackends(first.data(), first.size()),
+        std::forward<std::initializer_list<Params>&&>(params)...);
+}
+template <typename Param, typename... Params>
+auto MakeParamGenerator(std::vector<BackendTestConfig>&& first, std::vector<Params>&&... params) {
+    return ParamGenerator<Param, AdapterTestParam, Params...>(
+        ::dawn::detail::GetAvailableAdapterTestParamsForBackends(first.data(), first.size()),
+        std::forward<std::vector<Params>&&>(params)...);
+}
+
 }  // namespace dawn
 #endif  // SRC_DAWN_TESTS_DAWNTEST_H_
diff --git a/src/dawn/tests/ParamGenerator.h b/src/dawn/tests/ParamGenerator.h
index 390d1eb..22b7f4a 100644
--- a/src/dawn/tests/ParamGenerator.h
+++ b/src/dawn/tests/ParamGenerator.h
@@ -28,17 +28,97 @@
 #ifndef SRC_DAWN_TESTS_PARAMGENERATOR_H_
 #define SRC_DAWN_TESTS_PARAMGENERATOR_H_
 
+#include <array>
+#include <optional>
+#include <sstream>
+#include <string>
 #include <tuple>
 #include <utility>
 #include <vector>
 
-#include "dawn/tests/AdapterTestConfig.h"
+#include "dawn/common/Preprocessor.h"
+
+#include "gtest/gtest.h"
 
 namespace dawn {
+namespace detail {
+
+template <typename T>
+struct IsOptional {
+    static constexpr bool value = false;
+};
+template <typename T>
+struct IsOptional<std::optional<T>> {
+    static constexpr bool value = true;
+};
+template <typename T>
+struct IsOptional<const std::optional<T>> {
+    static constexpr bool value = true;
+};
+
+}  // namespace detail
+
+template <typename T>
+void PrintParamStructField(std::ostream& o, const T& param, const char* type) {
+    if constexpr (detail::IsOptional<T>::value) {
+        if (param) {
+            o << "_" << type << "_" << *param;
+        }
+    } else {
+        o << "_" << type << "_" << param;
+    }
+}
+
+// Implementation for DAWN_TEST_PARAM_STRUCT to declare/print struct fields.
+#define DAWN_TEST_PARAM_STRUCT_DECL_STRUCT_FIELD(Type) Type DAWN_PP_CONCATENATE(m, Type);
+#define DAWN_TEST_PARAM_STRUCT_PRINT_STRUCT_FIELD(Type) \
+    PrintParamStructField(o, static_cast<const Type&>(param.DAWN_PP_CONCATENATE(m, Type)), #Type);
+
+// Usage: DAWN_TEST_PARAM_STRUCT_BASE(BaseParam, Foo, TypeA, TypeB, ...)
+// Generate a test param struct called Foo which extends BaseParam and generated
+// struct _Dawn_Foo. _Dawn_Foo has members of types TypeA, TypeB, etc. which are named mTypeA,
+// mTypeB, etc. in the order they are placed in the macro argument list. Struct Foo should be
+// constructed with an BaseParam as the first argument, followed by a list of values
+// to initialize the base _Dawn_Foo struct.
+// It is recommended to use alias declarations so that stringified types are more readable.
+// Example:
+//   using MyParam = unsigned int;
+//   DAWN_TEST_PARAM_STRUCT_BASE(AdapterTestParam, FooParams, MyParam);
+#define DAWN_TEST_PARAM_STRUCT_BASE(BaseStructName, StructName, ...)                               \
+    struct DAWN_PP_CONCATENATE(_Dawn_, StructName) {                                               \
+        DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH)(DAWN_TEST_PARAM_STRUCT_DECL_STRUCT_FIELD,  \
+                                                        __VA_ARGS__))                              \
+    };                                                                                             \
+    inline std::ostream& operator<<(std::ostream& o,                                               \
+                                    const DAWN_PP_CONCATENATE(_Dawn_, StructName) & param) {       \
+        DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH)(DAWN_TEST_PARAM_STRUCT_PRINT_STRUCT_FIELD, \
+                                                        __VA_ARGS__))                              \
+        return o;                                                                                  \
+    }                                                                                              \
+    struct StructName : BaseStructName, DAWN_PP_CONCATENATE(_Dawn_, StructName) {                  \
+        template <typename... Args>                                                                \
+        StructName(const BaseStructName& param, Args&&... args)                                    \
+            : BaseStructName(param),                                                               \
+              DAWN_PP_CONCATENATE(_Dawn_, StructName){std::forward<Args>(args)...} {}              \
+    };                                                                                             \
+    inline std::ostream& operator<<(std::ostream& o, const StructName& param) {                    \
+        o << static_cast<const BaseStructName&>(param);                                            \
+        o << static_cast<const DAWN_PP_CONCATENATE(_Dawn_, StructName)&>(param);                   \
+        return o;                                                                                  \
+    }                                                                                              \
+    static_assert(true, "require semicolon")
+
+template <typename ParamStruct>
+std::string TestParamToString(const testing::TestParamInfo<ParamStruct>& info) {
+    std::ostringstream output;
+    output << info.param;
+    return output.str();
+}
 
 // ParamStruct is a custom struct which ParamStruct will yield when iterating.
 // The types Params... should be the same as the types passed to the constructor
 // of ParamStruct.
+// TODO: When std::span becomes available via c++20, use std::span over std::vector.
 template <typename ParamStruct, typename... Params>
 class ParamGenerator {
     using ParamTuple = std::tuple<std::vector<Params>...>;
@@ -133,26 +213,6 @@
     bool mIsEmpty;
 };
 
-namespace detail {
-std::vector<AdapterTestParam> GetAvailableAdapterTestParamsForBackends(
-    const BackendTestConfig* params,
-    size_t numParams);
-}
-
-template <typename Param, typename... Params>
-auto MakeParamGenerator(std::vector<BackendTestConfig>&& first,
-                        std::initializer_list<Params>&&... params) {
-    return ParamGenerator<Param, AdapterTestParam, Params...>(
-        ::dawn::detail::GetAvailableAdapterTestParamsForBackends(first.data(), first.size()),
-        std::forward<std::initializer_list<Params>&&>(params)...);
-}
-template <typename Param, typename... Params>
-auto MakeParamGenerator(std::vector<BackendTestConfig>&& first, std::vector<Params>&&... params) {
-    return ParamGenerator<Param, AdapterTestParam, Params...>(
-        ::dawn::detail::GetAvailableAdapterTestParamsForBackends(first.data(), first.size()),
-        std::forward<std::vector<Params>&&>(params)...);
-}
-
 }  // namespace dawn
 
 #endif  // SRC_DAWN_TESTS_PARAMGENERATOR_H_
diff --git a/src/dawn/tests/end2end/CopyTextureForBrowserTests.cpp b/src/dawn/tests/end2end/CopyTextureForBrowserTests.cpp
index d292681..0f37f22 100644
--- a/src/dawn/tests/end2end/CopyTextureForBrowserTests.cpp
+++ b/src/dawn/tests/end2end/CopyTextureForBrowserTests.cpp
@@ -35,6 +35,19 @@
 #include "dawn/utils/TextureUtils.h"
 #include "dawn/utils/WGPUHelpers.h"
 
+// TODO: Remove these stream operators if we move them to a standard location.
+namespace wgpu {
+std::ostream& operator<<(std::ostream& o, Origin3D origin) {
+    o << origin.x << ", " << origin.y << ", " << origin.z;
+    return o;
+}
+
+std::ostream& operator<<(std::ostream& o, Extent3D copySize) {
+    o << copySize.width << ", " << copySize.height << ", " << copySize.depthOrArrayLayers;
+    return o;
+}
+}  // namespace wgpu
+
 namespace dawn {
 namespace {
 
@@ -60,16 +73,6 @@
 using SrcAlphaMode = wgpu::AlphaMode;
 using DstAlphaMode = wgpu::AlphaMode;
 
-std::ostream& operator<<(std::ostream& o, wgpu::Origin3D origin) {
-    o << origin.x << ", " << origin.y << ", " << origin.z;
-    return o;
-}
-
-std::ostream& operator<<(std::ostream& o, wgpu::Extent3D copySize) {
-    o << copySize.width << ", " << copySize.height << ", " << copySize.depthOrArrayLayers;
-    return o;
-}
-
 std::ostream& operator<<(std::ostream& o, ColorSpace space) {
     o << static_cast<uint32_t>(space);
     return o;
diff --git a/src/dawn/tests/unittests/wire/WireFutureTest.cpp b/src/dawn/tests/unittests/wire/WireFutureTest.cpp
new file mode 100644
index 0000000..45ae80f
--- /dev/null
+++ b/src/dawn/tests/unittests/wire/WireFutureTest.cpp
@@ -0,0 +1,52 @@
+// Copyright 2023 The Dawn 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 "dawn/tests/unittests/wire/WireFutureTest.h"
+
+#include "dawn/common/Assert.h"
+
+namespace dawn::wire {
+
+WGPUCallbackMode ToWGPUCallbackMode(CallbackMode callbackMode) {
+    switch (callbackMode) {
+        case CallbackMode::WaitAny:
+            return WGPUCallbackMode_WaitAnyOnly;
+        case CallbackMode::ProcessEvents:
+            return WGPUCallbackMode_AllowProcessEvents;
+        case CallbackMode::Spontaneous:
+            return WGPUCallbackMode_AllowSpontaneous;
+        default:
+            DAWN_UNREACHABLE();
+    }
+}
+
+std::ostream& operator<<(std::ostream& os, const WireFutureTestParam& param) {
+    switch (param.mCallbackMode) {
+        case CallbackMode::Async:
+            os << "Async";
+            break;
+        case CallbackMode::WaitAny:
+            os << "WaitOnly";
+            break;
+        case CallbackMode::ProcessEvents:
+            os << "ProcessEvents";
+            break;
+        case CallbackMode::Spontaneous:
+            os << "Spontaneous";
+            break;
+    }
+    return os;
+}
+
+}  // namespace dawn::wire
diff --git a/src/dawn/tests/unittests/wire/WireFutureTest.h b/src/dawn/tests/unittests/wire/WireFutureTest.h
new file mode 100644
index 0000000..4382d28
--- /dev/null
+++ b/src/dawn/tests/unittests/wire/WireFutureTest.h
@@ -0,0 +1,209 @@
+// Copyright 2023 The Dawn 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_DAWN_TESTS_UNITTESTS_WIRE_WIREFUTURETEST_H_
+#define SRC_DAWN_TESTS_UNITTESTS_WIRE_WIREFUTURETEST_H_
+
+#include <array>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "dawn/common/FutureUtils.h"
+#include "dawn/tests/MockCallback.h"
+#include "dawn/tests/ParamGenerator.h"
+#include "dawn/tests/unittests/wire/WireTest.h"
+#include "dawn/wire/WireServer.h"
+
+#include "gtest/gtest.h"
+
+namespace dawn::wire {
+
+enum class CallbackMode {
+    Async,  // Legacy mode that internally defers to Spontaneous.
+    WaitAny,
+    ProcessEvents,
+    Spontaneous,
+};
+WGPUCallbackMode ToWGPUCallbackMode(CallbackMode callbackMode);
+
+struct WireFutureTestParam {
+    CallbackMode mCallbackMode;
+};
+std::ostream& operator<<(std::ostream& os, const WireFutureTestParam& param);
+static constexpr std::array kCallbackModes = {WireFutureTestParam{CallbackMode::Async},
+                                              WireFutureTestParam{CallbackMode::WaitAny},
+                                              WireFutureTestParam{CallbackMode::ProcessEvents},
+                                              WireFutureTestParam{CallbackMode::Spontaneous}};
+
+template <typename Param, typename... Params>
+auto MakeParamGenerator(std::initializer_list<Params>&&... params) {
+    return ParamGenerator<Param, WireFutureTestParam, Params...>(
+        std::vector<WireFutureTestParam>(kCallbackModes.begin(), kCallbackModes.end()),
+        std::forward<std::initializer_list<Params>&&>(params)...);
+}
+
+// Usage: DAWN_WIRE_FUTURE_TEST_PARAM_STRUCT(Foo, TypeA, TypeB, ...)
+// Generate a test param struct called Foo which extends WireFutureTestParam and generated
+// struct _Dawn_Foo. _Dawn_Foo has members of types TypeA, TypeB, etc. which are named mTypeA,
+// mTypeB, etc. in the order they are placed in the macro argument list. Struct Foo should be
+// constructed with an WireFutureTestParam as the first argument, followed by a list of values
+// to initialize the base _Dawn_Foo struct.
+// It is recommended to use alias declarations so that stringified types are more readable.
+// Example:
+//   using MyParam = unsigned int;
+//   DAWN_WIRE_FUTURE_TEST_PARAM_STRUCT(FooParams, MyParam);
+#define DAWN_WIRE_FUTURE_TEST_PARAM_STRUCT(StructName, ...) \
+    DAWN_TEST_PARAM_STRUCT_BASE(WireFutureTestParam, StructName, __VA_ARGS__)
+
+#define DAWN_INSTANTIATE_WIRE_FUTURE_TEST_P(testName, ...)                                   \
+    INSTANTIATE_TEST_SUITE_P(                                                                \
+        , testName, testing::ValuesIn(MakeParamGenerator<testName::ParamType>(__VA_ARGS__)), \
+        &TestParamToString<testName::ParamType>)
+
+template <typename Callback,
+          typename CallbackInfo,
+          auto& AsyncF,
+          auto& FutureF,
+          typename Params = WireFutureTestParam,
+          typename AsyncFT = decltype(AsyncF),
+          typename FutureFT = decltype(FutureF)>
+class WireFutureTestWithParams : public WireTest, public testing::WithParamInterface<Params> {
+  protected:
+    using testing::WithParamInterface<Params>::GetParam;
+
+    void SetUp() override {
+        WireTest::SetUp();
+
+        auto reservation = GetWireClient()->ReserveInstance();
+        instance = reservation.instance;
+
+        apiInstance = api.GetNewInstance();
+        EXPECT_CALL(api, InstanceReference(apiInstance));
+        EXPECT_TRUE(
+            GetWireServer()->InjectInstance(apiInstance, reservation.id, reservation.generation));
+    }
+
+    void TearDown() override { WireTest::TearDown(); }
+
+    // Calls the actual API that the test suite is exercising given the callback mode. This should
+    // be used in favor of directly calling the API because the Async mode actually calls a
+    // different entry point.
+    template <typename... Args>
+    void CallImpl(void* userdata, Args&&... args) {
+        if (GetParam().mCallbackMode == CallbackMode::Async) {
+            mAsyncF(std::forward<Args>(args)..., mMockCb.Callback(),
+                    mMockCb.MakeUserdata(userdata));
+        } else {
+            CallbackInfo callbackInfo = {};
+            callbackInfo.mode = ToWGPUCallbackMode(GetParam().mCallbackMode);
+            callbackInfo.callback = mMockCb.Callback();
+            callbackInfo.userdata = mMockCb.MakeUserdata(userdata);
+            mFutureIDs.push_back(mFutureF(std::forward<Args>(args)..., callbackInfo).id);
+        }
+    }
+
+    // Events are considered spontaneous if either we are using the legacy Async or the new
+    // Spontaneous modes.
+    bool IsSpontaneous() {
+        CallbackMode callbackMode = GetParam().mCallbackMode;
+        return callbackMode == CallbackMode::Async || callbackMode == CallbackMode::Spontaneous;
+    }
+
+    // In order to tightly bound when callbacks are expected to occur, test writers only have access
+    // to the mock callback via the argument passed usually via a lamdba. The 'exp' lambda should
+    // generally be a block of expectations on the mock callback followed by one statement where we
+    // expect the callbacks to be called from. If the callbacks do not occur in the scope of the
+    // lambda, the mock will fail the test.
+    //
+    // Usage:
+    //   ExpectWireCallbackWhen([&](auto& mockCb) {
+    //       // Set scoped expectations on the mock callback.
+    //       EXPECT_CALL(mockCb, Call).Times(1);
+    //
+    //       // Call the statement where we want to ensure the callbacks occur.
+    //       FlushCallbacks();
+    //   });
+    void ExpectWireCallbacksWhen(std::function<void(testing::MockCallback<Callback>&)> exp) {
+        exp(mMockCb);
+        ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&mMockCb));
+    }
+
+    // Future suite adds the following flush mechanics for test writers so that they can have fine
+    // grained control over when expectations should be set and verified.
+    //
+    // FlushFutures ensures that all futures become ready regardless of callback mode, while
+    // FlushCallbacks ensures that all callbacks that were ready have been called. In most cases,
+    // the intended use-case would look as follows:
+    //
+    //     // Call the API under test
+    //     CallImpl(mockCb, this, args...);
+    //     EXPECT_CALL(api, OnAsyncAPI(...)).WillOnce(InvokeWithoutArgs([&] {
+    //         api.CallAsyncAPICallback(...);
+    //     }));
+    //
+    //     FlushClient();
+    //     FlushFutures(); // Ensures that the callbacks are ready (if applicable), but NOT called.
+    //     EXPECT_CALL(mockCb, Call(...));
+    //     FlushCallbacks();  // Calls the callbacks
+    //
+    // Note that in the example above we don't explicitly every call FlushServer and in most cases
+    // that is probably the way to go because for Async and Spontaneous events, FlushServer will
+    // actually trigger the callback. So instead, it is likely that the intention is instead to
+    // break the calls into FlushFutures and FlushCallbacks for more control.
+    void FlushFutures() {
+        // For non-spontaneous callback modes, we need the flush the server in order for
+        // the futures to become ready. For spontaneous modes, however, we don't flush the
+        // server yet because that would also trigger the callback immediately.
+        if (!IsSpontaneous()) {
+            WireTest::FlushServer();
+        }
+    }
+    void FlushCallbacks() {
+        // Flushing the server will cause Async and Spontaneous callbacks to trigger right away.
+        WireTest::FlushServer();
+
+        CallbackMode callbackMode = GetParam().mCallbackMode;
+        if (callbackMode == CallbackMode::WaitAny) {
+            if (mFutureIDs.empty()) {
+                return;
+            }
+            std::vector<WGPUFutureWaitInfo> waitInfos;
+            for (auto futureID : mFutureIDs) {
+                waitInfos.push_back({{futureID}, false});
+            }
+            EXPECT_EQ(wgpuInstanceWaitAny(instance, mFutureIDs.size(), waitInfos.data(), 0),
+                      WGPUWaitStatus_Success);
+        } else if (callbackMode == CallbackMode::ProcessEvents) {
+            wgpuInstanceProcessEvents(instance);
+        }
+    }
+
+    WGPUInstance instance;
+    WGPUInstance apiInstance;
+
+  private:
+    AsyncFT mAsyncF = AsyncF;
+    FutureFT mFutureF = FutureF;
+    std::vector<FutureID> mFutureIDs;
+
+    testing::MockCallback<Callback> mMockCb;
+};
+
+template <typename Callback, typename CallbackInfo, auto& AsyncF, auto& FutureF>
+using WireFutureTest = WireFutureTestWithParams<Callback, CallbackInfo, AsyncF, FutureF>;
+
+}  // namespace dawn::wire
+
+#endif  // SRC_DAWN_TESTS_UNITTESTS_WIRE_WIREFUTURETEST_H_
diff --git a/src/dawn/tests/unittests/wire/WireQueueTests.cpp b/src/dawn/tests/unittests/wire/WireQueueTests.cpp
index 7b7d25a..6e110b0 100644
--- a/src/dawn/tests/unittests/wire/WireQueueTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireQueueTests.cpp
@@ -27,6 +27,7 @@
 
 #include <memory>
 
+#include "dawn/tests/unittests/wire/WireFutureTest.h"
 #include "dawn/tests/unittests/wire/WireTest.h"
 #include "dawn/wire/WireClient.h"
 
@@ -35,122 +36,128 @@
 
 using testing::_;
 using testing::InvokeWithoutArgs;
-using testing::Mock;
+using testing::Return;
 
-class MockQueueWorkDoneCallback {
-  public:
-    MOCK_METHOD(void, Call, (WGPUQueueWorkDoneStatus status, void* userdata));
-};
-
-static std::unique_ptr<MockQueueWorkDoneCallback> mockQueueWorkDoneCallback;
-static void ToMockQueueWorkDone(WGPUQueueWorkDoneStatus status, void* userdata) {
-    mockQueueWorkDoneCallback->Call(status, userdata);
-}
-
-class WireQueueTests : public WireTest {
+using WireQueueTestBase = WireFutureTest<WGPUQueueWorkDoneCallback,
+                                         WGPUQueueWorkDoneCallbackInfo,
+                                         wgpuQueueOnSubmittedWorkDone,
+                                         wgpuQueueOnSubmittedWorkDoneF>;
+class WireQueueTests : public WireQueueTestBase {
   protected:
-    void SetUp() override {
-        WireTest::SetUp();
-        mockQueueWorkDoneCallback = std::make_unique<MockQueueWorkDoneCallback>();
-    }
-
-    void TearDown() override {
-        WireTest::TearDown();
-        mockQueueWorkDoneCallback = nullptr;
-    }
-
-    void FlushServer() {
-        WireTest::FlushServer();
-        Mock::VerifyAndClearExpectations(&mockQueueWorkDoneCallback);
-    }
+    // Overriden version of wgpuQueueOnSubmittedWorkDone that defers to the API call based on the
+    // test callback mode.
+    void QueueOnSubmittedWorkDone(WGPUQueue q, void* userdata = nullptr) { CallImpl(userdata, q); }
 };
 
+DAWN_INSTANTIATE_WIRE_FUTURE_TEST_P(WireQueueTests);
+
 // Test that a successful OnSubmittedWorkDone call is forwarded to the client.
-TEST_F(WireQueueTests, OnSubmittedWorkDoneSuccess) {
-    wgpuQueueOnSubmittedWorkDone(queue, ToMockQueueWorkDone, this);
+TEST_P(WireQueueTests, OnSubmittedWorkDoneSuccess) {
+    QueueOnSubmittedWorkDone(queue);
     EXPECT_CALL(api, OnQueueOnSubmittedWorkDone(apiQueue, _, _)).WillOnce(InvokeWithoutArgs([&] {
         api.CallQueueOnSubmittedWorkDoneCallback(apiQueue, WGPUQueueWorkDoneStatus_Success);
     }));
     FlushClient();
+    FlushFutures();
 
-    EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_Success, this)).Times(1);
-    FlushServer();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUQueueWorkDoneStatus_Success, nullptr)).Times(1);
+
+        FlushCallbacks();
+    });
 }
 
 // Test that an error OnSubmittedWorkDone call is forwarded as an error to the client.
-TEST_F(WireQueueTests, OnSubmittedWorkDoneError) {
-    wgpuQueueOnSubmittedWorkDone(queue, ToMockQueueWorkDone, this);
+TEST_P(WireQueueTests, OnSubmittedWorkDoneError) {
+    QueueOnSubmittedWorkDone(queue);
     EXPECT_CALL(api, OnQueueOnSubmittedWorkDone(apiQueue, _, _)).WillOnce(InvokeWithoutArgs([&] {
         api.CallQueueOnSubmittedWorkDoneCallback(apiQueue, WGPUQueueWorkDoneStatus_Error);
     }));
     FlushClient();
+    FlushFutures();
 
-    EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_Error, this)).Times(1);
-    FlushServer();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUQueueWorkDoneStatus_Error, nullptr)).Times(1);
+
+        FlushCallbacks();
+    });
 }
 
-// Test registering an OnSubmittedWorkDone then disconnecting the wire calls the callback with
-// device loss
-TEST_F(WireQueueTests, OnSubmittedWorkDoneBeforeDisconnect) {
-    wgpuQueueOnSubmittedWorkDone(queue, ToMockQueueWorkDone, this);
+// Test registering an OnSubmittedWorkDone then disconnecting the wire after the server responded to
+// the client will call the callback with the server response.
+TEST_P(WireQueueTests, OnSubmittedWorkDoneBeforeDisconnectAfterReply) {
+    // On Async and Spontaneous mode, it is not possible to simulate this because on the server
+    // reponse, the callback would also be fired.
+    DAWN_SKIP_TEST_IF(IsSpontaneous());
+
+    QueueOnSubmittedWorkDone(queue);
+    EXPECT_CALL(api, OnQueueOnSubmittedWorkDone(apiQueue, _, _)).WillOnce(InvokeWithoutArgs([&] {
+        api.CallQueueOnSubmittedWorkDoneCallback(apiQueue, WGPUQueueWorkDoneStatus_Error);
+    }));
+    FlushClient();
+    FlushFutures();
+
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUQueueWorkDoneStatus_Error, nullptr)).Times(1);
+
+        GetWireClient()->Disconnect();
+    });
+}
+
+// Test registering an OnSubmittedWorkDone then disconnecting the wire before the server responded
+// to the client (i.e. before the event was ever ready) will call the callback with success to make
+// the lost device appear to continue to function.
+TEST_P(WireQueueTests, OnSubmittedWorkDoneBeforeDisconnectBeforeReply) {
+    QueueOnSubmittedWorkDone(queue);
     EXPECT_CALL(api, OnQueueOnSubmittedWorkDone(apiQueue, _, _)).WillOnce(InvokeWithoutArgs([&] {
         api.CallQueueOnSubmittedWorkDoneCallback(apiQueue, WGPUQueueWorkDoneStatus_Error);
     }));
     FlushClient();
 
-    EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_DeviceLost, this))
-        .Times(1);
-    GetWireClient()->Disconnect();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUQueueWorkDoneStatus_Success, nullptr)).Times(1);
+
+        GetWireClient()->Disconnect();
+    });
 }
 
 // Test registering an OnSubmittedWorkDone after disconnecting the wire calls the callback with
-// device loss
-TEST_F(WireQueueTests, OnSubmittedWorkDoneAfterDisconnect) {
+// success.
+TEST_P(WireQueueTests, OnSubmittedWorkDoneAfterDisconnect) {
     GetWireClient()->Disconnect();
 
-    EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_DeviceLost, this))
-        .Times(1);
-    wgpuQueueOnSubmittedWorkDone(queue, ToMockQueueWorkDone, this);
-}
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUQueueWorkDoneStatus_Success, nullptr)).Times(1);
 
-// Hack to pass in test context into user callback
-struct TestData {
-    WireQueueTests* pTest;
-    WGPUQueue* pTestQueue;
-    size_t numRequests;
-};
-
-static void ToMockQueueWorkDoneWithNewRequests(WGPUQueueWorkDoneStatus status, void* userdata) {
-    TestData* testData = reinterpret_cast<TestData*>(userdata);
-    // Mimic the user callback is sending new requests
-    ASSERT_NE(testData, nullptr);
-    ASSERT_NE(testData->pTest, nullptr);
-    ASSERT_NE(testData->pTestQueue, nullptr);
-    mockQueueWorkDoneCallback->Call(status, testData->pTest);
-
-    // Send the requests a number of times
-    for (size_t i = 0; i < testData->numRequests; i++) {
-        wgpuQueueOnSubmittedWorkDone(*(testData->pTestQueue), ToMockQueueWorkDone, testData->pTest);
-    }
+        QueueOnSubmittedWorkDone(queue);
+    });
 }
 
 // Test that requests inside user callbacks before disconnect are called
-TEST_F(WireQueueTests, OnSubmittedWorkDoneInsideCallbackBeforeDisconnect) {
-    TestData testData = {this, &queue, 10};
-    wgpuQueueOnSubmittedWorkDone(queue, ToMockQueueWorkDoneWithNewRequests, &testData);
+TEST_P(WireQueueTests, OnSubmittedWorkDoneInsideCallbackBeforeDisconnect) {
+    static constexpr size_t kNumRequests = 10;
+    QueueOnSubmittedWorkDone(queue);
     EXPECT_CALL(api, OnQueueOnSubmittedWorkDone(apiQueue, _, _)).WillOnce(InvokeWithoutArgs([&] {
         api.CallQueueOnSubmittedWorkDoneCallback(apiQueue, WGPUQueueWorkDoneStatus_Error);
     }));
     FlushClient();
 
-    EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_DeviceLost, this))
-        .Times(1 + testData.numRequests);
-    GetWireClient()->Disconnect();
+    ExpectWireCallbacksWhen([&](auto& mockCb) {
+        EXPECT_CALL(mockCb, Call(WGPUQueueWorkDoneStatus_Success, nullptr))
+            .Times(kNumRequests + 1)
+            .WillOnce([&]() {
+                for (size_t i = 0; i < kNumRequests; i++) {
+                    QueueOnSubmittedWorkDone(queue);
+                }
+            })
+            .WillRepeatedly(Return());
+
+        GetWireClient()->Disconnect();
+    });
 }
 
-// Test releasing the default queue, then its device. Both should be
-// released when the device is released since the device holds a reference
-// to the queue. Regresssion test for crbug.com/1332926.
+// Test releasing the default queue, then its device. Both should be released when the device is
+// released since the device holds a reference to the queue. Regresssion test for crbug.com/1332926.
 TEST_F(WireQueueTests, DefaultQueueThenDeviceReleased) {
     // Note: The test fixture gets the default queue.
 
@@ -175,9 +182,9 @@
     DefaultApiDeviceWasReleased();
 }
 
-// Test the device, then its default queue. The default queue should be
-// released when its external reference is dropped since releasing the device
-// drops the internal reference. Regresssion test for crbug.com/1332926.
+// Test the device, then its default queue. The default queue should be released when its external
+// reference is dropped since releasing the device drops the internal reference. Regresssion test
+// for crbug.com/1332926.
 TEST_F(WireQueueTests, DeviceThenDefaultQueueReleased) {
     // Note: The test fixture gets the default queue.
 
diff --git a/src/dawn/tests/unittests/wire/WireTest.h b/src/dawn/tests/unittests/wire/WireTest.h
index a7b7e17..e7d6710 100644
--- a/src/dawn/tests/unittests/wire/WireTest.h
+++ b/src/dawn/tests/unittests/wire/WireTest.h
@@ -30,6 +30,7 @@
 
 #include <memory>
 
+#include "dawn/common/Log.h"
 #include "dawn/mock_webgpu.h"
 #include "gtest/gtest.h"
 
@@ -105,6 +106,16 @@
     return MakeMatcher(new StringMessageMatcher());
 }
 
+// Skip a test when the given condition is satisfied.
+#define DAWN_SKIP_TEST_IF(condition)                            \
+    do {                                                        \
+        if (condition) {                                        \
+            dawn::InfoLog() << "Test skipped: " #condition "."; \
+            GTEST_SKIP();                                       \
+            return;                                             \
+        }                                                       \
+    } while (0)
+
 namespace dawn::wire {
 class WireClient;
 class WireServer;
diff --git a/src/dawn/wire/client/Queue.cpp b/src/dawn/wire/client/Queue.cpp
index 7c748c2..6a956dd 100644
--- a/src/dawn/wire/client/Queue.cpp
+++ b/src/dawn/wire/client/Queue.cpp
@@ -51,24 +51,18 @@
 
   private:
     void CompleteImpl(EventCompletionType completionType) override {
-        WGPUQueueWorkDoneStatus status = completionType == EventCompletionType::Shutdown
-                                             ? WGPUQueueWorkDoneStatus_DeviceLost
-                                             : WGPUQueueWorkDoneStatus_Success;
-        if (mStatus) {
-            // TODO(crbug.com/dawn/2021): Pretend things success when the device is lost.
-            status = *mStatus == WGPUQueueWorkDoneStatus_DeviceLost
-                         ? WGPUQueueWorkDoneStatus_Success
-                         : *mStatus;
+        if (mStatus == WGPUQueueWorkDoneStatus_DeviceLost) {
+            mStatus = WGPUQueueWorkDoneStatus_Success;
         }
         if (mCallback) {
-            mCallback(status, mUserdata);
+            mCallback(mStatus, mUserdata);
         }
     }
 
     WGPUQueueWorkDoneCallback mCallback;
     void* mUserdata;
 
-    std::optional<WGPUQueueWorkDoneStatus> mStatus;
+    WGPUQueueWorkDoneStatus mStatus = WGPUQueueWorkDoneStatus_Success;
 };
 
 }  // anonymous namespace