#include "google_apis/gcm/engine/mcs_client.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/simple_test_clock.h"
-#include "components/webdata/encryptor/encryptor.h"
+#include "base/timer/timer.h"
+#include "google_apis/gcm/base/fake_encryptor.h"
#include "google_apis/gcm/base/mcs_util.h"
#include "google_apis/gcm/engine/fake_connection_factory.h"
#include "google_apis/gcm/engine/fake_connection_handler.h"
#include "google_apis/gcm/engine/gcm_store_impl.h"
+#include "google_apis/gcm/monitoring/fake_gcm_stats_recorder.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gcm {
public:
TestMCSClient(base::Clock* clock,
ConnectionFactory* connection_factory,
- GCMStore* gcm_store)
- : MCSClient("", clock, connection_factory, gcm_store),
+ GCMStore* gcm_store,
+ gcm::GCMStatsRecorder* recorder)
+ : MCSClient("", clock, connection_factory, gcm_store, recorder,
+ make_scoped_ptr(new base::Timer(true, false))),
next_id_(0) {
}
- virtual std::string GetNextPersistentId() OVERRIDE {
+ std::string GetNextPersistentId() override {
return base::UintToString(++next_id_);
}
MCSClientTest();
virtual ~MCSClientTest();
+ virtual void SetUp() override;
+
void BuildMCSClient();
void InitializeClient();
void StoreCredentials();
scoped_ptr<MCSMessage> received_message_;
std::string sent_message_id_;
MCSClient::MessageSendStatus message_send_status_;
+
+ gcm::FakeGCMStatsRecorder recorder_;
};
MCSClientTest::MCSClientTest()
EXPECT_TRUE(temp_directory_.CreateUniqueTempDir());
run_loop_.reset(new base::RunLoop());
- // On OSX, prevent the Keychain permissions popup during unit tests.
-#if defined(OS_MACOSX)
- Encryptor::UseMockKeychain(true);
-#endif
-
// Advance the clock to a non-zero time.
clock_.Advance(base::TimeDelta::FromSeconds(1));
}
MCSClientTest::~MCSClientTest() {}
+void MCSClientTest::SetUp() {
+ testing::Test::SetUp();
+}
+
void MCSClientTest::BuildMCSClient() {
- gcm_store_.reset(new GCMStoreImpl(true,
- temp_directory_.path(),
- message_loop_.message_loop_proxy()));
+ gcm_store_.reset(new GCMStoreImpl(
+ temp_directory_.path(),
+ message_loop_.message_loop_proxy(),
+ make_scoped_ptr<Encryptor>(new FakeEncryptor)));
mcs_client_.reset(new TestMCSClient(&clock_,
&connection_factory_,
- gcm_store_.get()));
+ gcm_store_.get(),
+ &recorder_));
}
void MCSClientTest::InitializeClient() {
for (size_t i = 0; i < acknowledged_ids.size(); ++i)
login_request->add_received_persistent_id(acknowledged_ids[i]);
GetFakeHandler()->ExpectOutgoingMessage(
- MCSMessage(kLoginRequestTag,
- login_request.PassAs<const google::protobuf::MessageLite>()));
+ MCSMessage(kLoginRequestTag, login_request.Pass()));
mcs_client_->Login(kAndroidId, kSecurityToken);
run_loop_->Run();
run_loop_.reset(new base::RunLoop());
// The initial (failed) send.
GetFakeHandler()->ExpectOutgoingMessage(message);
// The login request.
- GetFakeHandler()->ExpectOutgoingMessage(
- MCSMessage(
- kLoginRequestTag,
- BuildLoginRequest(kAndroidId, kSecurityToken, "").
- PassAs<const google::protobuf::MessageLite>()));
+ GetFakeHandler()->ExpectOutgoingMessage(MCSMessage(
+ kLoginRequestTag, BuildLoginRequest(kAndroidId, kSecurityToken, "")));
// The second (re)send.
MCSMessage message2(BuildDataMessage(
"from", "category", "X", 1, "1", kTTLValue, 1, kTTLValue - 1, "", 0));
mcs_client()->SendMessage(message);
PumpLoop(); // Wait for the queuing to happen.
EXPECT_EQ(MCSClient::QUEUED, message_send_status());
+ EXPECT_EQ("X", sent_message_id());
EXPECT_FALSE(GetFakeHandler()->AllOutgoingMessagesReceived());
GetFakeHandler()->set_fail_send(false);
clock()->Advance(base::TimeDelta::FromSeconds(kTTLValue - 1));
connection_factory()->Connect();
WaitForMCSEvent(); // Wait for the login to finish.
PumpLoop(); // Wait for the send to happen.
+
+ // Receive the ack.
+ scoped_ptr<mcs_proto::IqStanza> ack = BuildStreamAck();
+ ack->set_last_stream_id_received(2);
+ GetFakeHandler()->ReceiveMessage(MCSMessage(kIqStanzaTag, ack.Pass()));
+ WaitForMCSEvent();
+
+ EXPECT_EQ(MCSClient::SENT, message_send_status());
+ EXPECT_EQ("X", sent_message_id());
EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
}
// Receive the ack.
scoped_ptr<mcs_proto::IqStanza> ack = BuildStreamAck();
ack->set_last_stream_id_received(kMessageBatchSize + 1);
- GetFakeHandler()->ReceiveMessage(
- MCSMessage(kIqStanzaTag,
- ack.PassAs<const google::protobuf::MessageLite>()));
+ GetFakeHandler()->ReceiveMessage(MCSMessage(kIqStanzaTag, ack.Pass()));
WaitForMCSEvent();
// Reconnect and ensure no messages are resent.
InitializeClient();
LoginClient(std::vector<std::string>());
scoped_ptr<mcs_proto::IqStanza> ack(BuildSelectiveAck(id_list));
- GetFakeHandler()->ReceiveMessage(
- MCSMessage(kIqStanzaTag,
- ack.PassAs<const google::protobuf::MessageLite>()));
+ GetFakeHandler()->ReceiveMessage(MCSMessage(kIqStanzaTag, ack.Pass()));
EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
}
GetFakeHandler()->ExpectOutgoingMessage(message);
}
scoped_ptr<mcs_proto::IqStanza> ack(BuildSelectiveAck(acked_ids));
- GetFakeHandler()->ReceiveMessage(
- MCSMessage(kIqStanzaTag,
- ack.PassAs<const google::protobuf::MessageLite>()));
+ GetFakeHandler()->ReceiveMessage(MCSMessage(kIqStanzaTag, ack.Pass()));
WaitForMCSEvent();
PumpLoop();
EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
}
+// Handle a selective ack that only acks some messages. The remaining unacked
+// messages should be resent. On restart, those same unacked messages should be
+// resent, and any pending acks for incoming messages should also be resent.
+TEST_F(MCSClientTest, SelectiveAckMidStream) {
+ BuildMCSClient();
+ InitializeClient();
+ LoginClient(std::vector<std::string>());
+
+ // Server stream id 2 ("s1").
+ // Acks client stream id 0 (login).
+ MCSMessage sMessage1(BuildDataMessage(
+ "from", "category", "X", 0, "s1", kTTLValue, 1, 0, "", 0));
+ GetFakeHandler()->ReceiveMessage(sMessage1);
+ WaitForMCSEvent();
+ PumpLoop();
+
+ // Client stream id 1 ("1").
+ // Acks server stream id 2 ("s1").
+ MCSMessage cMessage1(BuildDataMessage(
+ "from", "category", "Y", 2, "1", kTTLValue, 1, 0, "", 0));
+ GetFakeHandler()->ExpectOutgoingMessage(cMessage1);
+ mcs_client()->SendMessage(cMessage1);
+ PumpLoop();
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
+
+ // Server stream id 3 ("s2").
+ // Acks client stream id 1 ("1").
+ // Confirms ack of server stream id 2 ("s1").
+ MCSMessage sMessage2(BuildDataMessage(
+ "from", "category", "X", 1, "s2", kTTLValue, 1, 0, "", 0));
+ GetFakeHandler()->ReceiveMessage(sMessage2);
+ WaitForMCSEvent();
+ PumpLoop();
+
+ // Client Stream id 2 ("2").
+ // Acks server stream id 3 ("s2").
+ MCSMessage cMessage2(BuildDataMessage(
+ "from", "category", "Y", 3, "2", kTTLValue, 1, 0, "", 0));
+ GetFakeHandler()->ExpectOutgoingMessage(cMessage2);
+ mcs_client()->SendMessage(cMessage2);
+ PumpLoop();
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
+
+ // Simulate the last message being dropped by having the server selectively
+ // ack client message "1".
+ // Client message "2" should be resent, acking server stream id 4 (selective
+ // ack).
+ MCSMessage cMessage3(BuildDataMessage(
+ "from", "category", "Y", 4, "2", kTTLValue, 1, 0, "", 0));
+ GetFakeHandler()->ExpectOutgoingMessage(cMessage3);
+ std::vector<std::string> acked_ids(1, "1");
+ scoped_ptr<mcs_proto::IqStanza> ack(BuildSelectiveAck(acked_ids));
+ GetFakeHandler()->ReceiveMessage(MCSMessage(kIqStanzaTag, ack.Pass()));
+ WaitForMCSEvent();
+ PumpLoop();
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
+
+ // Rebuild the client without any further acks from server. Note that this
+ // resets the stream ids.
+ // Sever message "s2" should be acked as part of login.
+ // Client message "2" should be resent.
+ StoreCredentials();
+ BuildMCSClient();
+ InitializeClient();
+
+ acked_ids[0] = "s2";
+ LoginClient(acked_ids);
+
+ MCSMessage cMessage4(BuildDataMessage(
+ "from", "category", "Y", 1, "2", kTTLValue, 1, 0, "", 0));
+ GetFakeHandler()->ExpectOutgoingMessage(cMessage4);
+ PumpLoop();
+ EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());
+}
+
// Receive some messages. On restart, the login request should contain the
// appropriate acknowledged ids.
TEST_F(MCSClientTest, AckOnLogin) {
// The stream ack.
scoped_ptr<mcs_proto::IqStanza> ack = BuildStreamAck();
ack->set_last_stream_id_received(kAckLimitSize + 1);
- GetFakeHandler()->ExpectOutgoingMessage(
- MCSMessage(kIqStanzaTag,
- ack.PassAs<const google::protobuf::MessageLite>()));
+ GetFakeHandler()->ExpectOutgoingMessage(MCSMessage(kIqStanzaTag, ack.Pass()));
// Receive some messages.
std::vector<std::string> id_list;
new mcs_proto::HeartbeatAck());
heartbeat_ack->set_last_stream_id_received(kAckLimitSize + 2);
GetFakeHandler()->ExpectOutgoingMessage(
- MCSMessage(kHeartbeatAckTag,
- heartbeat_ack.PassAs<const google::protobuf::MessageLite>()));
+ MCSMessage(kHeartbeatAckTag, heartbeat_ack.Pass()));
GetFakeHandler()->ReceiveMessage(
- MCSMessage(kHeartbeatPingTag,
- heartbeat.PassAs<const google::protobuf::MessageLite>()));
+ MCSMessage(kHeartbeatPingTag, heartbeat.Pass()));
PumpLoop();
EXPECT_TRUE(GetFakeHandler()->AllOutgoingMessagesReceived());