blob: ab72ef2b0a13dd81a7c20a345d14440f34ddfb81 [file] [log] [blame]
// Copyright 2012 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Unit tests for the InvalidationClientImpl class.
#include <vector>
#include "google/cacheinvalidation/client_test_internal.pb.h"
#include "google/cacheinvalidation/types.pb.h"
#include "google/cacheinvalidation/include/invalidation-listener.h"
#include "google/cacheinvalidation/include/types.h"
#include "google/cacheinvalidation/deps/gmock.h"
#include "google/cacheinvalidation/deps/googletest.h"
#include "google/cacheinvalidation/deps/random.h"
#include "google/cacheinvalidation/deps/string_util.h"
#include "google/cacheinvalidation/impl/basic-system-resources.h"
#include "google/cacheinvalidation/impl/constants.h"
#include "google/cacheinvalidation/impl/invalidation-client-impl.h"
#include "google/cacheinvalidation/impl/statistics.h"
#include "google/cacheinvalidation/impl/throttle.h"
#include "google/cacheinvalidation/impl/ticl-message-validator.h"
#include "google/cacheinvalidation/test/deterministic-scheduler.h"
#include "google/cacheinvalidation/test/test-logger.h"
#include "google/cacheinvalidation/test/test-utils.h"
namespace invalidation {
using ::ipc::invalidation::ClientType_Type_TEST;
using ::ipc::invalidation::RegistrationManagerStateP;
using ::ipc::invalidation::ObjectSource_Type_TEST;
using ::ipc::invalidation::StatusP_Code_PERMANENT_FAILURE;
using ::testing::_;
using ::testing::AllOf;
using ::testing::DeleteArg;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::EqualsProto;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::InvokeArgument;
using ::testing::Matcher;
using ::testing::Property;
using ::testing::Return;
using ::testing::ReturnPointee;
using ::testing::SaveArg;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
using ::testing::proto::WhenDeserializedAs;
// Creates an action SaveArgToVector<k>(vector*) that saves the kth argument in
// |vec|.
ACTION_TEMPLATE(
SaveArgToVector,
HAS_1_TEMPLATE_PARAMS(int, k),
AND_1_VALUE_PARAMS(vec)) {
vec->push_back(std::tr1::get<k>(args));
}
// Given the ReadCallback of Storage::ReadKey as argument 1, invokes it with a
// permanent failure status code.
ACTION(InvokeReadCallbackFailure) {
arg1->Run(pair<Status, string>(Status(Status::PERMANENT_FAILURE, ""), ""));
delete arg1;
}
// Given the WriteCallback of Storage::WriteKey as argument 2, invokes it with
// a success status code.
ACTION(InvokeWriteCallbackSuccess) {
arg2->Run(Status(Status::SUCCESS, ""));
delete arg2;
}
// Tests the basic functionality of the invalidation client.
class InvalidationClientImplTest : public UnitTestBase {
public:
virtual ~InvalidationClientImplTest() {}
// Performs setup for protocol handler unit tests, e.g. creating resource
// components and setting up common expectations for certain mock objects.
virtual void SetUp() {
UnitTestBase::SetUp();
InitCommonExpectations(); // Set up expectations for common mock operations
// Clear throttle limits so that it does not interfere with any test.
InvalidationClientImpl::InitConfig(&config);
config.set_smear_percent(kDefaultSmearPercent);
config.mutable_protocol_handler_config()->clear_rate_limit();
// Set up the listener scheduler to run any runnable that it receives.
EXPECT_CALL(*listener_scheduler, Schedule(_, _))
.WillRepeatedly(InvokeAndDeleteClosure<1>());
// Create the actual client.
Random* random = new Random(InvalidationClientUtil::GetCurrentTimeMs(
resources->internal_scheduler()));
client.reset(new InvalidationClientImpl(
resources.get(), random, ClientType_Type_TEST, "clientName", config,
"InvClientTest", &listener));
}
// Starts the Ticl and ensures that the initialize message is sent. In
// response, gives a tokencontrol message to the protocol handler and makes
// sure that ready is called. client_messages is the list of messages expected
// from the client. The 0th message corresponds to the initialization message
// sent out by the client.
void StartClient() {
// Start the client.
client.get()->Start();
// Let the message be sent out.
internal_scheduler->PassTime(
GetMaxBatchingDelay(config.protocol_handler_config()));
// Check that the message contains an initializeMessage.
ClientToServerMessage client_message;
client_message.ParseFromString(outgoing_messages[0]);
ASSERT_TRUE(client_message.has_initialize_message());
string nonce = client_message.initialize_message().nonce();
// Create the token control message and hand it to the protocol handler.
ServerToClientMessage sc_message;
InitServerHeader(nonce, sc_message.mutable_header());
string new_token = "new token";
sc_message.mutable_token_control_message()->set_new_token(new_token);
ProcessIncomingMessage(sc_message, MessageHandlingDelay());
}
// Sets the expectations so that the Ticl is ready to be started such that
// |num_outgoing_messages| are expected to be sent by the ticl. These messages
// will be saved in |outgoing_messages|.
void SetExpectationsForTiclStart(int num_outgoing_msgs) {
// Set up expectations for number of messages expected on the network.
EXPECT_CALL(*network, SendMessage(_))
.Times(num_outgoing_msgs)
.WillRepeatedly(SaveArgToVector<0>(&outgoing_messages));
// Expect the storage to perform a read key that we will fail.
EXPECT_CALL(*storage, ReadKey(_, _))
.WillOnce(InvokeReadCallbackFailure());
// Expect the listener to indicate that it is ready and let it reissue
// registrations.
EXPECT_CALL(listener, Ready(Eq(client.get())));
EXPECT_CALL(listener, ReissueRegistrations(Eq(client.get()), _, _));
// Expect the storage layer to receive the write of the session token.
EXPECT_CALL(*storage, WriteKey(_, _, _))
.WillOnce(InvokeWriteCallbackSuccess());
}
//
// Test state maintained for every test.
//
// Messages sent by the Ticl.
vector<string> outgoing_messages;
// Configuration for the protocol handler (uses defaults).
ClientConfigP config;
// The client being tested. Created fresh for each test function.
scoped_ptr<InvalidationClientImpl> client;
// A mock invalidation listener.
StrictMock<MockInvalidationListener> listener;
};
// Starts the ticl and checks that appropriate calls are made on the listener
// and that a proper message is sent on the network.
TEST_F(InvalidationClientImplTest, Start) {
SetExpectationsForTiclStart(1);
StartClient();
}
// Tests that GenerateNonce generates a unique nonce on every call.
TEST_F(InvalidationClientImplTest, GenerateNonce) {
// Create a random number generated seeded with the current time.
scoped_ptr<Random> random;
random.reset(new Random(InvalidationClientUtil::GetCurrentTimeMs(
resources->internal_scheduler())));
// Generate two nonces and make sure they are distinct. (The chances
// of a collision should be vanishingly small since our correctness
// relies upon no collisions.)
string nonce1 = InvalidationClientCore::GenerateNonce(random.get());
string nonce2 = InvalidationClientCore::GenerateNonce(random.get());
ASSERT_NE(nonce1, nonce2);
}
// Starts the Ticl, registers for a few objects, gets success and ensures that
// the right listener methods are invoked.
TEST_F(InvalidationClientImplTest, Register) {
SetExpectationsForTiclStart(2);
// Set some expectations for registration status messages.
vector<ObjectId> saved_oids;
EXPECT_CALL(listener,
InformRegistrationStatus(Eq(client.get()), _,
InvalidationListener::REGISTERED))
.Times(3)
.WillRepeatedly(SaveArgToVector<1>(&saved_oids));
// Start the Ticl.
StartClient();
// Synthesize a few test object ids.
int num_objects = 3;
vector<ObjectIdP> oid_protos;
vector<ObjectId> oids;
InitTestObjectIds(num_objects, &oid_protos);
ConvertFromObjectIdProtos(oid_protos, &oids);
// Register
client.get()->Register(oids);
// Let the message be sent out.
internal_scheduler->PassTime(
GetMaxBatchingDelay(config.protocol_handler_config()));
// Give a registration status message to the protocol handler and wait for
// the listener calls.
ServerToClientMessage message;
InitServerHeader(client.get()->GetClientToken(), message.mutable_header());
vector<RegistrationStatus> registration_statuses;
MakeRegistrationStatusesFromObjectIds(oid_protos, true, true,
&registration_statuses);
for (int i = 0; i < num_objects; ++i) {
message.mutable_registration_status_message()
->add_registration_status()->CopyFrom(registration_statuses[i]);
}
// Give this message to the protocol handler.
ProcessIncomingMessage(message, EndOfTestWaitTime());
// Check the object ids.
ASSERT_TRUE(CompareVectorsAsSets(saved_oids, oids));
// Check the registration message.
ClientToServerMessage client_msg;
client_msg.ParseFromString(outgoing_messages[1]);
ASSERT_TRUE(client_msg.has_registration_message());
ASSERT_FALSE(client_msg.has_info_message());
ASSERT_FALSE(client_msg.has_registration_sync_message());
RegistrationMessage expected_msg;
InitRegistrationMessage(oid_protos, true, &expected_msg);
const RegistrationMessage& actual_msg = client_msg.registration_message();
ASSERT_TRUE(CompareMessages(expected_msg, actual_msg));
}
// Tests that given invalidations from the server, the right listener methods
// are invoked. Ack the invalidations and make sure that the ack message is sent
// out. Include a payload in one invalidation and make sure the client does not
// include it in the ack.
TEST_F(InvalidationClientImplTest, Invalidations) {
// Set some expectations for starting the client.
SetExpectationsForTiclStart(2);
// Synthesize a few test object ids.
int num_objects = 3;
vector<ObjectIdP> oid_protos;
vector<ObjectId> oids;
InitTestObjectIds(num_objects, &oid_protos);
ConvertFromObjectIdProtos(oid_protos, &oids);
// Set up listener invalidation calls.
vector<InvalidationP> invalidations;
vector<Invalidation> expected_invs;
MakeInvalidationsFromObjectIds(oid_protos, &invalidations);
// Put a payload in one of the invalidations.
invalidations[0].set_payload("this is a payload");
ConvertFromInvalidationProtos(invalidations, &expected_invs);
// Set up expectations for the acks.
vector<Invalidation> saved_invs;
vector<AckHandle> ack_handles;
EXPECT_CALL(listener, Invalidate(Eq(client.get()), _, _))
.Times(3)
.WillRepeatedly(DoAll(SaveArgToVector<1>(&saved_invs),
SaveArgToVector<2>(&ack_handles)));
// Start the Ticl.
StartClient();
// Give this message to the protocol handler.
ServerToClientMessage message;
InitServerHeader(client.get()->GetClientToken(), message.mutable_header());
InitInvalidationMessage(invalidations,
message.mutable_invalidation_message());
// Process the incoming invalidation message.
ProcessIncomingMessage(message, MessageHandlingDelay());
// Check the invalidations.
ASSERT_TRUE(CompareVectorsAsSets(expected_invs, saved_invs));
// Ack the invalidations now and wait for them to be sent out.
for (int i = 0; i < num_objects; i++) {
client.get()->Acknowledge(ack_handles[i]);
}
internal_scheduler->PassTime(
GetMaxBatchingDelay(config.protocol_handler_config()));
// Check that the ack message is as expected.
ClientToServerMessage client_msg;
client_msg.ParseFromString(outgoing_messages[1]);
ASSERT_TRUE(client_msg.has_invalidation_ack_message());
InvalidationMessage expected_msg;
// The client should strip the payload from the invalidation.
invalidations[0].clear_payload();
InitInvalidationMessage(invalidations, &expected_msg);
const InvalidationMessage& actual_msg =
client_msg.invalidation_ack_message();
ASSERT_TRUE(CompareMessages(expected_msg, actual_msg));
}
// Give a registration sync request message and an info request message to the
// client and wait for the sync message and the info message to go out.
TEST_F(InvalidationClientImplTest, ServerRequests) {
// Set some expectations for starting the client.
SetExpectationsForTiclStart(2);
// Start the ticl.
StartClient();
// Make the server to client message.
ServerToClientMessage message;
InitServerHeader(client.get()->GetClientToken(), message.mutable_header());
// Add a registration sync request message.
message.mutable_registration_sync_request_message();
// Add an info request message.
message.mutable_info_request_message()->add_info_type(
InfoRequestMessage_InfoType_GET_PERFORMANCE_COUNTERS);
// Give it to the prototol handler.
ProcessIncomingMessage(message, EndOfTestWaitTime());
// Make sure that the message is as expected.
ClientToServerMessage client_msg;
client_msg.ParseFromString(outgoing_messages[1]);
ASSERT_TRUE(client_msg.has_info_message());
ASSERT_TRUE(client_msg.has_registration_sync_message());
}
// Tests that an incoming unknown failure message results in the app being
// informed about it.
TEST_F(InvalidationClientImplTest, IncomingErrorMessage) {
SetExpectationsForTiclStart(1);
// Set up listener expectation for error.
EXPECT_CALL(listener, InformError(Eq(client.get()), _));
// Start the ticl.
StartClient();
// Give the error message to the protocol handler.
ServerToClientMessage message;
InitServerHeader(client.get()->GetClientToken(), message.mutable_header());
InitErrorMessage(ErrorMessage_Code_UNKNOWN_FAILURE, "Some error message",
message.mutable_error_message());
ProcessIncomingMessage(message, EndOfTestWaitTime());
}
// Tests that an incoming auth failure message results in the app being informed
// about it and the registrations being removed.
TEST_F(InvalidationClientImplTest, IncomingAuthErrorMessage) {
SetExpectationsForTiclStart(2);
// One object to register for.
int num_objects = 1;
vector<ObjectIdP> oid_protos;
vector<ObjectId> oids;
InitTestObjectIds(num_objects, &oid_protos);
ConvertFromObjectIdProtos(oid_protos, &oids);
// Expect error and registration failure from the ticl.
EXPECT_CALL(listener, InformError(Eq(client.get()), _));
EXPECT_CALL(listener, InformRegistrationFailure(Eq(client.get()), Eq(oids[0]),
Eq(false), _));
// Start the client.
StartClient();
// Register and let the message be sent out.
client.get()->Register(oids[0]);
internal_scheduler->PassTime(
GetMaxBatchingDelay(config.protocol_handler_config()));
// Give this message to the protocol handler.
ServerToClientMessage message;
InitServerHeader(client.get()->GetClientToken(), message.mutable_header());
InitErrorMessage(ErrorMessage_Code_AUTH_FAILURE, "Auth error message",
message.mutable_error_message());
ProcessIncomingMessage(message, EndOfTestWaitTime());
}
// Tests that a registration that times out results in a reg sync message being
// sent out.
TEST_F(InvalidationClientImplTest, NetworkTimeouts) {
// Set some expectations for starting the client.
SetExpectationsForTiclStart(3);
// One object to register for.
int num_objects = 1;
vector<ObjectIdP> oid_protos;
vector<ObjectId> oids;
InitTestObjectIds(num_objects, &oid_protos);
ConvertFromObjectIdProtos(oid_protos, &oids);
// Start the client.
StartClient();
// Register for an object.
client.get()->Register(oids[0]);
// Let the registration message be sent out.
internal_scheduler->PassTime(
GetMaxBatchingDelay(config.protocol_handler_config()));
// Now let the network timeout occur and an info message be sent.
TimeDelta timeout_delay = GetMaxDelay(config.network_timeout_delay_ms());
internal_scheduler->PassTime(timeout_delay);
// Check that the message sent out is an info message asking for the server's
// summary.
ClientToServerMessage client_msg2;
client_msg2.ParseFromString(outgoing_messages[2]);
ASSERT_TRUE(client_msg2.has_info_message());
ASSERT_TRUE(
client_msg2.info_message().server_registration_summary_requested());
internal_scheduler->PassTime(EndOfTestWaitTime());
}
// Tests that an incoming message without registration summary does not
// cause the registration summary in the client to be changed.
TEST_F(InvalidationClientImplTest, NoRegistrationSummary) {
// Test plan: Initialze the ticl, let it get a token with a ServerToClient
// message that has no registration summary.
// Set some expectations for starting the client and start the client.
// Give it a summary with 1 reg.
reg_summary.get()->set_num_registrations(1);
SetExpectationsForTiclStart(1);
StartClient();
// Now give it an message with no summary. It should not reset to a summary
// with zero registrations.
reg_summary.reset(NULL);
ServerToClientMessage message;
InitServerHeader(client.get()->GetClientToken(), message.mutable_header());
ProcessIncomingMessage(message, EndOfTestWaitTime());
// Check that the registration manager state did not change.
string manager_serial_state;
client->GetRegistrationManagerStateAsSerializedProto(&manager_serial_state);
RegistrationManagerStateP reg_manager_state;
reg_manager_state.ParseFromString(manager_serial_state);
// Check that the registration manager state's number of registrations is 1.
TLOG(logger, INFO, "Reg manager state: %s",
ProtoHelpers::ToString(reg_manager_state).c_str());
ASSERT_EQ(1, reg_manager_state.server_summary().num_registrations());
}
// Tests that heartbeats are sent out as time advances.
TEST_F(InvalidationClientImplTest, Heartbeats) {
// Set some expectations for starting the client.
SetExpectationsForTiclStart(2);
// Start the client.
StartClient();
// Now let the heartbeat occur and an info message be sent.
TimeDelta heartbeat_delay = GetMaxDelay(config.heartbeat_interval_ms() +
config.protocol_handler_config().batching_delay_ms());
internal_scheduler->PassTime(heartbeat_delay);
// Check that the heartbeat is sent and it does not ask for the server's
// summary.
ClientToServerMessage client_msg1;
client_msg1.ParseFromString(outgoing_messages[1]);
ASSERT_TRUE(client_msg1.has_info_message());
ASSERT_FALSE(
client_msg1.info_message().server_registration_summary_requested());
internal_scheduler->PassTime(EndOfTestWaitTime());
}
} // namespace invalidation