From 45238d775a234735b4ae316bdc6ce46dafc12144 Mon Sep 17 00:00:00 2001 From: Austin Eng Date: Wed, 4 Sep 2019 22:54:03 +0000 Subject: [PATCH] Add empty implementations of Push/PopErrorScope This adds Push/PopErrorScope to the API with empty implementations which just call the error callback. Also adds unittests that the wire callbacks return as expected. Bug: dawn:153 Change-Id: I63826360e39fbac4c9855d3d55a05b5ca26db450 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/10543 Commit-Queue: Austin Eng Reviewed-by: Kai Ninomiya --- dawn.json | 22 +++ dawn_wire.json | 11 ++ generator/templates/mock_api.cpp | 4 + generator/templates/mock_api.h | 5 + src/dawn_native/Device.cpp | 11 ++ src/dawn_native/Device.h | 3 + src/dawn_wire/client/ApiProcs.cpp | 10 ++ src/dawn_wire/client/ClientDoers.cpp | 6 + src/dawn_wire/client/Device.cpp | 71 ++++++++ src/dawn_wire/client/Device.h | 15 ++ src/dawn_wire/server/Server.h | 11 ++ src/dawn_wire/server/ServerDevice.cpp | 29 ++++ .../unittests/wire/WireErrorCallbackTests.cpp | 152 ++++++++++++++++++ 13 files changed, 350 insertions(+) diff --git a/dawn.json b/dawn.json index c4a1075b4f..036f132ddb 100644 --- a/dawn.json +++ b/dawn.json @@ -527,6 +527,20 @@ {"name": "callback", "type": "error callback"}, {"name": "userdata", "type": "void", "annotation": "*"} ] + }, + { + "name": "push error scope", + "args": [ + {"name": "filter", "type": "error filter"} + ] + }, + { + "name": "pop error scope", + "returns": "bool", + "args": [ + {"name": "callback", "type": "error callback"}, + {"name": "userdata", "type": "void", "annotation": "*"} + ] } ] }, @@ -546,6 +560,14 @@ "error callback": { "category": "natively defined" }, + "error filter": { + "category": "enum", + "values": [ + {"value": 0, "name": "none"}, + {"value": 1, "name": "validation"}, + {"value": 2, "name": "out of memory"} + ] + }, "error type": { "category": "enum", "values": [ diff --git a/dawn_wire.json b/dawn_wire.json index 509d8230f1..cbd8b30743 100644 --- a/dawn_wire.json +++ b/dawn_wire.json @@ -48,6 +48,10 @@ { "name": "handle create info length", "type": "uint64_t" }, { "name": "handle create info", "type": "uint8_t", "annotation": "const*", "length": "handle create info length", "skip_serialize": true} ], + "device pop error scope": [ + { "name": "device", "type": "device" }, + { "name": "request serial", "type": "uint64_t" } + ], "destroy object": [ { "name": "object type", "type": "ObjectType" }, { "name": "object id", "type": "ObjectId" } @@ -70,6 +74,11 @@ { "name": "type", "type": "error type"}, { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" } ], + "device pop error scope callback": [ + { "name": "request serial", "type": "uint64_t" }, + { "name": "type", "type": "error type" }, + { "name": "message", "type": "char", "annotation": "const*", "length": "strlen" } + ], "fence update completed value": [ { "name": "fence", "type": "ObjectHandle", "handle_type": "fence" }, { "name": "value", "type": "uint64_t" } @@ -89,6 +98,8 @@ "DeviceCreateBuffer", "DeviceCreateBufferMapped", "DeviceCreateBufferMappedAsync", + "DevicePushErrorScope", + "DevicePopErrorScope", "QueueCreateFence", "FenceGetCompletedValue", "QueueSignal" diff --git a/generator/templates/mock_api.cpp b/generator/templates/mock_api.cpp index e5d30b08d8..4b451a3de1 100644 --- a/generator/templates/mock_api.cpp +++ b/generator/templates/mock_api.cpp @@ -60,6 +60,10 @@ void ProcTableAsClass::DeviceSetUncapturedErrorCallback(DawnDevice self, OnDeviceSetUncapturedErrorCallback(self, callback, userdata); } +bool ProcTableAsClass::DevicePopErrorScope(DawnDevice self, DawnErrorCallback callback, void* userdata) { + return OnDevicePopErrorScopeCallback(self, callback, userdata); +} + void ProcTableAsClass::DeviceCreateBufferMappedAsync(DawnDevice self, const DawnBufferDescriptor* descriptor, DawnBufferCreateMappedCallback callback, diff --git a/generator/templates/mock_api.h b/generator/templates/mock_api.h index a159a250f6..6f37e2dc71 100644 --- a/generator/templates/mock_api.h +++ b/generator/templates/mock_api.h @@ -54,6 +54,7 @@ class ProcTableAsClass { void DeviceSetUncapturedErrorCallback(DawnDevice self, DawnErrorCallback callback, void* userdata); + bool DevicePopErrorScope(DawnDevice self, DawnErrorCallback callback, void* userdata); void DeviceCreateBufferMappedAsync(DawnDevice self, const DawnBufferDescriptor* descriptor, DawnBufferCreateMappedCallback callback, @@ -73,6 +74,9 @@ class ProcTableAsClass { virtual void OnDeviceSetUncapturedErrorCallback(DawnDevice device, DawnErrorCallback callback, void* userdata) = 0; + virtual bool OnDevicePopErrorScopeCallback(DawnDevice device, + DawnErrorCallback callback, + void* userdata) = 0; virtual void OnDeviceCreateBufferMappedAsyncCallback(DawnDevice self, const DawnBufferDescriptor* descriptor, DawnBufferCreateMappedCallback callback, @@ -134,6 +138,7 @@ class MockProcTable : public ProcTableAsClass { {% endfor %} MOCK_METHOD3(OnDeviceSetUncapturedErrorCallback, void(DawnDevice device, DawnErrorCallback callback, void* userdata)); + MOCK_METHOD3(OnDevicePopErrorScopeCallback, bool(DawnDevice device, DawnErrorCallback callback, void* userdata)); MOCK_METHOD4(OnDeviceCreateBufferMappedAsyncCallback, void(DawnDevice device, const DawnBufferDescriptor* descriptor, DawnBufferCreateMappedCallback callback, void* userdata)); MOCK_METHOD3(OnBufferMapReadAsyncCallback, void(DawnBuffer buffer, DawnBufferMapReadCallback callback, void* userdata)); MOCK_METHOD3(OnBufferMapWriteAsyncCallback, void(DawnBuffer buffer, DawnBufferMapWriteCallback callback, void* userdata)); diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp index 808f718f42..e667c12477 100644 --- a/src/dawn_native/Device.cpp +++ b/src/dawn_native/Device.cpp @@ -99,6 +99,17 @@ namespace dawn_native { mErrorUserdata = userdata; } + void DeviceBase::PushErrorScope(dawn::ErrorFilter filter) { + // TODO(crbug.com/dawn/153): Implement error scopes. + HandleError(dawn::ErrorType::Validation, "Error scopes not implemented"); + } + + bool DeviceBase::PopErrorScope(dawn::ErrorCallback callback, void* userdata) { + // TODO(crbug.com/dawn/153): Implement error scopes. + HandleError(dawn::ErrorType::Validation, "Error scopes not implemented"); + return false; + } + MaybeError DeviceBase::ValidateObject(const ObjectBase* object) const { if (DAWN_UNLIKELY(object->GetDevice() != this)) { return DAWN_VALIDATION_ERROR("Object from a different device."); diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h index f19b40f36b..60cdbba902 100644 --- a/src/dawn_native/Device.h +++ b/src/dawn_native/Device.h @@ -149,6 +149,9 @@ namespace dawn_native { void Tick(); void SetUncapturedErrorCallback(dawn::ErrorCallback callback, void* userdata); + void PushErrorScope(dawn::ErrorFilter filter); + bool PopErrorScope(dawn::ErrorCallback callback, void* userdata); + void Reference(); void Release(); diff --git a/src/dawn_wire/client/ApiProcs.cpp b/src/dawn_wire/client/ApiProcs.cpp index 2ce5083ed7..df6ed53535 100644 --- a/src/dawn_wire/client/ApiProcs.cpp +++ b/src/dawn_wire/client/ApiProcs.cpp @@ -264,6 +264,16 @@ namespace dawn_wire { namespace client { writeHandle->SerializeCreate(allocatedBuffer + commandSize); } + void ClientDevicePushErrorScope(DawnDevice cDevice, DawnErrorFilter filter) { + Device* device = reinterpret_cast(cDevice); + device->PushErrorScope(filter); + } + + bool ClientDevicePopErrorScope(DawnDevice cDevice, DawnErrorCallback callback, void* userdata) { + Device* device = reinterpret_cast(cDevice); + return device->RequestPopErrorScope(callback, userdata); + } + uint64_t ClientFenceGetCompletedValue(DawnFence cSelf) { auto fence = reinterpret_cast(cSelf); return fence->completedValue; diff --git a/src/dawn_wire/client/ClientDoers.cpp b/src/dawn_wire/client/ClientDoers.cpp index ded9544cae..f7b6ea1e68 100644 --- a/src/dawn_wire/client/ClientDoers.cpp +++ b/src/dawn_wire/client/ClientDoers.cpp @@ -33,6 +33,12 @@ namespace dawn_wire { namespace client { return true; } + bool Client::DoDevicePopErrorScopeCallback(uint64_t requestSerial, + DawnErrorType errorType, + const char* message) { + return mDevice->PopErrorScope(requestSerial, errorType, message); + } + bool Client::DoBufferMapReadAsyncCallback(Buffer* buffer, uint32_t requestSerial, uint32_t status, diff --git a/src/dawn_wire/client/Device.cpp b/src/dawn_wire/client/Device.cpp index fa5e876cc2..7682e1accf 100644 --- a/src/dawn_wire/client/Device.cpp +++ b/src/dawn_wire/client/Device.cpp @@ -14,6 +14,10 @@ #include "dawn_wire/client/Device.h" +#include "common/Assert.h" +#include "dawn_wire/WireCmd_autogen.h" +#include "dawn_wire/client/Client.h" + namespace dawn_wire { namespace client { Device::Device(Client* client, uint32_t refcount, uint32_t id) @@ -21,6 +25,13 @@ namespace dawn_wire { namespace client { this->device = this; } + Device::~Device() { + auto errorScopes = std::move(mErrorScopes); + for (const auto& it : errorScopes) { + it.second.callback(DAWN_ERROR_TYPE_UNKNOWN, "Device destroyed", it.second.userdata); + } + } + Client* Device::GetClient() { return mClient; } @@ -36,4 +47,64 @@ namespace dawn_wire { namespace client { mErrorUserdata = errorUserdata; } + void Device::PushErrorScope(DawnErrorFilter filter) { + mErrorScopeStackSize++; + + DevicePushErrorScopeCmd cmd; + cmd.self = reinterpret_cast(this); + cmd.filter = filter; + + Client* wireClient = GetClient(); + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(wireClient->GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer, *wireClient); + } + + bool Device::RequestPopErrorScope(DawnErrorCallback callback, void* userdata) { + if (mErrorScopeStackSize == 0) { + return false; + } + mErrorScopeStackSize--; + + uint64_t serial = mErrorScopeRequestSerial++; + ASSERT(mErrorScopes.find(serial) == mErrorScopes.end()); + + mErrorScopes[serial] = {callback, userdata}; + + DevicePopErrorScopeCmd cmd; + cmd.device = reinterpret_cast(this); + cmd.requestSerial = serial; + + Client* wireClient = GetClient(); + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(wireClient->GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer, *wireClient); + + return true; + } + + bool Device::PopErrorScope(uint64_t requestSerial, DawnErrorType type, const char* message) { + switch (type) { + case DAWN_ERROR_TYPE_NO_ERROR: + case DAWN_ERROR_TYPE_VALIDATION: + case DAWN_ERROR_TYPE_OUT_OF_MEMORY: + case DAWN_ERROR_TYPE_UNKNOWN: + case DAWN_ERROR_TYPE_DEVICE_LOST: + break; + default: + return false; + } + + auto requestIt = mErrorScopes.find(requestSerial); + if (requestIt == mErrorScopes.end()) { + return false; + } + + ErrorScopeData request = std::move(requestIt->second); + + mErrorScopes.erase(requestIt); + request.callback(type, message, request.userdata); + return true; + } + }} // namespace dawn_wire::client diff --git a/src/dawn_wire/client/Device.h b/src/dawn_wire/client/Device.h index 3c7c3774fb..600a1c367f 100644 --- a/src/dawn_wire/client/Device.h +++ b/src/dawn_wire/client/Device.h @@ -19,6 +19,8 @@ #include "dawn_wire/client/ObjectBase.h" +#include + namespace dawn_wire { namespace client { class Client; @@ -26,12 +28,25 @@ namespace dawn_wire { namespace client { class Device : public ObjectBase { public: Device(Client* client, uint32_t refcount, uint32_t id); + ~Device(); Client* GetClient(); void HandleError(DawnErrorType errorType, const char* message); void SetUncapturedErrorCallback(DawnErrorCallback errorCallback, void* errorUserdata); + void PushErrorScope(DawnErrorFilter filter); + bool RequestPopErrorScope(DawnErrorCallback callback, void* userdata); + bool PopErrorScope(uint64_t requestSerial, DawnErrorType type, const char* message); + private: + struct ErrorScopeData { + DawnErrorCallback callback = nullptr; + void* userdata = nullptr; + }; + std::map mErrorScopes; + uint64_t mErrorScopeRequestSerial = 0; + uint64_t mErrorScopeStackSize = 0; + Client* mClient = nullptr; DawnErrorCallback mErrorCallback = nullptr; void* mErrorUserdata; diff --git a/src/dawn_wire/server/Server.h b/src/dawn_wire/server/Server.h index 7f9ac15600..efbb46730a 100644 --- a/src/dawn_wire/server/Server.h +++ b/src/dawn_wire/server/Server.h @@ -32,6 +32,13 @@ namespace dawn_wire { namespace server { std::unique_ptr writeHandle = nullptr; }; + struct ErrorScopeUserdata { + Server* server; + // TODO(enga): ObjectHandle device; + // when the wire supports multiple devices. + uint32_t requestSerial; + }; + struct FenceCompletionUserdata { Server* server; ObjectHandle fence; @@ -55,6 +62,7 @@ namespace dawn_wire { namespace server { // Forwarding callbacks static void ForwardUncapturedError(DawnErrorType type, const char* message, void* userdata); + static void ForwardPopErrorScope(DawnErrorType type, const char* message, void* userdata); static void ForwardBufferMapReadAsync(DawnBufferMapAsyncStatus status, const void* ptr, uint64_t dataLength, @@ -67,6 +75,9 @@ namespace dawn_wire { namespace server { // Error callbacks void OnUncapturedError(DawnErrorType type, const char* message); + void OnDevicePopErrorScope(DawnErrorType type, + const char* message, + ErrorScopeUserdata* userdata); void OnBufferMapReadAsyncCallback(DawnBufferMapAsyncStatus status, const void* ptr, uint64_t dataLength, diff --git a/src/dawn_wire/server/ServerDevice.cpp b/src/dawn_wire/server/ServerDevice.cpp index 0ff3792532..4e86d3e571 100644 --- a/src/dawn_wire/server/ServerDevice.cpp +++ b/src/dawn_wire/server/ServerDevice.cpp @@ -31,4 +31,33 @@ namespace dawn_wire { namespace server { cmd.Serialize(allocatedBuffer); } + bool Server::DoDevicePopErrorScope(DawnDevice cDevice, uint64_t requestSerial) { + ErrorScopeUserdata* userdata = new ErrorScopeUserdata; + userdata->server = this; + userdata->requestSerial = requestSerial; + + return mProcs.devicePopErrorScope(cDevice, ForwardPopErrorScope, userdata); + } + + // static + void Server::ForwardPopErrorScope(DawnErrorType type, const char* message, void* userdata) { + auto* data = reinterpret_cast(userdata); + data->server->OnDevicePopErrorScope(type, message, data); + } + + void Server::OnDevicePopErrorScope(DawnErrorType type, + const char* message, + ErrorScopeUserdata* userdata) { + std::unique_ptr data{userdata}; + + ReturnDevicePopErrorScopeCallbackCmd cmd; + cmd.requestSerial = data->requestSerial; + cmd.type = type; + cmd.message = message; + + size_t requiredSize = cmd.GetRequiredSize(); + char* allocatedBuffer = static_cast(GetCmdSpace(requiredSize)); + cmd.Serialize(allocatedBuffer); + } + }} // namespace dawn_wire::server diff --git a/src/tests/unittests/wire/WireErrorCallbackTests.cpp b/src/tests/unittests/wire/WireErrorCallbackTests.cpp index 8bdcba64ac..f57ebf40e1 100644 --- a/src/tests/unittests/wire/WireErrorCallbackTests.cpp +++ b/src/tests/unittests/wire/WireErrorCallbackTests.cpp @@ -30,6 +30,16 @@ namespace { mockDeviceErrorCallback->Call(type, message, userdata); } + class MockDevicePopErrorScopeCallback { + public: + MOCK_METHOD3(Call, void(DawnErrorType type, const char* message, void* userdata)); + }; + + std::unique_ptr> mockDevicePopErrorScopeCallback; + void ToMockDevicePopErrorScopeCallback(DawnErrorType type, const char* message, void* userdata) { + mockDevicePopErrorScopeCallback->Call(type, message, userdata); + } + } // anonymous namespace class WireErrorCallbackTests : public WireTest { @@ -42,18 +52,21 @@ class WireErrorCallbackTests : public WireTest { WireTest::SetUp(); mockDeviceErrorCallback = std::make_unique>(); + mockDevicePopErrorScopeCallback = std::make_unique>(); } void TearDown() override { WireTest::TearDown(); mockDeviceErrorCallback = nullptr; + mockDevicePopErrorScopeCallback = nullptr; } void FlushServer() { WireTest::FlushServer(); Mock::VerifyAndClearExpectations(&mockDeviceErrorCallback); + Mock::VerifyAndClearExpectations(&mockDevicePopErrorScopeCallback); } }; @@ -72,3 +85,142 @@ TEST_F(WireErrorCallbackTests, DeviceErrorCallback) { FlushServer(); } + +// Test the return wire for error scopes. +TEST_F(WireErrorCallbackTests, PushPopErrorScopeCallback) { + dawnDevicePushErrorScope(device, DAWN_ERROR_FILTER_VALIDATION); + EXPECT_CALL(api, DevicePushErrorScope(apiDevice, DAWN_ERROR_FILTER_VALIDATION)).Times(1); + + FlushClient(); + + dawnDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this); + + DawnErrorCallback callback; + void* userdata; + EXPECT_CALL(api, OnDevicePopErrorScopeCallback(apiDevice, _, _)) + .WillOnce(DoAll(SaveArg<1>(&callback), SaveArg<2>(&userdata), Return(true))); + + FlushClient(); + + callback(DAWN_ERROR_TYPE_VALIDATION, "Some error message", userdata); + EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_VALIDATION, StrEq("Some error message"), this)).Times(1); + + FlushServer(); +} + +// Test the return wire for error scopes when callbacks return in a various orders. +TEST_F(WireErrorCallbackTests, PopErrorScopeCallbackOrdering) { + // Two error scopes are popped, and the first one returns first. + { + dawnDevicePushErrorScope(device, DAWN_ERROR_FILTER_VALIDATION); + dawnDevicePushErrorScope(device, DAWN_ERROR_FILTER_VALIDATION); + EXPECT_CALL(api, DevicePushErrorScope(apiDevice, DAWN_ERROR_FILTER_VALIDATION)).Times(2); + + FlushClient(); + + dawnDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this); + dawnDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this + 1); + + DawnErrorCallback callback1; + DawnErrorCallback callback2; + void* userdata1; + void* userdata2; + EXPECT_CALL(api, OnDevicePopErrorScopeCallback(apiDevice, _, _)) + .WillOnce(DoAll(SaveArg<1>(&callback1), SaveArg<2>(&userdata1), Return(true))) + .WillOnce(DoAll(SaveArg<1>(&callback2), SaveArg<2>(&userdata2), Return(true))); + + FlushClient(); + + callback1(DAWN_ERROR_TYPE_VALIDATION, "First error message", userdata1); + EXPECT_CALL(*mockDevicePopErrorScopeCallback, + Call(DAWN_ERROR_TYPE_VALIDATION, + StrEq("First error message"), this)).Times(1); + FlushServer(); + + callback2(DAWN_ERROR_TYPE_VALIDATION, "Second error message", userdata2); + EXPECT_CALL(*mockDevicePopErrorScopeCallback, + Call(DAWN_ERROR_TYPE_VALIDATION, + StrEq("Second error message"), this + 1)).Times(1); + FlushServer(); + } + + // Two error scopes are popped, and the second one returns first. + { + dawnDevicePushErrorScope(device, DAWN_ERROR_FILTER_VALIDATION); + dawnDevicePushErrorScope(device, DAWN_ERROR_FILTER_VALIDATION); + EXPECT_CALL(api, DevicePushErrorScope(apiDevice, DAWN_ERROR_FILTER_VALIDATION)).Times(2); + + FlushClient(); + + dawnDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this); + dawnDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this + 1); + + DawnErrorCallback callback1; + DawnErrorCallback callback2; + void* userdata1; + void* userdata2; + EXPECT_CALL(api, OnDevicePopErrorScopeCallback(apiDevice, _, _)) + .WillOnce(DoAll(SaveArg<1>(&callback1), SaveArg<2>(&userdata1), Return(true))) + .WillOnce(DoAll(SaveArg<1>(&callback2), SaveArg<2>(&userdata2), Return(true))); + + FlushClient(); + + callback2(DAWN_ERROR_TYPE_VALIDATION, "Second error message", userdata2); + EXPECT_CALL(*mockDevicePopErrorScopeCallback, + Call(DAWN_ERROR_TYPE_VALIDATION, + StrEq("Second error message"), this + 1)).Times(1); + FlushServer(); + + callback1(DAWN_ERROR_TYPE_VALIDATION, "First error message", userdata1); + EXPECT_CALL(*mockDevicePopErrorScopeCallback, + Call(DAWN_ERROR_TYPE_VALIDATION, + StrEq("First error message"), this)).Times(1); + FlushServer(); + } +} + +// Test the return wire for error scopes in flight when the device is destroyed. +TEST_F(WireErrorCallbackTests, PopErrorScopeDeviceDestroyed) { + dawnDevicePushErrorScope(device, DAWN_ERROR_FILTER_VALIDATION); + EXPECT_CALL(api, DevicePushErrorScope(apiDevice, DAWN_ERROR_FILTER_VALIDATION)).Times(1); + + FlushClient(); + + EXPECT_TRUE(dawnDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this)); + + EXPECT_CALL(api, OnDevicePopErrorScopeCallback(apiDevice, _, _)) + .WillOnce(Return(true)); + FlushClient(); + + // Incomplete callback called in Device destructor. + EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_UNKNOWN, _, this)).Times(1); +} + +// Test that PopErrorScope returns false if there are no error scopes. +TEST_F(WireErrorCallbackTests, PopErrorScopeEmptyStack) { + // Empty stack + { + EXPECT_FALSE(dawnDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this)); + } + + // Pop too many times + { + dawnDevicePushErrorScope(device, DAWN_ERROR_FILTER_VALIDATION); + EXPECT_CALL(api, DevicePushErrorScope(apiDevice, DAWN_ERROR_FILTER_VALIDATION)).Times(1); + + EXPECT_TRUE(dawnDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this)); + EXPECT_FALSE(dawnDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this + 1)); + + DawnErrorCallback callback; + void* userdata; + EXPECT_CALL(api, OnDevicePopErrorScopeCallback(apiDevice, _, _)) + .WillOnce(DoAll(SaveArg<1>(&callback), SaveArg<2>(&userdata), Return(true))); + + FlushClient(); + + callback(DAWN_ERROR_TYPE_VALIDATION, "Some error message", userdata); + EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(DAWN_ERROR_TYPE_VALIDATION, StrEq("Some error message"), this)).Times(1); + + FlushServer(); + } +}