| // 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, |
| ®istration_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 |