diff --git a/src/dawn/native/Blob.cpp b/src/dawn/native/Blob.cpp index 66ba0d7576..a3ac2b28ef 100644 --- a/src/dawn/native/Blob.cpp +++ b/src/dawn/native/Blob.cpp @@ -14,6 +14,7 @@ #include +#include "dawn/common/Assert.h" #include "dawn/native/Blob.h" namespace dawn::native { @@ -35,11 +36,24 @@ Blob Blob::UnsafeCreateWithDeleter(uint8_t* data, size_t size, std::function deleter) - : mData(data), mSize(size), mDeleter(std::move(deleter)) {} + : mData(data), mSize(size), mDeleter(std::move(deleter)) { + // It is invalid to make a blob that has null data unless its size is also zero. + ASSERT(data != nullptr || size == 0); +} -Blob::Blob(Blob&&) = default; +Blob::Blob(Blob&& rhs) : mData(rhs.mData), mSize(rhs.mSize) { + mDeleter = std::move(rhs.mDeleter); +} -Blob& Blob::operator=(Blob&&) = default; +Blob& Blob::operator=(Blob&& rhs) { + mData = rhs.mData; + mSize = rhs.mSize; + if (mDeleter) { + mDeleter(); + } + mDeleter = std::move(rhs.mDeleter); + return *this; +} Blob::~Blob() { if (mDeleter) { diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn index 9323299e24..69c41a2e1a 100644 --- a/src/dawn/tests/BUILD.gn +++ b/src/dawn/tests/BUILD.gn @@ -252,6 +252,7 @@ dawn_test("dawn_unittests") { "unittests/ToBackendTests.cpp", "unittests/TypedIntegerTests.cpp", "unittests/VersionTests.cpp", + "unittests/native/BlobTests.cpp", "unittests/native/CacheKeyTests.cpp", "unittests/native/CommandBufferEncodingTests.cpp", "unittests/native/CreatePipelineAsyncTaskTests.cpp", diff --git a/src/dawn/tests/unittests/native/BlobTests.cpp b/src/dawn/tests/unittests/native/BlobTests.cpp new file mode 100644 index 0000000000..580f0b704a --- /dev/null +++ b/src/dawn/tests/unittests/native/BlobTests.cpp @@ -0,0 +1,168 @@ +// 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 + +#include "dawn/native/Blob.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace dawn::native { + +namespace { + +// Test that a blob starts empty. +TEST(BlobTests, DefaultEmpty) { + Blob b; + EXPECT_TRUE(b.Empty()); + EXPECT_EQ(b.Data(), nullptr); + EXPECT_EQ(b.Size(), 0u); +} + +// Test that you can create a blob with a size in bytes and write/read its contents. +TEST(BlobTests, SizedCreation) { + Blob b = CreateBlob(10); + EXPECT_FALSE(b.Empty()); + EXPECT_EQ(b.Size(), 10u); + ASSERT_NE(b.Data(), nullptr); + // We should be able to copy 10 bytes into the blob. + char data[10] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; + memcpy(b.Data(), data, sizeof(data)); + // And retrieve the exact contents back. + EXPECT_EQ(memcmp(b.Data(), data, sizeof(data)), 0); +} + +// Test that you can create a zero-sized blob. +TEST(BlobTests, EmptySizedCreation) { + Blob b = CreateBlob(0); + EXPECT_TRUE(b.Empty()); + EXPECT_EQ(b.Data(), nullptr); + EXPECT_EQ(b.Size(), 0u); +} + +// Test that you can create a blob with UnsafeCreateWithDeleter, and the deleter is +// called on destruction. +TEST(BlobTests, UnsafeCreateWithDeleter) { + unsigned char data[13] = "hello world!"; + testing::StrictMock> mockDeleter; + { + // Make a blob with a mock deleter. + Blob b = Blob::UnsafeCreateWithDeleter(data, sizeof(data), [&]() { mockDeleter.Call(); }); + // Check the contents. + EXPECT_FALSE(b.Empty()); + EXPECT_EQ(b.Size(), sizeof(data)); + ASSERT_EQ(b.Data(), data); + EXPECT_EQ(memcmp(b.Data(), data, sizeof(data)), 0); + + // |b| is deleted when this scope exits. + EXPECT_CALL(mockDeleter, Call()); + } +} + +// Test that you can create a blob with UnsafeCreateWithDeleter with zero size but non-null data. +// The deleter is still called on destruction, and the blob is normalized to be empty. +TEST(BlobTests, UnsafeCreateWithDeleterZeroSize) { + unsigned char data[13] = "hello world!"; + testing::StrictMock> mockDeleter; + { + // Make a blob with a mock deleter. + Blob b = Blob::UnsafeCreateWithDeleter(data, 0, [&]() { mockDeleter.Call(); }); + // Check the contents. + EXPECT_TRUE(b.Empty()); + EXPECT_EQ(b.Size(), 0u); + // Data still points to the data. + EXPECT_EQ(b.Data(), data); + + // |b| is deleted when this scope exits. + EXPECT_CALL(mockDeleter, Call()); + } +} + +// Test that you can create a blob with UnsafeCreateWithDeleter that points to nothing. +// The deleter should still be called. +TEST(BlobTests, UnsafeCreateWithDeleterEmpty) { + testing::StrictMock> mockDeleter; + { + // Make a blob with a mock deleter. + Blob b = Blob::UnsafeCreateWithDeleter(nullptr, 0, [&]() { mockDeleter.Call(); }); + // Check the contents. + EXPECT_TRUE(b.Empty()); + EXPECT_EQ(b.Size(), 0u); + EXPECT_EQ(b.Data(), nullptr); + + // |b| is deleted when this scope exits. + EXPECT_CALL(mockDeleter, Call()); + } +} + +// Test that move construction moves the data from one blob into the new one. +TEST(BlobTests, MoveConstruct) { + // Create the blob. + Blob b1 = CreateBlob(10); + char data[10] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; + memcpy(b1.Data(), data, sizeof(data)); + + // Move construct b2 from b1. + Blob b2(std::move(b1)); + + // Data should be moved. + EXPECT_FALSE(b2.Empty()); + EXPECT_EQ(b2.Size(), 10u); + ASSERT_NE(b2.Data(), nullptr); + EXPECT_EQ(memcmp(b2.Data(), data, sizeof(data)), 0); +} + +// Test that move assignment moves the data from one blob into another. +TEST(BlobTests, MoveAssign) { + // Create the blob. + Blob b1 = CreateBlob(10); + char data[10] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; + memcpy(b1.Data(), data, sizeof(data)); + + // Move assign b2 from b1. + Blob b2; + b2 = std::move(b1); + + // Data should be moved. + EXPECT_FALSE(b2.Empty()); + EXPECT_EQ(b2.Size(), 10u); + ASSERT_NE(b2.Data(), nullptr); + EXPECT_EQ(memcmp(b2.Data(), data, sizeof(data)), 0); +} + +// Test that move assignment can replace the contents of the moved-to blob. +TEST(BlobTests, MoveAssignOver) { + // Create the blob. + Blob b1 = CreateBlob(10); + char data[10] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; + memcpy(b1.Data(), data, sizeof(data)); + + // Create another blob with a mock deleter. + testing::StrictMock> mockDeleter; + Blob b2 = Blob::UnsafeCreateWithDeleter(nullptr, 0, [&]() { mockDeleter.Call(); }); + + // Move b1 into b2, replacing b2's contents, and expect the deleter to be called. + EXPECT_CALL(mockDeleter, Call()); + b2 = std::move(b1); + + // Data should be moved. + EXPECT_FALSE(b2.Empty()); + EXPECT_EQ(b2.Size(), 10u); + ASSERT_NE(b2.Data(), nullptr); + EXPECT_EQ(memcmp(b2.Data(), data, sizeof(data)), 0); +} + +} // namespace + +} // namespace dawn::native