blob: 84d66b0b50157d005424db07d72e681fcdabccf6 [file] [log] [blame] [edit]
// Copyright 2021 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 <unordered_set>
#include <utility>
#include <vector>
#include "dawn/common/StringViewUtils.h"
#include "dawn/tests/MockCallback.h"
#include "dawn/tests/StringViewMatchers.h"
#include "dawn/tests/unittests/wire/WireFutureTest.h"
#include "dawn/tests/unittests/wire/WireTest.h"
#include "dawn/wire/WireClient.h"
#include "dawn/wire/WireServer.h"
#include "webgpu/webgpu_cpp.h"
namespace dawn::wire {
namespace {
using testing::_;
using testing::EmptySizedString;
using testing::InvokeWithoutArgs;
using testing::IsNull;
using testing::NonEmptySizedString;
using testing::NotNull;
using testing::Return;
using testing::SizedString;
using testing::WithArg;
using WireAdapterTestBase = WireFutureTest<wgpu::RequestDeviceCallback<void>*>;
class WireAdapterTests : public WireAdapterTestBase {
protected:
void RequestDevice(const wgpu::DeviceDescriptor* descriptor) {
this->mFutureIDs.push_back(
adapter
.RequestDevice(descriptor, this->GetParam().callbackMode, this->mMockCb.Callback())
.id);
}
};
DAWN_INSTANTIATE_WIRE_FUTURE_TEST_P(WireAdapterTests);
// Test that an empty DeviceDescriptor is passed from the client to the server.
TEST_P(WireAdapterTests, RequestDeviceEmptyDescriptor) {
wgpu::DeviceDescriptor desc = {};
RequestDevice(&desc);
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), _))
.WillOnce(WithArg<1>([&](const WGPUDeviceDescriptor* apiDesc) {
EXPECT_EQ(apiDesc->label.data, nullptr);
EXPECT_EQ(apiDesc->requiredFeatureCount, 0u);
EXPECT_EQ(apiDesc->requiredLimits, nullptr);
// Call the callback so the test doesn't wait indefinitely.
api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Error, nullptr,
kEmptyOutputStringView);
}));
FlushClient();
FlushFutures();
ExpectWireCallbacksWhen([&](auto& mockCb) {
EXPECT_CALL(mockCb, Call).Times(1);
FlushCallbacks();
});
}
// Test that a null DeviceDescriptor is passed from the client to the server as an empty one.
TEST_P(WireAdapterTests, RequestDeviceNullDescriptor) {
RequestDevice(nullptr);
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), _))
.WillOnce(WithArg<1>([&](const WGPUDeviceDescriptor* apiDesc) {
EXPECT_EQ(apiDesc->label.data, nullptr);
EXPECT_EQ(apiDesc->requiredFeatureCount, 0u);
EXPECT_EQ(apiDesc->requiredLimits, nullptr);
// Call the callback so the test doesn't wait indefinitely.
api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Error, nullptr,
kEmptyOutputStringView);
}));
FlushClient();
FlushFutures();
ExpectWireCallbacksWhen([&](auto& mockCb) {
EXPECT_CALL(mockCb, Call).Times(1);
FlushCallbacks();
});
}
// Test that the DeviceDescriptor is not allowed to pass callbacks from the client to
// the server.
TEST_P(WireAdapterTests, RequestDeviceCallbackPointers) {
wgpu::DeviceDescriptor desc = {};
desc.SetDeviceLostCallback(
wgpu::CallbackMode::AllowSpontaneous,
[](const wgpu::Device&, wgpu::DeviceLostReason, wgpu::StringView) {});
desc.SetUncapturedErrorCallback([](const wgpu::Device&, wgpu::ErrorType, wgpu::StringView) {});
RequestDevice(&desc);
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), _))
.WillOnce(WithArg<1>([&](const WGPUDeviceDescriptor* apiDesc) {
EXPECT_STREQ(apiDesc->label.data, desc.label.data);
// The callback should not be passed through to the server, and it should be overridden.
WGPUDeviceDescriptor& inputDesc = *reinterpret_cast<WGPUDeviceDescriptor*>(&desc);
ASSERT_NE(apiDesc->deviceLostCallbackInfo.callback,
inputDesc.deviceLostCallbackInfo.callback);
ASSERT_NE(apiDesc->deviceLostCallbackInfo.callback, nullptr);
ASSERT_NE(apiDesc->uncapturedErrorCallbackInfo.callback,
inputDesc.uncapturedErrorCallbackInfo.callback);
ASSERT_NE(apiDesc->uncapturedErrorCallbackInfo.callback, nullptr);
// Call the callback so the test doesn't wait indefinitely.
api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Error, nullptr,
kEmptyOutputStringView);
}));
FlushClient();
FlushFutures();
ExpectWireCallbacksWhen([&](auto& mockCb) {
EXPECT_CALL(mockCb, Call).Times(1);
FlushCallbacks();
});
}
// Test that RequestDevice forwards the device information to the client.
TEST_P(WireAdapterTests, RequestDeviceSuccess) {
wgpu::Limits fakeLimits = {};
fakeLimits.maxTextureDimension1D = 433;
fakeLimits.maxVertexAttributes = 1243;
std::initializer_list<WGPUFeatureName> fakeFeaturesList = {
WGPUFeatureName_Depth32FloatStencil8,
WGPUFeatureName_TextureCompressionBC,
};
WGPUSupportedFeatures fakeFeatures = {fakeFeaturesList.size(), std::data(fakeFeaturesList)};
wgpu::DeviceDescriptor desc = {};
RequestDevice(&desc);
// Expect the server to receive the message. Then, mock a fake reply.
WGPUDevice apiDevice = api.GetNewDevice();
// The backend device should not be known by the wire server.
EXPECT_FALSE(GetWireServer()->IsDeviceKnown(apiDevice));
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), _))
.WillOnce(InvokeWithoutArgs([&] {
// Set on device creation to forward callbacks to the client.
EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, _)).Times(1);
EXPECT_CALL(api, DeviceGetLimits(apiDevice, NotNull()))
.WillOnce(WithArg<1>([&](WGPULimits* limits) {
*reinterpret_cast<wgpu::Limits*>(limits) = fakeLimits;
return WGPUStatus_Success;
}));
EXPECT_CALL(api, DeviceGetFeatures(apiDevice, NotNull()))
.WillOnce(
WithArg<1>([&](WGPUSupportedFeatures* features) { *features = fakeFeatures; }));
// The backend device should still not be known by the wire server since the
// callback has not been called yet.
EXPECT_FALSE(GetWireServer()->IsDeviceKnown(apiDevice));
api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Success,
apiDevice, kEmptyOutputStringView);
// After the callback is called, the backend device is now known by the server.
EXPECT_TRUE(GetWireServer()->IsDeviceKnown(apiDevice));
}));
FlushClient();
FlushFutures();
wgpu::Device device;
// Expect the callback in the client and all the device information to match.
ExpectWireCallbacksWhen([&](auto& mockCb) {
EXPECT_CALL(mockCb, Call(wgpu::RequestDeviceStatus::Success, NotNull(), EmptySizedString()))
.WillOnce(WithArg<1>([&](wgpu::Device result) {
device = std::move(result);
wgpu::AdapterInfo adapterInfo;
EXPECT_EQ(device.GetAdapterInfo(&adapterInfo), wgpu::Status::Success);
EXPECT_EQ(adapterInfo.vendor, kEmptyOutputStringView);
EXPECT_EQ(adapterInfo.architecture, kEmptyOutputStringView);
EXPECT_EQ(adapterInfo.device, kEmptyOutputStringView);
EXPECT_EQ(adapterInfo.description, kEmptyOutputStringView);
wgpu::Limits limits;
EXPECT_EQ(device.GetLimits(&limits), wgpu::Status::Success);
EXPECT_EQ(limits.maxTextureDimension1D, fakeLimits.maxTextureDimension1D);
EXPECT_EQ(limits.maxVertexAttributes, fakeLimits.maxVertexAttributes);
WGPUSupportedFeatures features = {};
device.GetFeatures(reinterpret_cast<wgpu::SupportedFeatures*>(&features));
std::vector<WGPUFeatureName> featuresList(
features.features, features.features + features.featureCount);
ASSERT_EQ(featuresList.size(), fakeFeaturesList.size());
std::unordered_set<WGPUFeatureName> featureSet(fakeFeaturesList);
for (WGPUFeatureName feature : featuresList) {
EXPECT_EQ(featureSet.erase(feature), 1u);
}
}));
FlushCallbacks();
});
EXPECT_EQ(device.GetAdapter().Get(), adapter.Get());
device = nullptr;
// Cleared when the device is destroyed.
EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, _)).Times(1);
EXPECT_CALL(api, DeviceRelease(apiDevice));
// Server has not recevied the release yet, so the device should be known.
EXPECT_TRUE(GetWireServer()->IsDeviceKnown(apiDevice));
FlushClient();
// After receiving the release call, the device is no longer known by the server.
EXPECT_FALSE(GetWireServer()->IsDeviceKnown(apiDevice));
}
// Test that features requested that the implementation supports, but not the
// wire reject the callback.
TEST_P(WireAdapterTests, RequestFeatureUnsupportedByWire) {
std::initializer_list<WGPUFeatureName> fakeFeaturesList = {
// Default feature is an undefined feature.
{},
WGPUFeatureName_TextureCompressionASTC,
};
WGPUSupportedFeatures fakeFeatures = {fakeFeaturesList.size(), std::data(fakeFeaturesList)};
wgpu::DeviceDescriptor desc = {};
RequestDevice(&desc);
// Expect the server to receive the message. Then, mock a fake reply.
// The reply contains features that the device implementation supports, but the
// wire does not.
WGPUDevice apiDevice = api.GetNewDevice();
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), _))
.WillOnce(InvokeWithoutArgs([&] {
EXPECT_CALL(api, DeviceGetFeatures(apiDevice, NotNull()))
.WillOnce(
WithArg<1>([&](WGPUSupportedFeatures* features) { *features = fakeFeatures; }));
// The device was actually created, but the wire didn't support its features.
// Expect it to be released.
EXPECT_CALL(api, DeviceRelease(apiDevice));
// Fake successful creation. The client still receives a failure due to
// unsupported features.
api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Success,
apiDevice, kEmptyOutputStringView);
}));
FlushClient();
FlushFutures();
// Expect an error callback since the feature is not supported.
ExpectWireCallbacksWhen([&](auto& mockCb) {
EXPECT_CALL(mockCb, Call(wgpu::RequestDeviceStatus::Error, IsNull(), NonEmptySizedString()))
.Times(1);
FlushCallbacks();
});
}
// Test that RequestDevice errors forward to the client.
TEST_P(WireAdapterTests, RequestDeviceError) {
wgpu::DeviceDescriptor desc = {};
RequestDevice(&desc);
// Expect the server to receive the message. Then, mock an error.
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), _))
.WillOnce(InvokeWithoutArgs([&] {
api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Error, nullptr,
ToOutputStringView("Request device failed"));
}));
FlushClient();
FlushFutures();
// Expect the callback in the client.
ExpectWireCallbacksWhen([&](auto& mockCb) {
EXPECT_CALL(mockCb, Call(wgpu::RequestDeviceStatus::Error, IsNull(),
SizedString("Request device failed")))
.Times(1);
FlushCallbacks();
});
}
// Test that RequestDevice can complete successfully even if the adapter is deleted
// before the callback happens.
TEST_P(WireAdapterTests, RequestDeviceAdapterDestroyedBeforeCallback) {
wgpu::DeviceDescriptor desc = {};
RequestDevice(&desc);
adapter = nullptr;
// Mock a reply from the server.
WGPUDevice apiDevice = api.GetNewDevice();
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), _))
.WillOnce(InvokeWithoutArgs([&] {
// Set on device creation to forward callbacks to the client.
EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, _)).Times(1);
EXPECT_CALL(api, DeviceGetLimits(apiDevice, NotNull())).Times(1);
EXPECT_CALL(api, DeviceGetFeatures(apiDevice, NotNull())).Times(1);
api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Success,
apiDevice, kEmptyOutputStringView);
}));
FlushClient();
FlushFutures();
wgpu::Device device;
// Expect the callback in the client.
ExpectWireCallbacksWhen([&](auto& mockCb) {
EXPECT_CALL(mockCb, Call(wgpu::RequestDeviceStatus::Success, NotNull(), EmptySizedString()))
.WillOnce(WithArg<1>([&](wgpu::Device result) { device = std::move(result); }));
FlushCallbacks();
});
device = nullptr;
// Cleared when the device is destroyed.
EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, _)).Times(1);
EXPECT_CALL(api, DeviceRelease(apiDevice));
FlushClient();
}
// Test that RequestDevice receives unknown status if the wire is disconnected
// before the callback happens.
TEST_P(WireAdapterTests, RequestDeviceWireDisconnectedBeforeCallback) {
wgpu::DeviceDescriptor desc = {};
RequestDevice(&desc);
ExpectWireCallbacksWhen([&](auto& mockCb) {
EXPECT_CALL(mockCb, Call(wgpu::RequestDeviceStatus::CallbackCancelled, IsNull(),
NonEmptySizedString()))
.Times(1);
GetWireClient()->Disconnect();
});
}
// Test that a device's wire handle (id and generation) matches between the client and server.
TEST_P(WireAdapterTests, RequestDeviceWireHandle) {
RequestDevice(nullptr);
// Expect the server to receive the message. Then, mock a fake reply.
WGPUDevice apiDevice = api.GetNewDevice();
EXPECT_CALL(api, OnAdapterRequestDevice(apiAdapter, NotNull(), _))
.WillOnce(InvokeWithoutArgs([&] {
// Set on device creation to forward callbacks to the client.
EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, _)).Times(1);
EXPECT_CALL(api, DeviceGetLimits(apiDevice, NotNull())).Times(1);
EXPECT_CALL(api, DeviceGetFeatures(apiDevice, NotNull())).Times(1);
api.CallAdapterRequestDeviceCallback(apiAdapter, WGPURequestDeviceStatus_Success,
apiDevice, kEmptyOutputStringView);
}));
FlushClient();
FlushFutures();
wgpu::Device device;
// Expect the callback in the client and the wire handles match.
ExpectWireCallbacksWhen([&](auto& mockCb) {
EXPECT_CALL(mockCb, Call(wgpu::RequestDeviceStatus::Success, NotNull(), EmptySizedString()))
.WillOnce(WithArg<1>([&](wgpu::Device result) {
device = std::move(result);
Handle deviceWireHandle = GetWireClient()->GetWireHandle(device.Get());
WGPUDevice apiDeviceExpected =
GetWireServer()->GetDevice(deviceWireHandle.id, deviceWireHandle.generation);
EXPECT_EQ(apiDevice, apiDeviceExpected);
}));
FlushCallbacks();
});
device = nullptr;
// Cleared when the device is destroyed.
EXPECT_CALL(api, OnDeviceSetLoggingCallback(apiDevice, _)).Times(1);
EXPECT_CALL(api, DeviceRelease(apiDevice));
FlushClient();
}
} // anonymous namespace
} // namespace dawn::wire