blob: 54f8392efe8717457bedf351cb071d619794b4af [file] [log] [blame] [edit]
// Copyright 2019 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <memory>
#include <tuple>
#include <utility>
#include "dawn/common/StringViewUtils.h"
#include "dawn/tests/MockCallback.h"
#include "dawn/tests/ParamGenerator.h"
#include "dawn/tests/StringViewMatchers.h"
#include "dawn/tests/unittests/wire/WireTest.h"
#include "dawn/wire/client/ClientMemoryTransferService_mock.h"
#include "dawn/wire/server/ServerMemoryTransferService_mock.h"
namespace wgpu {
// Define a stream operator for wgpu::MapMode so that it can be found on resolution for test name
// generation.
// TODO(dawn:2205) Remove this in favor of custom serializer.
static std::ostream& operator<<(std::ostream& os, const MapMode& param) {
switch (param) {
case wgpu::MapMode::Read:
os << "Read";
break;
case wgpu::MapMode::Write:
os << "Write";
break;
default:
DAWN_UNREACHABLE();
}
return os;
}
} // namespace wgpu
namespace dawn::wire {
namespace {
using testing::_;
using testing::InvokeWithoutArgs;
using testing::MockCppCallback;
using testing::Ne;
using testing::NotNull;
using testing::Return;
using testing::SizedString;
using testing::StrictMock;
using testing::WithArg;
using MapMode = wgpu::MapMode;
using MappedAtCreation = bool;
DAWN_TEST_PARAM_STRUCT_TYPES(MapModeParam, MapMode, MappedAtCreation);
MATCHER_P(AsUint32Eq, value, "") {
return *reinterpret_cast<const uint32_t*>(arg) == value;
}
using MockClientReadHandle = client::MockMemoryTransferService::MockReadHandle;
using MockClientWriteHandle = client::MockMemoryTransferService::MockWriteHandle;
using MockServerReadHandle = server::MockMemoryTransferService::MockReadHandle;
using MockServerWriteHandle = server::MockMemoryTransferService::MockWriteHandle;
using MockClientHandles = std::tuple<MockClientReadHandle*, MockClientWriteHandle*>;
using MockServerHandles = std::tuple<MockServerReadHandle*, MockServerWriteHandle*>;
// WireMemoryTransferServiceTests test the MemoryTransferService with buffer mapping.
// They test the basic success and error cases for buffer mapping, and they test
// mocked failures of each fallible MemoryTransferService method that an embedder
// could implement.
// The test harness defines multiple helpers for expecting operations on Read/Write handles
// and for mocking failures.
// There are tests which check for Success for every mapping operation which mock an entire
// mapping operation from map to unmap, and add all MemoryTransferService expectations. Tests
// which check for errors perform the same mapping operations but insert mocked failures for
// various mapping or MemoryTransferService operations.
class WireMemoryTransferServiceTestBase : public WireTest,
public testing::WithParamInterface<MapModeParam> {
protected:
client::MemoryTransferService* GetClientMemoryTransferService() override { return &mClientMTS; }
server::MemoryTransferService* GetServerMemoryTransferService() override { return &mServerMTS; }
wgpu::BufferUsage GetUsage() {
switch (GetParam().mMapMode) {
case wgpu::MapMode::Read:
return wgpu::BufferUsage::MapRead;
case wgpu::MapMode::Write:
return wgpu::BufferUsage::MapWrite;
default:
DAWN_UNREACHABLE();
}
}
std::pair<WGPUBuffer, wgpu::Buffer> CreateBuffer() {
wgpu::BufferUsage usage = GetUsage();
bool mappedAtCreation = GetParam().mMappedAtCreation;
wgpu::BufferDescriptor descriptor = {};
descriptor.size = kBufferSize;
descriptor.mappedAtCreation = mappedAtCreation;
descriptor.usage = usage;
WGPUBuffer apiBuffer = api.GetNewBuffer();
wgpu::Buffer buffer = device.CreateBuffer(&descriptor);
EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)).WillOnce(Return(apiBuffer));
return std::make_pair(apiBuffer, buffer);
}
std::tuple<WGPUBuffer, wgpu::Buffer, MockClientHandles, MockServerHandles> CreateValidBuffer() {
WGPUBuffer apiBuffer;
wgpu::Buffer buffer;
// The client should create and serialize the appropriate handles on buffer creation.
auto clientHandles = ExpectHandleCreation(true);
ExpectHandleSerialization(clientHandles);
std::tie(apiBuffer, buffer) = CreateBuffer();
// When the commands are flushed, the server should appropriately deserialize the handles.
auto serverHandles = ExpectHandleDeserialization(true);
if (GetParam().mMappedAtCreation) {
EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
.WillOnce(Return(&mServerBufferContent));
}
FlushClient();
return std::make_tuple(apiBuffer, buffer, clientHandles, serverHandles);
}
MockClientHandles ExpectHandleCreation(bool success) {
wgpu::MapMode mode = GetParam().mMapMode;
bool mappedAtCreation = GetParam().mMappedAtCreation;
switch (mode) {
case wgpu::MapMode::Read: {
auto* readHandle = success ? new StrictMock<MockClientReadHandle>() : nullptr;
EXPECT_CALL(mClientMTS, CreateReadHandle(kBufferSize)).WillOnce(Return(readHandle));
if (!success) {
return std::make_tuple(nullptr, nullptr);
}
if (mappedAtCreation) {
auto* writeHandle = new StrictMock<MockClientWriteHandle>();
EXPECT_CALL(mClientMTS, CreateWriteHandle(kBufferSize))
.WillOnce(Return(writeHandle));
EXPECT_CALL(*writeHandle, GetData).WillOnce(Return(&mClientBufferContent));
return std::make_tuple(readHandle, writeHandle);
}
return std::make_tuple(readHandle, nullptr);
}
case wgpu::MapMode::Write: {
auto* writeHandle = success ? new StrictMock<MockClientWriteHandle>() : nullptr;
EXPECT_CALL(mClientMTS, CreateWriteHandle(kBufferSize))
.WillOnce(Return(writeHandle));
if (!success) {
return std::make_tuple(nullptr, nullptr);
}
if (mappedAtCreation) {
EXPECT_CALL(*writeHandle, GetData).WillOnce(Return(&mClientBufferContent));
}
return std::make_tuple(nullptr, writeHandle);
}
default:
DAWN_UNREACHABLE();
}
}
void ExpectHandleSerialization(MockClientHandles& clientHandles) {
auto* readHandle = std::get<MockClientReadHandle*>(clientHandles);
if (readHandle) {
EXPECT_CALL(*readHandle, SerializeCreateSize).WillOnce(InvokeWithoutArgs([&] {
return sizeof(mSerializeCreateInfo);
}));
EXPECT_CALL(*readHandle, SerializeCreate(_))
.WillOnce(WithArg<0>([&](void* serializePointer) {
memcpy(serializePointer, &mSerializeCreateInfo, kDataSize);
return kDataSize;
}));
}
auto* writeHandle = std::get<MockClientWriteHandle*>(clientHandles);
if (writeHandle) {
EXPECT_CALL(*writeHandle, SerializeCreateSize).WillOnce(InvokeWithoutArgs([&] {
return sizeof(mSerializeCreateInfo);
}));
EXPECT_CALL(*writeHandle, SerializeCreate(_))
.WillOnce(WithArg<0>([&](void* serializePointer) {
memcpy(serializePointer, &mSerializeCreateInfo, kDataSize);
return kDataSize;
}));
}
}
MockServerHandles ExpectHandleDeserialization(bool success) {
wgpu::MapMode mode = GetParam().mMapMode;
bool mappedAtCreation = GetParam().mMappedAtCreation;
switch (mode) {
case wgpu::MapMode::Read: {
MockServerWriteHandle* writeHandle = nullptr;
if (mappedAtCreation) {
writeHandle = success ? new StrictMock<MockServerWriteHandle>() : nullptr;
EXPECT_CALL(mServerMTS, DeserializeWriteHandle(AsUint32Eq(mSerializeCreateInfo),
kDataSize, _))
.WillOnce(
WithArg<2>([=](server::MemoryTransferService::WriteHandle** handle) {
*handle = writeHandle;
return success;
}));
if (!success) {
return std::make_tuple(nullptr, nullptr);
}
}
auto* readHandle = success ? new StrictMock<MockServerReadHandle>() : nullptr;
EXPECT_CALL(mServerMTS,
DeserializeReadHandle(AsUint32Eq(mSerializeCreateInfo), kDataSize, _))
.WillOnce(WithArg<2>([=](server::MemoryTransferService::ReadHandle** handle) {
*handle = readHandle;
return success;
}));
return std::make_tuple(readHandle, writeHandle);
}
case wgpu::MapMode::Write: {
auto* writeHandle = success ? new StrictMock<MockServerWriteHandle>() : nullptr;
EXPECT_CALL(mServerMTS,
DeserializeWriteHandle(AsUint32Eq(mSerializeCreateInfo), kDataSize, _))
.WillOnce(WithArg<2>([=](server::MemoryTransferService::WriteHandle** handle) {
*handle = writeHandle;
return success;
}));
return std::make_tuple(nullptr, writeHandle);
}
default:
DAWN_UNREACHABLE();
}
}
void ExpectClientSerializeData(MockClientHandles& clientHandles) {
auto* clientHandle = std::get<MockClientWriteHandle*>(clientHandles);
if (!clientHandle) {
return;
}
EXPECT_CALL(*clientHandle, SizeOfSerializeDataUpdate(_, _)).WillOnce(Return(kDataSize));
EXPECT_CALL(*clientHandle, SerializeDataUpdate)
.WillOnce(WithArg<0>([&](void* serializePointer) {
memcpy(serializePointer, &mClientBufferContent, kBufferSize);
}));
}
void ExpectServerSerializeData(MockServerHandles& serverHandles) {
auto* serverHandle = std::get<MockServerReadHandle*>(serverHandles);
if (!serverHandle) {
return;
}
EXPECT_CALL(*serverHandle, SizeOfSerializeDataUpdate(_, _)).WillOnce(Return(kDataSize));
EXPECT_CALL(*serverHandle, SerializeDataUpdate)
.WillOnce(WithArg<3>([&](void* serializePointer) {
memcpy(serializePointer, &mServerBufferContent, kBufferSize);
return kBufferSize;
}));
}
void ExpectClientDeserializeData(bool success, MockClientHandles& clientHandles) {
auto* clientHandle = std::get<MockClientReadHandle*>(clientHandles);
if (!clientHandle) {
return;
}
EXPECT_CALL(*clientHandle, DeserializeDataUpdate(_, kBufferSize, 0, kBufferSize))
.WillOnce(WithArg<0>([&, success](const void* deserializePointer) {
if (success) {
// Copy the data manually here.
memcpy(&mClientBufferContent, deserializePointer, kBufferSize);
}
return success;
}));
}
void ExpectServerDeserializeData(bool success, MockServerHandles& serverHandles) {
auto* serverHandle = std::get<MockServerWriteHandle*>(serverHandles);
if (!serverHandle) {
return;
}
EXPECT_CALL(*serverHandle, DeserializeDataUpdate(_, kBufferSize, 0, kBufferSize))
.WillOnce(WithArg<0>([&, success](const void* deserializePointer) {
if (success) {
// Copy the data manually here.
memcpy(&mServerBufferContent, deserializePointer, kBufferSize);
}
return success;
}));
}
void ExpectClientHandleDestruction(MockClientHandles& clientHandles) {
auto* readHandle = std::get<MockClientReadHandle*>(clientHandles);
if (readHandle) {
EXPECT_CALL(*readHandle, Destroy).Times(1);
}
auto* writeHandle = std::get<MockClientWriteHandle*>(clientHandles);
if (writeHandle) {
EXPECT_CALL(*writeHandle, Destroy).Times(1);
}
}
void ExpectServerHandleDestruction(MockServerHandles& serverHandles) {
auto* readHandle = std::get<MockServerReadHandle*>(serverHandles);
if (readHandle) {
EXPECT_CALL(*readHandle, Destroy).Times(1);
}
auto* writeHandle = std::get<MockServerWriteHandle*>(serverHandles);
if (writeHandle) {
EXPECT_CALL(*writeHandle, Destroy).Times(1);
}
}
// Sets expectations for a successful map async call and verifies that the results match for the
// MapMode.
void ExpectSuccessfulMapAsync(WGPUBuffer apiBuffer,
wgpu::Buffer buffer,
MockClientHandles& clientHandles,
MockServerHandles& serverHandles) {
wgpu::MapMode mode = GetParam().mMapMode;
// Mode independent expectations.
EXPECT_CALL(api,
OnBufferMapAsync(apiBuffer, static_cast<WGPUMapMode>(mode), 0, kBufferSize, _))
.WillOnce(InvokeWithoutArgs([&] {
api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
kEmptyOutputStringView);
}));
EXPECT_CALL(mMapAsyncCb, Call(wgpu::MapAsyncStatus::Success, _)).Times(1);
switch (mode) {
case wgpu::MapMode::Read: {
auto* clientHandle = std::get<MockClientReadHandle*>(clientHandles);
ASSERT_THAT(clientHandle, NotNull());
EXPECT_CALL(*clientHandle, GetData).WillOnce(Return(&mClientBufferContent));
EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
.WillOnce(Return(&mServerBufferContent));
buffer.MapAsync(mode, 0, kBufferSize, wgpu::CallbackMode::AllowSpontaneous,
mMapAsyncCb.Callback());
// The server should serialize its buffer when the client flushes.
ExpectServerSerializeData(serverHandles);
FlushClient();
// The client should deserialize into its buffer when the server flushes.
ExpectClientDeserializeData(true, clientHandles);
FlushServer();
// The data between the server and the client should be the same now.
EXPECT_EQ(mServerBufferContent, mClientBufferContent);
break;
}
case wgpu::MapMode::Write: {
auto* clientHandle = std::get<MockClientWriteHandle*>(clientHandles);
ASSERT_THAT(clientHandle, NotNull());
EXPECT_CALL(*clientHandle, GetData).WillOnce(Return(&mClientBufferContent));
EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
.WillOnce(Return(&mClientBufferContent));
buffer.MapAsync(mode, 0, kBufferSize, wgpu::CallbackMode::AllowSpontaneous,
mMapAsyncCb.Callback());
FlushClient();
FlushServer();
break;
}
default:
DAWN_UNREACHABLE();
}
}
// Note that we pass in the mode explicitly here to allow reuse for mappedAtCreation.
void ExpectSuccessfulUnmap(wgpu::MapMode mode,
WGPUBuffer apiBuffer,
wgpu::Buffer buffer,
MockClientHandles& clientHandles,
MockServerHandles& serverHandles) {
switch (mode) {
case wgpu::MapMode::Read: {
// Unmap the buffer.
buffer.Unmap();
EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
FlushClient();
break;
}
case wgpu::MapMode::Write: {
// The client should serialize its buffer when Unmap is called.
ExpectClientSerializeData(clientHandles);
buffer.Unmap();
// The server should deserialize into its buffer when the client flushes.
EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
ExpectServerDeserializeData(true, serverHandles);
FlushClient();
// The data between the server and the client should be the same now.
EXPECT_EQ(mServerBufferContent, mClientBufferContent);
break;
}
default:
DAWN_UNREACHABLE();
}
}
void ExpectSuccessfulUnmapAtCreation(WGPUBuffer apiBuffer,
wgpu::Buffer buffer,
MockClientHandles& clientHandles,
MockServerHandles& serverHandles) {
ASSERT_TRUE(GetParam().mMappedAtCreation);
// Ensure that the contents on the client and server side are different now as a part of the
// test.
mClientBufferContent = kDataGenerator++;
mServerBufferContent = kDataGenerator++;
ASSERT_THAT(mClientBufferContent, Ne(mServerBufferContent));
// If the map mode of the buffer was actually MapRead, we should expect destruction of the
// WriteHandles, and setting the ReadHandles instead.
bool isRead = GetParam().mMapMode == wgpu::MapMode::Read;
if (isRead) {
auto* clientWriteHandle = std::get<MockClientWriteHandle*>(clientHandles);
ASSERT_THAT(clientWriteHandle, NotNull());
EXPECT_CALL(*clientWriteHandle, Destroy).Times(1);
auto* clientReadHandle = std::get<MockClientReadHandle*>(clientHandles);
ASSERT_THAT(clientReadHandle, NotNull());
EXPECT_CALL(*clientReadHandle, GetData).WillOnce(Return(&mClientBufferContent));
auto* serverHandle = std::get<MockServerWriteHandle*>(serverHandles);
ASSERT_THAT(serverHandle, NotNull());
EXPECT_CALL(*serverHandle, Destroy).Times(1);
}
ExpectSuccessfulUnmap(wgpu::MapMode::Write, apiBuffer, buffer, clientHandles,
serverHandles);
// Update the handles accordingly if we are MapRead.
if (isRead) {
clientHandles =
std::make_tuple(std::get<MockClientReadHandle*>(clientHandles), nullptr);
serverHandles =
std::make_tuple(std::get<MockServerReadHandle*>(serverHandles), nullptr);
}
}
// Static atomic that's used to generate different values across tests.
static std::atomic<uint32_t> kDataGenerator;
uint32_t mClientBufferContent = 0;
uint32_t mServerBufferContent = 0;
static constexpr size_t kBufferSize = sizeof(uint32_t);
uint32_t mSerializeCreateInfo = kDataGenerator++;
static constexpr size_t kDataSize = sizeof(uint32_t);
StrictMock<MockCppCallback<wgpu::BufferMapCallback<void>*>> mMapAsyncCb;
StrictMock<wire::server::MockMemoryTransferService> mServerMTS;
StrictMock<wire::client::MockMemoryTransferService> mClientMTS;
};
std::atomic<uint32_t> WireMemoryTransferServiceTestBase::kDataGenerator = 1337;
class WireMemoryTransferServiceBufferHandleTests : public WireMemoryTransferServiceTestBase {};
INSTANTIATE_TEST_SUITE_P(
,
WireMemoryTransferServiceBufferHandleTests,
testing::ValuesIn(ParamGenerator<WireMemoryTransferServiceBufferHandleTests::ParamType,
MapMode,
MappedAtCreation>({wgpu::MapMode::Read, wgpu::MapMode::Write},
{true, false})),
&TestParamToString<WireMemoryTransferServiceBufferHandleTests::ParamType>);
// Test handle(s) destroy behavior.
TEST_P(WireMemoryTransferServiceBufferHandleTests, Destroy) {
WGPUBuffer apiBuffer;
wgpu::Buffer buffer;
MockClientHandles clientHandles;
MockServerHandles serverHandles;
std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer();
// The client handles are destroyed on buffer.Destroy().
ExpectClientHandleDestruction(clientHandles);
buffer.Destroy();
// The server handles are destroyed when the destroy command is flushed.
ExpectServerHandleDestruction(serverHandles);
EXPECT_CALL(api, BufferDestroy(apiBuffer)).Times(1);
FlushClient();
// Releasing the buffer should be reflected on the server when flushed.
buffer = nullptr;
EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1);
FlushClient();
}
// Test handle(s) creation failure.
TEST_P(WireMemoryTransferServiceBufferHandleTests, CreationFailure) {
ExpectHandleCreation(false);
wgpu::BufferDescriptor descriptor = {};
descriptor.size = kBufferSize;
descriptor.usage = GetUsage();
WGPUBuffer apiErrorBuffer = api.GetNewBuffer();
wgpu::Buffer errorBuffer = device.CreateBuffer(&descriptor);
EXPECT_CALL(api, DeviceCreateErrorBuffer(apiDevice, _)).WillOnce(Return(apiErrorBuffer));
FlushClient();
// Releasing the buffer should be reflected on the server when flushed.
errorBuffer = nullptr;
EXPECT_CALL(api, BufferRelease(apiErrorBuffer)).Times(1);
FlushClient();
}
// Test handle(s) deserialization (only the handles across the wire, not the data) failure.
TEST_P(WireMemoryTransferServiceBufferHandleTests, DeserializationFailure) {
WGPUBuffer apiBuffer;
wgpu::Buffer buffer;
// The client should create and serialize the appropriate handles on buffer creation.
auto clientHandles = ExpectHandleCreation(true);
ExpectHandleSerialization(clientHandles);
std::tie(apiBuffer, buffer) = CreateBuffer();
// When the commands are flushed, mock that the server fails to deserialize the handle.
ExpectHandleDeserialization(false);
FlushClient(false);
// The client handles are destroyed when the buffer is released.
ExpectClientHandleDestruction(clientHandles);
buffer = nullptr;
// Releasing the buffer should be reflected on the server when flushed.
EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1);
FlushClient();
}
class WireMemoryTransferServiceBufferMapAsyncTests : public WireMemoryTransferServiceTestBase {};
INSTANTIATE_TEST_SUITE_P(
,
WireMemoryTransferServiceBufferMapAsyncTests,
testing::ValuesIn(ParamGenerator<WireMemoryTransferServiceBufferMapAsyncTests::ParamType,
MapMode,
MappedAtCreation>({wgpu::MapMode::Read, wgpu::MapMode::Write},
{true, false})),
&TestParamToString<WireMemoryTransferServiceBufferMapAsyncTests::ParamType>);
// Test successful mapping.
TEST_P(WireMemoryTransferServiceBufferMapAsyncTests, Success) {
WGPUBuffer apiBuffer;
wgpu::Buffer buffer;
MockClientHandles clientHandles;
MockServerHandles serverHandles;
std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer();
// If we were mappedAtCreation, successfully handle that initial unmapping now.
if (GetParam().mMappedAtCreation) {
ExpectSuccessfulUnmapAtCreation(apiBuffer, buffer, clientHandles, serverHandles);
}
// Ensure that the contents on the client and server side are different now as a part of the
// test.
mClientBufferContent = kDataGenerator++;
mServerBufferContent = kDataGenerator++;
ASSERT_THAT(mClientBufferContent, Ne(mServerBufferContent));
ExpectSuccessfulMapAsync(apiBuffer, buffer, clientHandles, serverHandles);
ExpectSuccessfulUnmap(GetParam().mMapMode, apiBuffer, buffer, clientHandles, serverHandles);
// The client handles are destroyed when the buffer is released.
ExpectClientHandleDestruction(clientHandles);
buffer = nullptr;
// The server handles are destroyed when the release command is flushed.
ExpectServerHandleDestruction(serverHandles);
EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1);
FlushClient();
}
// Test unsuccessful mapping with error.
TEST_P(WireMemoryTransferServiceBufferMapAsyncTests, Error) {
wgpu::MapMode mode = GetParam().mMapMode;
WGPUBuffer apiBuffer;
wgpu::Buffer buffer;
MockClientHandles clientHandles;
MockServerHandles serverHandles;
std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer();
// If we were mappedAtCreation, successfully handle that initial unmapping now.
if (GetParam().mMappedAtCreation) {
ExpectSuccessfulUnmapAtCreation(apiBuffer, buffer, clientHandles, serverHandles);
}
buffer.MapAsync(mode, 0, kBufferSize, wgpu::CallbackMode::AllowSpontaneous,
mMapAsyncCb.Callback());
// Make the server respond to the callback with an error.
EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, static_cast<WGPUMapMode>(mode), 0, kBufferSize, _))
.WillOnce(InvokeWithoutArgs([&] {
api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Error,
ToOutputStringView("Validation error"));
}));
FlushClient();
// The callback should happen when the server flushes the response.
EXPECT_CALL(mMapAsyncCb, Call(wgpu::MapAsyncStatus::Error, SizedString("Validation error")))
.Times(1);
FlushServer();
// Unmap the buffer.
buffer.Unmap();
EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
FlushClient();
// The client handles are destroyed when the buffer is released.
ExpectClientHandleDestruction(clientHandles);
buffer = nullptr;
// The server handles are destroyed when the release command is flushed.
ExpectServerHandleDestruction(serverHandles);
EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1);
FlushClient();
}
// Test DeserializeDataUpdate (actual data) failure w.r.t MapAsync.
TEST_P(WireMemoryTransferServiceBufferMapAsyncTests, DeserializeDataUpdateFailure) {
wgpu::MapMode mode = GetParam().mMapMode;
WGPUBuffer apiBuffer;
wgpu::Buffer buffer;
MockClientHandles clientHandles;
MockServerHandles serverHandles;
std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer();
// If we were mappedAtCreation, successfully handle that initial unmapping now.
if (GetParam().mMappedAtCreation) {
ExpectSuccessfulUnmapAtCreation(apiBuffer, buffer, clientHandles, serverHandles);
}
// Set mode independent expectations for the map async call now.
EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, static_cast<WGPUMapMode>(mode), 0, kBufferSize, _))
.WillOnce(InvokeWithoutArgs([&] {
api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
kEmptyOutputStringView);
}));
switch (mode) {
case wgpu::MapMode::Read: {
EXPECT_CALL(mMapAsyncCb, Call(Ne(wgpu::MapAsyncStatus::Success), _)).Times(1);
EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
.WillOnce(Return(&mServerBufferContent));
buffer.MapAsync(mode, 0, kBufferSize, wgpu::CallbackMode::AllowSpontaneous,
mMapAsyncCb.Callback());
// The server should serialize its buffer when the client flushes.
ExpectServerSerializeData(serverHandles);
FlushClient();
// Mock that the client fails to deserialize into its buffer when the server flushes.
ExpectClientDeserializeData(false, clientHandles);
FlushServer(false);
break;
}
case wgpu::MapMode::Write: {
EXPECT_CALL(mMapAsyncCb, Call(wgpu::MapAsyncStatus::Success, _)).Times(1);
EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
.WillOnce(Return(&mClientBufferContent));
auto* clientHandle = std::get<MockClientWriteHandle*>(clientHandles);
ASSERT_THAT(clientHandle, NotNull());
EXPECT_CALL(*clientHandle, GetData).WillOnce(Return(&mClientBufferContent));
buffer.MapAsync(mode, 0, kBufferSize, wgpu::CallbackMode::AllowSpontaneous,
mMapAsyncCb.Callback());
FlushClient();
FlushServer();
// The client should serialize its buffer when Unmap is called.
ExpectClientSerializeData(clientHandles);
buffer.Unmap();
// Mock that the server fails to deserialize into its buffer when the client flushes.
ExpectServerDeserializeData(false, serverHandles);
FlushClient(false);
break;
}
default:
DAWN_UNREACHABLE();
}
// The client handles are destroyed when the buffer is released.
ExpectClientHandleDestruction(clientHandles);
buffer = nullptr;
// The server handles are destroyed when the release command is flushed.
ExpectServerHandleDestruction(serverHandles);
EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1);
FlushClient();
}
// Test mapping and then destroying the buffer before unmapping on the client side.
TEST_P(WireMemoryTransferServiceBufferMapAsyncTests, DestroyBeforeUnmap) {
WGPUBuffer apiBuffer;
wgpu::Buffer buffer;
MockClientHandles clientHandles;
MockServerHandles serverHandles;
std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer();
// If we were mappedAtCreation, successfully handle that initial unmapping now.
if (GetParam().mMappedAtCreation) {
ExpectSuccessfulUnmapAtCreation(apiBuffer, buffer, clientHandles, serverHandles);
}
ExpectSuccessfulMapAsync(apiBuffer, buffer, clientHandles, serverHandles);
// THIS IS THE TEST: destroy the buffer before unmapping and check it destroyed the mapping
// immediately, both in the client and server side.
{
// Destroying the buffer should immediately destroy the client handles.
ExpectClientHandleDestruction(clientHandles);
buffer.Destroy();
// Flushing the client should destroy the server handles.
ExpectServerHandleDestruction(serverHandles);
EXPECT_CALL(api, BufferDestroy(apiBuffer)).Times(1);
FlushClient();
// The handle(s) are already destroyed so unmap only results in a server unmap call.
buffer.Unmap();
EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
FlushClient();
}
// The handle(s) are already destroyed so release only results in a server release call.
buffer = nullptr;
EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1);
FlushClient();
}
class WireMemoryTransferServiceBufferMappedAtCreationTests
: public WireMemoryTransferServiceTestBase {};
INSTANTIATE_TEST_SUITE_P(
,
WireMemoryTransferServiceBufferMappedAtCreationTests,
testing::ValuesIn(
ParamGenerator<WireMemoryTransferServiceBufferMappedAtCreationTests::ParamType,
MapMode,
MappedAtCreation>({wgpu::MapMode::Read, wgpu::MapMode::Write}, {true})),
&TestParamToString<WireMemoryTransferServiceBufferMappedAtCreationTests::ParamType>);
// Test DeserializeDataUpdate (actual data) failure w.r.t for the initial Unmap.
TEST_P(WireMemoryTransferServiceBufferMappedAtCreationTests, DeserializeDataUpdateFailure) {
WGPUBuffer apiBuffer;
wgpu::Buffer buffer;
MockClientHandles clientHandles;
MockServerHandles serverHandles;
std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer();
// The client should serialize its buffer when Unmap is called. Additionally, if we are in read
// mode, the client side WriteHandle used for mappedAtCreation should be destroyed now and it
// should reset to use the ReadHandle.
ExpectClientSerializeData(clientHandles);
if (GetParam().mMapMode == wgpu::MapMode::Read) {
auto* clientWriteHandle = std::get<MockClientWriteHandle*>(clientHandles);
ASSERT_THAT(clientWriteHandle, NotNull());
EXPECT_CALL(*clientWriteHandle, Destroy).Times(1);
auto* clientReadHandle = std::get<MockClientReadHandle*>(clientHandles);
ASSERT_THAT(clientReadHandle, NotNull());
EXPECT_CALL(*clientReadHandle, GetData).WillOnce(Return(&mClientBufferContent));
clientHandles = std::make_tuple(std::get<MockClientReadHandle*>(clientHandles), nullptr);
}
buffer.Unmap();
// Mock that the server fails to deserialize into its buffer when the client flushes.
ExpectServerDeserializeData(false, serverHandles);
FlushClient(false);
// The client handles are destroyed when the buffer is released.
ExpectClientHandleDestruction(clientHandles);
buffer = nullptr;
// The server handles are destroyed when the release command is flushed.
ExpectServerHandleDestruction(serverHandles);
EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1);
FlushClient();
}
// Test mapping and then destroying the buffer before unmapping on the client side.
TEST_P(WireMemoryTransferServiceBufferMappedAtCreationTests, DestroyBeforeUnmap) {
WGPUBuffer apiBuffer;
wgpu::Buffer buffer;
MockClientHandles clientHandles;
MockServerHandles serverHandles;
std::tie(apiBuffer, buffer, clientHandles, serverHandles) = CreateValidBuffer();
// THIS IS THE TEST: destroy the buffer before unmapping and check it destroyed the mapping
// immediately, both in the client and server side.
{
// Destroying the buffer should immediately destroy the client handles.
ExpectClientHandleDestruction(clientHandles);
buffer.Destroy();
// Flushing the client should destroy the server handles.
ExpectServerHandleDestruction(serverHandles);
EXPECT_CALL(api, BufferDestroy(apiBuffer)).Times(1);
FlushClient();
// The handle(s) are already destroyed so unmap only results in a server unmap call.
buffer.Unmap();
EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
FlushClient();
}
// The handle(s) are already destroyed so release only results in a server release call.
buffer = nullptr;
EXPECT_CALL(api, BufferRelease(apiBuffer)).Times(1);
FlushClient();
}
} // anonymous namespace
} // namespace dawn::wire