[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