Don't call the uncaptured error callback after the device is lost

Bug: dawn:2459
Change-Id: I7d633e26d29b8c391be0c0376570707352a71553
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/176801
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Austin Eng <enga@chromium.org>
diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp
index 27c74ac..89a55c9 100644
--- a/src/dawn/native/Device.cpp
+++ b/src/dawn/native/Device.cpp
@@ -581,7 +581,7 @@
 
 void DeviceBase::HandleError(std::unique_ptr<ErrorData> error,
                              InternalErrorType additionalAllowedErrors,
-                             WGPUDeviceLostReason lost_reason) {
+                             WGPUDeviceLostReason lostReason) {
     AppendDebugLayerMessages(error.get());
 
     InternalErrorType type = error->GetType();
@@ -634,13 +634,13 @@
 
     const std::string messageStr = error->GetFormattedMessage();
     if (type == InternalErrorType::DeviceLost) {
-        // The device was lost, schedule the application callback's executation.
+        // The device was lost, schedule the application callback's execution.
         // Note: we don't invoke the callbacks directly here because it could cause re-entrances ->
         // possible deadlock.
         if (mDeviceLostCallback != nullptr) {
-            mCallbackTaskManager->AddCallbackTask([callback = mDeviceLostCallback, lost_reason,
+            mCallbackTaskManager->AddCallbackTask([callback = mDeviceLostCallback, lostReason,
                                                    messageStr, userdata = mDeviceLostUserdata] {
-                callback(lost_reason, messageStr.c_str(), userdata);
+                callback(lostReason, messageStr.c_str(), userdata);
             });
             mDeviceLostCallback = nullptr;
         }
@@ -658,9 +658,13 @@
         // if it isn't handled. DeviceLost is not handled here because it should be
         // handled by the lost callback.
         bool captured = mErrorScopeStack->HandleError(ToWGPUErrorType(type), messageStr);
-        if (!captured && mUncapturedErrorCallback != nullptr) {
-            mUncapturedErrorCallback(static_cast<WGPUErrorType>(ToWGPUErrorType(type)),
-                                     messageStr.c_str(), mUncapturedErrorUserdata);
+        if (!captured) {
+            // Only call the uncaptured error callback if the device is alive. After the
+            // device is lost, the uncaptured error callback should cease firing.
+            if (mUncapturedErrorCallback != nullptr && mState == State::Alive) {
+                mUncapturedErrorCallback(static_cast<WGPUErrorType>(ToWGPUErrorType(type)),
+                                         messageStr.c_str(), mUncapturedErrorUserdata);
+            }
         }
     }
 }
diff --git a/src/dawn/tests/end2end/D3DResourceWrappingTests.cpp b/src/dawn/tests/end2end/D3DResourceWrappingTests.cpp
index c3ef050..b45a1d9 100644
--- a/src/dawn/tests/end2end/D3DResourceWrappingTests.cpp
+++ b/src/dawn/tests/end2end/D3DResourceWrappingTests.cpp
@@ -1051,8 +1051,8 @@
 
     DestroyDevice();
 
-    ASSERT_DEVICE_ERROR(WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture,
-                                         &d3d11Texture, &externalImage));
+    WrapSharedHandle(&baseDawnDescriptor, &baseD3dDescriptor, &texture, &d3d11Texture,
+                     &externalImage);
 
     EXPECT_EQ(externalImage, nullptr);
     EXPECT_EQ(texture, nullptr);
diff --git a/src/dawn/tests/end2end/DestroyTests.cpp b/src/dawn/tests/end2end/DestroyTests.cpp
index 050ddf1..4c79683 100644
--- a/src/dawn/tests/end2end/DestroyTests.cpp
+++ b/src/dawn/tests/end2end/DestroyTests.cpp
@@ -191,14 +191,14 @@
     device.SetLabel(label.c_str());
 }
 
-// Device destroy before buffer submit will result in error.
+// Test device destroy before submit.
 TEST_P(DestroyTest, DestroyDeviceBeforeSubmit) {
     // TODO(crbug.com/dawn/628) Add more comprehensive tests with destroy and backends.
     DAWN_TEST_UNSUPPORTED_IF(UsesWire());
     wgpu::CommandBuffer commands = CreateTriangleCommandBuffer();
 
     DestroyDevice();
-    ASSERT_DEVICE_ERROR_MSG(queue.Submit(1, &commands), HasSubstr("[Device] is lost."));
+    queue.Submit(1, &commands);
 }
 
 // Regression test for crbug.com/1276928 where a lingering BGL reference in Vulkan with at least one
@@ -220,7 +220,7 @@
     DestroyDevice();
 
     wgpu::Queue queue = device.GetQueue();
-    ASSERT_DEVICE_ERROR(queue.OnSubmittedWorkDone(
+    queue.OnSubmittedWorkDone(
         [](WGPUQueueWorkDoneStatus status, void* userdata) {
             // TODO(crbug.com/dawn/2021): Wire and native differ slightly for now. Unify once we
             // decide on the correct result. In theory maybe we want to pretend that things succeed
@@ -232,7 +232,7 @@
                 EXPECT_EQ(status, WGPUQueueWorkDoneStatus_DeviceLost);
             }
         },
-        this));
+        this);
 }
 
 DAWN_INSTANTIATE_TEST(DestroyTest,
diff --git a/src/dawn/tests/end2end/DeviceLostTests.cpp b/src/dawn/tests/end2end/DeviceLostTests.cpp
index db93da0..5c8ebad 100644
--- a/src/dawn/tests/end2end/DeviceLostTests.cpp
+++ b/src/dawn/tests/end2end/DeviceLostTests.cpp
@@ -63,6 +63,7 @@
     }
 
     void TearDown() override {
+        instance.ProcessEvents();  // Flush all callbacks.
         mockQueueWorkDoneCallback = nullptr;
         DawnTest::TearDown();
     }
@@ -89,6 +90,11 @@
             WaitABit();
         }
     }
+
+    template <typename T>
+    void ExpectObjectIsError(const T& object) {
+        EXPECT_TRUE(dawn::native::CheckIsErrorForTesting(object.Get()));
+    }
 };
 
 // Test that DeviceLostCallback is invoked when LostForTestimg is called
@@ -96,14 +102,14 @@
     LoseDeviceForTesting();
 }
 
-// Test that submit fails when device is lost
-TEST_P(DeviceLostTest, SubmitFails) {
+// Test that submit fails after the device is lost
+TEST_P(DeviceLostTest, SubmitAfterDeviceLost) {
     wgpu::CommandBuffer commands;
     wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
     commands = encoder.Finish();
 
     LoseDeviceForTesting();
-    ASSERT_DEVICE_ERROR(queue.Submit(0, &commands));
+    queue.Submit(0, &commands);
 }
 
 // Test that CreateBindGroupLayout fails when device is lost
@@ -117,7 +123,7 @@
     wgpu::BindGroupLayoutDescriptor descriptor;
     descriptor.entryCount = 1;
     descriptor.entries = &entry;
-    ASSERT_DEVICE_ERROR(device.CreateBindGroupLayout(&descriptor));
+    ExpectObjectIsError(device.CreateBindGroupLayout(&descriptor));
 }
 
 // Test that GetBindGroupLayout fails when device is lost
@@ -137,7 +143,7 @@
     wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&descriptor);
 
     LoseDeviceForTesting();
-    ASSERT_DEVICE_ERROR(pipeline.GetBindGroupLayout(0).Get());
+    ExpectObjectIsError(pipeline.GetBindGroupLayout(0));
 }
 
 // Test that CreateBindGroup fails when device is lost
@@ -156,7 +162,7 @@
     descriptor.layout = nullptr;
     descriptor.entryCount = 1;
     descriptor.entries = &entry;
-    ASSERT_DEVICE_ERROR(device.CreateBindGroup(&descriptor));
+    ExpectObjectIsError(device.CreateBindGroup(&descriptor));
 }
 
 // Test that CreatePipelineLayout fails when device is lost
@@ -166,7 +172,7 @@
     wgpu::PipelineLayoutDescriptor descriptor;
     descriptor.bindGroupLayoutCount = 0;
     descriptor.bindGroupLayouts = nullptr;
-    ASSERT_DEVICE_ERROR(device.CreatePipelineLayout(&descriptor));
+    ExpectObjectIsError(device.CreatePipelineLayout(&descriptor));
 }
 
 // Tests that CreateRenderBundleEncoder fails when device is lost
@@ -176,7 +182,7 @@
     wgpu::RenderBundleEncoderDescriptor descriptor;
     descriptor.colorFormatCount = 0;
     descriptor.colorFormats = nullptr;
-    ASSERT_DEVICE_ERROR(device.CreateRenderBundleEncoder(&descriptor));
+    ExpectObjectIsError(device.CreateRenderBundleEncoder(&descriptor));
 }
 
 // Tests that CreateComputePipeline fails when device is lost
@@ -186,7 +192,7 @@
     wgpu::ComputePipelineDescriptor descriptor = {};
     descriptor.layout = nullptr;
     descriptor.compute.module = nullptr;
-    ASSERT_DEVICE_ERROR(device.CreateComputePipeline(&descriptor));
+    ExpectObjectIsError(device.CreateComputePipeline(&descriptor));
 }
 
 // Tests that CreateRenderPipeline fails when device is lost
@@ -194,21 +200,21 @@
     LoseDeviceForTesting();
 
     utils::ComboRenderPipelineDescriptor descriptor;
-    ASSERT_DEVICE_ERROR(device.CreateRenderPipeline(&descriptor));
+    ExpectObjectIsError(device.CreateRenderPipeline(&descriptor));
 }
 
 // Tests that CreateSampler fails when device is lost
 TEST_P(DeviceLostTest, CreateSamplerFails) {
     LoseDeviceForTesting();
 
-    ASSERT_DEVICE_ERROR(device.CreateSampler());
+    ExpectObjectIsError(device.CreateSampler());
 }
 
 // Tests that CreateShaderModule fails when device is lost
 TEST_P(DeviceLostTest, CreateShaderModuleFails) {
     LoseDeviceForTesting();
 
-    ASSERT_DEVICE_ERROR(utils::CreateShaderModule(device, R"(
+    ExpectObjectIsError(utils::CreateShaderModule(device, R"(
         @fragment
         fn main(@location(0) color : vec4f) -> @location(0) vec4f {
             return color;
@@ -231,7 +237,7 @@
     descriptor.dimension = wgpu::TextureDimension::e2D;
     descriptor.usage = wgpu::TextureUsage::RenderAttachment;
 
-    ASSERT_DEVICE_ERROR(device.CreateTexture(&descriptor));
+    ExpectObjectIsError(device.CreateTexture(&descriptor));
 }
 
 // Test that CreateBuffer fails when device is lost
@@ -241,7 +247,7 @@
     wgpu::BufferDescriptor bufferDescriptor;
     bufferDescriptor.size = sizeof(float);
     bufferDescriptor.usage = wgpu::BufferUsage::CopySrc;
-    ASSERT_DEVICE_ERROR(device.CreateBuffer(&bufferDescriptor));
+    ExpectObjectIsError(device.CreateBuffer(&bufferDescriptor));
 }
 
 // Test that buffer.MapAsync for writing fails after device is lost
@@ -252,8 +258,7 @@
     wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
 
     LoseDeviceForTesting();
-    ASSERT_DEVICE_ERROR(buffer.MapAsync(wgpu::MapMode::Write, 0, 4, MapFailCallback,
-                                        const_cast<int*>(&fakeUserData)));
+    buffer.MapAsync(wgpu::MapMode::Write, 0, 4, MapFailCallback, const_cast<int*>(&fakeUserData));
 }
 
 // Test that BufferMapAsync for writing calls back with device lost status when device lost after
@@ -269,8 +274,8 @@
     LoseDeviceForTesting();
 }
 
-// Test that buffer.Unmap fails after device is lost
-TEST_P(DeviceLostTest, BufferUnmapFails) {
+// Test that buffer.Unmap after device is lost
+TEST_P(DeviceLostTest, BufferUnmapAfterDeviceLost) {
     wgpu::BufferDescriptor bufferDescriptor;
     bufferDescriptor.size = sizeof(float);
     bufferDescriptor.usage = wgpu::BufferUsage::MapWrite;
@@ -278,7 +283,7 @@
     wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
 
     LoseDeviceForTesting();
-    ASSERT_DEVICE_ERROR(buffer.Unmap());
+    buffer.Unmap();
 }
 
 // Test that mappedAtCreation fails after device is lost
@@ -289,7 +294,7 @@
     bufferDescriptor.mappedAtCreation = true;
 
     LoseDeviceForTesting();
-    ASSERT_DEVICE_ERROR(device.CreateBuffer(&bufferDescriptor));
+    ExpectObjectIsError(device.CreateBuffer(&bufferDescriptor));
 }
 
 // Test that BufferMapAsync for reading fails after device is lost
@@ -301,8 +306,7 @@
     wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
 
     LoseDeviceForTesting();
-    ASSERT_DEVICE_ERROR(buffer.MapAsync(wgpu::MapMode::Read, 0, 4, MapFailCallback,
-                                        const_cast<int*>(&fakeUserData)));
+    buffer.MapAsync(wgpu::MapMode::Read, 0, 4, MapFailCallback, const_cast<int*>(&fakeUserData));
 }
 
 // Test that BufferMapAsync for reading calls back with device lost status when device lost after
@@ -319,8 +323,8 @@
     LoseDeviceForTesting();
 }
 
-// Test that WriteBuffer fails after device is lost
-TEST_P(DeviceLostTest, WriteBufferFails) {
+// Test that WriteBuffer after device is lost
+TEST_P(DeviceLostTest, WriteBufferAfterDeviceLost) {
     wgpu::BufferDescriptor bufferDescriptor;
     bufferDescriptor.size = sizeof(float);
     bufferDescriptor.usage = wgpu::BufferUsage::MapRead | wgpu::BufferUsage::CopyDst;
@@ -329,7 +333,7 @@
 
     LoseDeviceForTesting();
     float data = 12.0f;
-    ASSERT_DEVICE_ERROR(queue.WriteBuffer(buffer, 0, &data, sizeof(data)));
+    queue.WriteBuffer(buffer, 0, &data, sizeof(data));
 }
 
 // Test it's possible to GetMappedRange on a buffer created mapped after device loss
@@ -340,7 +344,8 @@
     desc.size = 4;
     desc.usage = wgpu::BufferUsage::CopySrc;
     desc.mappedAtCreation = true;
-    ASSERT_DEVICE_ERROR(wgpu::Buffer buffer = device.CreateBuffer(&desc));
+    wgpu::Buffer buffer = device.CreateBuffer(&desc);
+    ExpectObjectIsError(buffer);
 
     ASSERT_NE(buffer.GetMappedRange(), nullptr);
 }
@@ -403,17 +408,17 @@
     wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
 
     LoseDeviceForTesting();
-    ASSERT_DEVICE_ERROR(encoder.Finish());
+    ExpectObjectIsError(encoder.Finish());
 }
 
-// Test that QueueOnSubmittedWorkDone fails after device is lost.
-TEST_P(DeviceLostTest, QueueOnSubmittedWorkDoneFails) {
+// Test that QueueOnSubmittedWorkDone after device is lost.
+TEST_P(DeviceLostTest, QueueOnSubmittedWorkDoneAfterDeviceLost) {
     LoseDeviceForTesting();
 
     // callback should have device lost status
     EXPECT_CALL(*mockQueueWorkDoneCallback, Call(WGPUQueueWorkDoneStatus_DeviceLost, nullptr))
         .Times(1);
-    ASSERT_DEVICE_ERROR(queue.OnSubmittedWorkDone(ToMockQueueWorkDone, nullptr));
+    queue.OnSubmittedWorkDone(ToMockQueueWorkDone, nullptr);
 }
 
 // Test that QueueOnSubmittedWorkDone when the device is lost after calling OnSubmittedWorkDone
diff --git a/src/dawn/tests/end2end/EventTests.cpp b/src/dawn/tests/end2end/EventTests.cpp
index e1a6371..f05939c 100644
--- a/src/dawn/tests/end2end/EventTests.cpp
+++ b/src/dawn/tests/end2end/EventTests.cpp
@@ -360,14 +360,8 @@
 TEST_P(EventCompletionTests, WorkDoneAfterDeviceLoss) {
     TrivialSubmit();
     LoseTestDevice();
-    // Tracking and waiting need to be done together w.r.t the device error assertion because error
-    // assertion in DawnTest.h currently calls ProcessEvents which will cause the work done event to
-    // trigger before the TestWaitAll call.
-    auto TestF = [&]() {
-        TrackForTest(OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success));
-        TestWaitAll();
-    };
-    ASSERT_DEVICE_ERROR_ON(testDevice, TestF());
+    TrackForTest(OnSubmittedWorkDone(WGPUQueueWorkDoneStatus_Success));
+    TestWaitAll();
 }
 
 // WorkDone event twice after submitting some trivial work.
diff --git a/src/dawn/tests/end2end/SwapChainValidationTests.cpp b/src/dawn/tests/end2end/SwapChainValidationTests.cpp
index 6f0e6c5..a902e25 100644
--- a/src/dawn/tests/end2end/SwapChainValidationTests.cpp
+++ b/src/dawn/tests/end2end/SwapChainValidationTests.cpp
@@ -387,13 +387,13 @@
     ASSERT_DEVICE_ERROR(replacedSwapChain.Present());
 }
 
-// Test that new swap chain present fails after device is lost
-TEST_P(SwapChainValidationTests, SwapChainPresentFailsAfterDeviceLost) {
+// Test that new swap chain present after device is lost
+TEST_P(SwapChainValidationTests, SwapChainPresentAfterDeviceLost) {
     wgpu::SwapChain swapchain = CreateSwapChain(surface, &goodDescriptor);
     swapchain.GetCurrentTexture();
 
     LoseDeviceForTesting();
-    ASSERT_DEVICE_ERROR(swapchain.Present());
+    swapchain.Present();
 }
 
 // Test that new swap chain get current texture fails after device is lost
@@ -401,13 +401,14 @@
     wgpu::SwapChain swapchain = CreateSwapChain(surface, &goodDescriptor);
 
     LoseDeviceForTesting();
-    ASSERT_DEVICE_ERROR(swapchain.GetCurrentTexture());
+    EXPECT_TRUE(dawn::native::CheckIsErrorForTesting(swapchain.GetCurrentTexture().Get()));
 }
 
 // Test that creation of a new swapchain fails after device is lost
 TEST_P(SwapChainValidationTests, CreateSwapChainFailsAfterDevLost) {
     LoseDeviceForTesting();
-    ASSERT_DEVICE_ERROR(CreateSwapChain(surface, &goodDescriptor));
+    EXPECT_TRUE(
+        dawn::native::CheckIsErrorForTesting(CreateSwapChain(surface, &goodDescriptor).Get()));
 }
 
 DAWN_INSTANTIATE_TEST(SwapChainValidationTests, MetalBackend(), NullBackend());
diff --git a/src/dawn/tests/unittests/validation/CommandBufferValidationTests.cpp b/src/dawn/tests/unittests/validation/CommandBufferValidationTests.cpp
index 72324a5..093876f 100644
--- a/src/dawn/tests/unittests/validation/CommandBufferValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/CommandBufferValidationTests.cpp
@@ -429,21 +429,19 @@
         // encoding.
         wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&placeholderRenderPass);
         pass.End();
-        ASSERT_DEVICE_ERROR(encoder.Finish(), HasSubstr("Destroyed encoder cannot be finished."));
+        encoder.Finish();
     }
 
     // Device destroyed after encoding.
     {
         ExpectDeviceDestruction();
         device.Destroy();
-        ASSERT_DEVICE_ERROR(wgpu::CommandEncoder encoder = device.CreateCommandEncoder(),
-                            HasSubstr("[Device] is lost"));
+        wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
         // The encoder should not accessing any device info if device is destroyed when try
         // encoding.
         wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&placeholderRenderPass);
         pass.End();
-        ASSERT_DEVICE_ERROR(encoder.Finish(),
-                            HasSubstr("[Invalid CommandEncoder (unlabeled)] is invalid."));
+        encoder.Finish();
     }
 }
 
diff --git a/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp b/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
index 635f17d..f3ae18f 100644
--- a/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
+++ b/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
@@ -64,9 +64,7 @@
     device.Destroy();
 
     wgpu::SharedBufferMemoryDescriptor desc;
-    ASSERT_DEVICE_ERROR_MSG(
-        wgpu::SharedBufferMemory memory = device.ImportSharedBufferMemory(&desc),
-        HasSubstr("lost"));
+    wgpu::SharedBufferMemory memory = device.ImportSharedBufferMemory(&desc);
 }
 
 // Test that SharedBufferMemory::IsDeviceLost() returns the expected value before and
diff --git a/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp b/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp
index cc9e1c7..db95ffe 100644
--- a/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp
+++ b/src/dawn/tests/white_box/SharedTextureMemoryTests.cpp
@@ -735,14 +735,29 @@
     EXPECT_EQ(exportInfo.type, wgpu::SharedFenceType::Undefined);
 }
 
-// Test that it is an error to import a shared texture memory when the device is destroyed
+// Test importing a shared texture memory when the device is destroyed
 TEST_P(SharedTextureMemoryTests, ImportSharedTextureMemoryDeviceDestroyed) {
     device.Destroy();
 
-    wgpu::SharedTextureMemoryDescriptor desc;
-    ASSERT_DEVICE_ERROR_MSG(
-        wgpu::SharedTextureMemory memory = device.ImportSharedTextureMemory(&desc),
-        HasSubstr("lost"));
+    wgpu::SharedTextureMemory memory;
+    if (GetParam().mBackend->Name().rfind("OpaqueFD", 0) == 0) {
+        // The OpaqueFD backend for `CreateSharedTextureMemory` uses several
+        // Vulkan device internals before and after the actual call to
+        // ImportSharedTextureMemory. We can't easily make it import with
+        // a destroyed device, so create the SharedTextureMemory with
+        // an invalid descriptor instead. This still tests that an uncaptured
+        // error is not generated on the import call when the device is lost.
+        wgpu::SharedTextureMemoryDescriptor desc;
+        memory = device.ImportSharedTextureMemory(&desc);
+    } else {
+        memory = GetParam().mBackend->CreateSharedTextureMemory(device);
+    }
+
+    wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
+    beginDesc.concurrentRead = false;
+    beginDesc.initialized = true;
+    // That the begin access does not succeed since the device is destroyed.
+    EXPECT_FALSE(memory.BeginAccess(memory.CreateTexture(), &beginDesc));
 }
 
 // Test that SharedTextureMemory::IsDeviceLost() returns the expected value before and
@@ -765,13 +780,61 @@
     EXPECT_TRUE(memory.IsDeviceLost());
 }
 
-// Test that it is an error to import a shared fence when the device is destroyed
+// Test importing a shared fence when the device is destroyed
 TEST_P(SharedTextureMemoryTests, ImportSharedFenceDeviceDestroyed) {
+    // Create a shared texture memory and texture
+    wgpu::SharedTextureMemory memory = GetParam().mBackend->CreateSharedTextureMemory(device);
+    wgpu::Texture texture = memory.CreateTexture();
+
+    // Begin access to use the texture
+    wgpu::SharedTextureMemoryBeginAccessDescriptor beginDesc = {};
+    beginDesc.concurrentRead = false;
+    beginDesc.initialized = true;
+    auto backendBeginState = GetParam().mBackend->ChainInitialBeginState(&beginDesc);
+    EXPECT_TRUE(memory.BeginAccess(texture, &beginDesc));
+
+    // Use the texture so there is a fence to export on end access.
+    wgpu::SharedTextureMemoryProperties properties;
+    memory.GetProperties(&properties);
+    if (properties.usage & wgpu::TextureUsage::RenderAttachment) {
+        UseInRenderPass(device, texture);
+    } else if (properties.format != wgpu::TextureFormat::R8BG8Biplanar420Unorm &&
+               properties.format != wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm &&
+               properties.format != wgpu::TextureFormat::R8BG8A8Triplanar420Unorm) {
+        if (properties.usage & wgpu::TextureUsage::CopySrc) {
+            UseInCopy(device, texture);
+        } else if (properties.usage & wgpu::TextureUsage::CopyDst) {
+            wgpu::Extent3D writeSize = {1, 1, 1};
+            wgpu::ImageCopyTexture dest = {};
+            dest.texture = texture;
+            wgpu::TextureDataLayout dataLayout = {};
+            uint64_t data[2];
+            device.GetQueue().WriteTexture(&dest, &data, sizeof(data), &dataLayout, &writeSize);
+        }
+    }
+
+    // End access to export a fence.
+    wgpu::SharedTextureMemoryEndAccessState endState = {};
+    auto backendEndState = GetParam().mBackend->ChainEndState(&endState);
+    EXPECT_TRUE(memory.EndAccess(texture, &endState));
+
+    // Destroy the device.
     device.Destroy();
 
-    wgpu::SharedFenceDescriptor desc;
-    ASSERT_DEVICE_ERROR_MSG(wgpu::SharedFence fence = device.ImportSharedFence(&desc),
-                            HasSubstr("lost"));
+    // Import the shared fence to the destroyed device.
+    std::vector<wgpu::SharedFence> sharedFences(endState.fenceCount);
+    for (size_t i = 0; i < endState.fenceCount; ++i) {
+        sharedFences[i] = GetParam().mBackend->ImportFenceTo(device, endState.fences[i]);
+    }
+    beginDesc.fenceCount = endState.fenceCount;
+    beginDesc.fences = sharedFences.data();
+    beginDesc.signaledValues = endState.signaledValues;
+    beginDesc.concurrentRead = false;
+    beginDesc.initialized = endState.initialized;
+    backendBeginState = GetParam().mBackend->ChainBeginState(&beginDesc, endState);
+
+    // Begin access should fail.
+    EXPECT_FALSE(memory.BeginAccess(texture, &beginDesc));
 }
 
 // Test calling GetProperties with an error memory. The properties are filled with 0/None/Undefined.