| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/bind.h" |
| #include "base/run_loop.h" |
| #include "mojo/common/common_type_converters.h" |
| #include "mojo/public/cpp/application/application_impl.h" |
| #include "mojo/public/cpp/application/application_test_base.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| #include "services/reaper/diagnostics.mojom.h" |
| #include "services/reaper/reaper.mojom.h" |
| #include "services/reaper/scythe.mojom.h" |
| #include "services/reaper/transfer.mojom.h" |
| |
| namespace reaper { |
| |
| namespace { |
| |
| template <typename T> |
| void Ping(T* service) { |
| base::RunLoop run_loop; |
| (*service) |
| ->Ping(base::Bind(&base::RunLoop::Quit, base::Unretained(&run_loop))); |
| run_loop.Run(); |
| } |
| |
| struct SecretCatcher { |
| SecretCatcher(uint64* secret) : secret(secret) {} |
| void Run(uint64 secret) const { *(this->secret) = secret; } |
| uint64* secret; |
| }; |
| |
| class ScytheImpl : public Scythe { |
| public: |
| ScytheImpl(mojo::InterfaceRequest<Scythe> request) |
| : binding_(this, request.Pass()) {} |
| void WaitForKills(size_t num) { |
| while (deds.size() < num) { |
| binding_.WaitForIncomingMethodCall(); |
| } |
| } |
| std::vector<std::string> deds; |
| |
| private: |
| void KillApplication(const mojo::String& url) override { |
| deds.push_back(url); |
| std::sort(deds.begin(), deds.end()); |
| } |
| void Ping(const mojo::Closure& callback) override { callback.Run(); } |
| mojo::StrongBinding<Scythe> binding_; |
| }; |
| |
| class ReaperAppTest : public mojo::test::ApplicationTestBase { |
| public: |
| ReaperAppTest() : ApplicationTestBase(), scythe_(NULL) {} |
| ~ReaperAppTest() override {} |
| |
| void SetUp() override { |
| mojo::test::ApplicationTestBase::SetUp(); |
| application_impl()->ConnectToService("mojo:reaper", &diagnostics_); |
| |
| diagnostics_->Reset(); |
| ScythePtr scythe_ptr; |
| scythe_ = new ScytheImpl(GetProxy(&scythe_ptr).Pass()); |
| diagnostics_->SetScythe(scythe_ptr.Pass()); |
| Ping(&diagnostics_); |
| |
| diagnostics_->GetReaperForApp(app1_url_, GetProxy(&reaper1_)); |
| diagnostics_->GetReaperForApp(app2_url_, GetProxy(&reaper2_)); |
| diagnostics_->GetReaperForApp(app3_url_, GetProxy(&reaper3_)); |
| reaper1_->GetApplicationSecret(SecretCatcher(&app1_secret_)); |
| reaper2_->GetApplicationSecret(SecretCatcher(&app2_secret_)); |
| reaper3_->GetApplicationSecret(SecretCatcher(&app3_secret_)); |
| Ping(&reaper1_); |
| Ping(&reaper2_); |
| Ping(&reaper3_); |
| } |
| |
| protected: |
| TransferPtr StartTransfer(ReaperPtr* reaper, uint32 node_id) { |
| TransferPtr transfer; |
| (*reaper)->StartTransfer(node_id, GetProxy(&transfer)); |
| Ping(reaper); |
| return transfer.Pass(); |
| } |
| |
| void CompleteTransfer(TransferPtr* transfer, |
| uint64 app_secret, |
| uint32 node_id) { |
| (*transfer)->Complete(app_secret, node_id); |
| Ping(transfer); |
| } |
| |
| void Transfer(ReaperPtr* from_app_reaper, |
| uint32 from_node, |
| uint64 to_app_secret, |
| uint32 to_node) { |
| TransferPtr transfer(StartTransfer(from_app_reaper, from_node)); |
| CompleteTransfer(&transfer, to_app_secret, to_node); |
| } |
| |
| ScytheImpl* scythe_; |
| DiagnosticsPtr diagnostics_; |
| std::string app1_url_ = "https://a1/"; |
| std::string app2_url_ = "https://a2/"; |
| std::string app3_url_ = "https://a3/"; |
| std::string reaper_url_ = "mojo:reaper"; |
| uint64 app1_secret_; |
| uint64 app2_secret_; |
| uint64 app3_secret_; |
| ReaperPtr reaper1_; |
| ReaperPtr reaper2_; |
| ReaperPtr reaper3_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ReaperAppTest); |
| }; |
| |
| struct NodeCatcher { |
| NodeCatcher(mojo::Array<NodePtr>* nodes) : nodes(nodes) {} |
| void Run(mojo::Array<NodePtr> nodes) const { *(this->nodes) = nodes.Pass(); } |
| mojo::Array<NodePtr>* nodes; |
| }; |
| |
| NodePtr CreateNode(const std::string& app_url, |
| uint32 node_id, |
| const std::string& other_app_url, |
| uint32 other_node_id, |
| bool is_source) { |
| NodePtr result(Node::New()); |
| result->app_url = app_url; |
| result->node_id = node_id; |
| result->other_app_url = other_app_url; |
| result->other_id = other_node_id; |
| result->is_source = is_source; |
| return result.Pass(); |
| } |
| |
| bool CompareNodes(const NodePtr* a, const NodePtr* b) { |
| if ((*a)->app_url < (*b)->app_url) |
| return true; |
| else if ((*a)->app_url == (*b)->app_url && (*a)->node_id < (*b)->node_id) |
| return true; |
| else |
| return false; |
| } |
| |
| std::vector<const NodePtr*> GetNodeVec(const mojo::Array<NodePtr>& node_arr) { |
| std::vector<const NodePtr*> result; |
| for (size_t i = 0; i < node_arr.size(); ++i) |
| result.push_back(&node_arr[i]); |
| std::sort(result.begin(), result.end(), CompareNodes); |
| return result; |
| } |
| |
| void AddReference(mojo::Array<NodePtr>* nodes, |
| const std::string& source_app, |
| uint32 source_node_id, |
| const std::string& dest_app, |
| uint32 dest_node_id) { |
| nodes->push_back(CreateNode(source_app, source_node_id, dest_app, |
| dest_node_id, true).Pass()); |
| nodes->push_back(CreateNode(dest_app, dest_node_id, source_app, |
| source_node_id, false).Pass()); |
| } |
| |
| void DumpNodes(DiagnosticsPtr* diagnostics, mojo::Array<NodePtr>* nodes) { |
| nodes->reset(); |
| (*diagnostics)->DumpNodes(NodeCatcher(nodes)); |
| Ping(diagnostics); |
| } |
| |
| void ExpectEqual(const mojo::Array<NodePtr>& expected, |
| const mojo::Array<NodePtr>& actual, |
| const std::string& message) { |
| auto expected_vec = GetNodeVec(expected); |
| auto actual_vec = GetNodeVec(actual); |
| EXPECT_EQ(expected_vec.size(), actual_vec.size()) << message; |
| for (size_t i = 0; i < expected.size(); ++i) { |
| EXPECT_TRUE(expected_vec[i]->Equals(*actual_vec[i])) << message << " : " |
| << i; |
| } |
| } |
| |
| void ExpectEqual(const mojo::Array<NodePtr>& expected, |
| const mojo::Array<NodePtr>& actual) { |
| ExpectEqual(expected, actual, ""); |
| } |
| |
| } // namespace |
| |
| TEST_F(ReaperAppTest, CreateAndRead) { |
| reaper1_->CreateReference(1u, 2u); |
| Ping(&reaper1_); |
| |
| mojo::Array<NodePtr> actual; |
| DumpNodes(&diagnostics_, &actual); |
| ASSERT_EQ(2u, actual.size()); |
| |
| mojo::Array<NodePtr> expected; |
| expected.push_back(CreateNode(app1_url_, 1u, app1_url_, 2u, true).Pass()); |
| expected.push_back(CreateNode(app1_url_, 2u, app1_url_, 1u, false).Pass()); |
| ExpectEqual(expected, actual); |
| } |
| |
| TEST_F(ReaperAppTest, CreateDuplicate) { |
| reaper1_->CreateReference(1u, 2u); |
| |
| // The duplicate nodes should be silently ignored. |
| reaper1_->CreateReference(1u, 2u); |
| Ping(&reaper1_); |
| |
| mojo::Array<NodePtr> nodes; |
| DumpNodes(&diagnostics_, &nodes); |
| ASSERT_EQ(2u, nodes.size()); |
| } |
| |
| TEST_F(ReaperAppTest, DropOneNode) { |
| reaper1_->CreateReference(1u, 2u); |
| reaper1_->DropNode(1u); |
| Ping(&reaper1_); |
| |
| mojo::Array<NodePtr> nodes; |
| DumpNodes(&diagnostics_, &nodes); |
| |
| // The other node gets dropped immediately. |
| ASSERT_EQ(0u, nodes.size()); |
| } |
| |
| TEST_F(ReaperAppTest, GetApplicationSecret) { |
| EXPECT_NE(0u, app1_secret_); |
| EXPECT_NE(0u, app2_secret_); |
| |
| uint64 secret1b = 0u; |
| reaper1_->GetApplicationSecret(SecretCatcher(&secret1b)); |
| Ping(&reaper1_); |
| EXPECT_EQ(app1_secret_, secret1b); |
| } |
| |
| TEST_F(ReaperAppTest, Transfer) { |
| reaper1_->CreateReference(1u, 2u); |
| Ping(&reaper1_); |
| |
| diagnostics_->SetIsRoot(app1_url_, true); |
| Ping(&diagnostics_); |
| |
| mojo::Array<NodePtr> expected; |
| AddReference(&expected, app1_url_, 1u, app1_url_, 2u); |
| mojo::Array<NodePtr> actual; |
| DumpNodes(&diagnostics_, &actual); |
| ExpectEqual(expected, actual, "before start"); |
| |
| TransferPtr transfer(StartTransfer(&reaper1_, 2u)); |
| expected.reset(); |
| AddReference(&expected, app1_url_, 1u, reaper_url_, 1u); |
| DumpNodes(&diagnostics_, &actual); |
| ExpectEqual(expected, actual, "after start"); |
| |
| CompleteTransfer(&transfer, app2_secret_, 1u); |
| expected.reset(); |
| AddReference(&expected, app1_url_, 1u, app2_url_, 1u); |
| DumpNodes(&diagnostics_, &actual); |
| ExpectEqual(expected, actual, "after complete"); |
| |
| // Shouldn't crash or change anything on second call. |
| CompleteTransfer(&transfer, app1_secret_, 3u); |
| DumpNodes(&diagnostics_, &actual); |
| ExpectEqual(expected, actual, "after reuse transfer"); |
| } |
| |
| TEST_F(ReaperAppTest, Collect1) { |
| // Create a reference that goes nowhere and drop it. This triggers a GC, so we |
| // should see the app be killed. |
| reaper1_->CreateReference(1u, 2u); |
| reaper1_->DropNode(1u); |
| Ping(&reaper1_); |
| scythe_->WaitForKills(1u); |
| EXPECT_EQ(1u, scythe_->deds.size()); |
| EXPECT_EQ(app1_url_, scythe_->deds[0]); |
| |
| // Create a ref that goes from a root to itself, and drop it. Nothing should |
| // be killed. |
| scythe_->deds.clear(); |
| diagnostics_->SetIsRoot(app1_url_, true); |
| reaper1_->CreateReference(3u, 4u); |
| reaper1_->CreateReference(5u, 6u); |
| reaper1_->DropNode(3u); |
| Ping(&reaper1_); |
| |
| mojo::Array<NodePtr> expected; |
| AddReference(&expected, app1_url_, 5u, app1_url_, 6u); |
| mojo::Array<NodePtr> actual; |
| DumpNodes(&diagnostics_, &actual); |
| ExpectEqual(expected, actual); |
| } |
| |
| TEST_F(ReaperAppTest, CollectNodesAreDirected) { |
| diagnostics_->SetIsRoot(app1_url_, true); |
| Ping(&diagnostics_); |
| reaper1_->CreateReference(1u, 2u); |
| |
| // Transfer the *source* node to app2. Now app2 is referencing app1. |
| Transfer(&reaper1_, 1u, app2_secret_, 1u); |
| |
| // Since app2 is not a root, it should get collected. |
| scythe_->WaitForKills(1u); |
| ASSERT_EQ(1u, scythe_->deds.size()); |
| EXPECT_EQ(app2_url_, scythe_->deds[0]); |
| |
| // Should have cleaned up the graph too. |
| mojo::Array<NodePtr> expected; |
| mojo::Array<NodePtr> actual; |
| DumpNodes(&diagnostics_, &actual); |
| ExpectEqual(expected, actual); |
| |
| } |
| |
| } // namespace reaper |