1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This class defines tests that implementations of Invalidator should pass in
6// order to be conformant.  Here's how you use it to test your implementation.
7//
8// Say your class is called MyInvalidator.  Then you need to define a class
9// called MyInvalidatorTestDelegate in my_sync_notifier_unittest.cc like this:
10//
11//   class MyInvalidatorTestDelegate {
12//    public:
13//     MyInvalidatorTestDelegate() ...
14//
15//     ~MyInvalidatorTestDelegate() {
16//       // DestroyInvalidator() may not be explicitly called by tests.
17//       DestroyInvalidator();
18//     }
19//
20//     // Create the Invalidator implementation with the given parameters.
21//     void CreateInvalidator(
22//         const std::string& initial_state,
23//         const base::WeakPtr<InvalidationStateTracker>&
24//             invalidation_state_tracker) {
25//       ...
26//     }
27//
28//     // Should return the Invalidator implementation.  Only called after
29//     // CreateInvalidator and before DestroyInvalidator.
30//     MyInvalidator* GetInvalidator() {
31//       ...
32//     }
33//
34//     // Destroy the Invalidator implementation.
35//     void DestroyInvalidator() {
36//       ...
37//     }
38//
39//     // Called after a call to SetUniqueId(), or UpdateCredentials() on the
40//     // Invalidator implementation.  Should block until the effects of the
41//     // call are visible on the current thread.
42//     void WaitForInvalidator() {
43//       ...
44//     }
45//
46//     // The Trigger* functions below should block until the effects of
47//     // the call are visible on the current thread.
48//
49//     // Should cause OnInvalidatorStateChange() to be called on all
50//     // observers of the Invalidator implementation with the given
51//     // parameters.
52//     void TriggerOnInvalidatorStateChange(InvalidatorState state) {
53//       ...
54//     }
55//
56//     // Should cause OnIncomingInvalidation() to be called on all
57//     // observers of the Invalidator implementation with the given
58//     // parameters.
59//     void TriggerOnIncomingInvalidation(
60//         const ObjectIdInvalidationMap& invalidation_map) {
61//       ...
62//     }
63//   };
64//
65// The InvalidatorTest test harness will have a member variable of
66// this delegate type and will call its functions in the various
67// tests.
68//
69// Then you simply #include this file as well as gtest.h and add the
70// following statement to my_sync_notifier_unittest.cc:
71//
72//   INSTANTIATE_TYPED_TEST_CASE_P(
73//       MyInvalidator, InvalidatorTest, MyInvalidatorTestDelegate);
74//
75// Easy!
76
77#ifndef COMPONENTS_INVALIDATION_INVALIDATOR_TEST_TEMPLATE_H_
78#define COMPONENTS_INVALIDATION_INVALIDATOR_TEST_TEMPLATE_H_
79
80#include "base/basictypes.h"
81#include "base/compiler_specific.h"
82#include "components/invalidation/fake_invalidation_handler.h"
83#include "components/invalidation/fake_invalidation_state_tracker.h"
84#include "components/invalidation/invalidator.h"
85#include "components/invalidation/object_id_invalidation_map_test_util.h"
86#include "google/cacheinvalidation/include/types.h"
87#include "google/cacheinvalidation/types.pb.h"
88#include "testing/gtest/include/gtest/gtest.h"
89
90namespace syncer {
91
92template <typename InvalidatorTestDelegate>
93class InvalidatorTest : public testing::Test {
94 protected:
95  InvalidatorTest()
96      : id1(ipc::invalidation::ObjectSource::TEST, "a"),
97        id2(ipc::invalidation::ObjectSource::TEST, "b"),
98        id3(ipc::invalidation::ObjectSource::TEST, "c"),
99        id4(ipc::invalidation::ObjectSource::TEST, "d") {
100  }
101
102  Invalidator* CreateAndInitializeInvalidator() {
103    this->delegate_.CreateInvalidator("fake_invalidator_client_id",
104                                      "fake_initial_state",
105                                      this->fake_tracker_.AsWeakPtr());
106    Invalidator* const invalidator = this->delegate_.GetInvalidator();
107
108    this->delegate_.WaitForInvalidator();
109    invalidator->UpdateCredentials("foo@bar.com", "fake_token");
110    this->delegate_.WaitForInvalidator();
111
112    return invalidator;
113  }
114
115  FakeInvalidationStateTracker fake_tracker_;
116  InvalidatorTestDelegate delegate_;
117
118  const invalidation::ObjectId id1;
119  const invalidation::ObjectId id2;
120  const invalidation::ObjectId id3;
121  const invalidation::ObjectId id4;
122};
123
124TYPED_TEST_CASE_P(InvalidatorTest);
125
126// Initialize the invalidator, register a handler, register some IDs for that
127// handler, and then unregister the handler, dispatching invalidations in
128// between.  The handler should only see invalidations when its registered and
129// its IDs are registered.
130TYPED_TEST_P(InvalidatorTest, Basic) {
131  Invalidator* const invalidator = this->CreateAndInitializeInvalidator();
132
133  FakeInvalidationHandler handler;
134
135  invalidator->RegisterHandler(&handler);
136
137  ObjectIdInvalidationMap invalidation_map;
138  invalidation_map.Insert(Invalidation::Init(this->id1, 1, "1"));
139  invalidation_map.Insert(Invalidation::Init(this->id2, 2, "2"));
140  invalidation_map.Insert(Invalidation::Init(this->id3, 3, "3"));
141
142  // Should be ignored since no IDs are registered to |handler|.
143  this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
144  EXPECT_EQ(0, handler.GetInvalidationCount());
145
146  ObjectIdSet ids;
147  ids.insert(this->id1);
148  ids.insert(this->id2);
149  invalidator->UpdateRegisteredIds(&handler, ids);
150
151  this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
152  EXPECT_EQ(INVALIDATIONS_ENABLED, handler.GetInvalidatorState());
153
154  ObjectIdInvalidationMap expected_invalidations;
155  expected_invalidations.Insert(Invalidation::Init(this->id1, 1, "1"));
156  expected_invalidations.Insert(Invalidation::Init(this->id2, 2, "2"));
157
158  this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
159  EXPECT_EQ(1, handler.GetInvalidationCount());
160  EXPECT_THAT(expected_invalidations, Eq(handler.GetLastInvalidationMap()));
161
162  ids.erase(this->id1);
163  ids.insert(this->id3);
164  invalidator->UpdateRegisteredIds(&handler, ids);
165
166  expected_invalidations = ObjectIdInvalidationMap();
167  expected_invalidations.Insert(Invalidation::Init(this->id2, 2, "2"));
168  expected_invalidations.Insert(Invalidation::Init(this->id3, 3, "3"));
169
170  // Removed object IDs should not be notified, newly-added ones should.
171  this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
172  EXPECT_EQ(2, handler.GetInvalidationCount());
173  EXPECT_THAT(expected_invalidations, Eq(handler.GetLastInvalidationMap()));
174
175  this->delegate_.TriggerOnInvalidatorStateChange(TRANSIENT_INVALIDATION_ERROR);
176  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR,
177            handler.GetInvalidatorState());
178
179  this->delegate_.TriggerOnInvalidatorStateChange(
180      INVALIDATION_CREDENTIALS_REJECTED);
181  EXPECT_EQ(INVALIDATION_CREDENTIALS_REJECTED,
182            handler.GetInvalidatorState());
183
184  invalidator->UnregisterHandler(&handler);
185
186  // Should be ignored since |handler| isn't registered anymore.
187  this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
188  EXPECT_EQ(2, handler.GetInvalidationCount());
189}
190
191// Register handlers and some IDs for those handlers, register a handler with
192// no IDs, and register a handler with some IDs but unregister it.  Then,
193// dispatch some invalidations and invalidations.  Handlers that are registered
194// should get invalidations, and the ones that have registered IDs should
195// receive invalidations for those IDs.
196TYPED_TEST_P(InvalidatorTest, MultipleHandlers) {
197  Invalidator* const invalidator = this->CreateAndInitializeInvalidator();
198
199  FakeInvalidationHandler handler1;
200  FakeInvalidationHandler handler2;
201  FakeInvalidationHandler handler3;
202  FakeInvalidationHandler handler4;
203
204  invalidator->RegisterHandler(&handler1);
205  invalidator->RegisterHandler(&handler2);
206  invalidator->RegisterHandler(&handler3);
207  invalidator->RegisterHandler(&handler4);
208
209  {
210    ObjectIdSet ids;
211    ids.insert(this->id1);
212    ids.insert(this->id2);
213    invalidator->UpdateRegisteredIds(&handler1, ids);
214  }
215
216  {
217    ObjectIdSet ids;
218    ids.insert(this->id3);
219    invalidator->UpdateRegisteredIds(&handler2, ids);
220  }
221
222  // Don't register any IDs for handler3.
223
224  {
225    ObjectIdSet ids;
226    ids.insert(this->id4);
227    invalidator->UpdateRegisteredIds(&handler4, ids);
228  }
229
230  invalidator->UnregisterHandler(&handler4);
231
232  this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
233  EXPECT_EQ(INVALIDATIONS_ENABLED, handler1.GetInvalidatorState());
234  EXPECT_EQ(INVALIDATIONS_ENABLED, handler2.GetInvalidatorState());
235  EXPECT_EQ(INVALIDATIONS_ENABLED, handler3.GetInvalidatorState());
236  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler4.GetInvalidatorState());
237
238  {
239    ObjectIdInvalidationMap invalidation_map;
240    invalidation_map.Insert(Invalidation::Init(this->id1, 1, "1"));
241    invalidation_map.Insert(Invalidation::Init(this->id2, 2, "2"));
242    invalidation_map.Insert(Invalidation::Init(this->id3, 3, "3"));
243    invalidation_map.Insert(Invalidation::Init(this->id4, 4, "4"));
244
245    this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
246
247    ObjectIdInvalidationMap expected_invalidations;
248    expected_invalidations.Insert(Invalidation::Init(this->id1, 1, "1"));
249    expected_invalidations.Insert(Invalidation::Init(this->id2, 2, "2"));
250
251    EXPECT_EQ(1, handler1.GetInvalidationCount());
252    EXPECT_THAT(expected_invalidations, Eq(handler1.GetLastInvalidationMap()));
253
254    expected_invalidations = ObjectIdInvalidationMap();
255    expected_invalidations.Insert(Invalidation::Init(this->id3, 3, "3"));
256
257    EXPECT_EQ(1, handler2.GetInvalidationCount());
258    EXPECT_THAT(expected_invalidations, Eq(handler2.GetLastInvalidationMap()));
259
260    EXPECT_EQ(0, handler3.GetInvalidationCount());
261    EXPECT_EQ(0, handler4.GetInvalidationCount());
262  }
263
264  this->delegate_.TriggerOnInvalidatorStateChange(TRANSIENT_INVALIDATION_ERROR);
265  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler1.GetInvalidatorState());
266  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler2.GetInvalidatorState());
267  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler3.GetInvalidatorState());
268  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler4.GetInvalidatorState());
269
270  invalidator->UnregisterHandler(&handler3);
271  invalidator->UnregisterHandler(&handler2);
272  invalidator->UnregisterHandler(&handler1);
273}
274
275// Make sure that passing an empty set to UpdateRegisteredIds clears the
276// corresponding entries for the handler.
277TYPED_TEST_P(InvalidatorTest, EmptySetUnregisters) {
278  Invalidator* const invalidator = this->CreateAndInitializeInvalidator();
279
280  FakeInvalidationHandler handler1;
281
282  // Control observer.
283  FakeInvalidationHandler handler2;
284
285  invalidator->RegisterHandler(&handler1);
286  invalidator->RegisterHandler(&handler2);
287
288  {
289    ObjectIdSet ids;
290    ids.insert(this->id1);
291    ids.insert(this->id2);
292    invalidator->UpdateRegisteredIds(&handler1, ids);
293  }
294
295  {
296    ObjectIdSet ids;
297    ids.insert(this->id3);
298    invalidator->UpdateRegisteredIds(&handler2, ids);
299  }
300
301  // Unregister the IDs for the first observer. It should not receive any
302  // further invalidations.
303  invalidator->UpdateRegisteredIds(&handler1, ObjectIdSet());
304
305  this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
306  EXPECT_EQ(INVALIDATIONS_ENABLED, handler1.GetInvalidatorState());
307  EXPECT_EQ(INVALIDATIONS_ENABLED, handler2.GetInvalidatorState());
308
309  {
310    ObjectIdInvalidationMap invalidation_map;
311    invalidation_map.Insert(Invalidation::Init(this->id1, 1, "1"));
312    invalidation_map.Insert(Invalidation::Init(this->id2, 2, "2"));
313    invalidation_map.Insert(Invalidation::Init(this->id3, 3, "3"));
314    this->delegate_.TriggerOnIncomingInvalidation(invalidation_map);
315    EXPECT_EQ(0, handler1.GetInvalidationCount());
316    EXPECT_EQ(1, handler2.GetInvalidationCount());
317  }
318
319  this->delegate_.TriggerOnInvalidatorStateChange(TRANSIENT_INVALIDATION_ERROR);
320  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler1.GetInvalidatorState());
321  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler2.GetInvalidatorState());
322
323  invalidator->UnregisterHandler(&handler2);
324  invalidator->UnregisterHandler(&handler1);
325}
326
327namespace internal {
328
329// A FakeInvalidationHandler that is "bound" to a specific
330// Invalidator.  This is for cross-referencing state information with
331// the bound Invalidator.
332class BoundFakeInvalidationHandler : public FakeInvalidationHandler {
333 public:
334  explicit BoundFakeInvalidationHandler(const Invalidator& invalidator);
335  virtual ~BoundFakeInvalidationHandler();
336
337  // Returns the last return value of GetInvalidatorState() on the
338  // bound invalidator from the last time the invalidator state
339  // changed.
340  InvalidatorState GetLastRetrievedState() const;
341
342  // InvalidationHandler implementation.
343  virtual void OnInvalidatorStateChange(InvalidatorState state) OVERRIDE;
344
345 private:
346  const Invalidator& invalidator_;
347  InvalidatorState last_retrieved_state_;
348
349  DISALLOW_COPY_AND_ASSIGN(BoundFakeInvalidationHandler);
350};
351
352}  // namespace internal
353
354TYPED_TEST_P(InvalidatorTest, GetInvalidatorStateAlwaysCurrent) {
355  Invalidator* const invalidator = this->CreateAndInitializeInvalidator();
356
357  internal::BoundFakeInvalidationHandler handler(*invalidator);
358  invalidator->RegisterHandler(&handler);
359
360  this->delegate_.TriggerOnInvalidatorStateChange(INVALIDATIONS_ENABLED);
361  EXPECT_EQ(INVALIDATIONS_ENABLED, handler.GetInvalidatorState());
362  EXPECT_EQ(INVALIDATIONS_ENABLED, handler.GetLastRetrievedState());
363
364  this->delegate_.TriggerOnInvalidatorStateChange(TRANSIENT_INVALIDATION_ERROR);
365  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler.GetInvalidatorState());
366  EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, handler.GetLastRetrievedState());
367
368  invalidator->UnregisterHandler(&handler);
369}
370
371REGISTER_TYPED_TEST_CASE_P(InvalidatorTest,
372                           Basic, MultipleHandlers, EmptySetUnregisters,
373                           GetInvalidatorStateAlwaysCurrent);
374
375}  // namespace syncer
376
377#endif  // COMPONENTS_INVALIDATION_INVALIDATOR_TEST_TEMPLATE_H_
378