| // 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(); | |
| } | |
| } |