| // Copyright 2021 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" |
| #include "dawn_wire/WireServer.h" |
| |
| using namespace testing; |
| using namespace dawn::wire; |
| |
| class WireInjectDeviceTests : public WireTest { |
| public: |
| WireInjectDeviceTests() { |
| } |
| ~WireInjectDeviceTests() override = default; |
| }; |
| |
| // Test that reserving and injecting a device makes calls on the client object forward to the |
| // server object correctly. |
| TEST_F(WireInjectDeviceTests, CallAfterReserveInject) { |
| ReservedDevice reservation = GetWireClient()->ReserveDevice(); |
| |
| WGPUDevice serverDevice = api.GetNewDevice(); |
| EXPECT_CALL(api, DeviceReference(serverDevice)); |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, _, _)); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, _, _)); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, _, _)); |
| ASSERT_TRUE( |
| GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation)); |
| |
| WGPUBufferDescriptor bufferDesc = {}; |
| wgpuDeviceCreateBuffer(reservation.device, &bufferDesc); |
| WGPUBuffer serverBuffer = api.GetNewBuffer(); |
| EXPECT_CALL(api, DeviceCreateBuffer(serverDevice, _)).WillOnce(Return(serverBuffer)); |
| FlushClient(); |
| |
| // Called on shutdown. |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, nullptr, nullptr)) |
| .Times(Exactly(1)); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, nullptr, nullptr)).Times(Exactly(1)); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, nullptr, nullptr)) |
| .Times(Exactly(1)); |
| } |
| |
| // Test that reserve correctly returns different IDs each time. |
| TEST_F(WireInjectDeviceTests, ReserveDifferentIDs) { |
| ReservedDevice reservation1 = GetWireClient()->ReserveDevice(); |
| ReservedDevice reservation2 = GetWireClient()->ReserveDevice(); |
| |
| ASSERT_NE(reservation1.id, reservation2.id); |
| ASSERT_NE(reservation1.device, reservation2.device); |
| } |
| |
| // Test that injecting the same id without a destroy first fails. |
| TEST_F(WireInjectDeviceTests, InjectExistingID) { |
| ReservedDevice reservation = GetWireClient()->ReserveDevice(); |
| |
| WGPUDevice serverDevice = api.GetNewDevice(); |
| EXPECT_CALL(api, DeviceReference(serverDevice)); |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, _, _)); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, _, _)); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, _, _)); |
| ASSERT_TRUE( |
| GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation)); |
| |
| // ID already in use, call fails. |
| ASSERT_FALSE( |
| GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation)); |
| |
| // Called on shutdown. |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, nullptr, nullptr)) |
| .Times(Exactly(1)); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, nullptr, nullptr)).Times(Exactly(1)); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, nullptr, nullptr)) |
| .Times(Exactly(1)); |
| } |
| |
| // Test that the server only borrows the device and does a single reference-release |
| TEST_F(WireInjectDeviceTests, InjectedDeviceLifetime) { |
| ReservedDevice reservation = GetWireClient()->ReserveDevice(); |
| |
| // Injecting the device adds a reference |
| WGPUDevice serverDevice = api.GetNewDevice(); |
| EXPECT_CALL(api, DeviceReference(serverDevice)); |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, _, _)); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, _, _)); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, _, _)); |
| ASSERT_TRUE( |
| GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation)); |
| |
| // Releasing the device removes a single reference and clears its error callbacks. |
| wgpuDeviceRelease(reservation.device); |
| EXPECT_CALL(api, DeviceRelease(serverDevice)); |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, nullptr, nullptr)).Times(1); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, nullptr, nullptr)).Times(1); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, nullptr, nullptr)).Times(1); |
| FlushClient(); |
| |
| // Deleting the server doesn't release a second reference. |
| DeleteServer(); |
| Mock::VerifyAndClearExpectations(&api); |
| } |
| |
| // Test that it is an error to get the primary queue of a device before it has been |
| // injected on the server. |
| TEST_F(WireInjectDeviceTests, GetQueueBeforeInject) { |
| ReservedDevice reservation = GetWireClient()->ReserveDevice(); |
| |
| wgpuDeviceGetQueue(reservation.device); |
| FlushClient(false); |
| } |
| |
| // Test that it is valid to get the primary queue of a device after it has been |
| // injected on the server. |
| TEST_F(WireInjectDeviceTests, GetQueueAfterInject) { |
| ReservedDevice reservation = GetWireClient()->ReserveDevice(); |
| |
| WGPUDevice serverDevice = api.GetNewDevice(); |
| EXPECT_CALL(api, DeviceReference(serverDevice)); |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, _, _)); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, _, _)); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, _, _)); |
| ASSERT_TRUE( |
| GetWireServer()->InjectDevice(serverDevice, reservation.id, reservation.generation)); |
| |
| wgpuDeviceGetQueue(reservation.device); |
| |
| WGPUQueue apiQueue = api.GetNewQueue(); |
| EXPECT_CALL(api, DeviceGetQueue(serverDevice)).WillOnce(Return(apiQueue)); |
| FlushClient(); |
| |
| // Called on shutdown. |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice, nullptr, nullptr)) |
| .Times(Exactly(1)); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice, nullptr, nullptr)).Times(Exactly(1)); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice, nullptr, nullptr)) |
| .Times(Exactly(1)); |
| } |
| |
| // Test that the list of live devices can be reflected using GetDevice. |
| TEST_F(WireInjectDeviceTests, ReflectLiveDevices) { |
| // Reserve two devices. |
| ReservedDevice reservation1 = GetWireClient()->ReserveDevice(); |
| ReservedDevice reservation2 = GetWireClient()->ReserveDevice(); |
| |
| // Inject both devices. |
| |
| WGPUDevice serverDevice1 = api.GetNewDevice(); |
| EXPECT_CALL(api, DeviceReference(serverDevice1)); |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice1, _, _)); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice1, _, _)); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice1, _, _)); |
| ASSERT_TRUE( |
| GetWireServer()->InjectDevice(serverDevice1, reservation1.id, reservation1.generation)); |
| |
| WGPUDevice serverDevice2 = api.GetNewDevice(); |
| EXPECT_CALL(api, DeviceReference(serverDevice2)); |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice2, _, _)); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice2, _, _)); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice2, _, _)); |
| ASSERT_TRUE( |
| GetWireServer()->InjectDevice(serverDevice2, reservation2.id, reservation2.generation)); |
| |
| // Test that both devices can be reflected. |
| ASSERT_EQ(serverDevice1, GetWireServer()->GetDevice(reservation1.id, reservation1.generation)); |
| ASSERT_EQ(serverDevice2, GetWireServer()->GetDevice(reservation2.id, reservation2.generation)); |
| |
| // Release the first device |
| wgpuDeviceRelease(reservation1.device); |
| EXPECT_CALL(api, DeviceRelease(serverDevice1)); |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice1, nullptr, nullptr)).Times(1); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice1, nullptr, nullptr)).Times(1); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice1, nullptr, nullptr)).Times(1); |
| FlushClient(); |
| |
| // The first device should no longer reflect, but the second should |
| ASSERT_EQ(nullptr, GetWireServer()->GetDevice(reservation1.id, reservation1.generation)); |
| ASSERT_EQ(serverDevice2, GetWireServer()->GetDevice(reservation2.id, reservation2.generation)); |
| |
| // Called on shutdown. |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice2, nullptr, nullptr)).Times(1); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice2, nullptr, nullptr)).Times(1); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice2, nullptr, nullptr)).Times(1); |
| } |
| |
| // This is a regression test where a second device reservation invalidated pointers into the |
| // KnownObjects std::vector of devices. The fix was to store pointers to heap allocated |
| // objects instead. |
| TEST_F(WireInjectDeviceTests, TrackChildObjectsWithTwoReservedDevices) { |
| // Reserve one device, inject it, and get the primary queue. |
| ReservedDevice reservation1 = GetWireClient()->ReserveDevice(); |
| |
| WGPUDevice serverDevice1 = api.GetNewDevice(); |
| EXPECT_CALL(api, DeviceReference(serverDevice1)); |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice1, _, _)); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice1, _, _)); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice1, _, _)); |
| ASSERT_TRUE( |
| GetWireServer()->InjectDevice(serverDevice1, reservation1.id, reservation1.generation)); |
| |
| WGPUCommandEncoder commandEncoder = |
| wgpuDeviceCreateCommandEncoder(reservation1.device, nullptr); |
| |
| WGPUCommandEncoder serverCommandEncoder = api.GetNewCommandEncoder(); |
| EXPECT_CALL(api, DeviceCreateCommandEncoder(serverDevice1, _)) |
| .WillOnce(Return(serverCommandEncoder)); |
| FlushClient(); |
| |
| // Reserve a second device, and inject it. |
| ReservedDevice reservation2 = GetWireClient()->ReserveDevice(); |
| |
| WGPUDevice serverDevice2 = api.GetNewDevice(); |
| EXPECT_CALL(api, DeviceReference(serverDevice2)); |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice2, _, _)); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice2, _, _)); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice2, _, _)); |
| ASSERT_TRUE( |
| GetWireServer()->InjectDevice(serverDevice2, reservation2.id, reservation2.generation)); |
| |
| // Release the encoder. This should work without error because it stores a stable |
| // pointer to its device's list of child objects. On destruction, it removes itself from the |
| // list. |
| wgpuCommandEncoderRelease(commandEncoder); |
| EXPECT_CALL(api, CommandEncoderRelease(serverCommandEncoder)); |
| FlushClient(); |
| |
| // Called on shutdown. |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice1, nullptr, nullptr)).Times(1); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice1, nullptr, nullptr)).Times(1); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice1, nullptr, nullptr)).Times(1); |
| EXPECT_CALL(api, OnDeviceSetUncapturedErrorCallback(serverDevice2, nullptr, nullptr)).Times(1); |
| EXPECT_CALL(api, OnDeviceSetLoggingCallback(serverDevice2, nullptr, nullptr)).Times(1); |
| EXPECT_CALL(api, OnDeviceSetDeviceLostCallback(serverDevice2, nullptr, nullptr)).Times(1); |
| } |
| |
| // Test that a device reservation can be reclaimed. This is necessary to |
| // avoid leaking ObjectIDs for reservations that are never injected. |
| TEST_F(WireInjectDeviceTests, ReclaimDeviceReservation) { |
| // Test that doing a reservation and full release is an error. |
| { |
| ReservedDevice reservation = GetWireClient()->ReserveDevice(); |
| wgpuDeviceRelease(reservation.device); |
| FlushClient(false); |
| } |
| |
| // Test that doing a reservation and then reclaiming it recycles the ID. |
| { |
| ReservedDevice reservation1 = GetWireClient()->ReserveDevice(); |
| GetWireClient()->ReclaimDeviceReservation(reservation1); |
| |
| ReservedDevice reservation2 = GetWireClient()->ReserveDevice(); |
| |
| // The ID is the same, but the generation is still different. |
| ASSERT_EQ(reservation1.id, reservation2.id); |
| ASSERT_NE(reservation1.generation, reservation2.generation); |
| |
| // No errors should occur. |
| FlushClient(); |
| } |
| } |