// Copyright 2019 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 "tests/unittests/wire/WireTest.h" #include "dawn/wire/WireClient.h" using namespace testing; using namespace dawn::wire; namespace { // Mock classes to add expectations on the wire calling callbacks class MockDeviceErrorCallback { public: MOCK_METHOD(void, Call, (WGPUErrorType type, const char* message, void* userdata)); }; std::unique_ptr> mockDeviceErrorCallback; void ToMockDeviceErrorCallback(WGPUErrorType type, const char* message, void* userdata) { mockDeviceErrorCallback->Call(type, message, userdata); } class MockDevicePopErrorScopeCallback { public: MOCK_METHOD(void, Call, (WGPUErrorType type, const char* message, void* userdata)); }; std::unique_ptr> mockDevicePopErrorScopeCallback; void ToMockDevicePopErrorScopeCallback(WGPUErrorType type, const char* message, void* userdata) { mockDevicePopErrorScopeCallback->Call(type, message, userdata); } class MockDeviceLoggingCallback { public: MOCK_METHOD(void, Call, (WGPULoggingType type, const char* message, void* userdata)); }; std::unique_ptr> mockDeviceLoggingCallback; void ToMockDeviceLoggingCallback(WGPULoggingType type, const char* message, void* userdata) { mockDeviceLoggingCallback->Call(type, message, userdata); } class MockDeviceLostCallback { public: MOCK_METHOD(void, Call, (WGPUDeviceLostReason reason, const char* message, void* userdata)); }; std::unique_ptr> mockDeviceLostCallback; void ToMockDeviceLostCallback(WGPUDeviceLostReason reason, const char* message, void* userdata) { mockDeviceLostCallback->Call(reason, message, userdata); } } // anonymous namespace class WireErrorCallbackTests : public WireTest { public: WireErrorCallbackTests() { } ~WireErrorCallbackTests() override = default; void SetUp() override { WireTest::SetUp(); mockDeviceErrorCallback = std::make_unique>(); mockDeviceLoggingCallback = std::make_unique>(); mockDevicePopErrorScopeCallback = std::make_unique>(); mockDeviceLostCallback = std::make_unique>(); } void TearDown() override { WireTest::TearDown(); mockDeviceErrorCallback = nullptr; mockDeviceLoggingCallback = nullptr; mockDevicePopErrorScopeCallback = nullptr; mockDeviceLostCallback = nullptr; } void FlushServer() { WireTest::FlushServer(); Mock::VerifyAndClearExpectations(&mockDeviceErrorCallback); Mock::VerifyAndClearExpectations(&mockDevicePopErrorScopeCallback); } }; // Test the return wire for device error callbacks TEST_F(WireErrorCallbackTests, DeviceErrorCallback) { wgpuDeviceSetUncapturedErrorCallback(device, ToMockDeviceErrorCallback, this); // Setting the error callback should stay on the client side and do nothing FlushClient(); // Calling the callback on the server side will result in the callback being called on the // client side api.CallDeviceSetUncapturedErrorCallbackCallback(apiDevice, WGPUErrorType_Validation, "Some error message"); EXPECT_CALL(*mockDeviceErrorCallback, Call(WGPUErrorType_Validation, StrEq("Some error message"), this)) .Times(1); FlushServer(); } // Test the return wire for device user warning callbacks TEST_F(WireErrorCallbackTests, DeviceLoggingCallback) { wgpuDeviceSetLoggingCallback(device, ToMockDeviceLoggingCallback, this); // Setting the injected warning callback should stay on the client side and do nothing FlushClient(); // Calling the callback on the server side will result in the callback being called on the // client side api.CallDeviceSetLoggingCallbackCallback(apiDevice, WGPULoggingType_Info, "Some message"); EXPECT_CALL(*mockDeviceLoggingCallback, Call(WGPULoggingType_Info, StrEq("Some message"), this)) .Times(1); FlushServer(); } // Test the return wire for error scopes. TEST_F(WireErrorCallbackTests, PushPopErrorScopeCallback) { wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation); EXPECT_CALL(api, DevicePushErrorScope(apiDevice, WGPUErrorFilter_Validation)).Times(1); FlushClient(); wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this); WGPUErrorCallback callback; void* userdata; EXPECT_CALL(api, OnDevicePopErrorScope(apiDevice, _, _)) .WillOnce(DoAll(SaveArg<1>(&callback), SaveArg<2>(&userdata), Return(true))); FlushClient(); callback(WGPUErrorType_Validation, "Some error message", userdata); EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_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. { wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation); wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation); EXPECT_CALL(api, DevicePushErrorScope(apiDevice, WGPUErrorFilter_Validation)).Times(2); FlushClient(); wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this); wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this + 1); WGPUErrorCallback callback1; WGPUErrorCallback callback2; void* userdata1; void* userdata2; EXPECT_CALL(api, OnDevicePopErrorScope(apiDevice, _, _)) .WillOnce(DoAll(SaveArg<1>(&callback1), SaveArg<2>(&userdata1), Return(true))) .WillOnce(DoAll(SaveArg<1>(&callback2), SaveArg<2>(&userdata2), Return(true))); FlushClient(); callback1(WGPUErrorType_Validation, "First error message", userdata1); EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_Validation, StrEq("First error message"), this)) .Times(1); FlushServer(); callback2(WGPUErrorType_Validation, "Second error message", userdata2); EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_Validation, StrEq("Second error message"), this + 1)) .Times(1); FlushServer(); } // Two error scopes are popped, and the second one returns first. { wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation); wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation); EXPECT_CALL(api, DevicePushErrorScope(apiDevice, WGPUErrorFilter_Validation)).Times(2); FlushClient(); wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this); wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this + 1); WGPUErrorCallback callback1; WGPUErrorCallback callback2; void* userdata1; void* userdata2; EXPECT_CALL(api, OnDevicePopErrorScope(apiDevice, _, _)) .WillOnce(DoAll(SaveArg<1>(&callback1), SaveArg<2>(&userdata1), Return(true))) .WillOnce(DoAll(SaveArg<1>(&callback2), SaveArg<2>(&userdata2), Return(true))); FlushClient(); callback2(WGPUErrorType_Validation, "Second error message", userdata2); EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_Validation, StrEq("Second error message"), this + 1)) .Times(1); FlushServer(); callback1(WGPUErrorType_Validation, "First error message", userdata1); EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_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) { wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation); EXPECT_CALL(api, DevicePushErrorScope(apiDevice, WGPUErrorFilter_Validation)).Times(1); FlushClient(); EXPECT_TRUE(wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this)); EXPECT_CALL(api, OnDevicePopErrorScope(apiDevice, _, _)).WillOnce(Return(true)); FlushClient(); // Incomplete callback called in Device destructor. EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_Unknown, ValidStringMessage(), this)) .Times(1); } // Test that registering a callback then wire disconnect calls the callback with // DeviceLost. TEST_F(WireErrorCallbackTests, PopErrorScopeThenDisconnect) { wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation); EXPECT_CALL(api, DevicePushErrorScope(apiDevice, WGPUErrorFilter_Validation)).Times(1); EXPECT_TRUE(wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this)); EXPECT_CALL(api, OnDevicePopErrorScope(apiDevice, _, _)).WillOnce(Return(true)); FlushClient(); EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_DeviceLost, ValidStringMessage(), this)) .Times(1); GetWireClient()->Disconnect(); } // Test that registering a callback after wire disconnect calls the callback with // DeviceLost. TEST_F(WireErrorCallbackTests, PopErrorScopeAfterDisconnect) { wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation); EXPECT_CALL(api, DevicePushErrorScope(apiDevice, WGPUErrorFilter_Validation)).Times(1); FlushClient(); GetWireClient()->Disconnect(); EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_DeviceLost, ValidStringMessage(), this)) .Times(1); EXPECT_TRUE(wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this)); } // Test that PopErrorScope returns false if there are no error scopes. TEST_F(WireErrorCallbackTests, PopErrorScopeEmptyStack) { // Empty stack { EXPECT_FALSE(wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this)); } // Pop too many times { wgpuDevicePushErrorScope(device, WGPUErrorFilter_Validation); EXPECT_CALL(api, DevicePushErrorScope(apiDevice, WGPUErrorFilter_Validation)).Times(1); EXPECT_TRUE(wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this)); EXPECT_FALSE(wgpuDevicePopErrorScope(device, ToMockDevicePopErrorScopeCallback, this + 1)); WGPUErrorCallback callback; void* userdata; EXPECT_CALL(api, OnDevicePopErrorScope(apiDevice, _, _)) .WillOnce(DoAll(SaveArg<1>(&callback), SaveArg<2>(&userdata), Return(true))); FlushClient(); callback(WGPUErrorType_Validation, "Some error message", userdata); EXPECT_CALL(*mockDevicePopErrorScopeCallback, Call(WGPUErrorType_Validation, StrEq("Some error message"), this)) .Times(1); FlushServer(); } } // Test the return wire for device lost callback TEST_F(WireErrorCallbackTests, DeviceLostCallback) { wgpuDeviceSetDeviceLostCallback(device, ToMockDeviceLostCallback, this); // Setting the error callback should stay on the client side and do nothing FlushClient(); // Calling the callback on the server side will result in the callback being called on the // client side api.CallDeviceSetDeviceLostCallbackCallback(apiDevice, WGPUDeviceLostReason_Undefined, "Some error message"); EXPECT_CALL(*mockDeviceLostCallback, Call(WGPUDeviceLostReason_Undefined, StrEq("Some error message"), this)) .Times(1); FlushServer(); }