blob: 9df58226b38e2b20497f9b638513168016525e91 [file] [log] [blame]
// Copyright 2014 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.
// This class defines tests that implementations of InvalidationService should
// pass in order to be conformant. Here's how you use it to test your
// implementation.
//
// Say your class is called MyInvalidationService. Then you need to define a
// class called MyInvalidationServiceTestDelegate in
// my_invalidation_frontend_unittest.cc like this:
//
// class MyInvalidationServiceTestDelegate {
// public:
// MyInvalidationServiceTestDelegate() ...
//
// ~MyInvalidationServiceTestDelegate() {
// // DestroyInvalidator() may not be explicitly called by tests.
// DestroyInvalidator();
// }
//
// // Create the InvalidationService implementation with the given params.
// void CreateInvalidationService() {
// ...
// }
//
// // Should return the InvalidationService implementation. Only called
// // after CreateInvalidator and before DestroyInvalidator.
// MyInvalidationService* GetInvalidationService() {
// ...
// }
//
// // Destroy the InvalidationService implementation.
// void DestroyInvalidationService() {
// ...
// }
//
// // The Trigger* functions below should block until the effects of
// // the call are visible on the current thread.
//
// // Should cause OnInvalidatorStateChange() to be called on all
// // observers of the InvalidationService implementation with the given
// // parameters.
// void TriggerOnInvalidatorStateChange(InvalidatorState state) {
// ...
// }
//
// // Should cause OnIncomingInvalidation() to be called on all
// // observers of the InvalidationService implementation with the given
// // parameters.
// void TriggerOnIncomingInvalidation(
// const TopicInvalidationMap& invalidation_map) {
// ...
// }
// };
//
// The InvalidationServiceTest test harness will have a member variable of
// this delegate type and will call its functions in the various
// tests.
//
// Then you simply #include this file as well as gtest.h and add the
// following statement to my_sync_notifier_unittest.cc:
//
// INSTANTIATE_TYPED_TEST_SUITE_P(
// MyInvalidationService,
// InvalidationServiceTest,
// MyInvalidatorTestDelegate);
//
// Easy!
#ifndef COMPONENTS_INVALIDATION_IMPL_INVALIDATION_SERVICE_TEST_TEMPLATE_H_
#define COMPONENTS_INVALIDATION_IMPL_INVALIDATION_SERVICE_TEST_TEMPLATE_H_
#include "base/compiler_specific.h"
#include "components/invalidation/impl/fake_invalidation_handler.h"
#include "components/invalidation/impl/topic_invalidation_map_test_util.h"
#include "components/invalidation/public/ack_handle.h"
#include "components/invalidation/public/invalidation.h"
#include "components/invalidation/public/invalidation_service.h"
#include "components/invalidation/public/invalidation_util.h"
#include "components/invalidation/public/topic_invalidation_map.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace invalidation {
template <typename InvalidatorTestDelegate>
class InvalidationServiceTest : public testing::Test {
protected:
InvalidationServiceTest() = default;
InvalidationService* CreateAndInitializeInvalidationService() {
this->delegate_.CreateInvalidationService();
return this->delegate_.GetInvalidationService();
}
InvalidatorTestDelegate delegate_;
const Topic topic1 = "BOOKMARK";
const Topic topic2 = "PREFERENCE";
const Topic topic3 = "AUTOFILL";
const Topic topic4 = "PUSH_MESSAGE";
};
TYPED_TEST_SUITE_P(InvalidationServiceTest);
// Initialize the invalidator, register a handler, register some IDs for that
// handler, and then unregister the handler, dispatching invalidations in
// between. The handler should only see invalidations when its registered and
// its IDs are registered.
TYPED_TEST_P(InvalidationServiceTest, Basic) {
InvalidationService* const invalidator =
this->CreateAndInitializeInvalidationService();
FakeInvalidationHandler handler;
invalidator->RegisterInvalidationHandler(&handler);
TopicInvalidationMap invalidation_map;
invalidation_map.Insert(Invalidation::Init(this->topic1, 1, "1"));
invalidation_map.Insert(Invalidation::Init(this->topic2, 2, "2"));
invalidation_map.Insert(Invalidation::Init(this->topic3, 3, "3"));
// Should be ignored since no IDs are registered to |handler|.
this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
EXPECT_EQ(0, handler.GetInvalidationCount());
TopicSet topics;
topics.insert(this->topic1);
topics.insert(this->topic2);
EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler, topics));
this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
EXPECT_EQ(INVALIDATIONS_ENABLED, handler.GetInvalidatorState());
TopicInvalidationMap expected_invalidations;
expected_invalidations.Insert(Invalidation::Init(this->topic1, 1, "1"));
expected_invalidations.Insert(Invalidation::Init(this->topic2, 2, "2"));
this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
EXPECT_EQ(1, handler.GetInvalidationCount());
EXPECT_THAT(expected_invalidations, Eq(handler.GetLastInvalidationMap()));
topics.erase(this->topic1);
topics.insert(this->topic3);
EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler, topics));
expected_invalidations = TopicInvalidationMap();
expected_invalidations.Insert(Invalidation::Init(this->topic2, 2, "2"));
expected_invalidations.Insert(Invalidation::Init(this->topic3, 3, "3"));
// Removed Topics should not be notified, newly-added ones should.
this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
EXPECT_EQ(2, handler.GetInvalidationCount());
EXPECT_THAT(expected_invalidations, Eq(handler.GetLastInvalidationMap()));
this->delegate_.TriggerOnInvalidatorStateChange(TRANSIENT_INVALIDATION_ERROR);
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler.GetInvalidatorState());
this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
EXPECT_EQ(INVALIDATIONS_ENABLED, handler.GetInvalidatorState());
invalidator->UnregisterInvalidationHandler(&handler);
// Should be ignored since |handler| isn't registered anymore.
this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
EXPECT_EQ(2, handler.GetInvalidationCount());
}
// Register handlers and some topics for those handlers, register a handler
// with no topics, and register a handler with some topics but unregister it.
// Then, dispatch some invalidations and invalidations. Handlers that are
// registered should get invalidations, and the ones that have registered
// topics should receive invalidations for those topics.
TYPED_TEST_P(InvalidationServiceTest, MultipleHandlers) {
InvalidationService* const invalidator =
this->CreateAndInitializeInvalidationService();
FakeInvalidationHandler handler1;
FakeInvalidationHandler handler2;
FakeInvalidationHandler handler3;
FakeInvalidationHandler handler4;
invalidator->RegisterInvalidationHandler(&handler1);
invalidator->RegisterInvalidationHandler(&handler2);
invalidator->RegisterInvalidationHandler(&handler3);
invalidator->RegisterInvalidationHandler(&handler4);
{
TopicSet topics;
topics.insert(this->topic1);
topics.insert(this->topic2);
EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler1, topics));
}
{
TopicSet topics;
topics.insert(this->topic3);
EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler2, topics));
}
// Don't register any topics for handler3.
{
TopicSet topics;
topics.insert(this->topic4);
EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler4, topics));
}
invalidator->UnregisterInvalidationHandler(&handler4);
this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
EXPECT_EQ(INVALIDATIONS_ENABLED, handler1.GetInvalidatorState());
EXPECT_EQ(INVALIDATIONS_ENABLED, handler2.GetInvalidatorState());
EXPECT_EQ(INVALIDATIONS_ENABLED, handler3.GetInvalidatorState());
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler4.GetInvalidatorState());
{
TopicInvalidationMap invalidation_map;
invalidation_map.Insert(Invalidation::Init(this->topic1, 1, "1"));
invalidation_map.Insert(Invalidation::Init(this->topic2, 2, "2"));
invalidation_map.Insert(Invalidation::Init(this->topic3, 3, "3"));
invalidation_map.Insert(Invalidation::Init(this->topic4, 4, "4"));
this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
TopicInvalidationMap expected_invalidations;
expected_invalidations.Insert(Invalidation::Init(this->topic1, 1, "1"));
expected_invalidations.Insert(Invalidation::Init(this->topic2, 2, "2"));
EXPECT_EQ(1, handler1.GetInvalidationCount());
EXPECT_THAT(expected_invalidations, Eq(handler1.GetLastInvalidationMap()));
expected_invalidations = TopicInvalidationMap();
expected_invalidations.Insert(Invalidation::Init(this->topic3, 3, "3"));
EXPECT_EQ(1, handler2.GetInvalidationCount());
EXPECT_THAT(expected_invalidations, Eq(handler2.GetLastInvalidationMap()));
EXPECT_EQ(0, handler3.GetInvalidationCount());
EXPECT_EQ(0, handler4.GetInvalidationCount());
}
this->delegate_.TriggerOnInvalidatorStateChange(TRANSIENT_INVALIDATION_ERROR);
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler1.GetInvalidatorState());
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler2.GetInvalidatorState());
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler3.GetInvalidatorState());
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler4.GetInvalidatorState());
invalidator->UnregisterInvalidationHandler(&handler3);
invalidator->UnregisterInvalidationHandler(&handler2);
invalidator->UnregisterInvalidationHandler(&handler1);
}
// Multiple registrations by different handlers on the same Topic should return
// false.
TYPED_TEST_P(InvalidationServiceTest, MultipleRegistrations) {
InvalidationService* const invalidator =
this->CreateAndInitializeInvalidationService();
FakeInvalidationHandler handler1;
FakeInvalidationHandler handler2;
invalidator->RegisterInvalidationHandler(&handler1);
invalidator->RegisterInvalidationHandler(&handler2);
// Registering both handlers for the same topic. First call should succeed,
// second should fail.
TopicSet topics;
topics.insert(this->topic1);
EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler1, topics));
EXPECT_FALSE(invalidator->UpdateInterestedTopics(&handler2, topics));
invalidator->UnregisterInvalidationHandler(&handler2);
invalidator->UnregisterInvalidationHandler(&handler1);
}
// Make sure that passing an empty set to UpdateInterestedTopics clears
// the corresponding entries for the handler.
TYPED_TEST_P(InvalidationServiceTest, EmptySetUnregisters) {
InvalidationService* const invalidator =
this->CreateAndInitializeInvalidationService();
FakeInvalidationHandler handler1;
// Control observer.
FakeInvalidationHandler handler2;
invalidator->RegisterInvalidationHandler(&handler1);
invalidator->RegisterInvalidationHandler(&handler2);
{
TopicSet topics;
topics.insert(this->topic1);
topics.insert(this->topic2);
EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler1, topics));
}
{
TopicSet topics;
topics.insert(this->topic3);
EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler2, topics));
}
// Unregister the topics for the first observer. It should not receive any
// further invalidations.
EXPECT_TRUE(invalidator->UpdateInterestedTopics(&handler1, TopicSet()));
this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
EXPECT_EQ(INVALIDATIONS_ENABLED, handler1.GetInvalidatorState());
EXPECT_EQ(INVALIDATIONS_ENABLED, handler2.GetInvalidatorState());
{
TopicInvalidationMap invalidation_map;
invalidation_map.Insert(Invalidation::Init(this->topic1, 1, "1"));
invalidation_map.Insert(Invalidation::Init(this->topic2, 2, "2"));
invalidation_map.Insert(Invalidation::Init(this->topic3, 3, "3"));
this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
EXPECT_EQ(0, handler1.GetInvalidationCount());
EXPECT_EQ(1, handler2.GetInvalidationCount());
}
this->delegate_.TriggerOnInvalidatorStateChange(TRANSIENT_INVALIDATION_ERROR);
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler1.GetInvalidatorState());
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler2.GetInvalidatorState());
invalidator->UnregisterInvalidationHandler(&handler2);
invalidator->UnregisterInvalidationHandler(&handler1);
}
// A FakeInvalidationHandler that is "bound" to a specific
// InvalidationService. This is for cross-referencing state information with
// the bound InvalidationService.
class BoundFakeInvalidationHandler : public FakeInvalidationHandler {
public:
explicit BoundFakeInvalidationHandler(const InvalidationService& invalidator);
BoundFakeInvalidationHandler(const BoundFakeInvalidationHandler&) = delete;
BoundFakeInvalidationHandler& operator=(const BoundFakeInvalidationHandler&) =
delete;
~BoundFakeInvalidationHandler() override;
// Returns the last return value of GetInvalidatorState() on the
// bound invalidator from the last time the invalidator state
// changed.
InvalidatorState GetLastRetrievedState() const;
// InvalidationHandler implementation.
void OnInvalidatorStateChange(InvalidatorState state) override;
private:
const InvalidationService& invalidator_;
InvalidatorState last_retrieved_state_;
};
TYPED_TEST_P(InvalidationServiceTest, GetInvalidatorStateAlwaysCurrent) {
InvalidationService* const invalidator =
this->CreateAndInitializeInvalidationService();
BoundFakeInvalidationHandler handler(*invalidator);
invalidator->RegisterInvalidationHandler(&handler);
this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
EXPECT_EQ(INVALIDATIONS_ENABLED, handler.GetInvalidatorState());
EXPECT_EQ(INVALIDATIONS_ENABLED, handler.GetLastRetrievedState());
this->delegate_.TriggerOnInvalidatorStateChange(TRANSIENT_INVALIDATION_ERROR);
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler.GetInvalidatorState());
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler.GetLastRetrievedState());
invalidator->UnregisterInvalidationHandler(&handler);
}
REGISTER_TYPED_TEST_SUITE_P(InvalidationServiceTest,
Basic,
MultipleHandlers,
MultipleRegistrations,
EmptySetUnregisters,
GetInvalidatorStateAlwaysCurrent);
} // namespace invalidation
#endif // COMPONENTS_INVALIDATION_IMPL_INVALIDATION_SERVICE_TEST_TEMPLATE_H_