// Copyright 2020 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/WireTest.h" #include "dawn/common/Assert.h" #include "dawn/tests/MockCallback.h" #include "dawn/wire/WireClient.h" namespace dawn::wire { using testing::_; using testing::Exactly; using testing::InvokeWithoutArgs; using testing::MockCallback; using testing::Return; using testing::Sequence; using testing::StrEq; namespace { class WireDisconnectTests : public WireTest {}; } // anonymous namespace // Test that commands are not received if the client disconnects. TEST_F(WireDisconnectTests, CommandsAfterDisconnect) { // Check that commands work at all. wgpuDeviceCreateCommandEncoder(device, nullptr); WGPUCommandEncoder apiCmdBufEncoder = api.GetNewCommandEncoder(); EXPECT_CALL(api, DeviceCreateCommandEncoder(apiDevice, nullptr)) .WillOnce(Return(apiCmdBufEncoder)); FlushClient(); // Disconnect. GetWireClient()->Disconnect(); // Command is not received because client disconnected. wgpuDeviceCreateCommandEncoder(device, nullptr); EXPECT_CALL(api, DeviceCreateCommandEncoder(_, _)).Times(Exactly(0)); FlushClient(); } // Test that commands that are serialized before a disconnect but flushed // after are received. TEST_F(WireDisconnectTests, FlushAfterDisconnect) { // Check that commands work at all. wgpuDeviceCreateCommandEncoder(device, nullptr); // Disconnect. GetWireClient()->Disconnect(); // Already-serialized commmands are still received. WGPUCommandEncoder apiCmdBufEncoder = api.GetNewCommandEncoder(); EXPECT_CALL(api, DeviceCreateCommandEncoder(apiDevice, nullptr)) .WillOnce(Return(apiCmdBufEncoder)); FlushClient(); } // Check that disconnecting the wire client calls the device lost callback exacty once. TEST_F(WireDisconnectTests, CallsDeviceLostCallback) { MockCallback mockDeviceLostCallback; wgpuDeviceSetDeviceLostCallback(device, mockDeviceLostCallback.Callback(), mockDeviceLostCallback.MakeUserdata(this)); // Disconnect the wire client. We should receive device lost only once. EXPECT_CALL(mockDeviceLostCallback, Call(WGPUDeviceLostReason_Undefined, _, this)) .Times(Exactly(1)); GetWireClient()->Disconnect(); GetWireClient()->Disconnect(); } // Check that disconnecting the wire client after a device loss does not trigger the callback // again. TEST_F(WireDisconnectTests, ServerLostThenDisconnect) { MockCallback mockDeviceLostCallback; wgpuDeviceSetDeviceLostCallback(device, mockDeviceLostCallback.Callback(), mockDeviceLostCallback.MakeUserdata(this)); api.CallDeviceSetDeviceLostCallbackCallback(apiDevice, WGPUDeviceLostReason_Undefined, "some reason"); // Flush the device lost return command. EXPECT_CALL(mockDeviceLostCallback, Call(WGPUDeviceLostReason_Undefined, StrEq("some reason"), this)) .Times(Exactly(1)); FlushServer(); // Disconnect the client. We shouldn't see the lost callback again. EXPECT_CALL(mockDeviceLostCallback, Call(_, _, _)).Times(Exactly(0)); GetWireClient()->Disconnect(); } // Check that disconnecting the wire client inside the device loss callback does not trigger the // callback again. TEST_F(WireDisconnectTests, ServerLostThenDisconnectInCallback) { MockCallback mockDeviceLostCallback; wgpuDeviceSetDeviceLostCallback(device, mockDeviceLostCallback.Callback(), mockDeviceLostCallback.MakeUserdata(this)); api.CallDeviceSetDeviceLostCallbackCallback(apiDevice, WGPUDeviceLostReason_Undefined, "lost reason"); // Disconnect the client inside the lost callback. We should see the callback // only once. EXPECT_CALL(mockDeviceLostCallback, Call(WGPUDeviceLostReason_Undefined, StrEq("lost reason"), this)) .WillOnce(InvokeWithoutArgs([&]() { EXPECT_CALL(mockDeviceLostCallback, Call(_, _, _)).Times(Exactly(0)); GetWireClient()->Disconnect(); })); FlushServer(); } // Check that a device loss after a disconnect does not trigger the callback again. TEST_F(WireDisconnectTests, DisconnectThenServerLost) { MockCallback mockDeviceLostCallback; wgpuDeviceSetDeviceLostCallback(device, mockDeviceLostCallback.Callback(), mockDeviceLostCallback.MakeUserdata(this)); // Disconnect the client. We should see the callback once. EXPECT_CALL(mockDeviceLostCallback, Call(WGPUDeviceLostReason_Undefined, _, this)) .Times(Exactly(1)); GetWireClient()->Disconnect(); // Lose the device on the server. The client callback shouldn't be // called again. api.CallDeviceSetDeviceLostCallbackCallback(apiDevice, WGPUDeviceLostReason_Undefined, "lost reason"); EXPECT_CALL(mockDeviceLostCallback, Call(_, _, _)).Times(Exactly(0)); FlushServer(); } // Test that client objects are all destroyed if the WireClient is destroyed. TEST_F(WireDisconnectTests, DeleteClientDestroysObjects) { WGPUSamplerDescriptor desc = {}; wgpuDeviceCreateCommandEncoder(device, nullptr); wgpuDeviceCreateSampler(device, &desc); WGPUCommandEncoder apiCommandEncoder = api.GetNewCommandEncoder(); EXPECT_CALL(api, DeviceCreateCommandEncoder(apiDevice, nullptr)) .WillOnce(Return(apiCommandEncoder)); WGPUSampler apiSampler = api.GetNewSampler(); EXPECT_CALL(api, DeviceCreateSampler(apiDevice, _)).WillOnce(Return(apiSampler)); FlushClient(); DeleteClient(); // Expect release on all objects created by the client. Note: the device // should be deleted first because it may free its reference to the default queue // on deletion. Sequence s1, s2, s3; EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(apiDevice, nullptr, nullptr)) .Times(1) .InSequence(s1, s2); EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, nullptr, nullptr)) .Times(1) .InSequence(s1, s2); EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(apiDevice, nullptr, nullptr)) .Times(1) .InSequence(s1, s2); EXPECT_CALL(api, DeviceRelease(apiDevice)).Times(1).InSequence(s1, s2, s3); EXPECT_CALL(api, QueueRelease(apiQueue)).Times(1).InSequence(s1); EXPECT_CALL(api, CommandEncoderRelease(apiCommandEncoder)).Times(1).InSequence(s2); EXPECT_CALL(api, SamplerRelease(apiSampler)).Times(1).InSequence(s3); FlushClient(); // Signal that we already released and cleared callbacks for |apiDevice| DefaultApiDeviceWasReleased(); } } // namespace dawn::wire