blob: 35fbd39c7235f5b04caf3325a648a3e605c41bfb [file] [log] [blame]
// Copyright 2014 The Chromium OS 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 <avahi-common/defs.h>
#include <base/memory/ref_counted.h>
#include <chromeos/dbus/data_serialization.h>
#include <chromeos/dbus/dbus_param_writer.h>
#include <dbus/message.h>
#include <dbus/mock_bus.h>
#include <dbus/mock_object_proxy.h>
#include <dbus/object_path.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "peerd/avahi_client.h"
#include "peerd/complete_mock_object_proxy.h"
#include "peerd/dbus_constants.h"
#include "peerd/mock_peer_manager.h"
#include "peerd/test_util.h"
using chromeos::dbus_utils::AsyncEventSequencer;
using dbus::MockBus;
using dbus::ObjectPath;
using dbus::Response;
using peerd::dbus_constants::avahi::kServerInterface;
using peerd::dbus_constants::avahi::kServerMethodGetHostName;
using peerd::dbus_constants::avahi::kServerMethodGetState;
using peerd::dbus_constants::avahi::kServerMethodServiceBrowserNew;
using peerd::dbus_constants::avahi::kServerPath;
using peerd::dbus_constants::avahi::kServerSignalStateChanged;
using peerd::dbus_constants::avahi::kServiceName;
using peerd::dbus_constants::avahi::kServiceBrowserInterface;
using peerd::dbus_constants::avahi::kServiceBrowserMethodFree;
using peerd::dbus_constants::avahi::kServiceBrowserSignalItemNew;
using peerd::dbus_constants::avahi::kServiceBrowserSignalItemRemove;
using peerd::dbus_constants::avahi::kServiceBrowserSignalFailure;
using peerd::test_util::IsDBusMethodCallTo;
using std::string;
using testing::AnyNumber;
using testing::DoAll;
using testing::Invoke;
using testing::Mock;
using testing::Property;
using testing::Return;
using testing::SaveArg;
using testing::Unused;
using testing::_;
namespace {
const char kDiscovererPath[] = "/path/to/avahi/discoverer";
Response* ReturnsHostName(dbus::MethodCall* method_call, Unused, Unused) {
method_call->SetSerial(87);
scoped_ptr<Response> response = Response::FromMethodCall(method_call);
dbus::MessageWriter writer(response.get());
chromeos::dbus_utils::AppendValueToWriter(&writer,
string("this_is_a_hostname"));
return response.release();
}
Response* ReturnsDiscovererPath(dbus::MethodCall* method_call, Unused, Unused) {
method_call->SetSerial(87);
scoped_ptr<Response> response = Response::FromMethodCall(method_call);
dbus::MessageWriter writer(response.get());
chromeos::dbus_utils::AppendValueToWriter(
&writer, ObjectPath{string{kDiscovererPath}});
return response.release();
}
} // namespace
namespace peerd {
class AvahiClientTest : public ::testing::Test {
public:
void SetUp() override {
avahi_proxy_ = new peerd::CompleteMockObjectProxy(
mock_bus_.get(), kServiceName, ObjectPath(kServerPath));
// Ignore threading concerns.
EXPECT_CALL(*mock_bus_, AssertOnOriginThread()).Times(AnyNumber());
EXPECT_CALL(*mock_bus_, AssertOnDBusThread()).Times(AnyNumber());
// Return the ObjectProxy for Avahi when asked.
EXPECT_CALL(*mock_bus_,
GetObjectProxy(kServiceName,
Property(&ObjectPath::value, kServerPath)))
.WillRepeatedly(Return(avahi_proxy_.get()));
client_.reset(new AvahiClient(mock_bus_, &peer_manager_));
}
void TearDown() override {
EXPECT_CALL(*avahi_proxy_, Detach());
if (client_->discoverer_) {
EXPECT_CALL(*service_browser_proxy_, Detach());
}
}
void RegisterAsyncWhenAvahiIs(bool is_running, AvahiServerState state) {
// Immediately signal that signal connection has succeeded.
EXPECT_CALL(*avahi_proxy_,
ConnectToSignal(kServerInterface,
kServerSignalStateChanged,
_, _))
.WillOnce(DoAll(Invoke(&test_util::HandleConnectToSignal),
SaveArg<2>(&state_changed_signal_callback_)));
// After we connect the signal, we'll be asked to handle a callback
// for the initial state of Avahi.
EXPECT_CALL(*avahi_proxy_, WaitForServiceToBeAvailable(_));
if (is_running) {
EXPECT_CALL(*avahi_proxy_,
MockCallMethodAndBlockWithErrorDetails(IsDBusMethodCallTo(
kServerInterface, kServerMethodGetState), _, _))
.WillOnce(Invoke(this, &AvahiClientTest::ReturnsCurrentState));
} else {
EXPECT_CALL(*avahi_proxy_,
MockCallMethodAndBlockWithErrorDetails(IsDBusMethodCallTo(
kServerInterface, kServerMethodGetState), _, _))
.Times(0);
}
current_avahi_state_ = state;
client_->RegisterAsync(AsyncEventSequencer::GetDefaultCompletionAction());
// This is the callback that would normally require a task runner.
client_->OnServiceAvailable(is_running);
}
Response *ReturnsCurrentState(dbus::MethodCall* method_call, Unused, Unused) {
method_call->SetSerial(87);
scoped_ptr<Response> response = Response::FromMethodCall(method_call);
dbus::MessageWriter writer(response.get());
chromeos::dbus_utils::AppendValueToWriter(&writer,
int32_t{current_avahi_state_});
// The mock wraps this back in a scoped_ptr in the function calling us.
return response.release();
}
void SendOnStateChanged(AvahiServerState state) {
dbus::Signal signal(kServerInterface, kServerSignalStateChanged);
dbus::MessageWriter writer(&signal);
// No error message to report cap'n.
chromeos::dbus_utils::DBusParamWriter::Append(&writer,
int32_t{state},
string{});
ASSERT_FALSE(state_changed_signal_callback_.is_null());
state_changed_signal_callback_.Run(&signal);
}
void ExpectServiceDiscovererCreated(bool will_be_created) {
Mock::VerifyAndClearExpectations(avahi_proxy_.get());
Mock::VerifyAndClearExpectations(service_browser_proxy_.get());
if (!will_be_created) {
EXPECT_CALL(*avahi_proxy_,
MockCallMethodAndBlockWithErrorDetails(IsDBusMethodCallTo(
kServerInterface, kServerMethodServiceBrowserNew), _, _))
.Times(0);
return;
}
EXPECT_CALL(*avahi_proxy_,
MockCallMethodAndBlockWithErrorDetails(IsDBusMethodCallTo(
kServerInterface, kServerMethodServiceBrowserNew), _, _))
.WillOnce(Invoke(&ReturnsDiscovererPath));
EXPECT_CALL(*mock_bus_,
GetObjectProxy(kServiceName,
Property(&ObjectPath::value, kDiscovererPath)))
.WillRepeatedly(Return(service_browser_proxy_.get()));
EXPECT_CALL(*service_browser_proxy_,
ConnectToSignal(kServiceBrowserInterface,
kServiceBrowserSignalItemNew,
_, _)).Times(1);
EXPECT_CALL(*service_browser_proxy_,
ConnectToSignal(kServiceBrowserInterface,
kServiceBrowserSignalItemRemove,
_, _)).Times(1);
EXPECT_CALL(*service_browser_proxy_,
ConnectToSignal(kServiceBrowserInterface,
kServiceBrowserSignalFailure,
_, _)).Times(1);
EXPECT_CALL(*service_browser_proxy_, Detach()).Times(0);
}
MOCK_METHOD0(AvahiReady, void());
scoped_refptr<MockBus> mock_bus_{new MockBus{dbus::Bus::Options{}}};
MockPeerManager peer_manager_;
scoped_refptr<peerd::CompleteMockObjectProxy> avahi_proxy_;
scoped_refptr<dbus::MockObjectProxy> service_browser_proxy_{
new dbus::MockObjectProxy{mock_bus_.get(), kServiceName,
ObjectPath{kDiscovererPath}}};
std::unique_ptr<AvahiClient> client_;
AvahiServerState current_avahi_state_;
dbus::ObjectProxy::SignalCallback state_changed_signal_callback_;
}; // class AvahiClientTest
TEST_F(AvahiClientTest, ShouldNotifyRestart_WhenAvahiIsUp) {
// We don't get called back until we finish RegisterAsync().
EXPECT_CALL(*this, AvahiReady()).Times(0);
client_->RegisterOnAvahiRestartCallback(
base::Bind(&AvahiClientTest::AvahiReady, base::Unretained(this)));
// However, on finishing RegisterAsync(), we should notice Avahi is up, and
// signal this to interested parties.
EXPECT_CALL(*this, AvahiReady()).Times(1);
RegisterAsyncWhenAvahiIs(true, AVAHI_SERVER_RUNNING);
}
TEST_F(AvahiClientTest, ShouldNotifyRestart_WhenAvahiComesUpLater) {
// Avahi should remain down until we say otherwise.
EXPECT_CALL(*this, AvahiReady()).Times(0);
client_->RegisterOnAvahiRestartCallback(
base::Bind(&AvahiClientTest::AvahiReady, base::Unretained(this)));
RegisterAsyncWhenAvahiIs(false, AVAHI_SERVER_INVALID);
// The StateChanged signal should trigger our callback.
EXPECT_CALL(*this, AvahiReady()).Times(1);
SendOnStateChanged(AVAHI_SERVER_RUNNING);
}
TEST_F(AvahiClientTest, CanGetPublisherWhenAvahiIsUp) {
RegisterAsyncWhenAvahiIs(true, AVAHI_SERVER_RUNNING);
EXPECT_CALL(*avahi_proxy_,
MockCallMethodAndBlockWithErrorDetails(IsDBusMethodCallTo(
kServerInterface, kServerMethodGetHostName), _, _))
.WillOnce(Invoke(&ReturnsHostName));
EXPECT_NE(nullptr, client_->GetPublisher("uuid", "name", "note"));
}
TEST_F(AvahiClientTest, ShouldStartMonitoringWhenUp) {
RegisterAsyncWhenAvahiIs(false, AVAHI_SERVER_INVALID);
ExpectServiceDiscovererCreated(false);
client_->StartMonitoring();
ExpectServiceDiscovererCreated(true);
SendOnStateChanged(AVAHI_SERVER_RUNNING);
}
TEST_F(AvahiClientTest, ShouldStopMonitoringWhenDown) {
RegisterAsyncWhenAvahiIs(true, AVAHI_SERVER_RUNNING);
ExpectServiceDiscovererCreated(true);
client_->StartMonitoring();
EXPECT_CALL(*service_browser_proxy_, Detach());
SendOnStateChanged(AVAHI_SERVER_INVALID);
}
TEST_F(AvahiClientTest, ShouldStopMonitoringWhenRequested) {
RegisterAsyncWhenAvahiIs(true, AVAHI_SERVER_RUNNING);
ExpectServiceDiscovererCreated(true);
client_->StartMonitoring();
EXPECT_CALL(*service_browser_proxy_, Detach());
client_->StopMonitoring();
}
TEST_F(AvahiClientTest, ShouldResumeMonitoring) {
RegisterAsyncWhenAvahiIs(true, AVAHI_SERVER_RUNNING);
ExpectServiceDiscovererCreated(true);
client_->StartMonitoring();
EXPECT_CALL(*service_browser_proxy_, Detach());
// We're not monitoring now, but if Avahi comes back, resume monitoring.
SendOnStateChanged(AVAHI_SERVER_INVALID);
ExpectServiceDiscovererCreated(true);
SendOnStateChanged(AVAHI_SERVER_RUNNING);
}
TEST_F(AvahiClientTest, StopMonitoringIsPersistent) {
RegisterAsyncWhenAvahiIs(true, AVAHI_SERVER_RUNNING);
ExpectServiceDiscovererCreated(true);
client_->StartMonitoring();
EXPECT_CALL(*service_browser_proxy_, Detach());
client_->StopMonitoring();
ExpectServiceDiscovererCreated(false);
// Changes in Avahi state going forward don't matter, no monitoring
// should happen.
SendOnStateChanged(AVAHI_SERVER_INVALID);
SendOnStateChanged(AVAHI_SERVER_RUNNING);
}
} // namespace peerd