blob: 3d8524a9f71aacade52fc1d8b64e78ec6262d91d [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/zx/eventpair.h>
#include "garnet/lib/ui/gfx/resources/nodes/entity_node.h"
#include "garnet/lib/ui/gfx/tests/session_test.h"
#include "garnet/lib/ui/gfx/tests/util.h"
#include "gtest/gtest.h"
#include "lib/fsl/tasks/message_loop.h"
#include "lib/fsl/threading/thread.h"
#include "lib/fxl/functional/make_copyable.h"
#include "lib/fxl/synchronization/waitable_event.h"
#include "lib/ui/scenic/fidl_helpers.h"
namespace scenic {
namespace gfx {
namespace test {
using ImportTest = SessionTest;
using ImportThreadedTest = SessionThreadedTest;
TEST_F(ImportTest, ExportsResourceViaCommand) {
// Create the event pair.
zx::eventpair source, destination;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
// Setup the resource to export.
scenic::ResourceId resource_id = 1;
// Create an entity node.
ASSERT_TRUE(Apply(scenic_lib::NewCreateEntityNodeCommand(resource_id)));
// Assert that the entity node was correctly mapped in.
ASSERT_EQ(1u, session_->GetMappedResourceCount());
// Apply the export command.
ASSERT_TRUE(Apply(
scenic_lib::NewExportResourceCommand(resource_id, std::move(source))));
}
TEST_F(ImportTest, ImportsUnlinkedImportViaCommand) {
// Create the event pair.
zx::eventpair source, destination;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
// Apply the import command.
ASSERT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
1 /* import resource ID */, ::gfx::ImportSpec::NODE, /* spec */
std::move(destination)) /* endpoint */
));
// Assert that the import node was correctly mapped in. It has not been linked
// yet.
ASSERT_EQ(1u, session_->GetMappedResourceCount());
// Assert that the import node was setup with the correct properties.
auto import_node = FindResource<Import>(1);
ASSERT_TRUE(import_node);
// No one has exported a resource so there should be no binding.
ASSERT_EQ(nullptr, import_node->imported_resource());
// Import specs should match.
ASSERT_EQ(::gfx::ImportSpec::NODE, import_node->import_spec());
}
TEST_F(ImportTest, PerformsFullLinking) {
// Create the event pair.
zx::eventpair source, destination;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
// Perform the import
{
// Apply the import command.
ASSERT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
1 /* import resource ID */, ::gfx::ImportSpec::NODE, /* spec */
std::move(destination)) /* endpoint */
));
// Assert that the import node was correctly mapped in. It has not been
// linked yet.
ASSERT_EQ(1u, session_->GetMappedResourceCount());
}
// Bindings not yet resolved.
{
// Assert that the import node was setup with the correct properties.
auto import_node = FindResource<Import>(1);
ASSERT_TRUE(import_node);
// No one has exported a resource so there should be no binding.
ASSERT_EQ(nullptr, import_node->imported_resource());
// Import specs should match.
ASSERT_EQ(::gfx::ImportSpec::NODE, import_node->import_spec());
}
// Perform the export
{
// Create an entity node.
ASSERT_TRUE(Apply(scenic_lib::NewCreateEntityNodeCommand(2)));
// Assert that the entity node was correctly mapped in.
ASSERT_EQ(2u, session_->GetMappedResourceCount());
// Apply the export command.
ASSERT_TRUE(
Apply(scenic_lib::NewExportResourceCommand(2, std::move(source))));
}
// Bindings should have been resolved.
{
// Assert that the import node was setup with the correct properties.
auto import_node = FindResource<Import>(1);
ASSERT_TRUE(import_node);
// Bindings should be resolved by now.
ASSERT_NE(nullptr, import_node->imported_resource());
// Import specs should match.
ASSERT_EQ(::gfx::ImportSpec::NODE, import_node->import_spec());
// Check that it was bound to the right object.
ASSERT_NE(nullptr, import_node->imported_resource());
auto entity = FindResource<EntityNode>(2);
ASSERT_TRUE(entity);
ASSERT_EQ(import_node->imported_resource(), entity.get());
ASSERT_TRUE(import_node->delegate());
ASSERT_EQ(import_node->delegate()->type_info().flags,
entity->type_info().flags);
ASSERT_EQ(1u, entity->imports().size());
ASSERT_EQ(import_node.get(), *(entity->imports().begin()));
}
}
TEST_F(ImportTest, HandlesDeadSourceHandle) {
zx::eventpair source_out;
zx::eventpair destination;
{
zx::eventpair source;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
source_out = zx::eventpair{source.get()};
// source dies now.
}
// Export an entity node with a dead handle.
ASSERT_TRUE(Apply(scenic_lib::NewCreateEntityNodeCommand(1)));
EXPECT_FALSE(Apply(scenic_lib::NewExportResourceCommand(
1 /* resource id */, std::move(source_out))));
}
TEST_F(ImportTest, HandlesDeadDestinationHandle) {
zx::eventpair source_out;
zx::eventpair destination_out;
{
zx::eventpair source;
zx::eventpair destination;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
source_out = zx::eventpair{source.get()};
destination_out = zx::eventpair{destination.get()};
// source and destination dies now.
}
// Import an entity node with a dead handle.
ASSERT_TRUE(Apply(scenic_lib::NewCreateEntityNodeCommand(1)));
EXPECT_FALSE(Apply(scenic_lib::NewImportResourceCommand(
1 /* resource id */, ::gfx::ImportSpec::NODE,
std::move(destination_out))));
}
TEST_F(ImportTest, DestroyingExportedResourceSendsEvent) {
zx::eventpair source;
zx::eventpair destination;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
// Export an entity node.
scenic::ResourceId node_id = 1;
scenic::ResourceId import_node = 2;
ASSERT_TRUE(Apply(scenic_lib::NewCreateEntityNodeCommand(node_id)));
EXPECT_TRUE(
Apply(scenic_lib::NewExportResourceCommand(node_id, std::move(source))));
EXPECT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
import_node, ::gfx::ImportSpec::NODE, std::move(destination))));
// Release the entity node.
EXPECT_TRUE(Apply(scenic_lib::NewReleaseResourceCommand(node_id)));
// Run the message loop until we get an event.
ASSERT_TRUE(RunLoopUntilWithTimeout([this]() -> bool {
return events_.size() > 0;
}));
// Verify that we got an ImportUnboundEvent.
EXPECT_EQ(1u, events_.size());
ui::Event event = std::move(events_[0]);
EXPECT_EQ(ui::Event::Tag::kGfx, event.Which());
EXPECT_EQ(::gfx::Event::Tag::kImportUnbound, event.gfx().Which());
ASSERT_EQ(import_node, event.gfx().import_unbound().resource_id);
}
TEST_F(ImportTest, ImportingNodeAfterDestroyingExportedResourceSendsEvent) {
zx::eventpair source;
zx::eventpair destination;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
// Export an entity node.
scenic::ResourceId node_id = 1;
scenic::ResourceId import_node = 2;
ASSERT_TRUE(Apply(scenic_lib::NewCreateEntityNodeCommand(node_id)));
EXPECT_TRUE(
Apply(scenic_lib::NewExportResourceCommand(node_id, std::move(source))));
// Release the entity node.
EXPECT_TRUE(Apply(scenic_lib::NewReleaseResourceCommand(node_id)));
// Try to import after the entity node has been released.
EXPECT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
import_node, ::gfx::ImportSpec::NODE, std::move(destination))));
// Run the message loop until we get an event.
ASSERT_TRUE(RunLoopUntilWithTimeout([this]() -> bool {
return events_.size() > 0;
}));
// Verify that we got an ImportUnboundEvent.
EXPECT_EQ(1u, events_.size());
ui::Event event = std::move(events_[0]);
EXPECT_EQ(ui::Event::Tag::kGfx, event.Which());
EXPECT_EQ(::gfx::Event::Tag::kImportUnbound, event.gfx().Which());
ASSERT_EQ(import_node, event.gfx().import_unbound().resource_id);
}
TEST_F(ImportThreadedTest, KillingImportedResourceEvictsFromResourceLinker) {
// Setup a latch on the resource expiring in the linker.
fxl::AutoResetWaitableEvent import_expired_latch;
engine_->resource_linker()->SetOnExpiredCallback(
[this, &import_expired_latch](Resource*,
ResourceLinker::ExpirationCause cause) {
ASSERT_EQ(ResourceLinker::ExpirationCause::kResourceDestroyed, cause);
import_expired_latch.Signal();
});
zx::eventpair source;
PostTaskSync([this, &source]() {
// Create the event pair.
zx::eventpair destination;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
// Apply the import command.
ASSERT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
1 /* import resource ID */, ::gfx::ImportSpec::NODE, /* spec */
std::move(destination)) /* endpoint */
));
// Assert that the import node was correctly mapped in. It has not been
// linked yet.
ASSERT_EQ(1u, session_->GetMappedResourceCount());
// Assert that the resource linker is ready to potentially link the
// resource.
ASSERT_EQ(1u, engine_->resource_linker()->NumUnresolvedImports());
// Assert that the import node was setup with the correct properties.
auto import_node = FindResource<Import>(1);
ASSERT_TRUE(import_node);
// No one has exported a resource so there should be no binding.
ASSERT_EQ(nullptr, import_node->imported_resource());
// Import specs should match.
ASSERT_EQ(::gfx::ImportSpec::NODE, import_node->import_spec());
// Release the import resource.
ASSERT_TRUE(Apply(
scenic_lib::NewReleaseResourceCommand(1 /* import resource ID */)));
});
// Make sure the expiry handle tells us that the resource has expired.
import_expired_latch.Wait();
// Assert that the resource linker has removed the unresolved import
// registration. We have already asserted that the unresolved import was
// registered in the initial post task.
ASSERT_EQ(engine_->resource_linker()->NumUnresolvedImports(), 0u);
}
// For a given resource, export it and bind a node to it. Additionally, keep
// an import handle open. Then, verify that the resource is not unexported until
// both the import node and the import handle are released.
TEST_F(ImportThreadedTest, ResourceUnexportedAfterImportsAndImportHandlesDie1) {
scenic::ResourceId exported_node_id = 1;
scenic::ResourceId import_node_id = 2;
bool destination_handle_released = false;
bool import_node_released = false;
// Setup a latch on the resource becoming unexported in the linker.
fxl::AutoResetWaitableEvent export_expired_latch;
engine_->resource_linker()->SetOnExpiredCallback(
[&](Resource*, ResourceLinker::ExpirationCause cause) {
ASSERT_EQ(ResourceLinker::ExpirationCause::kNoImportsBound, cause);
ASSERT_EQ(0u, engine_->resource_linker()->NumExports());
ASSERT_EQ(0u, engine_->resource_linker()->NumUnresolvedImports());
// Ensure that our export was unbound after all the necessary
// preconditions were met.
ASSERT_EQ(1u, session_->GetMappedResourceCount());
ASSERT_TRUE(destination_handle_released);
ASSERT_TRUE(import_node_released);
// Ensure the node is no longer marked as exported.
auto exported_node = FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(false, exported_node->is_exported());
export_expired_latch.Signal();
});
// Create the event pair.
zx::eventpair source, destination;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
fsl::Thread thread;
thread.Run();
thread.TaskRunner()->PostTask([&]() {
// Create the resource being exported.
Apply(scenic_lib::NewCreateEntityNodeCommand(exported_node_id));
auto exported_node = FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(false, exported_node->is_exported());
// Apply the export command.
ASSERT_TRUE(Apply(scenic_lib::NewExportResourceCommand(exported_node_id,
std::move(source))));
ASSERT_EQ(true, exported_node->is_exported());
// Apply the import command.
ASSERT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
import_node_id, ::gfx::ImportSpec::NODE, /* spec */
CopyEventPair(destination)) /* endpoint */
));
auto import_node = FindResource<Import>(import_node_id);
ASSERT_TRUE(import_node);
// Assert that the nodes were correctly mapped in.
ASSERT_EQ(2u, session_->GetMappedResourceCount());
// Nodes should be bound together.
ASSERT_EQ(exported_node.get(), import_node->imported_resource());
ASSERT_EQ(true, exported_node->is_exported());
ASSERT_EQ(1u, exported_node->imports().size());
ASSERT_EQ(1u, engine_->resource_linker()->NumExports());
// Post two tasks in the future. We assume the export will be released
// after the second one. Post tasks with a slight delay so we can identify
// the stage accurately.
thread.TaskRunner()->PostTask([&]() {
// Release the only import bound to the exported node.
import_node_released = true;
EXPECT_TRUE(Apply(scenic_lib::NewReleaseResourceCommand(import_node_id)));
thread.TaskRunner()->PostDelayedTask(
[&]() {
// Exported node should still be marked as exported.
auto exported_node = FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(true, exported_node->is_exported());
// List of imports should be empty.
ASSERT_EQ(0u, exported_node->imports().size());
// Reset the only import handle.
destination_handle_released = true;
destination.reset();
},
kPumpMessageLoopDuration);
});
});
// Make sure the expiry handle tells us that the resource has expired.
export_expired_latch.Wait();
thread.TaskRunner()->PostTask(
[]() { fsl::MessageLoop::GetCurrent()->QuitNow(); });
thread.Join();
}
// For a given resource, export it and bind a node to it. Additionally, keep
// an import handle open. Then, verify that the resource is not unexported until
// both the import node and the import handle are released.
// This test is identical to the previous one except the order in which the
// import node and import handle are released is switched.
TEST_F(ImportThreadedTest, ResourceUnexportedAfterImportsAndImportHandlesDie2) {
scenic::ResourceId exported_node_id = 1;
scenic::ResourceId import_node_id = 2;
bool destination_handle_released = false;
bool import_node_released = false;
// Setup a latch on the resource becoming unexported in the linker.
fxl::AutoResetWaitableEvent export_expired_latch;
engine_->resource_linker()->SetOnExpiredCallback(
[&](Resource*, ResourceLinker::ExpirationCause cause) {
ASSERT_EQ(ResourceLinker::ExpirationCause::kNoImportsBound, cause);
ASSERT_EQ(0u, engine_->resource_linker()->NumExports());
ASSERT_EQ(0u, engine_->resource_linker()->NumUnresolvedImports());
// Ensure that our export was unbound after all the necessary
// preconditions were met.
ASSERT_EQ(1u, session_->GetMappedResourceCount());
ASSERT_TRUE(destination_handle_released);
ASSERT_TRUE(import_node_released);
// Ensure the node is no longer marked as exported.
auto exported_node = FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(false, exported_node->is_exported());
ASSERT_EQ(0u, exported_node->imports().size());
export_expired_latch.Signal();
});
// Create the event pair.
zx::eventpair source, destination;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
fsl::Thread thread;
thread.Run();
thread.TaskRunner()->PostTask([&]() {
// Create the resource being exported.
Apply(scenic_lib::NewCreateEntityNodeCommand(exported_node_id));
auto exported_node = FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(false, exported_node->is_exported());
// Apply the export command.
ASSERT_TRUE(Apply(scenic_lib::NewExportResourceCommand(exported_node_id,
std::move(source))));
ASSERT_EQ(true, exported_node->is_exported());
// Apply the import command.
ASSERT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
import_node_id, ::gfx::ImportSpec::NODE, /* spec */
CopyEventPair(destination)) /* endpoint */
));
auto import_node = FindResource<Import>(import_node_id);
ASSERT_TRUE(import_node);
// Assert that the nodes were correctly mapped in.
ASSERT_EQ(2u, session_->GetMappedResourceCount());
// Nodes should be bound together.
ASSERT_EQ(exported_node.get(), import_node->imported_resource());
ASSERT_EQ(true, exported_node->is_exported());
ASSERT_EQ(1u, exported_node->imports().size());
ASSERT_EQ(1u, engine_->resource_linker()->NumExports());
// Post two tasks in the future. We assume the export will be released
// after the second one. Post tasks with a slight delay so we can identify
// the stage accurately.
thread.TaskRunner()->PostTask([&]() {
// Reset the only import handle.
destination_handle_released = true;
destination.reset();
thread.TaskRunner()->PostDelayedTask(
[&]() {
// Exported node should still be marked as exported.
auto exported_node = FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(true, exported_node->is_exported());
ASSERT_EQ(1u, exported_node->imports().size());
// Release the only import bound to the exported node.
import_node_released = true;
EXPECT_TRUE(
Apply(scenic_lib::NewReleaseResourceCommand(import_node_id)));
},
kPumpMessageLoopDuration);
});
});
// Make sure the expiry handle tells us that the resource has expired.
export_expired_latch.Wait();
thread.TaskRunner()->PostTask(
[]() { fsl::MessageLoop::GetCurrent()->QuitNow(); });
thread.Join();
}
// For a given resource, export it and bind a node to it. Additionally, keep
// two import handles open. Then, verify that the resource is not unexported
// until both the import node and all the import handles are released. This test
// is identical to the previous one except there is an additional import handle
// that must be destroyed.
TEST_F(ImportThreadedTest, ResourceUnexportedAfterImportsAndImportHandlesDie3) {
scenic::ResourceId exported_node_id = 1;
scenic::ResourceId import_node_id = 2;
bool destination_handle1_released = false;
bool destination_handle2_released = false;
bool import_node_released = false;
// Setup a latch on the resource becoming unexported in the linker.
fxl::AutoResetWaitableEvent export_expired_latch;
engine_->resource_linker()->SetOnExpiredCallback(
[&](Resource*, ResourceLinker::ExpirationCause cause) {
ASSERT_EQ(ResourceLinker::ExpirationCause::kNoImportsBound, cause);
ASSERT_EQ(0u, engine_->resource_linker()->NumExports());
ASSERT_EQ(0u, engine_->resource_linker()->NumUnresolvedImports());
// Ensure that our export was unbound after all the necessary
// preconditions were met.
ASSERT_EQ(1u, session_->GetMappedResourceCount());
ASSERT_TRUE(destination_handle1_released);
ASSERT_TRUE(destination_handle2_released);
ASSERT_TRUE(import_node_released);
// Ensure the node is no longer marked as exported.
auto exported_node = FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(false, exported_node->is_exported());
ASSERT_EQ(0u, exported_node->imports().size());
export_expired_latch.Signal();
});
// Create the event pair.
zx::eventpair source, destination1;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination1));
zx::eventpair destination2 = CopyEventPair(destination1);
fsl::Thread thread;
thread.Run();
thread.TaskRunner()->PostTask([&]() {
// Create the resource being exported.
Apply(scenic_lib::NewCreateEntityNodeCommand(exported_node_id));
auto exported_node = FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(false, exported_node->is_exported());
// Apply the export command.
ASSERT_TRUE(Apply(scenic_lib::NewExportResourceCommand(exported_node_id,
std::move(source))));
ASSERT_EQ(true, exported_node->is_exported());
// Apply the import command.
ASSERT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
import_node_id, ::gfx::ImportSpec::NODE, /* spec */
CopyEventPair(destination1)) /* endpoint */
));
auto import_node = FindResource<Import>(import_node_id);
ASSERT_TRUE(import_node);
// Assert that the nodes were correctly mapped in.
ASSERT_EQ(2u, session_->GetMappedResourceCount());
// Nodes should be bound together.
ASSERT_EQ(exported_node.get(), import_node->imported_resource());
ASSERT_EQ(true, exported_node->is_exported());
ASSERT_EQ(1u, exported_node->imports().size());
ASSERT_EQ(1u, engine_->resource_linker()->NumExports());
// Post three tasks in the future. We assume the export will be released
// after the second one. Post tasks with a slight delay so we can identify
// the stage accurately.
thread.TaskRunner()->PostTask([&]() {
// Reset the first import handle.
destination_handle1_released = true;
destination1.reset();
thread.TaskRunner()->PostDelayedTask(
[&]() {
// Exported node should still be marked as exported.
auto exported_node = FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(true, exported_node->is_exported());
// Release the only import bound to the exported node.
import_node_released = true;
EXPECT_TRUE(
Apply(scenic_lib::NewReleaseResourceCommand(import_node_id)));
thread.TaskRunner()->PostDelayedTask(
[&]() {
// Exported node should still be marked as exported.
auto exported_node =
FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(true, exported_node->is_exported());
// List of imports should be empty.
ASSERT_EQ(0u, exported_node->imports().size());
// Reset the second import handle.
destination_handle2_released = true;
destination2.reset();
},
kPumpMessageLoopDuration);
},
kPumpMessageLoopDuration);
});
});
// Make sure the expiry handle tells us that the resource has expired.
export_expired_latch.Wait();
thread.TaskRunner()->PostTask(
[]() { fsl::MessageLoop::GetCurrent()->QuitNow(); });
thread.Join();
}
// For a given resource, export it and bind two nodes to it. Additionally, keep
// two import handles open. Then, verify that the resource is not unexported
// until both the import nodes and all the import handles are released. This
// test is identical to the previous one except there is an additional import
// node that must be released.
TEST_F(ImportThreadedTest, ResourceUnexportedAfterImportsAndImportHandlesDie4) {
scenic::ResourceId exported_node_id = 1;
scenic::ResourceId import_node_id1 = 2;
scenic::ResourceId import_node_id2 = 3;
bool destination_handle1_released = false;
bool destination_handle2_released = false;
bool import_node1_released = false;
bool import_node2_released = false;
// Setup a latch on the resource becoming unexported in the linker.
fxl::AutoResetWaitableEvent export_expired_latch;
engine_->resource_linker()->SetOnExpiredCallback(
[&](Resource*, ResourceLinker::ExpirationCause cause) {
ASSERT_EQ(ResourceLinker::ExpirationCause::kNoImportsBound, cause);
ASSERT_EQ(0u, engine_->resource_linker()->NumExports());
ASSERT_EQ(0u, engine_->resource_linker()->NumUnresolvedImports());
// Ensure that our export was unbound after all the necessary
// preconditions were met.
ASSERT_EQ(1u, session_->GetMappedResourceCount());
ASSERT_TRUE(destination_handle1_released);
ASSERT_TRUE(destination_handle2_released);
ASSERT_TRUE(import_node1_released);
ASSERT_TRUE(import_node2_released);
// Ensure the node is no longer marked as exported.
auto exported_node = FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(false, exported_node->is_exported());
ASSERT_EQ(0u, exported_node->imports().size());
export_expired_latch.Signal();
});
// Create the event pair.
zx::eventpair source, destination1;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination1));
zx::eventpair destination2 = CopyEventPair(destination1);
fsl::Thread thread;
thread.Run();
thread.TaskRunner()->PostTask([&]() {
// Create the resource being exported.
Apply(scenic_lib::NewCreateEntityNodeCommand(exported_node_id));
auto exported_node = FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(false, exported_node->is_exported());
// Apply the export command.
ASSERT_TRUE(Apply(scenic_lib::NewExportResourceCommand(exported_node_id,
std::move(source))));
ASSERT_EQ(true, exported_node->is_exported());
// Apply the import commands.
ASSERT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
import_node_id1, ::gfx::ImportSpec::NODE, /* spec */
CopyEventPair(destination1)) /* endpoint */
));
ASSERT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
import_node_id2, ::gfx::ImportSpec::NODE, /* spec */
CopyEventPair(destination1)) /* endpoint */
));
auto import_node1 = FindResource<Import>(import_node_id1);
ASSERT_TRUE(import_node1);
auto import_node2 = FindResource<Import>(import_node_id2);
ASSERT_TRUE(import_node2);
// Assert that the nodes were correctly mapped in.
ASSERT_EQ(3u, session_->GetMappedResourceCount());
// Nodes should be bound together.
ASSERT_EQ(exported_node.get(), import_node1->imported_resource());
ASSERT_EQ(exported_node.get(), import_node2->imported_resource());
ASSERT_EQ(true, exported_node->is_exported());
ASSERT_EQ(2u, exported_node->imports().size());
ASSERT_EQ(1u, engine_->resource_linker()->NumExports());
// Post three tasks in the future. We assume the export will be released
// after the second one. Post tasks with a slight delay so we can identify
// the stage accurately.
thread.TaskRunner()->PostTask([&]() {
// Reset the first import handle.
destination_handle1_released = true;
destination1.reset();
thread.TaskRunner()->PostDelayedTask(
[&]() {
// Exported node should still be marked as exported.
auto exported_node = FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(true, exported_node->is_exported());
// Release the only import bound to the exported node.
import_node1_released = true;
EXPECT_TRUE(
Apply(scenic_lib::NewReleaseResourceCommand(import_node_id1)));
thread.TaskRunner()->PostDelayedTask(
[&]() {
// Exported node should still be marked as exported.
auto exported_node =
FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(true, exported_node->is_exported());
// One import should remain bound.
ASSERT_EQ(1u, exported_node->imports().size());
// Reset the second import handle.
destination_handle2_released = true;
destination2.reset();
thread.TaskRunner()->PostDelayedTask(
[&]() {
// Exported node should still be marked as exported.
auto exported_node =
FindResource<EntityNode>(exported_node_id);
ASSERT_TRUE(exported_node);
ASSERT_EQ(true, exported_node->is_exported());
import_node2_released = true;
EXPECT_TRUE(Apply(scenic_lib::NewReleaseResourceCommand(
import_node_id2)));
},
kPumpMessageLoopDuration);
},
kPumpMessageLoopDuration);
},
kPumpMessageLoopDuration);
});
});
// Make sure the expiry handle tells us that the resource has expired.
export_expired_latch.Wait();
thread.TaskRunner()->PostTask(
[]() { fsl::MessageLoop::GetCurrent()->QuitNow(); });
thread.Join();
}
TEST_F(ImportTest,
ProxiesCanBeFoundByTheirContainerOrTheirUnderlyingEntityType) {
// Create an unlinked import resource.
zx::eventpair source, destination;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
// Apply the import command.
ASSERT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
1 /* import resource ID */, ::gfx::ImportSpec::NODE, /* spec */
std::move(destination)) /* endpoint */
));
// Assert that the import node was correctly mapped in. It has not been
// linked yet.
ASSERT_EQ(1u, session_->GetMappedResourceCount());
// Resolve by the import container.
{
// Assert that the import node was setup with the correct properties.
auto import_node = FindResource<Import>(1);
ASSERT_TRUE(import_node);
// No one has exported a resource so there should be no binding.
ASSERT_EQ(nullptr, import_node->imported_resource());
// Import specs should match.
ASSERT_EQ(::gfx::ImportSpec::NODE, import_node->import_spec());
}
// Resolve by the resource owned by the import container.
{
// Assert that the import node contains a node with the correct
// properties.
auto import_node_backing = FindResource<EntityNode>(1);
ASSERT_TRUE(import_node_backing);
// The imported node has the same id as the import resource.
ASSERT_EQ(1u, import_node_backing->id());
}
}
TEST_F(ImportTest, UnlinkedImportedResourceCanAcceptCommands) {
// Create an unlinked import resource.
zx::eventpair source, destination;
{
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
// Apply the import command.
ASSERT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
1 /* import resource ID */, ::gfx::ImportSpec::NODE, /* spec */
std::move(destination)) /* endpoint */
));
// Assert that the import node was correctly mapped in. It has not been
// linked yet.
ASSERT_EQ(1u, session_->GetMappedResourceCount());
// Assert that the import node was setup with the correct properties.
auto import_node = FindResource<Import>(1);
ASSERT_TRUE(import_node);
// No one has exported a resource so there should be no binding.
ASSERT_EQ(import_node->imported_resource(), nullptr);
// Import specs should match.
ASSERT_EQ(::gfx::ImportSpec::NODE, import_node->import_spec());
}
// Attempt to add an entity node as a child to an unlinked resource.
{
// Create the entity node.
ASSERT_TRUE(Apply(
scenic_lib::NewCreateEntityNodeCommand(2 /* child resource id */)));
// Add the entity node to the import.
ASSERT_TRUE(Apply(scenic_lib::NewAddChildCommand(
1 /* unlinked import resource */, 2 /* child resource */)));
}
}
TEST_F(ImportTest, LinkedResourceShouldBeAbleToAcceptCommands) {
// Create the event pair.
zx::eventpair source, destination;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &source, &destination));
// Perform the import
{
// Apply the import command.
ASSERT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
1 /* import resource ID */, ::gfx::ImportSpec::NODE, /* spec */
std::move(destination)) /* endpoint */
));
// Assert that the import node was correctly mapped in. It has not been
// linked yet.
ASSERT_EQ(1u, session_->GetMappedResourceCount());
}
// Bindings not yet resolved.
{
// Assert that the import node was setup with the correct properties.
auto import_node = FindResource<Import>(1);
ASSERT_TRUE(import_node);
// No one has exported a resource so there should be no binding.
ASSERT_EQ(nullptr, import_node->imported_resource());
// Import specs should match.
ASSERT_EQ(::gfx::ImportSpec::NODE, import_node->import_spec());
}
// Perform the export
{
// Create an entity node.
ASSERT_TRUE(Apply(scenic_lib::NewCreateEntityNodeCommand(2)));
// Assert that the entity node was correctly mapped in.
ASSERT_EQ(2u, session_->GetMappedResourceCount());
// Apply the export command.
ASSERT_TRUE(
Apply(scenic_lib::NewExportResourceCommand(2, std::move(source))));
}
// Bindings should have been resolved.
{
// Assert that the import node was setup with the correct properties.
auto import_node = FindResource<Import>(1);
ASSERT_TRUE(import_node);
// Bindings should be resolved by now.
ASSERT_NE(import_node->imported_resource(), nullptr);
// Import specs should match.
ASSERT_EQ(import_node->import_spec(), ::gfx::ImportSpec::NODE);
}
// Attempt to add an entity node as a child to an linked resource.
{
// Create the entity node.
ASSERT_TRUE(Apply(
scenic_lib::NewCreateEntityNodeCommand(3 /* child resource id */)));
// Add the entity node to the import.
ASSERT_TRUE(Apply(scenic_lib::NewAddChildCommand(
1 /* unlinked import resource */, 3 /* child resource */)));
}
}
TEST_F(ImportTest, EmbedderCanEmbedNodesFromElsewhere) {
// Create the token pair.
zx::eventpair import_token, export_token;
ASSERT_EQ(ZX_OK, zx::eventpair::create(0, &import_token, &export_token));
// Effective node hierarchy must be:
//
// +----+
// | 1 |
// +----+
// |
// +----------+ Import
// | |
// v v
// +----+ +----+
// | 2 | |1001|
// +----+ +----+
// | |
// | |
// | |
// v v
// +----+ +----+
// | 3 | |1002|
// +----+ +----+
// |
// |
// v
// +----+
// |1003|
// +----+
// Embedder.
{
ASSERT_TRUE(Apply(scenic_lib::NewCreateSceneCommand(1)));
ASSERT_TRUE(Apply(scenic_lib::NewCreateEntityNodeCommand(2)));
ASSERT_TRUE(Apply(scenic_lib::NewCreateEntityNodeCommand(3)));
ASSERT_TRUE(Apply(scenic_lib::NewAddChildCommand(1, 2)));
ASSERT_TRUE(Apply(scenic_lib::NewAddChildCommand(2, 3)));
// Export.
ASSERT_TRUE(Apply(
scenic_lib::NewExportResourceCommand(1, std::move(export_token))));
ASSERT_EQ(1u, engine_->resource_linker()->NumExports());
}
// Embeddee.
{
ASSERT_TRUE(Apply(scenic_lib::NewCreateEntityNodeCommand(1001)));
ASSERT_TRUE(Apply(scenic_lib::NewCreateEntityNodeCommand(1002)));
ASSERT_TRUE(Apply(scenic_lib::NewCreateEntityNodeCommand(1003)));
ASSERT_TRUE(Apply(scenic_lib::NewAddChildCommand(1001, 1002)));
ASSERT_TRUE(Apply(scenic_lib::NewAddChildCommand(1002, 1003)));
// Import.
ASSERT_TRUE(Apply(scenic_lib::NewImportResourceCommand(
500, ::gfx::ImportSpec::NODE, std::move(import_token))));
ASSERT_TRUE(Apply(scenic_lib::NewAddChildCommand(500, 1001)));
}
// Check that the scene has an item in its imports. That is how the visitor
// will visit the imported node.
{
auto scene = FindResource<Scene>(1);
ASSERT_TRUE(scene);
ASSERT_EQ(1u, scene->imports().size());
}
}
} // namespace test
} // namespace gfx
} // namespace scenic