profile_sync_service_harness.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2010 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#include "chrome/browser/sync/profile_sync_service_harness.h"
6
7#include <algorithm>
8#include <vector>
9
10#include "base/message_loop.h"
11#include "chrome/browser/profiles/profile.h"
12#include "chrome/browser/net/gaia/token_service.h"
13#include "chrome/browser/sync/glue/sync_backend_host.h"
14#include "chrome/browser/sync/sessions/session_state.h"
15#include "chrome/common/net/gaia/gaia_constants.h"
16#include "chrome/common/net/gaia/google_service_auth_error.h"
17#include "chrome/common/notification_source.h"
18
19// The amount of time for which we wait for a live sync operation to complete.
20static const int kLiveSyncOperationTimeoutMs = 45000;
21
22// Simple class to implement a timeout using PostDelayedTask.  If it is not
23// aborted before picked up by a message queue, then it asserts with the message
24// provided.  This class is not thread safe.
25class StateChangeTimeoutEvent
26    : public base::RefCountedThreadSafe<StateChangeTimeoutEvent> {
27 public:
28  StateChangeTimeoutEvent(ProfileSyncServiceHarness* caller,
29                          const std::string& message);
30
31  // The entry point to the class from PostDelayedTask.
32  void Callback();
33
34  // Cancels the actions of the callback.  Returns true if success, false
35  // if the callback has already timed out.
36  bool Abort();
37
38 private:
39  friend class base::RefCountedThreadSafe<StateChangeTimeoutEvent>;
40
41  ~StateChangeTimeoutEvent();
42
43  bool aborted_;
44  bool did_timeout_;
45
46  // Due to synchronization of the IO loop, the caller will always be alive
47  // if the class is not aborted.
48  ProfileSyncServiceHarness* caller_;
49
50  // Informative message to assert in the case of a timeout.
51  std::string message_;
52
53  DISALLOW_COPY_AND_ASSIGN(StateChangeTimeoutEvent);
54};
55
56StateChangeTimeoutEvent::StateChangeTimeoutEvent(
57    ProfileSyncServiceHarness* caller,
58    const std::string& message)
59    : aborted_(false), did_timeout_(false), caller_(caller), message_(message) {
60}
61
62StateChangeTimeoutEvent::~StateChangeTimeoutEvent() {
63}
64
65void StateChangeTimeoutEvent::Callback() {
66  if (!aborted_) {
67    if (!caller_->RunStateChangeMachine()) {
68      // Report the message.
69      did_timeout_ = true;
70      DCHECK(!aborted_) << message_;
71      caller_->SignalStateComplete();
72    }
73  }
74}
75
76bool StateChangeTimeoutEvent::Abort() {
77  aborted_ = true;
78  caller_ = NULL;
79  return !did_timeout_;
80}
81
82ProfileSyncServiceHarness::ProfileSyncServiceHarness(
83    Profile* profile,
84    const std::string& username,
85    const std::string& password,
86    int id)
87    : wait_state_(INITIAL_WAIT_STATE),
88      profile_(profile),
89      service_(NULL),
90      timestamp_match_partner_(NULL),
91      username_(username),
92      password_(password),
93      id_(id) {
94  if (IsSyncAlreadySetup()) {
95    service_ = profile_->GetProfileSyncService();
96    service_->AddObserver(this);
97    wait_state_ = FULLY_SYNCED;
98  }
99}
100
101// static
102ProfileSyncServiceHarness* ProfileSyncServiceHarness::CreateAndAttach(
103    Profile* profile) {
104  if (!profile->HasProfileSyncService()) {
105    NOTREACHED() << "Profile has never signed into sync.";
106    return NULL;
107  }
108  return new ProfileSyncServiceHarness(profile, "", "", 0);
109}
110
111void ProfileSyncServiceHarness::SetCredentials(const std::string& username,
112                                               const std::string& password) {
113  username_ = username;
114  password_ = password;
115}
116
117bool ProfileSyncServiceHarness::IsSyncAlreadySetup() {
118  return profile_->HasProfileSyncService();
119}
120
121bool ProfileSyncServiceHarness::SetupSync() {
122  syncable::ModelTypeSet synced_datatypes;
123  for (int i = syncable::FIRST_REAL_MODEL_TYPE;
124      i < syncable::MODEL_TYPE_COUNT; ++i) {
125    synced_datatypes.insert(syncable::ModelTypeFromInt(i));
126  }
127  return SetupSync(synced_datatypes);
128}
129
130bool ProfileSyncServiceHarness::SetupSync(
131    const syncable::ModelTypeSet& synced_datatypes) {
132  // Initialize the sync client's profile sync service object.
133  service_ = profile_->GetProfileSyncService("");
134  if (service_ == NULL) {
135    LOG(ERROR) << "SetupSync(): service_ is null.";
136    return false;
137  }
138
139  // Subscribe sync client to notifications from the profile sync service.
140  if (!service_->HasObserver(this))
141    service_->AddObserver(this);
142
143  // Authenticate sync client using GAIA credentials.
144  service_->signin()->StartSignIn(username_, password_, "", "");
145
146  // Wait for the OnBackendInitialized() callback.
147  wait_state_ = WAITING_FOR_ON_BACKEND_INITIALIZED;
148  if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs,
149      "Waiting for OnBackendInitialized().")) {
150    LOG(ERROR) << "OnBackendInitialized() not seen after "
151               << kLiveSyncOperationTimeoutMs / 1000
152               << " seconds.";
153    return false;
154  }
155
156  // Choose the datatypes to be synced. If all datatypes are to be synced,
157  // set sync_everything to true; otherwise, set it to false.
158  bool sync_everything = (synced_datatypes.size() ==
159      (syncable::MODEL_TYPE_COUNT - syncable::FIRST_REAL_MODEL_TYPE));
160  service()->OnUserChoseDatatypes(sync_everything, synced_datatypes);
161
162  // Wait for a passphrase to be required.
163  DCHECK_EQ(wait_state_, WAITING_FOR_PASSPHRASE_REQUIRED);
164  if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs,
165      "Waiting for Passphrase required.")) {
166    LOG(ERROR) << "Passphrase required not seen after "
167               << kLiveSyncOperationTimeoutMs / 1000
168               << " seconds.";
169    return false;
170  }
171
172  // Wait for initial gaia passphrase to be accepted.
173  DCHECK_EQ(wait_state_, WAITING_FOR_PASSPHRASE_ACCEPTED);
174  if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs,
175      "Waiting for Passphrase accept.")) {
176    LOG(ERROR) << "Passphrase accept not seen after "
177               << kLiveSyncOperationTimeoutMs / 1000
178               << " seconds.";
179    return false;
180  }
181
182  DCHECK_EQ(wait_state_, WAITING_FOR_INITIAL_SYNC);
183  // Wait for initial sync cycle to be completed.
184  if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs,
185      "Waiting for initial sync cycle to complete.")) {
186    LOG(ERROR) << "Initial sync cycle did not complete after "
187               << kLiveSyncOperationTimeoutMs / 1000
188               << " seconds.";
189    return false;
190  }
191
192  // Indicate to the browser that sync setup is complete.
193  service()->SetSyncSetupCompleted();
194
195  return true;
196}
197
198void ProfileSyncServiceHarness::SignalStateCompleteWithNextState(
199    WaitState next_state) {
200  wait_state_ = next_state;
201  SignalStateComplete();
202}
203
204void ProfileSyncServiceHarness::SignalStateComplete() {
205  MessageLoop::current()->Quit();
206}
207
208bool ProfileSyncServiceHarness::RunStateChangeMachine() {
209  WaitState original_wait_state = wait_state_;
210  switch (wait_state_) {
211    case WAITING_FOR_ON_BACKEND_INITIALIZED: {
212      LogClientInfo("WAITING_FOR_ON_BACKEND_INITIALIZED");
213      if (service()->sync_initialized()) {
214        // The sync backend is initialized. We now wait for passphrase events.
215        SignalStateCompleteWithNextState(WAITING_FOR_PASSPHRASE_REQUIRED);
216      }
217      break;
218    }
219    case WAITING_FOR_PASSPHRASE_REQUIRED: {
220      LogClientInfo("WAITING_FOR_PASSPHRASE_REQUIRED");
221      if (service()->observed_passphrase_required()) {
222        // Special case when the first client signs in to sync.
223        if (id_ == 0)
224          DCHECK(!service()->passphrase_required_for_decryption());
225        // The SYNC_PASSPHRASE_REQUIRED notification has been seen.
226        SignalStateCompleteWithNextState(WAITING_FOR_PASSPHRASE_ACCEPTED);
227      }
228      break;
229    }
230    case WAITING_FOR_PASSPHRASE_ACCEPTED: {
231      LogClientInfo("WAITING_FOR_PASSPHRASE_ACCEPTED");
232      if (service()->ShouldPushChanges())
233        // The SYNC_PASSPHRASE_ACCEPTED notification has been seen.
234        SignalStateCompleteWithNextState(WAITING_FOR_INITIAL_SYNC);
235      break;
236    }
237    case WAITING_FOR_INITIAL_SYNC: {
238      LogClientInfo("WAITING_FOR_INITIAL_SYNC");
239      if (IsSynced()) {
240        // The first sync cycle is now complete. We can start running tests.
241        SignalStateCompleteWithNextState(FULLY_SYNCED);
242      }
243      break;
244    }
245    case WAITING_FOR_SYNC_TO_FINISH: {
246      LogClientInfo("WAITING_FOR_SYNC_TO_FINISH");
247      if (!IsSynced()) {
248        // The client is not yet fully synced. Continue waiting.
249        if (!GetStatus().server_reachable) {
250          // The client cannot reach the sync server because the network is
251          // disabled. There is no need to wait anymore.
252          SignalStateCompleteWithNextState(SERVER_UNREACHABLE);
253        }
254        break;
255      }
256      SignalStateCompleteWithNextState(FULLY_SYNCED);
257      break;
258    }
259    case WAITING_FOR_UPDATES: {
260      LogClientInfo("WAITING_FOR_UPDATES");
261      DCHECK(timestamp_match_partner_);
262      if (!MatchesOtherClient(timestamp_match_partner_)) {
263        // The client is not yet fully synced; keep waiting until we converge.
264        break;
265      }
266      timestamp_match_partner_->service()->RemoveObserver(this);
267      timestamp_match_partner_ = NULL;
268
269      SignalStateCompleteWithNextState(FULLY_SYNCED);
270      break;
271    }
272    case SERVER_UNREACHABLE: {
273      LogClientInfo("SERVER_UNREACHABLE");
274      if (GetStatus().server_reachable) {
275        // The client was offline due to the network being disabled, but is now
276        // back online. Wait for the pending sync cycle to complete.
277        SignalStateCompleteWithNextState(WAITING_FOR_SYNC_TO_FINISH);
278      }
279      break;
280    }
281    case FULLY_SYNCED: {
282      // The client is online and fully synced. There is nothing to do.
283      LogClientInfo("FULLY_SYNCED");
284      break;
285    }
286    case SYNC_DISABLED: {
287      // Syncing is disabled for the client. There is nothing to do.
288      LogClientInfo("SYNC_DISABLED");
289      break;
290    }
291    default:
292      // Invalid state during observer callback which may be triggered by other
293      // classes using the the UI message loop.  Defer to their handling.
294      break;
295  }
296  return original_wait_state != wait_state_;
297}
298
299void ProfileSyncServiceHarness::OnStateChanged() {
300  RunStateChangeMachine();
301}
302
303bool ProfileSyncServiceHarness::AwaitPassphraseAccepted() {
304  LogClientInfo("AwaitPassphraseAccepted");
305  if (wait_state_ == SYNC_DISABLED) {
306    LOG(ERROR) << "Sync disabled for Client " << id_ << ".";
307    return false;
308  }
309  wait_state_ = WAITING_FOR_PASSPHRASE_ACCEPTED;
310  return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs,
311                                      "Waiting for passphrase accepted.");
312}
313
314bool ProfileSyncServiceHarness::AwaitSyncCycleCompletion(
315    const std::string& reason) {
316  LogClientInfo("AwaitSyncCycleCompletion");
317  if (wait_state_ == SYNC_DISABLED) {
318    LOG(ERROR) << "Sync disabled for Client " << id_ << ".";
319    return false;
320  }
321  if (!IsSynced()) {
322    if (wait_state_ == SERVER_UNREACHABLE) {
323      // Client was offline; wait for it to go online, and then wait for sync.
324      AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason);
325      DCHECK_EQ(wait_state_, WAITING_FOR_SYNC_TO_FINISH);
326      return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason);
327    } else {
328      DCHECK(service()->sync_initialized());
329      wait_state_ = WAITING_FOR_SYNC_TO_FINISH;
330      AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason);
331      if (wait_state_ == FULLY_SYNCED) {
332        // Client is online; sync was successful.
333        return true;
334      } else if (wait_state_ == SERVER_UNREACHABLE) {
335        // Client is offline; sync was unsuccessful.
336        return false;
337      } else {
338        LOG(ERROR) << "Invalid wait state:" << wait_state_;
339        return false;
340      }
341    }
342  } else {
343    // Client is already synced; don't wait.
344    return true;
345  }
346}
347
348bool ProfileSyncServiceHarness::AwaitMutualSyncCycleCompletion(
349    ProfileSyncServiceHarness* partner) {
350  LogClientInfo("AwaitMutualSyncCycleCompletion");
351  if (!AwaitSyncCycleCompletion("Sync cycle completion on active client."))
352    return false;
353  return partner->WaitUntilTimestampMatches(this,
354      "Sync cycle completion on passive client.");
355}
356
357bool ProfileSyncServiceHarness::AwaitGroupSyncCycleCompletion(
358    std::vector<ProfileSyncServiceHarness*>& partners) {
359  LogClientInfo("AwaitGroupSyncCycleCompletion");
360  if (!AwaitSyncCycleCompletion("Sync cycle completion on active client."))
361    return false;
362  bool return_value = true;
363  for (std::vector<ProfileSyncServiceHarness*>::iterator it =
364      partners.begin(); it != partners.end(); ++it) {
365    if ((this != *it) && ((*it)->wait_state_ != SYNC_DISABLED)) {
366      return_value = return_value &&
367          (*it)->WaitUntilTimestampMatches(this,
368          "Sync cycle completion on partner client.");
369    }
370  }
371  return return_value;
372}
373
374// static
375bool ProfileSyncServiceHarness::AwaitQuiescence(
376    std::vector<ProfileSyncServiceHarness*>& clients) {
377  VLOG(1) << "AwaitQuiescence.";
378  bool return_value = true;
379  for (std::vector<ProfileSyncServiceHarness*>::iterator it =
380      clients.begin(); it != clients.end(); ++it) {
381    if ((*it)->wait_state_ != SYNC_DISABLED)
382      return_value = return_value &&
383          (*it)->AwaitGroupSyncCycleCompletion(clients);
384  }
385  return return_value;
386}
387
388bool ProfileSyncServiceHarness::WaitUntilTimestampMatches(
389    ProfileSyncServiceHarness* partner, const std::string& reason) {
390  LogClientInfo("WaitUntilTimestampMatches");
391  if (wait_state_ == SYNC_DISABLED) {
392    LOG(ERROR) << "Sync disabled for Client " << id_ << ".";
393    return false;
394  }
395  DCHECK(!timestamp_match_partner_);
396  if (MatchesOtherClient(partner))
397    return true;
398
399  timestamp_match_partner_ = partner;
400  partner->service()->AddObserver(this);
401  wait_state_ = WAITING_FOR_UPDATES;
402  return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason);
403}
404
405bool ProfileSyncServiceHarness::AwaitStatusChangeWithTimeout(
406    int timeout_milliseconds,
407    const std::string& reason) {
408  LogClientInfo("AwaitStatusChangeWithTimeout");
409  if (wait_state_ == SYNC_DISABLED) {
410    LOG(ERROR) << "Sync disabled for Client " << id_ << ".";
411    return false;
412  }
413  scoped_refptr<StateChangeTimeoutEvent> timeout_signal(
414      new StateChangeTimeoutEvent(this, reason));
415  MessageLoop* loop = MessageLoop::current();
416  bool did_allow_nestable_tasks = loop->NestableTasksAllowed();
417  loop->SetNestableTasksAllowed(true);
418  loop->PostDelayedTask(
419      FROM_HERE,
420      NewRunnableMethod(timeout_signal.get(),
421                        &StateChangeTimeoutEvent::Callback),
422      timeout_milliseconds);
423  loop->Run();
424  loop->SetNestableTasksAllowed(did_allow_nestable_tasks);
425  if (timeout_signal->Abort()) {
426    LogClientInfo("AwaitStatusChangeWithTimeout succeeded");
427    return true;
428  } else {
429    LogClientInfo("AwaitStatusChangeWithTimeout timed out");
430    return false;
431  }
432}
433
434ProfileSyncService::Status ProfileSyncServiceHarness::GetStatus() {
435  DCHECK(service() != NULL) << "GetStatus(): service() is NULL.";
436  return service()->QueryDetailedSyncStatus();
437}
438
439bool ProfileSyncServiceHarness::IsSynced() {
440  LogClientInfo("IsSynced");
441  if (service() == NULL)
442    return false;
443  const SyncSessionSnapshot* snap = GetLastSessionSnapshot();
444  // TODO(rsimha): Remove additional checks of snap->has_more_to_sync and
445  // snap->unsynced_count once http://crbug.com/48989 is fixed.
446  return (snap &&
447          snap->num_conflicting_updates == 0 &&  // We can decrypt everything.
448          ServiceIsPushingChanges() &&
449          GetStatus().notifications_enabled &&
450          !service()->HasUnsyncedItems() &&
451          !snap->has_more_to_sync &&
452          snap->unsynced_count == 0);
453}
454
455bool ProfileSyncServiceHarness::MatchesOtherClient(
456    ProfileSyncServiceHarness* partner) {
457  if (!IsSynced())
458    return false;
459
460  // Only look for a match if we have at least one enabled datatype in
461  // common with the partner client.
462  syncable::ModelTypeSet types, other_types, intersection_types;
463  service()->GetPreferredDataTypes(&types);
464  partner->service()->GetPreferredDataTypes(&other_types);
465  std::set_intersection(types.begin(), types.end(), other_types.begin(),
466                        other_types.end(),
467                        inserter(intersection_types,
468                                 intersection_types.begin()));
469  for (syncable::ModelTypeSet::iterator i = intersection_types.begin();
470       i != intersection_types.end();
471       ++i) {
472    if (!partner->IsSynced() ||
473        partner->GetUpdatedTimestamp(*i) != GetUpdatedTimestamp(*i)) {
474      return false;
475    }
476  }
477  return true;
478}
479
480const SyncSessionSnapshot*
481    ProfileSyncServiceHarness::GetLastSessionSnapshot() const {
482  DCHECK(service_ != NULL) << "Sync service has not yet been set up.";
483  if (service_->sync_initialized()) {
484    return service_->GetLastSessionSnapshot();
485  }
486  return NULL;
487}
488
489void ProfileSyncServiceHarness::EnableSyncForDatatype(
490    syncable::ModelType datatype) {
491  LogClientInfo("EnableSyncForDatatype");
492  syncable::ModelTypeSet synced_datatypes;
493  if (wait_state_ == SYNC_DISABLED) {
494    wait_state_ = WAITING_FOR_ON_BACKEND_INITIALIZED;
495    synced_datatypes.insert(datatype);
496    DCHECK(SetupSync(synced_datatypes)) << "Reinitialization of Client " << id_
497                                        << " failed.";
498  } else {
499    DCHECK(service() != NULL) << "EnableSyncForDatatype(): service() is null.";
500    service()->GetPreferredDataTypes(&synced_datatypes);
501    syncable::ModelTypeSet::iterator it = synced_datatypes.find(
502        syncable::ModelTypeFromInt(datatype));
503    if (it == synced_datatypes.end()) {
504      synced_datatypes.insert(syncable::ModelTypeFromInt(datatype));
505      service()->OnUserChoseDatatypes(false, synced_datatypes);
506      wait_state_ = WAITING_FOR_SYNC_TO_FINISH;
507      AwaitSyncCycleCompletion("Waiting for datatype configuration.");
508      VLOG(1) << "EnableSyncForDatatype(): Enabled sync for datatype "
509              << syncable::ModelTypeToString(datatype) << " on Client " << id_;
510    } else {
511      VLOG(1) << "EnableSyncForDatatype(): Sync already enabled for datatype "
512              << syncable::ModelTypeToString(datatype) << " on Client " << id_;
513    }
514  }
515}
516
517void ProfileSyncServiceHarness::DisableSyncForDatatype(
518    syncable::ModelType datatype) {
519  LogClientInfo("DisableSyncForDatatype");
520  syncable::ModelTypeSet synced_datatypes;
521  DCHECK(service() != NULL) << "DisableSyncForDatatype(): service() is null.";
522  service()->GetPreferredDataTypes(&synced_datatypes);
523  syncable::ModelTypeSet::iterator it = synced_datatypes.find(datatype);
524  if (it != synced_datatypes.end()) {
525    synced_datatypes.erase(it);
526    service()->OnUserChoseDatatypes(false, synced_datatypes);
527    AwaitSyncCycleCompletion("Waiting for datatype configuration.");
528    VLOG(1) << "DisableSyncForDatatype(): Disabled sync for datatype "
529            << syncable::ModelTypeToString(datatype) << " on Client " << id_;
530  } else {
531    VLOG(1) << "DisableSyncForDatatype(): Sync already disabled for datatype "
532            << syncable::ModelTypeToString(datatype) << " on Client " << id_;
533  }
534}
535
536void ProfileSyncServiceHarness::EnableSyncForAllDatatypes() {
537  LogClientInfo("EnableSyncForAllDatatypes");
538  if (wait_state_ == SYNC_DISABLED) {
539    wait_state_ = WAITING_FOR_ON_BACKEND_INITIALIZED;
540    DCHECK(SetupSync()) << "Reinitialization of Client " << id_ << " failed.";
541  } else {
542    syncable::ModelTypeSet synced_datatypes;
543    for (int i = syncable::FIRST_REAL_MODEL_TYPE;
544        i < syncable::MODEL_TYPE_COUNT; ++i) {
545      synced_datatypes.insert(syncable::ModelTypeFromInt(i));
546    }
547    DCHECK(service() != NULL) << "EnableSyncForAllDatatypes(): service() is "
548                                 " null.";
549    service()->OnUserChoseDatatypes(true, synced_datatypes);
550    wait_state_ = WAITING_FOR_SYNC_TO_FINISH;
551    AwaitSyncCycleCompletion("Waiting for datatype configuration.");
552    VLOG(1) << "EnableSyncForAllDatatypes(): Enabled sync for all datatypes on "
553               "Client " << id_;
554  }
555}
556
557void ProfileSyncServiceHarness::DisableSyncForAllDatatypes() {
558  LogClientInfo("DisableSyncForAllDatatypes");
559  DCHECK(service() != NULL) << "EnableSyncForAllDatatypes(): service() is "
560                               "null.";
561  service()->DisableForUser();
562  wait_state_ = SYNC_DISABLED;
563  VLOG(1) << "DisableSyncForAllDatatypes(): Disabled sync for all datatypes on "
564             "Client " << id_;
565}
566
567std::string ProfileSyncServiceHarness::GetUpdatedTimestamp(
568    syncable::ModelType model_type) {
569  const SyncSessionSnapshot* snap = GetLastSessionSnapshot();
570  DCHECK(snap != NULL) << "GetUpdatedTimestamp(): Sync snapshot is NULL.";
571  return snap->download_progress_markers[model_type];
572}
573
574void ProfileSyncServiceHarness::LogClientInfo(std::string message) {
575  if (service()) {
576    const SyncSessionSnapshot* snap = GetLastSessionSnapshot();
577    if (snap) {
578      VLOG(1) << "Client " << id_ << ": " << message
579              << ": num_updates_downloaded : "
580              << snap->syncer_status.num_updates_downloaded_total
581              << ", has_more_to_sync: " << snap->has_more_to_sync
582              << ", unsynced_count: " << snap->unsynced_count
583              << ", num_conflicting_updates: " << snap->num_conflicting_updates
584              << ", has_unsynced_items: "
585              << service()->HasUnsyncedItems()
586              << ", observed_passphrase_required: "
587              << service()->observed_passphrase_required()
588              << ", notifications_enabled: "
589              << GetStatus().notifications_enabled
590              << ", service_is_pushing_changes: " << ServiceIsPushingChanges();
591    } else {
592      VLOG(1) << "Client " << id_ << ": " << message
593              << ": Sync session snapshot not available.";
594    }
595  } else {
596    VLOG(1) << "Client " << id_ << ": " << message
597            << ": Sync service not available.";
598  }
599}
600