Adds destroy handling for BindGroupLayout without new backend changes yet.

- Start tracking BindGroupLayout objects at construction
- Utilizes untrack tag for blueprint layouts for caching purposes
- Adds dawn native test file for testing utilities that require static dawn native lib
- Adds testing macros and mocks for simple sanity unit testing

Bug: dawn:628
Change-Id: Ic85b60e9574e67cc5fc1804cc5300cd1f3a0f6fd
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/65862
Commit-Queue: Loko Kung <lokokung@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/src/dawn_native/BindGroupLayout.cpp b/src/dawn_native/BindGroupLayout.cpp
index 5c2ea42..f047692 100644
--- a/src/dawn_native/BindGroupLayout.cpp
+++ b/src/dawn_native/BindGroupLayout.cpp
@@ -360,7 +360,8 @@
 
     BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device,
                                              const BindGroupLayoutDescriptor* descriptor,
-                                             PipelineCompatibilityToken pipelineCompatibilityToken)
+                                             PipelineCompatibilityToken pipelineCompatibilityToken,
+                                             ApiObjectBase::UntrackedByDeviceTag tag)
         : ApiObjectBase(device, kLabelNotImplemented),
           mBindingInfo(BindingIndex(descriptor->entryCount)),
           mPipelineCompatibilityToken(pipelineCompatibilityToken) {
@@ -387,15 +388,31 @@
         ASSERT(mBindingInfo.size() <= kMaxBindingsPerPipelineLayoutTyped);
     }
 
+    BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device,
+                                             const BindGroupLayoutDescriptor* descriptor,
+                                             PipelineCompatibilityToken pipelineCompatibilityToken)
+        : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken, kUntrackedByDevice) {
+        TrackInDevice();
+    }
+
     BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device, ObjectBase::ErrorTag tag)
         : ApiObjectBase(device, tag) {
     }
 
-    BindGroupLayoutBase::~BindGroupLayoutBase() {
-        // Do not uncache the actual cached object if we are a blueprint
-        if (IsCachedReference()) {
-            GetDevice()->UncacheBindGroupLayout(this);
+    BindGroupLayoutBase::BindGroupLayoutBase(DeviceBase* device)
+        : ApiObjectBase(device, kLabelNotImplemented) {
+        TrackInDevice();
+    }
+
+    bool BindGroupLayoutBase::DestroyApiObject() {
+        bool wasDestroyed = ApiObjectBase::DestroyApiObject();
+        if (wasDestroyed) {
+            // Do not uncache the actual cached object if we are a blueprint
+            if (IsCachedReference()) {
+                GetDevice()->UncacheBindGroupLayout(this);
+            }
         }
+        return wasDestroyed;
     }
 
     // static
diff --git a/src/dawn_native/BindGroupLayout.h b/src/dawn_native/BindGroupLayout.h
index 5f75eb6..61b811f 100644
--- a/src/dawn_native/BindGroupLayout.h
+++ b/src/dawn_native/BindGroupLayout.h
@@ -44,11 +44,15 @@
       public:
         BindGroupLayoutBase(DeviceBase* device,
                             const BindGroupLayoutDescriptor* descriptor,
+                            PipelineCompatibilityToken pipelineCompatibilityToken,
+                            ApiObjectBase::UntrackedByDeviceTag tag);
+        BindGroupLayoutBase(DeviceBase* device,
+                            const BindGroupLayoutDescriptor* descriptor,
                             PipelineCompatibilityToken pipelineCompatibilityToken);
-        ~BindGroupLayoutBase() override;
 
         static BindGroupLayoutBase* MakeError(DeviceBase* device);
 
+        bool DestroyApiObject() override;
         ObjectType GetType() const override;
 
         // A map from the BindingNumber to its packed BindingIndex.
@@ -85,7 +89,6 @@
         // ignoring their compatibility groups.
         bool IsLayoutEqual(const BindGroupLayoutBase* other,
                            bool excludePipelineCompatibiltyToken = false) const;
-
         PipelineCompatibilityToken GetPipelineCompatibilityToken() const;
 
         struct BufferBindingData {
@@ -110,6 +113,9 @@
         BindingDataPointers ComputeBindingDataPointers(void* dataStart) const;
 
       protected:
+        // Constructor used only for mocking and testing.
+        BindGroupLayoutBase(DeviceBase* device);
+
         template <typename BindGroup>
         SlabAllocator<BindGroup> MakeFrontendBindGroupAllocator(size_t size) {
             return SlabAllocator<BindGroup>(
diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp
index bc6f6a8..a867110 100644
--- a/src/dawn_native/Device.cpp
+++ b/src/dawn_native/Device.cpp
@@ -200,6 +200,10 @@
         }
     }
 
+    DeviceBase::DeviceBase() : mState(State::Alive) {
+        mCaches = std::make_unique<DeviceBase::Caches>();
+    }
+
     DeviceBase::~DeviceBase() = default;
 
     MaybeError DeviceBase::Initialize(QueueBase* defaultQueue) {
@@ -270,7 +274,9 @@
         // that this only considers the immediate frontend dependencies, while backend objects could
         // add complications and extra dependencies.
         // TODO(dawn/628) Add types into the array as they are implemented.
-        static constexpr std::array<ObjectType, 0> kObjectTypeDependencyOrder = {};
+        static constexpr std::array<ObjectType, 1> kObjectTypeDependencyOrder = {
+            ObjectType::BindGroupLayout,
+        };
 
         // We first move all objects out from the tracking list into a separate list so that we can
         // avoid locking the same mutex twice. We can then iterate across the separate list to call
@@ -631,7 +637,8 @@
     ResultOrError<Ref<BindGroupLayoutBase>> DeviceBase::GetOrCreateBindGroupLayout(
         const BindGroupLayoutDescriptor* descriptor,
         PipelineCompatibilityToken pipelineCompatibilityToken) {
-        BindGroupLayoutBase blueprint(this, descriptor, pipelineCompatibilityToken);
+        BindGroupLayoutBase blueprint(this, descriptor, pipelineCompatibilityToken,
+                                      ApiObjectBase::kUntrackedByDevice);
 
         const size_t blueprintHash = blueprint.ComputeContentHash();
         blueprint.SetContentHash(blueprintHash);
diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h
index 5d51096..c2969c3 100644
--- a/src/dawn_native/Device.h
+++ b/src/dawn_native/Device.h
@@ -23,6 +23,7 @@
 #include "dawn_native/Limits.h"
 #include "dawn_native/ObjectBase.h"
 #include "dawn_native/ObjectType_autogen.h"
+#include "dawn_native/RenderPipeline.h"
 #include "dawn_native/StagingBuffer.h"
 #include "dawn_native/Toggles.h"
 
@@ -354,6 +355,9 @@
         void APISetLabel(const char* label);
 
       protected:
+        // Constructor used only for mocking and testing.
+        DeviceBase();
+
         void SetToggle(Toggle toggle, bool isEnabled);
         void ForceSetToggle(Toggle toggle, bool isEnabled);
 
diff --git a/src/dawn_native/Error.h b/src/dawn_native/Error.h
index f5ed5a9..7e6726e 100644
--- a/src/dawn_native/Error.h
+++ b/src/dawn_native/Error.h
@@ -135,13 +135,52 @@
 
     // DAWN_TRY_ASSIGN is the same as DAWN_TRY for ResultOrError and assigns the success value, if
     // any, to VAR.
-#define DAWN_TRY_ASSIGN(VAR, EXPR)                                            \
+#define DAWN_TRY_ASSIGN(VAR, EXPR) DAWN_TRY_ASSIGN_WITH_CLEANUP(VAR, EXPR, {})
+
+    // Argument helpers are used to determine which macro implementations should be called when
+    // overloading with different number of variables.
+#define DAWN_ERROR_UNIMPLEMENTED_MACRO_(...) UNREACHABLE()
+#define DAWN_ERROR_GET_5TH_ARG_HELPER_(_1, _2, _3, _4, NAME, ...) NAME
+#define DAWN_ERROR_GET_5TH_ARG_(args) DAWN_ERROR_GET_5TH_ARG_HELPER_ args
+
+    // DAWN_TRY_ASSIGN_WITH_CLEANUP is overloaded with 2 version so that users can override the
+    // return value of the macro when necessary. This is particularly useful if the function
+    // calling the macro may want to return void instead of the error, i.e. in a test where we may
+    // just want to assert and fail if the assign cannot go through. In both the cleanup and return
+    // clauses, users can use the `error` variable to access the pointer to the acquired error.
+    //
+    // Example usages:
+    //     3 Argument Case:
+    //          Result res;
+    //          DAWN_TRY_ASSIGN_WITH_CLEANUP(
+    //              res, GetResultOrErrorFunction(), { AddAdditionalErrorInformation(error.get()); }
+    //          );
+    //
+    //     4 Argument Case:
+    //          bool FunctionThatReturnsBool() {
+    //              DAWN_TRY_ASSIGN_WITH_CLEANUP(
+    //                  res, GetResultOrErrorFunction(),
+    //                  { AddAdditionalErrorInformation(error.get()); },
+    //                  false
+    //              );
+    //          }
+#define DAWN_TRY_ASSIGN_WITH_CLEANUP(...)                                       \
+    DAWN_ERROR_GET_5TH_ARG_((__VA_ARGS__, DAWN_TRY_ASSIGN_WITH_CLEANUP_IMPL_4_, \
+                             DAWN_TRY_ASSIGN_WITH_CLEANUP_IMPL_3_,              \
+                             DAWN_ERROR_UNIMPLEMENTED_MACRO_))                  \
+    (__VA_ARGS__)
+
+#define DAWN_TRY_ASSIGN_WITH_CLEANUP_IMPL_3_(VAR, EXPR, BODY) \
+    DAWN_TRY_ASSIGN_WITH_CLEANUP_IMPL_4_(VAR, EXPR, BODY, std::move(error))
+
+#define DAWN_TRY_ASSIGN_WITH_CLEANUP_IMPL_4_(VAR, EXPR, BODY, RET)            \
     {                                                                         \
         auto DAWN_LOCAL_VAR = EXPR;                                           \
         if (DAWN_UNLIKELY(DAWN_LOCAL_VAR.IsError())) {                        \
             std::unique_ptr<ErrorData> error = DAWN_LOCAL_VAR.AcquireError(); \
+            {BODY} /* comment to force the formatter to insert a newline */   \
             error->AppendBacktrace(__FILE__, __func__, __LINE__);             \
-            return {std::move(error)};                                        \
+            return (RET);                                                     \
         }                                                                     \
         VAR = DAWN_LOCAL_VAR.AcquireSuccess();                                \
     }                                                                         \
diff --git a/src/dawn_native/ObjectBase.h b/src/dawn_native/ObjectBase.h
index 8b14b77..6dd1824 100644
--- a/src/dawn_native/ObjectBase.h
+++ b/src/dawn_native/ObjectBase.h
@@ -45,6 +45,8 @@
       public:
         struct LabelNotImplementedTag {};
         static constexpr LabelNotImplementedTag kLabelNotImplemented = {};
+        struct UntrackedByDeviceTag {};
+        static constexpr UntrackedByDeviceTag kUntrackedByDevice = {};
 
         ApiObjectBase(DeviceBase* device, LabelNotImplementedTag tag);
         ApiObjectBase(DeviceBase* device, const char* label);
diff --git a/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp b/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
index 761b8f7..02fb9f1 100644
--- a/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
+++ b/src/dawn_native/d3d12/BindGroupLayoutD3D12.cpp
@@ -131,6 +131,10 @@
             device->GetSamplerStagingDescriptorAllocator(GetSamplerDescriptorCount());
     }
 
+    BindGroupLayout::~BindGroupLayout() {
+        DestroyApiObject();
+    }
+
     ResultOrError<Ref<BindGroup>> BindGroupLayout::AllocateBindGroup(
         Device* device,
         const BindGroupDescriptor* descriptor) {
diff --git a/src/dawn_native/d3d12/BindGroupLayoutD3D12.h b/src/dawn_native/d3d12/BindGroupLayoutD3D12.h
index abf6702..e55c3df 100644
--- a/src/dawn_native/d3d12/BindGroupLayoutD3D12.h
+++ b/src/dawn_native/d3d12/BindGroupLayoutD3D12.h
@@ -64,7 +64,7 @@
         BindGroupLayout(Device* device,
                         const BindGroupLayoutDescriptor* descriptor,
                         PipelineCompatibilityToken pipelineCompatibilityToken);
-        ~BindGroupLayout() override = default;
+        ~BindGroupLayout() override;
 
         // Contains the offset into the descriptor heap for the given resource view. Samplers and
         // non-samplers are stored in separate descriptor heaps, so the offsets should be unique
diff --git a/src/dawn_native/metal/BindGroupLayoutMTL.h b/src/dawn_native/metal/BindGroupLayoutMTL.h
index 1d2c2a9..bbbc959 100644
--- a/src/dawn_native/metal/BindGroupLayoutMTL.h
+++ b/src/dawn_native/metal/BindGroupLayoutMTL.h
@@ -36,7 +36,7 @@
         BindGroupLayout(DeviceBase* device,
                         const BindGroupLayoutDescriptor* descriptor,
                         PipelineCompatibilityToken pipelineCompatibilityToken);
-        ~BindGroupLayout() override = default;
+        ~BindGroupLayout() override;
 
         SlabAllocator<BindGroup> mBindGroupAllocator;
     };
diff --git a/src/dawn_native/metal/BindGroupLayoutMTL.mm b/src/dawn_native/metal/BindGroupLayoutMTL.mm
index 5d748c1..a1c8255 100644
--- a/src/dawn_native/metal/BindGroupLayoutMTL.mm
+++ b/src/dawn_native/metal/BindGroupLayoutMTL.mm
@@ -33,6 +33,10 @@
           mBindGroupAllocator(MakeFrontendBindGroupAllocator<BindGroup>(4096)) {
     }
 
+    BindGroupLayout::~BindGroupLayout() {
+        DestroyApiObject();
+    }
+
     Ref<BindGroup> BindGroupLayout::AllocateBindGroup(Device* device,
                                                       const BindGroupDescriptor* descriptor) {
         return AcquireRef(mBindGroupAllocator.Allocate(device, descriptor));
diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp
index 4f08b1b..17030dd 100644
--- a/src/dawn_native/null/DeviceNull.cpp
+++ b/src/dawn_native/null/DeviceNull.cpp
@@ -263,6 +263,18 @@
           BindGroupBase(device, descriptor, mBindingDataAllocation) {
     }
 
+    // BindGroupLayout
+
+    BindGroupLayout::BindGroupLayout(DeviceBase* device,
+                                     const BindGroupLayoutDescriptor* descriptor,
+                                     PipelineCompatibilityToken pipelineCompatibilityToken)
+        : BindGroupLayoutBase(device, descriptor, pipelineCompatibilityToken) {
+    }
+
+    BindGroupLayout::~BindGroupLayout() {
+        DestroyApiObject();
+    }
+
     // Buffer
 
     Buffer::Buffer(Device* device, const BufferDescriptor* descriptor)
diff --git a/src/dawn_native/null/DeviceNull.h b/src/dawn_native/null/DeviceNull.h
index be1c613..0f29139 100644
--- a/src/dawn_native/null/DeviceNull.h
+++ b/src/dawn_native/null/DeviceNull.h
@@ -40,7 +40,7 @@
 
     class Adapter;
     class BindGroup;
-    using BindGroupLayout = BindGroupLayoutBase;
+    class BindGroupLayout;
     class Buffer;
     class CommandBuffer;
     using ComputePipeline = ComputePipelineBase;
@@ -200,6 +200,16 @@
         ~BindGroup() override = default;
     };
 
+    class BindGroupLayout final : public BindGroupLayoutBase {
+      public:
+        BindGroupLayout(DeviceBase* device,
+                        const BindGroupLayoutDescriptor* descriptor,
+                        PipelineCompatibilityToken pipelineCompatibilityToken);
+
+      private:
+        ~BindGroupLayout() override;
+    };
+
     class Buffer final : public BufferBase {
       public:
         Buffer(Device* device, const BufferDescriptor* descriptor);
diff --git a/src/dawn_native/opengl/BindGroupLayoutGL.cpp b/src/dawn_native/opengl/BindGroupLayoutGL.cpp
index d008b1d..99cd5c23 100644
--- a/src/dawn_native/opengl/BindGroupLayoutGL.cpp
+++ b/src/dawn_native/opengl/BindGroupLayoutGL.cpp
@@ -25,6 +25,10 @@
           mBindGroupAllocator(MakeFrontendBindGroupAllocator<BindGroup>(4096)) {
     }
 
+    BindGroupLayout::~BindGroupLayout() {
+        DestroyApiObject();
+    }
+
     Ref<BindGroup> BindGroupLayout::AllocateBindGroup(Device* device,
                                                       const BindGroupDescriptor* descriptor) {
         return AcquireRef(mBindGroupAllocator.Allocate(device, descriptor));
diff --git a/src/dawn_native/opengl/BindGroupLayoutGL.h b/src/dawn_native/opengl/BindGroupLayoutGL.h
index 136bd0a..5061b02 100644
--- a/src/dawn_native/opengl/BindGroupLayoutGL.h
+++ b/src/dawn_native/opengl/BindGroupLayoutGL.h
@@ -33,7 +33,7 @@
         void DeallocateBindGroup(BindGroup* bindGroup);
 
       private:
-        ~BindGroupLayout() override = default;
+        ~BindGroupLayout() override;
         SlabAllocator<BindGroup> mBindGroupAllocator;
     };
 
diff --git a/src/dawn_native/vulkan/BindGroupLayoutVk.cpp b/src/dawn_native/vulkan/BindGroupLayoutVk.cpp
index b464758..eb52822 100644
--- a/src/dawn_native/vulkan/BindGroupLayoutVk.cpp
+++ b/src/dawn_native/vulkan/BindGroupLayoutVk.cpp
@@ -161,6 +161,7 @@
             device->fn.DestroyDescriptorSetLayout(device->GetVkDevice(), mHandle, nullptr);
             mHandle = VK_NULL_HANDLE;
         }
+        DestroyApiObject();
     }
 
     VkDescriptorSetLayout BindGroupLayout::GetHandle() const {
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 84a74f5..6105fb1 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -126,10 +126,29 @@
   ]
 }
 
+# Source code for mocks used for unit testing are separated from the rest of
+# sources so that they aren't included in non-test builds.
+source_set("dawn_native_mocks_sources") {
+  testonly = true
+
+  deps = [
+    ":gmock_and_gtest",
+    "${dawn_root}/src/dawn_native:dawn_native_sources",
+    "${dawn_root}/src/dawn_native:dawn_native_static",
+    "${dawn_root}/src/utils:dawn_utils",
+  ]
+
+  sources = [
+    "unittests/native/mocks/BindGroupLayoutMock.h",
+    "unittests/native/mocks/DeviceMock.h",
+  ]
+}
+
 test("dawn_unittests") {
   configs += [ "${dawn_root}/src/common:dawn_internal" ]
 
   deps = [
+    ":dawn_native_mocks_sources",
     ":gmock_and_gtest",
     ":mock_webgpu_gen",
     "${dawn_root}/src/common",
@@ -150,6 +169,8 @@
     "${dawn_root}/src/dawn_wire/client/ClientMemoryTransferService_mock.h",
     "${dawn_root}/src/dawn_wire/server/ServerMemoryTransferService_mock.cpp",
     "${dawn_root}/src/dawn_wire/server/ServerMemoryTransferService_mock.h",
+    "DawnNativeTest.cpp",
+    "DawnNativeTest.h",
     "MockCallback.h",
     "ToggleParser.cpp",
     "ToggleParser.h",
@@ -188,6 +209,7 @@
     "unittests/SystemUtilsTests.cpp",
     "unittests/ToBackendTests.cpp",
     "unittests/TypedIntegerTests.cpp",
+    "unittests/native/DestroyObjectTests.cpp",
     "unittests/validation/BindGroupValidationTests.cpp",
     "unittests/validation/BufferValidationTests.cpp",
     "unittests/validation/CommandBufferValidationTests.cpp",
diff --git a/src/tests/DawnNativeTest.cpp b/src/tests/DawnNativeTest.cpp
new file mode 100644
index 0000000..d39c8e0
--- /dev/null
+++ b/src/tests/DawnNativeTest.cpp
@@ -0,0 +1,30 @@
+// Copyright 2021 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 <gtest/gtest.h>
+
+#include "absl/strings/str_cat.h"
+#include "dawn_native/ErrorData.h"
+
+namespace dawn_native {
+
+    void AddFatalDawnFailure(const char* expression, const ErrorData* error) {
+        const auto& backtrace = error->GetBacktrace();
+        GTEST_MESSAGE_AT_(
+            backtrace.at(0).file, backtrace.at(0).line,
+            absl::StrCat(expression, " returned error: ", error->GetMessage()).c_str(),
+            ::testing::TestPartResult::kFatalFailure);
+    }
+
+}  // namespace dawn_native
diff --git a/src/tests/DawnNativeTest.h b/src/tests/DawnNativeTest.h
new file mode 100644
index 0000000..94fdafb
--- /dev/null
+++ b/src/tests/DawnNativeTest.h
@@ -0,0 +1,32 @@
+// Copyright 2021 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 TESTS_DAWNNATIVETEST_H_
+#define TESTS_DAWNNATIVETEST_H_
+
+#include <gtest/gtest.h>
+
+#include "dawn_native/ErrorData.h"
+
+namespace dawn_native {
+
+    // This is similar to DAWN_TRY_ASSIGN but produces a fatal GTest error if EXPR is an error.
+#define DAWN_ASSERT_AND_ASSIGN(VAR, EXPR) \
+    DAWN_TRY_ASSIGN_WITH_CLEANUP(VAR, EXPR, {}, AddFatalDawnFailure(#EXPR, error.get()))
+
+    void AddFatalDawnFailure(const char* expression, const ErrorData* error);
+
+}  // namespace dawn_native
+
+#endif  // TESTS_DAWNNATIVETEST_H_
diff --git a/src/tests/unittests/ErrorTests.cpp b/src/tests/unittests/ErrorTests.cpp
index 042784d..ff7b7fe 100644
--- a/src/tests/unittests/ErrorTests.cpp
+++ b/src/tests/unittests/ErrorTests.cpp
@@ -243,6 +243,78 @@
         ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage);
     }
 
+    // Check DAWN_TRY_ASSIGN handles successes correctly.
+    TEST(ErrorTests, TRY_RESULT_CLEANUP_Success) {
+        auto ReturnSuccess = []() -> ResultOrError<int*> { return &dummySuccess; };
+
+        // We need to check that DAWN_TRY_ASSIGN_WITH_CLEANUP doesn't return on successes and the
+        // cleanup is not called.
+        bool tryReturned = true;
+        bool tryCleanup = false;
+
+        auto Try = [ReturnSuccess, &tryReturned, &tryCleanup]() -> ResultOrError<int*> {
+            int* result = nullptr;
+            DAWN_TRY_ASSIGN_WITH_CLEANUP(result, ReturnSuccess(), { tryCleanup = true; });
+            tryReturned = false;
+
+            EXPECT_EQ(result, &dummySuccess);
+            return result;
+        };
+
+        ResultOrError<int*> result = Try();
+        ASSERT_TRUE(result.IsSuccess());
+        ASSERT_FALSE(tryReturned);
+        ASSERT_FALSE(tryCleanup);
+        ASSERT_EQ(result.AcquireSuccess(), &dummySuccess);
+    }
+
+    // Check DAWN_TRY_ASSIGN handles cleanups.
+    TEST(ErrorTests, TRY_RESULT_CLEANUP_Cleanup) {
+        auto ReturnError = []() -> ResultOrError<int*> {
+            return DAWN_VALIDATION_ERROR(dummyErrorMessage);
+        };
+
+        // We need to check that DAWN_TRY_ASSIGN_WITH_CLEANUP calls cleanup when error.
+        bool tryCleanup = false;
+
+        auto Try = [ReturnError, &tryCleanup]() -> ResultOrError<int*> {
+            int* result = nullptr;
+            DAWN_TRY_ASSIGN_WITH_CLEANUP(result, ReturnError(), { tryCleanup = true; });
+            DAWN_UNUSED(result);
+
+            // DAWN_TRY_ASSIGN_WITH_CLEANUP should return before this point
+            EXPECT_FALSE(true);
+            return &dummySuccess;
+        };
+
+        ResultOrError<int*> result = Try();
+        ASSERT_TRUE(result.IsError());
+
+        std::unique_ptr<ErrorData> errorData = result.AcquireError();
+        ASSERT_EQ(errorData->GetMessage(), dummyErrorMessage);
+        ASSERT_TRUE(tryCleanup);
+    }
+
+    // Check DAWN_TRY_ASSIGN can override return value when needed.
+    TEST(ErrorTests, TRY_RESULT_CLEANUP_OverrideReturn) {
+        auto ReturnError = []() -> ResultOrError<int*> {
+            return DAWN_VALIDATION_ERROR(dummyErrorMessage);
+        };
+
+        auto Try = [ReturnError]() -> bool {
+            int* result = nullptr;
+            DAWN_TRY_ASSIGN_WITH_CLEANUP(result, ReturnError(), {}, true);
+            DAWN_UNUSED(result);
+
+            // DAWN_TRY_ASSIGN_WITH_CLEANUP should return before this point
+            EXPECT_FALSE(true);
+            return false;
+        };
+
+        bool result = Try();
+        ASSERT_TRUE(result);
+    }
+
     // Check a MaybeError can be DAWN_TRIED in a function that returns an ResultOrError
     // Check DAWN_TRY handles errors correctly.
     TEST(ErrorTests, TRY_ConversionToErrorOrResult) {
diff --git a/src/tests/unittests/native/DestroyObjectTests.cpp b/src/tests/unittests/native/DestroyObjectTests.cpp
new file mode 100644
index 0000000..d197403
--- /dev/null
+++ b/src/tests/unittests/native/DestroyObjectTests.cpp
@@ -0,0 +1,73 @@
+// Copyright 2021 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 <gtest/gtest.h>
+
+#include "dawn_native/Toggles.h"
+#include "mocks/BindGroupLayoutMock.h"
+#include "mocks/DeviceMock.h"
+#include "tests/DawnNativeTest.h"
+
+namespace dawn_native { namespace {
+
+    using ::testing::ByMove;
+    using ::testing::InSequence;
+    using ::testing::Return;
+
+    TEST(DestroyObjectTests, BindGroupLayout) {
+        // Skipping validation on descriptors as coverage for validation is already present.
+        DeviceMock device;
+        device.SetToggle(Toggle::SkipValidation, true);
+
+        BindGroupLayoutMock* bindGroupLayoutMock = new BindGroupLayoutMock(&device);
+        EXPECT_CALL(*bindGroupLayoutMock, DestroyApiObjectImpl).Times(1);
+
+        BindGroupLayoutDescriptor desc = {};
+        Ref<BindGroupLayoutBase> bindGroupLayout;
+        EXPECT_CALL(device, CreateBindGroupLayoutImpl)
+            .WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock))));
+        DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, device.CreateBindGroupLayout(&desc));
+
+        EXPECT_TRUE(bindGroupLayout->IsAlive());
+        EXPECT_TRUE(bindGroupLayout->IsCachedReference());
+
+        bindGroupLayout->DestroyApiObject();
+        EXPECT_FALSE(bindGroupLayout->IsAlive());
+    }
+
+    // Destroying the objects on the device should result in all created objects being destroyed in
+    // order.
+    TEST(DestroyObjectTests, DestroyObjects) {
+        DeviceMock device;
+        device.SetToggle(Toggle::SkipValidation, true);
+
+        BindGroupLayoutMock* bindGroupLayoutMock = new BindGroupLayoutMock(&device);
+        {
+            InSequence seq;
+            EXPECT_CALL(*bindGroupLayoutMock, DestroyApiObjectImpl).Times(1);
+        }
+
+        BindGroupLayoutDescriptor desc = {};
+        Ref<BindGroupLayoutBase> bindGroupLayout;
+        EXPECT_CALL(device, CreateBindGroupLayoutImpl)
+            .WillOnce(Return(ByMove(AcquireRef(bindGroupLayoutMock))));
+        DAWN_ASSERT_AND_ASSIGN(bindGroupLayout, device.CreateBindGroupLayout(&desc));
+        EXPECT_TRUE(bindGroupLayout->IsAlive());
+        EXPECT_TRUE(bindGroupLayout->IsCachedReference());
+
+        device.DestroyObjects();
+        EXPECT_FALSE(bindGroupLayout->IsAlive());
+    }
+
+}}  // namespace dawn_native::
diff --git a/src/tests/unittests/native/mocks/BindGroupLayoutMock.h b/src/tests/unittests/native/mocks/BindGroupLayoutMock.h
new file mode 100644
index 0000000..6f8dba5
--- /dev/null
+++ b/src/tests/unittests/native/mocks/BindGroupLayoutMock.h
@@ -0,0 +1,38 @@
+// Copyright 2021 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 TESTS_UNITTESTS_NATIVE_MOCKS_BINDGROUPLAYOUT_MOCK_H_
+#define TESTS_UNITTESTS_NATIVE_MOCKS_BINDGROUPLAYOUT_MOCK_H_
+
+#include "dawn_native/BindGroupLayout.h"
+#include "dawn_native/Device.h"
+
+#include <gmock/gmock.h>
+
+namespace dawn_native {
+
+    class BindGroupLayoutMock final : public BindGroupLayoutBase {
+      public:
+        BindGroupLayoutMock(DeviceBase* device) : BindGroupLayoutBase(device) {
+        }
+        ~BindGroupLayoutMock() override {
+            DestroyApiObject();
+        }
+
+        MOCK_METHOD(void, DestroyApiObjectImpl, (), (override));
+    };
+
+}  // namespace dawn_native
+
+#endif  // TESTS_UNITTESTS_NATIVE_MOCKS_BINDGROUPLAYOUT_MOCK_H_
diff --git a/src/tests/unittests/native/mocks/DeviceMock.h b/src/tests/unittests/native/mocks/DeviceMock.h
new file mode 100644
index 0000000..e83e37a
--- /dev/null
+++ b/src/tests/unittests/native/mocks/DeviceMock.h
@@ -0,0 +1,116 @@
+// Copyright 2021 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 TESTS_UNITTESTS_NATIVE_MOCKS_DEVICE_MOCK_H_
+#define TESTS_UNITTESTS_NATIVE_MOCKS_DEVICE_MOCK_H_
+
+#include "dawn_native/Device.h"
+
+#include <gmock/gmock.h>
+
+namespace dawn_native {
+
+    class DeviceMock : public DeviceBase {
+      public:
+        // Exposes some protected functions for testing purposes.
+        using DeviceBase::DestroyObjects;
+        using DeviceBase::SetToggle;
+
+        MOCK_METHOD(ResultOrError<Ref<CommandBufferBase>>,
+                    CreateCommandBuffer,
+                    (CommandEncoder*, const CommandBufferDescriptor*),
+                    (override));
+
+        MOCK_METHOD(ResultOrError<std::unique_ptr<StagingBufferBase>>,
+                    CreateStagingBuffer,
+                    (size_t),
+                    (override));
+        MOCK_METHOD(MaybeError,
+                    CopyFromStagingToBuffer,
+                    (StagingBufferBase*, uint64_t, BufferBase*, uint64_t, uint64_t),
+                    (override));
+        MOCK_METHOD(
+            MaybeError,
+            CopyFromStagingToTexture,
+            (const StagingBufferBase*, const TextureDataLayout&, TextureCopy*, const Extent3D&),
+            (override));
+
+        MOCK_METHOD(uint32_t, GetOptimalBytesPerRowAlignment, (), (const, override));
+        MOCK_METHOD(uint64_t, GetOptimalBufferToTextureCopyOffsetAlignment, (), (const, override));
+
+        MOCK_METHOD(float, GetTimestampPeriodInNS, (), (const, override));
+
+        MOCK_METHOD(ResultOrError<Ref<BindGroupBase>>,
+                    CreateBindGroupImpl,
+                    (const BindGroupDescriptor*),
+                    (override));
+        MOCK_METHOD(ResultOrError<Ref<BindGroupLayoutBase>>,
+                    CreateBindGroupLayoutImpl,
+                    (const BindGroupLayoutDescriptor*, PipelineCompatibilityToken),
+                    (override));
+        MOCK_METHOD(ResultOrError<Ref<BufferBase>>,
+                    CreateBufferImpl,
+                    (const BufferDescriptor*),
+                    (override));
+        MOCK_METHOD(ResultOrError<Ref<ComputePipelineBase>>,
+                    CreateComputePipelineImpl,
+                    (const ComputePipelineDescriptor*),
+                    (override));
+        MOCK_METHOD(ResultOrError<Ref<PipelineLayoutBase>>,
+                    CreatePipelineLayoutImpl,
+                    (const PipelineLayoutDescriptor*),
+                    (override));
+        MOCK_METHOD(ResultOrError<Ref<QuerySetBase>>,
+                    CreateQuerySetImpl,
+                    (const QuerySetDescriptor*),
+                    (override));
+        MOCK_METHOD(Ref<RenderPipelineBase>,
+                    CreateUninitializedRenderPipelineImpl,
+                    (const RenderPipelineDescriptor*),
+                    (override));
+        MOCK_METHOD(ResultOrError<Ref<SamplerBase>>,
+                    CreateSamplerImpl,
+                    (const SamplerDescriptor*),
+                    (override));
+        MOCK_METHOD(ResultOrError<Ref<ShaderModuleBase>>,
+                    CreateShaderModuleImpl,
+                    (const ShaderModuleDescriptor*, ShaderModuleParseResult*),
+                    (override));
+        MOCK_METHOD(ResultOrError<Ref<SwapChainBase>>,
+                    CreateSwapChainImpl,
+                    (const SwapChainDescriptor*),
+                    (override));
+        MOCK_METHOD(ResultOrError<Ref<NewSwapChainBase>>,
+                    CreateSwapChainImpl,
+                    (Surface*, NewSwapChainBase*, const SwapChainDescriptor*),
+                    (override));
+        MOCK_METHOD(ResultOrError<Ref<TextureBase>>,
+                    CreateTextureImpl,
+                    (const TextureDescriptor*),
+                    (override));
+        MOCK_METHOD(ResultOrError<Ref<TextureViewBase>>,
+                    CreateTextureViewImpl,
+                    (TextureBase*, const TextureViewDescriptor*),
+                    (override));
+
+        MOCK_METHOD(MaybeError, TickImpl, (), (override));
+
+        MOCK_METHOD(ResultOrError<ExecutionSerial>, CheckAndUpdateCompletedSerials, (), (override));
+        MOCK_METHOD(void, DestroyImpl, (), (override));
+        MOCK_METHOD(MaybeError, WaitForIdleForDestruction, (), (override));
+    };
+
+}  // namespace dawn_native
+
+#endif  // TESTS_UNITTESTS_NATIVE_MOCKS_DEVICE_MOCK_H_