// 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" using namespace testing; using namespace dawn_wire; namespace { // Mock classes to add expectations on the wire calling callbacks class MockBufferMapReadCallback { public: MOCK_METHOD(void, Call, (WGPUBufferMapAsyncStatus status, const uint32_t* ptr, uint64_t dataLength, void* userdata)); }; std::unique_ptr> mockBufferMapReadCallback; void ToMockBufferMapReadCallback(WGPUBufferMapAsyncStatus status, const void* ptr, uint64_t dataLength, void* userdata) { // Assume the data is uint32_t to make writing matchers easier mockBufferMapReadCallback->Call(status, static_cast(ptr), dataLength, userdata); } class MockBufferMapWriteCallback { public: MOCK_METHOD( void, Call, (WGPUBufferMapAsyncStatus status, uint32_t* ptr, uint64_t dataLength, void* userdata)); }; std::unique_ptr> mockBufferMapWriteCallback; uint32_t* lastMapWritePointer = nullptr; void ToMockBufferMapWriteCallback(WGPUBufferMapAsyncStatus status, void* ptr, uint64_t dataLength, void* userdata) { // Assume the data is uint32_t to make writing matchers easier lastMapWritePointer = static_cast(ptr); mockBufferMapWriteCallback->Call(status, lastMapWritePointer, dataLength, userdata); } class MockBufferCreateMappedCallback { public: MOCK_METHOD(void, Call, (WGPUBufferMapAsyncStatus status, WGPUBuffer buffer, uint32_t* ptr, uint64_t dataLength, void* userdata)); }; } // anonymous namespace class WireBufferMappingTests : public WireTest { public: WireBufferMappingTests() { } ~WireBufferMappingTests() override = default; void SetUp() override { WireTest::SetUp(); mockBufferMapReadCallback = std::make_unique>(); mockBufferMapWriteCallback = std::make_unique>(); WGPUBufferDescriptor descriptor = {}; descriptor.size = kBufferSize; apiBuffer = api.GetNewBuffer(); buffer = wgpuDeviceCreateBuffer(device, &descriptor); EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)) .WillOnce(Return(apiBuffer)) .RetiresOnSaturation(); FlushClient(); } void TearDown() override { WireTest::TearDown(); // Delete mocks so that expectations are checked mockBufferMapReadCallback = nullptr; mockBufferMapWriteCallback = nullptr; } void FlushServer() { WireTest::FlushServer(); Mock::VerifyAndClearExpectations(&mockBufferMapReadCallback); Mock::VerifyAndClearExpectations(&mockBufferMapWriteCallback); } protected: static constexpr uint64_t kBufferSize = sizeof(uint32_t); // A successfully created buffer WGPUBuffer buffer; WGPUBuffer apiBuffer; }; // MapRead-specific tests // Check mapping for reading a succesfully created buffer TEST_F(WireBufferMappingTests, MappingForReadSuccessBuffer) { wgpuBufferMapReadAsync(buffer, ToMockBufferMapReadCallback, nullptr); uint32_t bufferContent = 31337; EXPECT_CALL(api, OnBufferMapReadAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapReadCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, &bufferContent, kBufferSize); })); FlushClient(); EXPECT_CALL(*mockBufferMapReadCallback, Call(WGPUBufferMapAsyncStatus_Success, Pointee(Eq(bufferContent)), kBufferSize, _)) .Times(1); FlushServer(); wgpuBufferUnmap(buffer); EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); FlushClient(); } // Check that things work correctly when a validation error happens when mapping the buffer for // reading TEST_F(WireBufferMappingTests, ErrorWhileMappingForRead) { wgpuBufferMapReadAsync(buffer, ToMockBufferMapReadCallback, nullptr); EXPECT_CALL(api, OnBufferMapReadAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapReadCallback(apiBuffer, WGPUBufferMapAsyncStatus_Error, nullptr, 0); })); FlushClient(); EXPECT_CALL(*mockBufferMapReadCallback, Call(WGPUBufferMapAsyncStatus_Error, nullptr, 0, _)) .Times(1); FlushServer(); } // Check that the map read callback is called with UNKNOWN when the buffer is destroyed before the // request is finished TEST_F(WireBufferMappingTests, DestroyBeforeReadRequestEnd) { wgpuBufferMapReadAsync(buffer, ToMockBufferMapReadCallback, nullptr); // Return success EXPECT_CALL(api, OnBufferMapReadAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapReadCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, nullptr, 0); })); // Destroy before the client gets the success, so the callback is called with unknown. EXPECT_CALL(*mockBufferMapReadCallback, Call(WGPUBufferMapAsyncStatus_Unknown, nullptr, 0, _)) .Times(1); wgpuBufferRelease(buffer); EXPECT_CALL(api, BufferRelease(apiBuffer)); FlushClient(); FlushServer(); } // Check the map read callback is called with UNKNOWN when the map request would have worked, but // Unmap was called TEST_F(WireBufferMappingTests, UnmapCalledTooEarlyForRead) { wgpuBufferMapReadAsync(buffer, ToMockBufferMapReadCallback, nullptr); uint32_t bufferContent = 31337; EXPECT_CALL(api, OnBufferMapReadAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapReadCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, &bufferContent, kBufferSize); })); FlushClient(); // Oh no! We are calling Unmap too early! EXPECT_CALL(*mockBufferMapReadCallback, Call(WGPUBufferMapAsyncStatus_Unknown, nullptr, 0, _)) .Times(1); wgpuBufferUnmap(buffer); // The callback shouldn't get called, even when the request succeeded on the server side FlushServer(); } // Check that an error map read callback gets nullptr while a buffer is already mapped TEST_F(WireBufferMappingTests, MappingForReadingErrorWhileAlreadyMappedGetsNullptr) { // Successful map wgpuBufferMapReadAsync(buffer, ToMockBufferMapReadCallback, nullptr); uint32_t bufferContent = 31337; EXPECT_CALL(api, OnBufferMapReadAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapReadCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, &bufferContent, kBufferSize); })) .RetiresOnSaturation(); FlushClient(); EXPECT_CALL(*mockBufferMapReadCallback, Call(WGPUBufferMapAsyncStatus_Success, Pointee(Eq(bufferContent)), kBufferSize, _)) .Times(1); FlushServer(); // Map failure while the buffer is already mapped wgpuBufferMapReadAsync(buffer, ToMockBufferMapReadCallback, nullptr); EXPECT_CALL(api, OnBufferMapReadAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapReadCallback(apiBuffer, WGPUBufferMapAsyncStatus_Error, nullptr, 0); })); FlushClient(); EXPECT_CALL(*mockBufferMapReadCallback, Call(WGPUBufferMapAsyncStatus_Error, nullptr, 0, _)) .Times(1); FlushServer(); } // Test that the MapReadCallback isn't fired twice when unmap() is called inside the callback TEST_F(WireBufferMappingTests, UnmapInsideMapReadCallback) { wgpuBufferMapReadAsync(buffer, ToMockBufferMapReadCallback, nullptr); uint32_t bufferContent = 31337; EXPECT_CALL(api, OnBufferMapReadAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapReadCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, &bufferContent, kBufferSize); })); FlushClient(); EXPECT_CALL(*mockBufferMapReadCallback, Call(WGPUBufferMapAsyncStatus_Success, Pointee(Eq(bufferContent)), kBufferSize, _)) .WillOnce(InvokeWithoutArgs([&]() { wgpuBufferUnmap(buffer); })); FlushServer(); EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); FlushClient(); } // Test that the MapReadCallback isn't fired twice the buffer external refcount reaches 0 in the // callback TEST_F(WireBufferMappingTests, DestroyInsideMapReadCallback) { wgpuBufferMapReadAsync(buffer, ToMockBufferMapReadCallback, nullptr); uint32_t bufferContent = 31337; EXPECT_CALL(api, OnBufferMapReadAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapReadCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, &bufferContent, kBufferSize); })); FlushClient(); EXPECT_CALL(*mockBufferMapReadCallback, Call(WGPUBufferMapAsyncStatus_Success, Pointee(Eq(bufferContent)), kBufferSize, _)) .WillOnce(InvokeWithoutArgs([&]() { wgpuBufferRelease(buffer); })); FlushServer(); EXPECT_CALL(api, BufferRelease(apiBuffer)); FlushClient(); } // MapWrite-specific tests // Check mapping for writing a succesfully created buffer TEST_F(WireBufferMappingTests, MappingForWriteSuccessBuffer) { wgpuBufferMapWriteAsync(buffer, ToMockBufferMapWriteCallback, nullptr); uint32_t serverBufferContent = 31337; uint32_t updatedContent = 4242; uint32_t zero = 0; EXPECT_CALL(api, OnBufferMapWriteAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapWriteCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, &serverBufferContent, kBufferSize); })); FlushClient(); // The map write callback always gets a buffer full of zeroes. EXPECT_CALL(*mockBufferMapWriteCallback, Call(WGPUBufferMapAsyncStatus_Success, Pointee(Eq(zero)), kBufferSize, _)) .Times(1); FlushServer(); // Write something to the mapped pointer *lastMapWritePointer = updatedContent; wgpuBufferUnmap(buffer); EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); FlushClient(); // After the buffer is unmapped, the content of the buffer is updated on the server ASSERT_EQ(serverBufferContent, updatedContent); } // Check that things work correctly when a validation error happens when mapping the buffer for // writing TEST_F(WireBufferMappingTests, ErrorWhileMappingForWrite) { wgpuBufferMapWriteAsync(buffer, ToMockBufferMapWriteCallback, nullptr); EXPECT_CALL(api, OnBufferMapWriteAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapWriteCallback(apiBuffer, WGPUBufferMapAsyncStatus_Error, nullptr, 0); })); FlushClient(); EXPECT_CALL(*mockBufferMapWriteCallback, Call(WGPUBufferMapAsyncStatus_Error, nullptr, 0, _)) .Times(1); FlushServer(); } // Check that the map write callback is called with UNKNOWN when the buffer is destroyed before the // request is finished TEST_F(WireBufferMappingTests, DestroyBeforeWriteRequestEnd) { wgpuBufferMapWriteAsync(buffer, ToMockBufferMapWriteCallback, nullptr); // Return success EXPECT_CALL(api, OnBufferMapWriteAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapWriteCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, nullptr, 0); })); // Destroy before the client gets the success, so the callback is called with unknown. EXPECT_CALL(*mockBufferMapWriteCallback, Call(WGPUBufferMapAsyncStatus_Unknown, nullptr, 0, _)) .Times(1); wgpuBufferRelease(buffer); EXPECT_CALL(api, BufferRelease(apiBuffer)); FlushClient(); FlushServer(); } // Check the map read callback is called with UNKNOWN when the map request would have worked, but // Unmap was called TEST_F(WireBufferMappingTests, UnmapCalledTooEarlyForWrite) { wgpuBufferMapWriteAsync(buffer, ToMockBufferMapWriteCallback, nullptr); uint32_t bufferContent = 31337; EXPECT_CALL(api, OnBufferMapWriteAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapWriteCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, &bufferContent, kBufferSize); })); FlushClient(); // Oh no! We are calling Unmap too early! EXPECT_CALL(*mockBufferMapWriteCallback, Call(WGPUBufferMapAsyncStatus_Unknown, nullptr, 0, _)) .Times(1); wgpuBufferUnmap(buffer); // The callback shouldn't get called, even when the request succeeded on the server side FlushServer(); } // Check that an error map read callback gets nullptr while a buffer is already mapped TEST_F(WireBufferMappingTests, MappingForWritingErrorWhileAlreadyMappedGetsNullptr) { // Successful map wgpuBufferMapWriteAsync(buffer, ToMockBufferMapWriteCallback, nullptr); uint32_t bufferContent = 31337; uint32_t zero = 0; EXPECT_CALL(api, OnBufferMapWriteAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapWriteCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, &bufferContent, kBufferSize); })) .RetiresOnSaturation(); FlushClient(); EXPECT_CALL(*mockBufferMapWriteCallback, Call(WGPUBufferMapAsyncStatus_Success, Pointee(Eq(zero)), kBufferSize, _)) .Times(1); FlushServer(); // Map failure while the buffer is already mapped wgpuBufferMapWriteAsync(buffer, ToMockBufferMapWriteCallback, nullptr); EXPECT_CALL(api, OnBufferMapWriteAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapWriteCallback(apiBuffer, WGPUBufferMapAsyncStatus_Error, nullptr, 0); })); FlushClient(); EXPECT_CALL(*mockBufferMapWriteCallback, Call(WGPUBufferMapAsyncStatus_Error, nullptr, 0, _)) .Times(1); FlushServer(); } // Test that the MapWriteCallback isn't fired twice when unmap() is called inside the callback TEST_F(WireBufferMappingTests, UnmapInsideMapWriteCallback) { wgpuBufferMapWriteAsync(buffer, ToMockBufferMapWriteCallback, nullptr); uint32_t bufferContent = 31337; uint32_t zero = 0; EXPECT_CALL(api, OnBufferMapWriteAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapWriteCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, &bufferContent, kBufferSize); })); FlushClient(); EXPECT_CALL(*mockBufferMapWriteCallback, Call(WGPUBufferMapAsyncStatus_Success, Pointee(Eq(zero)), kBufferSize, _)) .WillOnce(InvokeWithoutArgs([&]() { wgpuBufferUnmap(buffer); })); FlushServer(); EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); FlushClient(); } // Test that the MapWriteCallback isn't fired twice the buffer external refcount reaches 0 in the // callback TEST_F(WireBufferMappingTests, DestroyInsideMapWriteCallback) { wgpuBufferMapWriteAsync(buffer, ToMockBufferMapWriteCallback, nullptr); uint32_t bufferContent = 31337; uint32_t zero = 0; EXPECT_CALL(api, OnBufferMapWriteAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapWriteCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, &bufferContent, kBufferSize); })); FlushClient(); EXPECT_CALL(*mockBufferMapWriteCallback, Call(WGPUBufferMapAsyncStatus_Success, Pointee(Eq(zero)), kBufferSize, _)) .WillOnce(InvokeWithoutArgs([&]() { wgpuBufferRelease(buffer); })); FlushServer(); EXPECT_CALL(api, BufferRelease(apiBuffer)); FlushClient(); } // Test successful CreateBufferMapped TEST_F(WireBufferMappingTests, CreateBufferMappedSuccess) { WGPUBufferDescriptor descriptor = {}; descriptor.size = 4; WGPUBuffer apiBuffer = api.GetNewBuffer(); WGPUCreateBufferMappedResult apiResult; uint32_t apiBufferData = 1234; apiResult.buffer = apiBuffer; apiResult.data = reinterpret_cast(&apiBufferData); apiResult.dataLength = 4; WGPUCreateBufferMappedResult result = wgpuDeviceCreateBufferMapped(device, &descriptor); EXPECT_CALL(api, DeviceCreateBufferMapped(apiDevice, _)) .WillOnce(Return(apiResult)) .RetiresOnSaturation(); FlushClient(); wgpuBufferUnmap(result.buffer); EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); FlushClient(); } // Test that releasing after CreateBufferMapped does not call Unmap TEST_F(WireBufferMappingTests, ReleaseAfterCreateBufferMapped) { WGPUBufferDescriptor descriptor = {}; descriptor.size = 4; WGPUBuffer apiBuffer = api.GetNewBuffer(); WGPUCreateBufferMappedResult apiResult; uint32_t apiBufferData = 1234; apiResult.buffer = apiBuffer; apiResult.data = reinterpret_cast(&apiBufferData); apiResult.dataLength = 4; WGPUCreateBufferMappedResult result = wgpuDeviceCreateBufferMapped(device, &descriptor); EXPECT_CALL(api, DeviceCreateBufferMapped(apiDevice, _)) .WillOnce(Return(apiResult)) .RetiresOnSaturation(); FlushClient(); wgpuBufferRelease(result.buffer); EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1); FlushClient(); } // Test that it is valid to map a buffer after CreateBufferMapped and Unmap TEST_F(WireBufferMappingTests, CreateBufferMappedThenMapSuccess) { WGPUBufferDescriptor descriptor = {}; descriptor.size = 4; WGPUBuffer apiBuffer = api.GetNewBuffer(); WGPUCreateBufferMappedResult apiResult; uint32_t apiBufferData = 9863; apiResult.buffer = apiBuffer; apiResult.data = reinterpret_cast(&apiBufferData); apiResult.dataLength = 4; WGPUCreateBufferMappedResult result = wgpuDeviceCreateBufferMapped(device, &descriptor); EXPECT_CALL(api, DeviceCreateBufferMapped(apiDevice, _)) .WillOnce(Return(apiResult)) .RetiresOnSaturation(); FlushClient(); wgpuBufferUnmap(result.buffer); EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); FlushClient(); wgpuBufferMapWriteAsync(result.buffer, ToMockBufferMapWriteCallback, nullptr); uint32_t zero = 0; EXPECT_CALL(api, OnBufferMapWriteAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapWriteCallback(apiBuffer, WGPUBufferMapAsyncStatus_Success, &apiBufferData, kBufferSize); })); FlushClient(); EXPECT_CALL(*mockBufferMapWriteCallback, Call(WGPUBufferMapAsyncStatus_Success, Pointee(Eq(zero)), kBufferSize, _)) .Times(1); FlushServer(); } // Test that it is invalid to map a buffer after CreateBufferMapped before Unmap TEST_F(WireBufferMappingTests, CreateBufferMappedThenMapFailure) { WGPUBufferDescriptor descriptor = {}; descriptor.size = 4; WGPUBuffer apiBuffer = api.GetNewBuffer(); WGPUCreateBufferMappedResult apiResult; uint32_t apiBufferData = 9863; apiResult.buffer = apiBuffer; apiResult.data = reinterpret_cast(&apiBufferData); apiResult.dataLength = 4; WGPUCreateBufferMappedResult result = wgpuDeviceCreateBufferMapped(device, &descriptor); EXPECT_CALL(api, DeviceCreateBufferMapped(apiDevice, _)) .WillOnce(Return(apiResult)) .RetiresOnSaturation(); FlushClient(); wgpuBufferMapWriteAsync(result.buffer, ToMockBufferMapWriteCallback, nullptr); EXPECT_CALL(api, OnBufferMapWriteAsyncCallback(apiBuffer, _, _)) .WillOnce(InvokeWithoutArgs([&]() { api.CallMapWriteCallback(apiBuffer, WGPUBufferMapAsyncStatus_Error, nullptr, 0); })); FlushClient(); EXPECT_CALL(*mockBufferMapWriteCallback, Call(WGPUBufferMapAsyncStatus_Error, nullptr, 0, _)) .Times(1); FlushServer(); wgpuBufferUnmap(result.buffer); EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1); FlushClient(); }