blob: 244afcde6cffa002ee82bc9c3c75a135459b82b1 [file] [log] [blame]
// 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