// 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 "src/dawn/common/StringViewUtils.h"
#include "src/dawn/tests/MockCallback.h"
#include "src/dawn/tests/ParamGenerator.h"
#include "src/dawn/tests/StringViewMatchers.h"
#include "src/dawn/tests/unittests/wire/WireTest.h"
#include "src/dawn/wire/client/ClientMemoryTransferService_mock.h"
#include "src/dawn/wire/server/ServerMemoryTransferService_mock.h"
#include "src/utils/compiler.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);
        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) {
                    DAWN_UNSAFE_TODO(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) {
                    DAWN_UNSAFE_TODO(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>([&](std::span<char> serializeSpan) {
                DAWN_UNSAFE_TODO(memcpy(serializeSpan.data(), &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<2>([&](std::span<char> serializeSpan) {
                DAWN_UNSAFE_TODO(memcpy(serializeSpan.data(), &mServerBufferContent, kBufferSize));
            }));
    }

    void ExpectClientDeserializeData(bool success, MockClientHandles& clientHandles) {
        auto* clientHandle = std::get<MockClientReadHandle*>(clientHandles);
        if (!clientHandle) {
            return;
        }

        EXPECT_CALL(*clientHandle,
                    DeserializeDataUpdate(testing::Matcher<std::span<const uint8_t>>(
                                              testing::Truly([](std::span<const uint8_t> arg) {
                                                  return arg.size() == kBufferSize;
                                              })),
                                          static_cast<size_t>(0u)))
            .WillOnce(WithArg<0>([&, success](std::span<const uint8_t> deserializeSpan) {
                if (success) {
                    DAWN_UNSAFE_TODO(
                        memcpy(&mClientBufferContent, deserializeSpan.data(), kBufferSize));
                }
                return success;
            }));
    }
    void ExpectServerDeserializeData(bool success, MockServerHandles& serverHandles) {
        auto* serverHandle = std::get<MockServerWriteHandle*>(serverHandles);
        if (!serverHandle) {
            return;
        }

        std::span<uint8_t> target(reinterpret_cast<uint8_t*>(&mServerBufferContent),
                                  sizeof(mServerBufferContent));
        EXPECT_CALL(
            *serverHandle,
            DeserializeDataUpdate(
                testing::Matcher<std::span<const uint8_t>>(testing::Truly(
                    [](std::span<const uint8_t> arg) { return arg.size() == kBufferSize; })),
                testing::Matcher<std::span<uint8_t>>(
                    testing::Truly([target](std::span<uint8_t> arg) {
                        return arg.data() == target.data() && arg.size() == target.size();
                    })),
                static_cast<size_t>(0u)))
            .WillOnce(WithArg<0>([&, success](std::span<const uint8_t> deserializePointer) {
                if (success) {
                    // Copy the data manually here.
                    DAWN_UNSAFE_TODO(memcpy(&mServerBufferContent, deserializePointer.data(),
                                            deserializePointer.size()));
                }
                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));

                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);
                EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
                    .WillOnce(Return(&mServerBufferContent));
                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);

            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.
            EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
                .WillOnce(Return(&mServerBufferContent));
            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.
    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
        .WillOnce(Return(&mServerBufferContent));
    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
