// Copyright 2017 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. #ifndef SRC_DAWN_TESTS_DAWNTEST_H_ #define SRC_DAWN_TESTS_DAWNTEST_H_ #include #include #include #include #include #include #include "dawn/common/Log.h" #include "dawn/common/Platform.h" #include "dawn/common/Preprocessor.h" #include "dawn/dawn_proc_table.h" #include "dawn/native/DawnNative.h" #include "dawn/platform/DawnPlatform.h" #include "dawn/tests/AdapterTestConfig.h" #include "dawn/tests/MockCallback.h" #include "dawn/tests/ParamGenerator.h" #include "dawn/tests/ToggleParser.h" #include "dawn/utils/ScopedAutoreleasePool.h" #include "dawn/utils/TestUtils.h" #include "dawn/utils/TextureUtils.h" #include "dawn/webgpu_cpp.h" #include "dawn/webgpu_cpp_print.h" #include "gmock/gmock.h" #include "gtest/gtest.h" // Getting data back from Dawn is done in an async manners so all expectations are "deferred" // until the end of the test. Also expectations use a copy to a MapRead buffer to get the data // so resources should have the CopySrc allowed usage bit if you want to add expectations on // them. // AddBufferExpectation is defined in DawnTestBase as protected function. This ensures the macro can // only be used in derivd class of DawnTestBase. Use "this" pointer to ensure the macro works with // CRTP. #define EXPECT_BUFFER(buffer, offset, size, expectation) \ this->AddBufferExpectation(__FILE__, __LINE__, buffer, offset, size, expectation) #define EXPECT_BUFFER_U8_EQ(expected, buffer, offset) \ EXPECT_BUFFER(buffer, offset, sizeof(uint8_t), new ::detail::ExpectEq(expected)) #define EXPECT_BUFFER_U8_RANGE_EQ(expected, buffer, offset, count) \ EXPECT_BUFFER(buffer, offset, sizeof(uint8_t) * (count), \ new ::detail::ExpectEq(expected, count)) #define EXPECT_BUFFER_U16_EQ(expected, buffer, offset) \ EXPECT_BUFFER(buffer, offset, sizeof(uint16_t), new ::detail::ExpectEq(expected)) #define EXPECT_BUFFER_U16_RANGE_EQ(expected, buffer, offset, count) \ EXPECT_BUFFER(buffer, offset, sizeof(uint16_t) * (count), \ new ::detail::ExpectEq(expected, count)) #define EXPECT_BUFFER_U32_EQ(expected, buffer, offset) \ EXPECT_BUFFER(buffer, offset, sizeof(uint32_t), new ::detail::ExpectEq(expected)) #define EXPECT_BUFFER_U32_RANGE_EQ(expected, buffer, offset, count) \ EXPECT_BUFFER(buffer, offset, sizeof(uint32_t) * (count), \ new ::detail::ExpectEq(expected, count)) #define EXPECT_BUFFER_U64_EQ(expected, buffer, offset) \ EXPECT_BUFFER(buffer, offset, sizeof(uint64_t), new ::detail::ExpectEq(expected)) #define EXPECT_BUFFER_U64_RANGE_EQ(expected, buffer, offset, count) \ EXPECT_BUFFER(buffer, offset, sizeof(uint64_t) * (count), \ new ::detail::ExpectEq(expected, count)) #define EXPECT_BUFFER_FLOAT_EQ(expected, buffer, offset) \ EXPECT_BUFFER(buffer, offset, sizeof(float), new ::detail::ExpectEq(expected)) #define EXPECT_BUFFER_FLOAT_RANGE_EQ(expected, buffer, offset, count) \ EXPECT_BUFFER(buffer, offset, sizeof(float) * (count), \ new ::detail::ExpectEq(expected, count)) // Test a pixel of the mip level 0 of a 2D texture. #define EXPECT_PIXEL_RGBA8_EQ(expected, texture, x, y) \ AddTextureExpectation(__FILE__, __LINE__, expected, texture, {x, y}) #define EXPECT_PIXEL_FLOAT_EQ(expected, texture, x, y) \ AddTextureExpectation(__FILE__, __LINE__, expected, texture, {x, y}) #define EXPECT_PIXEL_FLOAT16_EQ(expected, texture, x, y) \ AddTextureExpectation(__FILE__, __LINE__, expected, texture, {x, y}) #define EXPECT_PIXEL_RGBA8_BETWEEN(color0, color1, texture, x, y) \ AddTextureBetweenColorsExpectation(__FILE__, __LINE__, color0, color1, texture, x, y) #define EXPECT_TEXTURE_EQ(...) AddTextureExpectation(__FILE__, __LINE__, __VA_ARGS__) #define EXPECT_TEXTURE_FLOAT16_EQ(...) \ AddTextureExpectation(__FILE__, __LINE__, __VA_ARGS__) #define ASSERT_DEVICE_ERROR_MSG_ON(device, statement, matcher) \ FlushWire(); \ EXPECT_CALL(mDeviceErrorCallback, \ Call(testing::Ne(WGPUErrorType_NoError), matcher, device.Get())); \ statement; \ device.Tick(); \ FlushWire(); \ testing::Mock::VerifyAndClearExpectations(&mDeviceErrorCallback); \ do { \ } while (0) #define ASSERT_DEVICE_ERROR_MSG(statement, matcher) \ ASSERT_DEVICE_ERROR_MSG_ON(this->device, statement, matcher) #define ASSERT_DEVICE_ERROR_ON(device, statement) \ ASSERT_DEVICE_ERROR_MSG_ON(device, statement, testing::_) #define ASSERT_DEVICE_ERROR(statement) ASSERT_DEVICE_ERROR_MSG(statement, testing::_) struct GLFWwindow; namespace utils { class PlatformDebugLogger; class TerribleCommandBuffer; class WireHelper; } // namespace utils namespace detail { class Expectation; class CustomTextureExpectation; template class ExpectConstant; template class ExpectEq; template class ExpectBetweenColors; } // namespace detail namespace dawn::wire { class CommandHandler; class WireClient; class WireServer; } // namespace dawn::wire void InitDawnEnd2EndTestEnvironment(int argc, char** argv); class DawnTestEnvironment : public testing::Environment { public: DawnTestEnvironment(int argc, char** argv); ~DawnTestEnvironment() override; static void SetEnvironment(DawnTestEnvironment* env); std::vector GetAvailableAdapterTestParamsForBackends( const BackendTestConfig* params, size_t numParams); void SetUp() override; void TearDown() override; bool UsesWire() const; dawn::native::BackendValidationLevel GetBackendValidationLevel() const; dawn::native::Instance* GetInstance() const; bool HasVendorIdFilter() const; uint32_t GetVendorIdFilter() const; bool HasBackendTypeFilter() const; wgpu::BackendType GetBackendTypeFilter() const; const char* GetWireTraceDir() const; const std::vector& GetEnabledToggles() const; const std::vector& GetDisabledToggles() const; bool RunSuppressedTests() const; protected: std::unique_ptr mInstance; private: void ParseArgs(int argc, char** argv); std::unique_ptr CreateInstanceAndDiscoverAdapters(); void SelectPreferredAdapterProperties(const dawn::native::Instance* instance); void PrintTestConfigurationAndAdapterInfo(dawn::native::Instance* instance) const; /// @returns true if all the toggles are recognised, otherwise prints an error and returns /// false. bool ValidateToggles(dawn::native::Instance* instance) const; bool mUseWire = false; dawn::native::BackendValidationLevel mBackendValidationLevel = dawn::native::BackendValidationLevel::Disabled; std::string mANGLEBackend; bool mBeginCaptureOnStartup = false; bool mHasVendorIdFilter = false; uint32_t mVendorIdFilter = 0; bool mHasBackendTypeFilter = false; wgpu::BackendType mBackendTypeFilter; std::string mWireTraceDir; bool mRunSuppressedTests = false; ToggleParser mToggleParser; std::vector mDevicePreferences; std::vector mAdapterProperties; std::unique_ptr mPlatformDebugLogger; }; class DawnTestBase { friend class DawnPerfTestBase; public: explicit DawnTestBase(const AdapterTestParam& param); virtual ~DawnTestBase(); void SetUp(); void TearDown(); bool IsD3D12() const; bool IsMetal() const; bool IsNull() const; bool IsOpenGL() const; bool IsOpenGLES() const; bool IsVulkan() const; bool IsAMD() const; bool IsApple() const; bool IsARM() const; bool IsImgTec() const; bool IsIntel() const; bool IsNvidia() const; bool IsQualcomm() const; bool IsSwiftshader() const; bool IsANGLE() const; bool IsANGLESwiftShader() const; bool IsWARP() const; bool IsIntelGen12() const; bool IsWindows() const; bool IsLinux() const; bool IsMacOS(int32_t majorVersion = -1, int32_t minorVersion = -1) const; bool IsAndroid() const; bool UsesWire() const; bool IsBackendValidationEnabled() const; bool IsFullBackendValidationEnabled() const; bool RunSuppressedTests() const; bool IsDXC() const; bool IsAsan() const; bool HasToggleEnabled(const char* workaround) const; void DestroyDevice(wgpu::Device device = nullptr); void LoseDeviceForTesting(wgpu::Device device = nullptr); bool HasVendorIdFilter() const; uint32_t GetVendorIdFilter() const; bool HasBackendTypeFilter() const; wgpu::BackendType GetBackendTypeFilter() const; wgpu::Instance GetInstance() const; dawn::native::Adapter GetAdapter() const; virtual std::unique_ptr CreateTestPlatform(); struct PrintToStringParamName { explicit PrintToStringParamName(const char* test); std::string SanitizeParamName(std::string paramName, size_t index) const; template std::string operator()(const ::testing::TestParamInfo& info) const { return SanitizeParamName(::testing::PrintToStringParamName()(info), info.index); } std::string mTest; }; // Resolve all the deferred expectations in mDeferredExpectations now to avoid letting // mDeferredExpectations get too big. void ResolveDeferredExpectationsNow(); protected: wgpu::Device device; wgpu::Queue queue; DawnProcTable backendProcs = {}; WGPUDevice backendDevice = nullptr; size_t mLastWarningCount = 0; // Mock callbacks tracking errors and destruction. These are strict mocks because any errors or // device loss that aren't expected should result in test failures and not just some warnings // printed to stdout. testing::StrictMock> mDeviceErrorCallback; testing::StrictMock> mDeviceLostCallback; // Helper methods to implement the EXPECT_ macros std::ostringstream& AddBufferExpectation(const char* file, int line, const wgpu::Buffer& buffer, uint64_t offset, uint64_t size, detail::Expectation* expectation); template std::ostringstream& AddTextureExpectation(const char* file, int line, const T* expectedData, const wgpu::Texture& texture, wgpu::Origin3D origin, wgpu::Extent3D extent, wgpu::TextureFormat format, T tolerance = 0, uint32_t level = 0, wgpu::TextureAspect aspect = wgpu::TextureAspect::All, uint32_t bytesPerRow = 0) { // No device passed explicitly. Default it, and forward the rest of the args. return AddTextureExpectation(file, line, this->device, expectedData, texture, origin, extent, format, tolerance, level, aspect, bytesPerRow); } // T - expected value Type // U - actual value Type (defaults = T) template std::ostringstream& AddTextureExpectation(const char* file, int line, wgpu::Device targetDevice, const T* expectedData, const wgpu::Texture& texture, wgpu::Origin3D origin, wgpu::Extent3D extent, wgpu::TextureFormat format, T tolerance = 0, uint32_t level = 0, wgpu::TextureAspect aspect = wgpu::TextureAspect::All, uint32_t bytesPerRow = 0) { uint32_t texelBlockSize = utils::GetTexelBlockSizeInBytes(format); uint32_t texelComponentCount = utils::GetWGSLRenderableColorTextureComponentCount(format); return AddTextureExpectationImpl( file, line, std::move(targetDevice), new detail::ExpectEq( expectedData, texelComponentCount * extent.width * extent.height * extent.depthOrArrayLayers, tolerance), texture, origin, extent, level, aspect, texelBlockSize, bytesPerRow); } template std::ostringstream& AddTextureExpectation(const char* file, int line, const T* expectedData, const wgpu::Texture& texture, wgpu::Origin3D origin, wgpu::Extent3D extent, uint32_t level = 0, wgpu::TextureAspect aspect = wgpu::TextureAspect::All, uint32_t bytesPerRow = 0, T tolerance = {}) { // No device passed explicitly. Default it, and forward the rest of the args. return AddTextureExpectation(file, line, this->device, expectedData, texture, origin, extent, level, aspect, bytesPerRow, tolerance); } template std::ostringstream& AddTextureExpectation(const char* file, int line, wgpu::Device targetDevice, const T* expectedData, const wgpu::Texture& texture, wgpu::Origin3D origin, wgpu::Extent3D extent, uint32_t level = 0, wgpu::TextureAspect aspect = wgpu::TextureAspect::All, uint32_t bytesPerRow = 0, T tolerance = {}) { return AddTextureExpectationImpl( file, line, std::move(targetDevice), new detail::ExpectEq( expectedData, extent.width * extent.height * extent.depthOrArrayLayers, tolerance), texture, origin, extent, level, aspect, sizeof(U), bytesPerRow); } template std::ostringstream& AddTextureExpectation(const char* file, int line, const T& expectedData, const wgpu::Texture& texture, wgpu::Origin3D origin, uint32_t level = 0, wgpu::TextureAspect aspect = wgpu::TextureAspect::All, uint32_t bytesPerRow = 0) { // No device passed explicitly. Default it, and forward the rest of the args. return AddTextureExpectation(file, line, this->device, expectedData, texture, origin, level, aspect, bytesPerRow); } template std::ostringstream& AddTextureExpectation(const char* file, int line, wgpu::Device targetDevice, const T& expectedData, const wgpu::Texture& texture, wgpu::Origin3D origin, uint32_t level = 0, wgpu::TextureAspect aspect = wgpu::TextureAspect::All, uint32_t bytesPerRow = 0) { return AddTextureExpectationImpl(file, line, std::move(targetDevice), new detail::ExpectEq(expectedData), texture, origin, {1, 1}, level, aspect, sizeof(U), bytesPerRow); } template ::value>::type> std::ostringstream& AddTextureExpectation(const char* file, int line, E* expectation, const wgpu::Texture& texture, wgpu::Origin3D origin, wgpu::Extent3D extent, uint32_t level = 0, wgpu::TextureAspect aspect = wgpu::TextureAspect::All, uint32_t bytesPerRow = 0) { // No device passed explicitly. Default it, and forward the rest of the args. return AddTextureExpectation(file, line, this->device, expectation, texture, origin, extent, level, aspect, bytesPerRow); } template ::value>::type> std::ostringstream& AddTextureExpectation(const char* file, int line, wgpu::Device targetDevice, E* expectation, const wgpu::Texture& texture, wgpu::Origin3D origin, wgpu::Extent3D extent, uint32_t level = 0, wgpu::TextureAspect aspect = wgpu::TextureAspect::All, uint32_t bytesPerRow = 0) { return AddTextureExpectationImpl(file, line, std::move(targetDevice), expectation, texture, origin, extent, level, aspect, expectation->DataSize(), bytesPerRow); } template std::ostringstream& AddTextureBetweenColorsExpectation( const char* file, int line, const T& color0, const T& color1, const wgpu::Texture& texture, uint32_t x, uint32_t y, uint32_t level = 0, wgpu::TextureAspect aspect = wgpu::TextureAspect::All, uint32_t bytesPerRow = 0) { // No device passed explicitly. Default it, and forward the rest of the args. return AddTextureBetweenColorsExpectation(file, line, this->device, color0, color1, texture, x, y, level, aspect, bytesPerRow); } template std::ostringstream& AddTextureBetweenColorsExpectation( const char* file, int line, const wgpu::Device& targetDevice, const T& color0, const T& color1, const wgpu::Texture& texture, uint32_t x, uint32_t y, uint32_t level = 0, wgpu::TextureAspect aspect = wgpu::TextureAspect::All, uint32_t bytesPerRow = 0) { return AddTextureExpectationImpl( file, line, std::move(targetDevice), new detail::ExpectBetweenColors(color0, color1), texture, {x, y}, {1, 1}, level, aspect, sizeof(T), bytesPerRow); } std::ostringstream& ExpectSampledFloatData(wgpu::Texture texture, uint32_t width, uint32_t height, uint32_t componentCount, uint32_t arrayLayer, uint32_t mipLevel, detail::Expectation* expectation); std::ostringstream& ExpectMultisampledFloatData(wgpu::Texture texture, uint32_t width, uint32_t height, uint32_t componentCount, uint32_t sampleCount, uint32_t arrayLayer, uint32_t mipLevel, detail::Expectation* expectation); std::ostringstream& ExpectSampledDepthData(wgpu::Texture depthTexture, uint32_t width, uint32_t height, uint32_t arrayLayer, uint32_t mipLevel, detail::Expectation* expectation); // Check depth by uploading expected data to a sampled texture, writing it out as a depth // attachment, and then using the "equals" depth test to check the contents are the same. // Check stencil by rendering a full screen quad and using the "equals" stencil test with // a stencil reference value. Note that checking stencil checks that the entire stencil // buffer is equal to the expected stencil value. std::ostringstream& ExpectAttachmentDepthStencilTestData(wgpu::Texture texture, wgpu::TextureFormat format, uint32_t width, uint32_t height, uint32_t arrayLayer, uint32_t mipLevel, std::vector expectedDepth, uint8_t* expectedStencil); std::ostringstream& ExpectAttachmentDepthTestData(wgpu::Texture texture, wgpu::TextureFormat format, uint32_t width, uint32_t height, uint32_t arrayLayer, uint32_t mipLevel, std::vector expectedDepth) { return ExpectAttachmentDepthStencilTestData(texture, format, width, height, arrayLayer, mipLevel, std::move(expectedDepth), nullptr); } std::ostringstream& ExpectAttachmentStencilTestData(wgpu::Texture texture, wgpu::TextureFormat format, uint32_t width, uint32_t height, uint32_t arrayLayer, uint32_t mipLevel, uint8_t expectedStencil) { return ExpectAttachmentDepthStencilTestData(texture, format, width, height, arrayLayer, mipLevel, {}, &expectedStencil); } void WaitABit(wgpu::Device = nullptr); void FlushWire(); void WaitForAllOperations(); bool SupportsFeatures(const std::vector& features); // Exposed device creation helper for tests to use when needing more than 1 device. wgpu::Device CreateDevice(std::string isolationKey = ""); // Called in SetUp() to get the features required to be enabled in the tests. The tests must // check if the required features are supported by the adapter in this function and guarantee // the returned features are all supported by the adapter. The tests may provide different // code path to handle the situation when not all features are supported. virtual std::vector GetRequiredFeatures(); virtual wgpu::RequiredLimits GetRequiredLimits(const wgpu::SupportedLimits&); const wgpu::AdapterProperties& GetAdapterProperties() const; wgpu::SupportedLimits GetAdapterLimits(); wgpu::SupportedLimits GetSupportedLimits(); private: utils::ScopedAutoreleasePool mObjCAutoreleasePool; AdapterTestParam mParam; std::unique_ptr mWireHelper; wgpu::Instance mInstance; wgpu::Adapter mAdapter; // Isolation keys are not exposed to the wire client. Device creation in the tests from // the client first push the key into this queue, which is then consumed by the server. std::queue mNextIsolationKeyQueue; // Internal device creation function for default device creation with some optional overrides. WGPUDevice CreateDeviceImpl(std::string isolationKey); std::ostringstream& AddTextureExpectationImpl(const char* file, int line, wgpu::Device targetDevice, detail::Expectation* expectation, const wgpu::Texture& texture, wgpu::Origin3D origin, wgpu::Extent3D extent, uint32_t level, wgpu::TextureAspect aspect, uint32_t dataSize, uint32_t bytesPerRow); std::ostringstream& ExpectSampledFloatDataImpl(wgpu::TextureView textureView, const char* wgslTextureType, uint32_t width, uint32_t height, uint32_t componentCount, uint32_t sampleCount, detail::Expectation* expectation); // MapRead buffers used to get data for the expectations struct ReadbackSlot { wgpu::Device device; wgpu::Buffer buffer; uint64_t bufferSize; const void* mappedData = nullptr; }; std::vector mReadbackSlots; // Maps all the buffers and fill ReadbackSlot::mappedData void MapSlotsSynchronously(); static void SlotMapCallback(WGPUBufferMapAsyncStatus status, void* userdata); size_t mNumPendingMapOperations = 0; // Reserve space where the data for an expectation can be copied struct ReadbackReservation { wgpu::Device device; wgpu::Buffer buffer; size_t slot; uint64_t offset; }; ReadbackReservation ReserveReadback(wgpu::Device targetDevice, uint64_t readbackSize); struct DeferredExpectation { const char* file; int line; size_t readbackSlot; uint64_t readbackOffset; uint64_t size; uint32_t rowBytes = 0; uint32_t bytesPerRow = 0; std::unique_ptr expectation; // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54316 // Use unique_ptr because of missing move/copy constructors on std::basic_ostringstream std::unique_ptr message; }; std::vector mDeferredExpectations; // Assuming the data is mapped, checks all expectations void ResolveExpectations(); dawn::native::Adapter mBackendAdapter; WGPUDevice mLastCreatedBackendDevice; std::unique_ptr mTestPlatform; }; #define DAWN_SKIP_TEST_IF_BASE(condition, type, reason) \ do { \ if (condition) { \ dawn::InfoLog() << "Test " type ": " #reason; \ GTEST_SKIP(); \ return; \ } \ } while (0) // Skip a test which requires a feature or a toggle to be present / not present or some WIP // features. #define DAWN_TEST_UNSUPPORTED_IF(condition) \ DAWN_SKIP_TEST_IF_BASE(condition, "unsupported", condition) // Skip a test when the test failing on a specific HW / backend / OS combination. We can disable // this macro with the command line parameter "--run-suppressed-tests". #define DAWN_SUPPRESS_TEST_IF(condition) \ DAWN_SKIP_TEST_IF_BASE(!RunSuppressedTests() && condition, "suppressed", condition) #define EXPECT_DEPRECATION_WARNINGS(statement, n) \ do { \ if (UsesWire()) { \ statement; \ } else { \ size_t warningsBefore = \ dawn::native::GetDeprecationWarningCountForTesting(device.Get()); \ statement; \ size_t warningsAfter = \ dawn::native::GetDeprecationWarningCountForTesting(device.Get()); \ EXPECT_EQ(mLastWarningCount, warningsBefore); \ if (!HasToggleEnabled("skip_validation")) { \ EXPECT_EQ(warningsAfter, warningsBefore + n); \ } \ mLastWarningCount = warningsAfter; \ } \ } while (0) #define EXPECT_DEPRECATION_WARNING(statement) EXPECT_DEPRECATION_WARNINGS(statement, 1) template class DawnTestWithParams : public DawnTestBase, public ::testing::TestWithParam { protected: DawnTestWithParams(); ~DawnTestWithParams() override = default; void SetUp() override { DawnTestBase::SetUp(); } void TearDown() override { DawnTestBase::TearDown(); } }; template DawnTestWithParams::DawnTestWithParams() : DawnTestBase(this->GetParam()) {} using DawnTest = DawnTestWithParams<>; // Instantiate the test once for each backend provided after the first argument. Use it like this: // DAWN_INSTANTIATE_TEST(MyTestFixture, MetalBackend, OpenGLBackend) #define DAWN_INSTANTIATE_TEST(testName, ...) \ const decltype(DAWN_PP_GET_HEAD(__VA_ARGS__)) testName##params[] = {__VA_ARGS__}; \ INSTANTIATE_TEST_SUITE_P( \ , testName, \ testing::ValuesIn(::detail::GetAvailableAdapterTestParamsForBackends( \ testName##params, sizeof(testName##params) / sizeof(testName##params[0]))), \ DawnTestBase::PrintToStringParamName(#testName)); \ GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(testName) // Instantiate the test once for each backend provided in the first param list. // The test will be parameterized over the following param lists. // Use it like this: // DAWN_INSTANTIATE_TEST_P(MyTestFixture, {MetalBackend(), OpenGLBackend()}, {A, B}, {1, 2}) // MyTestFixture must extend DawnTestWithParams where Param is a struct that extends // AdapterTestParam, and whose constructor looks like: // Param(AdapterTestParam, ABorC, 12or3, ..., otherParams... ) // You must also teach GTest how to print this struct. // https://github.com/google/googletest/blob/main/docs/advanced.md#teaching-googletest-how-to-print-your-values // Macro DAWN_TEST_PARAM_STRUCT can help generate this struct. #define DAWN_INSTANTIATE_TEST_P(testName, ...) \ INSTANTIATE_TEST_SUITE_P( \ , testName, ::testing::ValuesIn(MakeParamGenerator(__VA_ARGS__)), \ DawnTestBase::PrintToStringParamName(#testName)); \ GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(testName) // Implementation for DAWN_TEST_PARAM_STRUCT to declare/print struct fields. #define DAWN_TEST_PARAM_STRUCT_DECL_STRUCT_FIELD(Type) Type DAWN_PP_CONCATENATE(m, Type); #define DAWN_TEST_PARAM_STRUCT_PRINT_STRUCT_FIELD(Type) \ o << "; " << #Type << "=" << param.DAWN_PP_CONCATENATE(m, Type); // Usage: DAWN_TEST_PARAM_STRUCT(Foo, TypeA, TypeB, ...) // Generate a test param struct called Foo which extends AdapterTestParam and generated // struct _Dawn_Foo. _Dawn_Foo has members of types TypeA, TypeB, etc. which are named mTypeA, // mTypeB, etc. in the order they are placed in the macro argument list. Struct Foo should be // constructed with an AdapterTestParam as the first argument, followed by a list of values // to initialize the base _Dawn_Foo struct. // It is recommended to use alias declarations so that stringified types are more readable. // Example: // using MyParam = unsigned int; // DAWN_TEST_PARAM_STRUCT(FooParams, MyParam); #define DAWN_TEST_PARAM_STRUCT(StructName, ...) \ struct DAWN_PP_CONCATENATE(_Dawn_, StructName) { \ DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH)(DAWN_TEST_PARAM_STRUCT_DECL_STRUCT_FIELD, \ __VA_ARGS__)) \ }; \ std::ostream& operator<<(std::ostream& o, \ const DAWN_PP_CONCATENATE(_Dawn_, StructName) & param) { \ DAWN_PP_EXPAND(DAWN_PP_EXPAND(DAWN_PP_FOR_EACH)(DAWN_TEST_PARAM_STRUCT_PRINT_STRUCT_FIELD, \ __VA_ARGS__)) \ return o; \ } \ struct StructName : AdapterTestParam, DAWN_PP_CONCATENATE(_Dawn_, StructName) { \ template \ StructName(const AdapterTestParam& param, Args&&... args) \ : AdapterTestParam(param), \ DAWN_PP_CONCATENATE(_Dawn_, StructName){std::forward(args)...} {} \ }; \ std::ostream& operator<<(std::ostream& o, const StructName& param) { \ o << static_cast(param); \ o << "; " << static_cast(param); \ return o; \ } \ static_assert(true, "require semicolon") namespace detail { // Helper functions used for DAWN_INSTANTIATE_TEST std::vector GetAvailableAdapterTestParamsForBackends( const BackendTestConfig* params, size_t numParams); // All classes used to implement the deferred expectations should inherit from this. class Expectation { public: virtual ~Expectation() = default; // Will be called with the buffer or texture data the expectation should check. virtual testing::AssertionResult Check(const void* data, size_t size) = 0; }; template class ExpectConstant : public Expectation { public: explicit ExpectConstant(T constant); uint32_t DataSize(); testing::AssertionResult Check(const void* data, size_t size) override; private: T mConstant; }; extern template class ExpectConstant; // Expectation that checks the data is equal to some expected values. // T - expected value Type // U - actual value Type (defaults = T) // This is expanded for float16 mostly where T=float, U=uint16_t template class ExpectEq : public Expectation { public: explicit ExpectEq(T singleValue, T tolerance = {}); ExpectEq(const T* values, const unsigned int count, T tolerance = {}); testing::AssertionResult Check(const void* data, size_t size) override; private: std::vector mExpected; T mTolerance; }; extern template class ExpectEq; extern template class ExpectEq; extern template class ExpectEq; extern template class ExpectEq; extern template class ExpectEq; extern template class ExpectEq; extern template class ExpectEq; template class ExpectBetweenColors : public Expectation { public: // Inclusive for now ExpectBetweenColors(T value0, T value1); testing::AssertionResult Check(const void* data, size_t size) override; private: std::vector mLowerColorChannels; std::vector mHigherColorChannels; // used for printing error std::vector mValues0; std::vector mValues1; }; // A color is considered between color0 and color1 when all channel values are within range of // each counterparts. It doesn't matter which value is higher or lower. Essentially color = // lerp(color0, color1, t) where t is [0,1]. But I don't want to be too strict here. extern template class ExpectBetweenColors; class CustomTextureExpectation : public Expectation { public: ~CustomTextureExpectation() override = default; virtual uint32_t DataSize() = 0; }; } // namespace detail #endif // SRC_DAWN_TESTS_DAWNTEST_H_