blob: 3442690dc185ae1e4958b75473571cd114a2f4a0 [file] [log] [blame] [edit]
// Copyright 2022 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cstring>
#include <iomanip>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
#include "dawn/common/TypedInteger.h"
#include "dawn/native/Blob.h"
#include "dawn/native/Serializable.h"
#include "dawn/native/stream/BlobSource.h"
#include "dawn/native/stream/ByteVectorSink.h"
#include "dawn/native/stream/Stream.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "tint/tint.h"
namespace dawn::native::stream {
namespace {
// Testing classes with mock serializing implemented for testing.
class A {
public:
MOCK_METHOD(void, WriteMock, (Sink*, const A&), (const));
};
struct Nested {
A a1;
A a2;
};
} // anonymous namespace
template <>
void Stream<A>::Write(Sink* s, const A& t) {
t.WriteMock(s, t);
}
template <>
void Stream<Nested>::Write(Sink* s, const Nested& t) {
StreamIn(s, t.a1);
StreamIn(s, t.a2);
}
// Custom printer for ByteVectorSink for clearer debug testing messages.
void PrintTo(const ByteVectorSink& key, std::ostream* stream) {
*stream << std::hex;
for (const int b : key) {
*stream << std::setfill('0') << std::setw(2) << b << " ";
}
*stream << std::dec;
}
namespace {
using ::testing::InSequence;
using ::testing::NotNull;
using ::testing::PrintToString;
using ::testing::Ref;
using TypedIntegerForTest = TypedInteger<struct TypedIntegerForTestTag, uint32_t>;
// Matcher to compare ByteVectorSinks for easier testing.
MATCHER_P(VectorEq, key, PrintToString(key)) {
return arg.size() == key.size() && memcmp(arg.data(), key.data(), key.size()) == 0;
}
#define EXPECT_CACHE_KEY_EQ(lhs, rhs) \
do { \
ByteVectorSink actual; \
StreamIn(&actual, lhs); \
EXPECT_THAT(actual, VectorEq(rhs)); \
} while (0)
// Test that ByteVectorSink calls Write on a value.
TEST(SerializeTests, CallsWrite) {
ByteVectorSink sink;
A a;
EXPECT_CALL(a, WriteMock(NotNull(), Ref(a))).Times(1);
StreamIn(&sink, a);
}
// Test that ByteVectorSink calls Write on all elements of an iterable.
TEST(SerializeTests, StreamInIterable) {
constexpr size_t kIterableSize = 100;
std::vector<A> vec(kIterableSize);
auto iterable = Iterable(vec.data(), kIterableSize);
// Expect write to be called for each element
for (const auto& a : vec) {
EXPECT_CALL(a, WriteMock(NotNull(), Ref(a))).Times(1);
}
ByteVectorSink sink;
StreamIn(&sink, iterable);
// Expecting the size of the container.
ByteVectorSink expected;
StreamIn(&expected, kIterableSize);
EXPECT_THAT(sink, VectorEq(expected));
}
// Test that ByteVectorSink calls Write on all nested members of a struct.
TEST(SerializeTests, StreamInNested) {
ByteVectorSink sink;
Nested n;
EXPECT_CALL(n.a1, WriteMock(NotNull(), Ref(n.a1))).Times(1);
EXPECT_CALL(n.a2, WriteMock(NotNull(), Ref(n.a2))).Times(1);
StreamIn(&sink, n);
}
// Test that ByteVectorSink serializes integral data as expected.
TEST(SerializeTests, IntegralTypes) {
// Only testing explicitly sized types for simplicity, and using 0s for larger types to
// avoid dealing with endianess.
EXPECT_CACHE_KEY_EQ('c', ByteVectorSink({'c'}));
EXPECT_CACHE_KEY_EQ(uint8_t(255), ByteVectorSink({255}));
EXPECT_CACHE_KEY_EQ(uint16_t(0), ByteVectorSink({0, 0}));
EXPECT_CACHE_KEY_EQ(uint32_t(0), ByteVectorSink({0, 0, 0, 0}));
}
// Test that ByteVectorSink serializes floating-point data as expected.
TEST(SerializeTests, FloatingTypes) {
// Using 0s to avoid dealing with implementation specific float details.
ByteVectorSink k1, k2;
EXPECT_CACHE_KEY_EQ(float{0}, ByteVectorSink(sizeof(float), 0));
EXPECT_CACHE_KEY_EQ(double{0}, ByteVectorSink(sizeof(double), 0));
}
// Test that ByteVectorSink serializes literal strings as expected.
TEST(SerializeTests, LiteralStrings) {
// Using a std::string here to help with creating the expected result.
std::string str = "string";
ByteVectorSink expected;
expected.insert(expected.end(), str.begin(), str.end());
expected.push_back('\0');
EXPECT_CACHE_KEY_EQ("string", expected);
}
// Test that ByteVectorSink serializes std::strings as expected.
TEST(SerializeTests, StdStrings) {
std::string str = "string";
ByteVectorSink expected;
StreamIn(&expected, size_t(6));
expected.insert(expected.end(), str.begin(), str.end());
EXPECT_CACHE_KEY_EQ(str, expected);
}
// Test that ByteVectorSink serializes std::string_views as expected.
TEST(SerializeTests, StdStringViews) {
static constexpr std::string_view str("string");
ByteVectorSink expected;
StreamIn(&expected, size_t(6));
expected.insert(expected.end(), str.begin(), str.end());
EXPECT_CACHE_KEY_EQ(str, expected);
}
// Test that ByteVectorSink serializes std::wstring_views as expected.
TEST(SerializeTests, StdWStringViews) {
static constexpr std::wstring_view str(L"Hello world!");
ByteVectorSink expected;
StreamIn(&expected, size_t(str.length()));
size_t bytes = str.length() * sizeof(wchar_t);
memcpy(expected.GetSpace(bytes), str.data(), bytes);
EXPECT_CACHE_KEY_EQ(str, expected);
}
// Test that ByteVectorSink serializes Blobs as expected.
TEST(SerializeTests, Blob) {
uint8_t data[] = "dawn native Blob";
Blob blob = Blob::UnsafeCreateWithDeleter(data, sizeof(data), [] {});
ByteVectorSink expected;
StreamIn(&expected, sizeof(data));
expected.insert(expected.end(), data, data + sizeof(data));
EXPECT_CACHE_KEY_EQ(blob, expected);
}
// Test that ByteVectorSink serializes other ByteVectorSinks as expected.
TEST(SerializeTests, ByteVectorSinks) {
ByteVectorSink data = {'d', 'a', 't', 'a'};
ByteVectorSink expected;
expected.insert(expected.end(), data.begin(), data.end());
EXPECT_CACHE_KEY_EQ(data, expected);
}
// Test that ByteVectorSink serializes std::pair as expected.
TEST(SerializeTests, StdPair) {
std::string_view s = "hi!";
ByteVectorSink expected;
StreamIn(&expected, s, uint32_t(42));
EXPECT_CACHE_KEY_EQ(std::make_pair(s, uint32_t(42)), expected);
}
// Test that ByteVectorSink serializes std::optional as expected.
TEST(SerializeTests, StdOptional) {
std::string_view s = "webgpu";
{
ByteVectorSink expected;
StreamIn(&expected, true, s);
EXPECT_CACHE_KEY_EQ(std::optional(s), expected);
}
{
ByteVectorSink expected;
StreamIn(&expected, false);
EXPECT_CACHE_KEY_EQ(std::optional<std::string_view>(), expected);
}
}
// Test that ByteVectorSink serializes std::unordered_map as expected.
TEST(SerializeTests, StdUnorderedMap) {
std::unordered_map<uint32_t, std::string_view> m;
m[4] = "hello";
m[1] = "world";
m[7] = "test";
m[3] = "data";
// Expect the number of entries, followed by (K, V) pairs sorted in order of key.
ByteVectorSink expected;
StreamIn(&expected, size_t(4), std::make_pair(uint32_t(1), m[1]),
std::make_pair(uint32_t(3), m[3]), std::make_pair(uint32_t(4), m[4]),
std::make_pair(uint32_t(7), m[7]));
EXPECT_CACHE_KEY_EQ(m, expected);
}
// Test that ByteVectorSink serializes tint::BindingPoint as expected.
TEST(SerializeTests, TintSemBindingPoint) {
tint::BindingPoint bp{3, 6};
ByteVectorSink expected;
StreamIn(&expected, uint32_t(3), uint32_t(6));
EXPECT_CACHE_KEY_EQ(bp, expected);
}
// Test that ByteVectorSink serializes tint::ExternalTextureOptions::BindingPoints
// as expected.
TEST(SerializeTests, TintTransformBindingPoints) {
tint::ExternalTextureOptions::BindingPoints points{
tint::BindingPoint{1, 4},
tint::BindingPoint{3, 7},
};
ByteVectorSink expected;
StreamIn(&expected, uint32_t(1), uint32_t(4), uint32_t(3), uint32_t(7));
EXPECT_CACHE_KEY_EQ(points, expected);
}
// Test that serializing then deserializing a param pack yields the same values.
TEST(StreamTests, SerializeDeserializeParamPack) {
int a = 1;
float b = 2.0;
std::pair<std::string, double> c = std::make_pair("dawn", 3.4);
ByteVectorSink sink;
StreamIn(&sink, a, b, c);
BlobSource source(CreateBlob(std::move(sink)));
int aOut;
float bOut;
std::pair<std::string, double> cOut;
auto err = StreamOut(&source, &aOut, &bOut, &cOut);
if (err.IsError()) {
FAIL() << err.AcquireError()->GetFormattedMessage();
}
EXPECT_EQ(a, aOut);
EXPECT_EQ(b, bOut);
EXPECT_EQ(c, cOut);
}
#define FOO_MEMBERS(X) \
X(int, a) \
X(float, b) \
X(std::string, c)
DAWN_SERIALIZABLE(struct, Foo, FOO_MEMBERS){};
#undef FOO_MEMBERS
// Test that serializing then deserializing a struct made with DAWN_SERIALIZABLE works as
// expected.
TEST(StreamTests, SerializeDeserializeVisitable) {
Foo foo{{1, 2, "3"}};
ByteVectorSink sink;
StreamIn(&sink, foo);
// Test that the serialization is correct.
{
ByteVectorSink expected;
StreamIn(&expected, foo.a, foo.b, foo.c);
EXPECT_THAT(sink, VectorEq(expected));
}
// Test that deserialization works for StructMembers, passed inline.
{
BlobSource src(CreateBlob(sink));
Foo out;
auto err = StreamOut(&src, &out);
EXPECT_FALSE(err.IsError());
EXPECT_EQ(foo.a, out.a);
EXPECT_EQ(foo.b, out.b);
EXPECT_EQ(foo.c, out.c);
}
}
// Test that serializing then deserializing a Blob yields the same data.
// Tested here instead of in the type-parameterized tests since Blobs are not copyable.
TEST(StreamTests, SerializeDeserializeBlobs) {
// Test an empty blob
{
Blob blob;
EXPECT_EQ(blob.Size(), 0u);
ByteVectorSink sink;
StreamIn(&sink, blob);
BlobSource src(CreateBlob(sink));
Blob out;
auto err = StreamOut(&src, &out);
EXPECT_FALSE(err.IsError());
EXPECT_EQ(blob.Size(), out.Size());
EXPECT_EQ(memcmp(blob.Data(), out.Data(), blob.Size()), 0);
}
// Test a blob with some data
{
Blob blob = CreateBlob(std::vector<double>{6.24, 3.12222});
ByteVectorSink sink;
StreamIn(&sink, blob);
BlobSource src(CreateBlob(sink));
Blob out;
auto err = StreamOut(&src, &out);
EXPECT_FALSE(err.IsError());
EXPECT_EQ(blob.Size(), out.Size());
EXPECT_EQ(memcmp(blob.Data(), out.Data(), blob.Size()), 0);
}
}
template <size_t N>
std::bitset<N - 1> BitsetFromBitString(const char (&str)[N]) {
// N - 1 because the last character is the null terminator.
return std::bitset<N - 1>(str, N - 1);
}
static auto kStreamValueVectorParams = std::make_tuple(
// Test primitives.
std::vector<int>{4, 5, 6, 2},
std::vector<float>{6.50, 78.28, 92., 8.28},
// Test various types of strings.
std::vector<std::string>{"abcdefg", "9461849495", ""},
// Test pairs.
std::vector<std::pair<int, float>>{{1, 3.}, {6, 4.}},
// Test TypedIntegers
std::vector<TypedIntegerForTest>{TypedIntegerForTest(42), TypedIntegerForTest(13)},
// Test enums
std::vector<wgpu::TextureUsage>{wgpu::TextureUsage::CopyDst,
wgpu::TextureUsage::RenderAttachment},
// Test bitsets of various sizes.
std::vector<std::bitset<7>>{0b1001011, 0b0011010, 0b0000000, 0b1111111},
std::vector<std::bitset<17>>{0x0000, 0xFFFF1},
std::vector<std::bitset<32>>{0x0C0FFEE0, 0xDEADC0DE, 0x00000000, 0xFFFFFFFF},
std::vector<std::bitset<57>>{
BitsetFromBitString("100110010101011001100110101011001100101010110011001011011"),
BitsetFromBitString("000110010101011000100110101011001100101010010011001010100"),
BitsetFromBitString("111111111111111111111111111111111111111111111111111111111"), 0},
// Test vectors.
std::vector<std::vector<int>>{{}, {1, 5, 2, 7, 4}, {3, 3, 3, 3, 3, 3, 3}});
static auto kStreamValueInitListParams = std::make_tuple(
std::initializer_list<char[12]>{"test string", "string test"},
std::initializer_list<double[3]>{{5.435, 32.3, 1.23}, {8.2345, 0.234532, 4.435}});
template <typename, typename>
struct StreamValueTestTypesImpl;
template <typename... T, typename... T2>
struct StreamValueTestTypesImpl<std::tuple<std::vector<T>...>,
std::tuple<std::initializer_list<T2>...>> {
using type = ::testing::Types<T..., T2...>;
};
using StreamValueTestTypes =
typename StreamValueTestTypesImpl<decltype(kStreamValueVectorParams),
decltype(kStreamValueInitListParams)>::type;
template <typename T>
class StreamParameterizedTests : public ::testing::Test {
protected:
static std::vector<T> GetParams() { return std::get<std::vector<T>>(kStreamValueVectorParams); }
void ExpectEq(const T& lhs, const T& rhs) { EXPECT_EQ(lhs, rhs); }
};
template <typename T, size_t N>
class StreamParameterizedTests<T[N]> : public ::testing::Test {
protected:
static std::initializer_list<T[N]> GetParams() {
return std::get<std::initializer_list<T[N]>>(kStreamValueInitListParams);
}
void ExpectEq(const T lhs[N], const T rhs[N]) { EXPECT_EQ(memcmp(lhs, rhs, sizeof(T[N])), 0); }
};
TYPED_TEST_SUITE_P(StreamParameterizedTests);
// Test that serializing a value, then deserializing it yields the same value.
TYPED_TEST_P(StreamParameterizedTests, SerializeDeserialize) {
for (const auto& value : this->GetParams()) {
ByteVectorSink sink;
StreamIn(&sink, value);
BlobSource source(CreateBlob(std::move(sink)));
TypeParam deserialized;
auto err = StreamOut(&source, &deserialized);
if (err.IsError()) {
FAIL() << err.AcquireError()->GetFormattedMessage();
}
this->ExpectEq(deserialized, value);
}
}
// Test that serializing a value, then deserializing it with insufficient space, an error is raised.
TYPED_TEST_P(StreamParameterizedTests, SerializeDeserializeOutOfBounds) {
for (const auto& value : this->GetParams()) {
ByteVectorSink sink;
StreamIn(&sink, value);
// Make the vector 1 byte too small.
std::vector<uint8_t> src = sink;
src.pop_back();
BlobSource source(CreateBlob(std::move(src)));
TypeParam deserialized;
auto err = StreamOut(&source, &deserialized);
EXPECT_TRUE(err.IsError());
err.AcquireError();
}
}
// Test that deserializing from an empty source raises an error.
TYPED_TEST_P(StreamParameterizedTests, DeserializeEmpty) {
BlobSource source(CreateBlob(0));
TypeParam deserialized;
auto err = StreamOut(&source, &deserialized);
EXPECT_TRUE(err.IsError());
err.AcquireError();
}
REGISTER_TYPED_TEST_SUITE_P(StreamParameterizedTests,
SerializeDeserialize,
SerializeDeserializeOutOfBounds,
DeserializeEmpty);
INSTANTIATE_TYPED_TEST_SUITE_P(DawnUnittests, StreamParameterizedTests, StreamValueTestTypes, );
} // anonymous namespace
} // namespace dawn::native::stream