Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(73)

Side by Side Diff: sync/engine/syncer_unittest.cc

Issue 15764010: Experimental functionize patch (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: sync: Expose sync functionality as functions Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sync/engine/syncer.cc ('k') | sync/engine/syncer_util.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2012 The Chromium Authors. All rights reserved. 1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 // 4 //
5 // Syncer unit tests. Unfortunately a lot of these tests 5 // Syncer unit tests. Unfortunately a lot of these tests
6 // are outdated and need to be reworked and updated. 6 // are outdated and need to be reworked and updated.
7 7
8 #include <algorithm> 8 #include <algorithm>
9 #include <limits> 9 #include <limits>
10 #include <list> 10 #include <list>
(...skipping 170 matching lines...) Expand 10 before | Expand all | Expand 10 after
181 } 181 }
182 182
183 void SyncShareNudge() { 183 void SyncShareNudge() {
184 ModelSafeRoutingInfo info; 184 ModelSafeRoutingInfo info;
185 GetModelSafeRoutingInfo(&info); 185 GetModelSafeRoutingInfo(&info);
186 ModelTypeInvalidationMap invalidation_map = 186 ModelTypeInvalidationMap invalidation_map =
187 ModelSafeRoutingInfoToInvalidationMap(info, std::string()); 187 ModelSafeRoutingInfoToInvalidationMap(info, std::string());
188 sessions::SyncSourceInfo source_info( 188 sessions::SyncSourceInfo source_info(
189 sync_pb::GetUpdatesCallerInfo::LOCAL, 189 sync_pb::GetUpdatesCallerInfo::LOCAL,
190 invalidation_map); 190 invalidation_map);
191 // Use our dummy nudge tracker. These tests won't notice that it hasn't 191 session_.reset(SyncSession::Build(context_.get(), this, source_info));
192 // been tracking anything because the server is mocked out and ignores most
193 // of the content of requests sent by the client.
194 session_.reset(
195 SyncSession::BuildForNudge(context_.get(),
196 this,
197 source_info,
198 &nudge_tracker_));
199 192
200 EXPECT_TRUE(syncer_->SyncShare(session_.get(), SYNCER_BEGIN, SYNCER_END)); 193 // Pretend we've seen a local change, to make the nudge_tracker look normal.
194 nudge_tracker_.RecordLocalChange(ModelTypeSet(BOOKMARKS));
195
196 EXPECT_TRUE(
197 syncer_->NormalSyncShare(
198 session_.get(),
199 GetRoutingInfoTypes(context_->routing_info()),
200 nudge_tracker_));
201 } 201 }
202 202
203 void SyncShareConfigure() { 203 void SyncShareConfigure() {
204 ModelSafeRoutingInfo info; 204 ModelSafeRoutingInfo info;
205 GetModelSafeRoutingInfo(&info); 205 GetModelSafeRoutingInfo(&info);
206 ModelTypeInvalidationMap invalidation_map = 206 ModelTypeInvalidationMap invalidation_map =
207 ModelSafeRoutingInfoToInvalidationMap(info, std::string()); 207 ModelSafeRoutingInfoToInvalidationMap(info, std::string());
208 sessions::SyncSourceInfo source_info( 208 sessions::SyncSourceInfo source_info(
209 sync_pb::GetUpdatesCallerInfo::RECONFIGURATION, 209 sync_pb::GetUpdatesCallerInfo::RECONFIGURATION,
210 invalidation_map); 210 invalidation_map);
211 session_.reset(SyncSession::Build(context_.get(), 211 session_.reset(SyncSession::Build(context_.get(),
212 this, 212 this,
213 source_info)); 213 source_info));
214 EXPECT_TRUE( 214 EXPECT_TRUE(syncer_->ConfigureSyncShare(
215 syncer_->SyncShare(session_.get(), DOWNLOAD_UPDATES, APPLY_UPDATES)); 215 session_.get(),
216 GetRoutingInfoTypes(context_->routing_info())));
216 } 217 }
217 218
218 virtual void SetUp() { 219 virtual void SetUp() {
219 dir_maker_.SetUp(); 220 dir_maker_.SetUp();
220 mock_server_.reset(new MockConnectionManager(directory())); 221 mock_server_.reset(new MockConnectionManager(directory()));
221 EnableDatatype(BOOKMARKS); 222 EnableDatatype(BOOKMARKS);
222 EnableDatatype(NIGORI); 223 EnableDatatype(NIGORI);
223 EnableDatatype(PREFERENCES); 224 EnableDatatype(PREFERENCES);
224 EnableDatatype(NIGORI); 225 EnableDatatype(NIGORI);
225 worker_ = new FakeModelWorker(GROUP_PASSIVE); 226 worker_ = new FakeModelWorker(GROUP_PASSIVE);
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
297 EXPECT_FALSE(client_status.hierarchy_conflict_detected()); 298 EXPECT_FALSE(client_status.hierarchy_conflict_detected());
298 } 299 }
299 300
300 void VerifyHierarchyConflictsUnspecified( 301 void VerifyHierarchyConflictsUnspecified(
301 const sync_pb::ClientToServerMessage& message) { 302 const sync_pb::ClientToServerMessage& message) {
302 // Our request should have neither confirmed nor denied hierarchy conflicts. 303 // Our request should have neither confirmed nor denied hierarchy conflicts.
303 const sync_pb::ClientStatus& client_status = message.client_status(); 304 const sync_pb::ClientStatus& client_status = message.client_status();
304 EXPECT_FALSE(client_status.has_hierarchy_conflict_detected()); 305 EXPECT_FALSE(client_status.has_hierarchy_conflict_detected());
305 } 306 }
306 307
307 void SyncRepeatedlyToTriggerConflictResolution(SyncSession* session) {
308 // We should trigger after less than 6 syncs, but extra does no harm.
309 for (int i = 0 ; i < 6 ; ++i)
310 syncer_->SyncShare(session, SYNCER_BEGIN, SYNCER_END);
311 }
312 void SyncRepeatedlyToTriggerStuckSignal(SyncSession* session) {
313 // We should trigger after less than 10 syncs, but we want to avoid brittle
314 // tests.
315 for (int i = 0 ; i < 12 ; ++i)
316 syncer_->SyncShare(session, SYNCER_BEGIN, SYNCER_END);
317 }
318 sync_pb::EntitySpecifics DefaultBookmarkSpecifics() { 308 sync_pb::EntitySpecifics DefaultBookmarkSpecifics() {
319 sync_pb::EntitySpecifics result; 309 sync_pb::EntitySpecifics result;
320 AddDefaultFieldValue(BOOKMARKS, &result); 310 AddDefaultFieldValue(BOOKMARKS, &result);
321 return result; 311 return result;
322 } 312 }
323 313
324 sync_pb::EntitySpecifics DefaultPreferencesSpecifics() { 314 sync_pb::EntitySpecifics DefaultPreferencesSpecifics() {
325 sync_pb::EntitySpecifics result; 315 sync_pb::EntitySpecifics result;
326 AddDefaultFieldValue(PREFERENCES, &result); 316 AddDefaultFieldValue(PREFERENCES, &result);
327 return result; 317 return result;
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after
415 } 405 }
416 } 406 }
417 407
418 void DoTruncationTest(const vector<int64>& unsynced_handle_view, 408 void DoTruncationTest(const vector<int64>& unsynced_handle_view,
419 const vector<syncable::Id>& expected_id_order) { 409 const vector<syncable::Id>& expected_id_order) {
420 for (size_t limit = expected_id_order.size() + 2; limit > 0; --limit) { 410 for (size_t limit = expected_id_order.size() + 2; limit > 0; --limit) {
421 WriteTransaction wtrans(FROM_HERE, UNITTEST, directory()); 411 WriteTransaction wtrans(FROM_HERE, UNITTEST, directory());
422 412
423 ModelSafeRoutingInfo routes; 413 ModelSafeRoutingInfo routes;
424 GetModelSafeRoutingInfo(&routes); 414 GetModelSafeRoutingInfo(&routes);
415 ModelTypeSet types = GetRoutingInfoTypes(routes);
425 sessions::OrderedCommitSet output_set(routes); 416 sessions::OrderedCommitSet output_set(routes);
426 GetCommitIdsCommand command(&wtrans, limit, &output_set); 417 GetCommitIdsCommand command(&wtrans, types, limit, &output_set);
427 std::set<int64> ready_unsynced_set; 418 std::set<int64> ready_unsynced_set;
428 command.FilterUnreadyEntries(&wtrans, ModelTypeSet(), 419 command.FilterUnreadyEntries(&wtrans, types,
429 ModelTypeSet(), false, 420 ModelTypeSet(), false,
430 unsynced_handle_view, &ready_unsynced_set); 421 unsynced_handle_view, &ready_unsynced_set);
431 command.BuildCommitIds(&wtrans, routes, ready_unsynced_set); 422 command.BuildCommitIds(&wtrans, routes, ready_unsynced_set);
432 size_t truncated_size = std::min(limit, expected_id_order.size()); 423 size_t truncated_size = std::min(limit, expected_id_order.size());
433 ASSERT_EQ(truncated_size, output_set.Size()); 424 ASSERT_EQ(truncated_size, output_set.Size());
434 for (size_t i = 0; i < truncated_size; ++i) { 425 for (size_t i = 0; i < truncated_size; ++i) {
435 ASSERT_EQ(expected_id_order[i], output_set.GetCommitIdAt(i)) 426 ASSERT_EQ(expected_id_order[i], output_set.GetCommitIdAt(i))
436 << "At index " << i << " with batch size limited to " << limit; 427 << "At index " << i << " with batch size limited to " << limit;
437 } 428 }
438 sessions::OrderedCommitSet::Projection proj; 429 sessions::OrderedCommitSet::Projection proj;
(...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after
656 // appropriately. 647 // appropriately.
657 expected_order.push_back(ids_.MakeServer("x")); 648 expected_order.push_back(ids_.MakeServer("x"));
658 expected_order.push_back(ids_.MakeLocal("b")); 649 expected_order.push_back(ids_.MakeLocal("b"));
659 expected_order.push_back(ids_.MakeLocal("c")); 650 expected_order.push_back(ids_.MakeLocal("c"));
660 expected_order.push_back(ids_.MakeLocal("d")); 651 expected_order.push_back(ids_.MakeLocal("d"));
661 expected_order.push_back(ids_.MakeLocal("e")); 652 expected_order.push_back(ids_.MakeLocal("e"));
662 expected_order.push_back(ids_.MakeLocal("j")); 653 expected_order.push_back(ids_.MakeLocal("j"));
663 DoTruncationTest(unsynced_handle_view, expected_order); 654 DoTruncationTest(unsynced_handle_view, expected_order);
664 } 655 }
665 656
666 // TODO(rlarocque): re-enable this test. 657 TEST_F(SyncerTest, GetCommitIdsFiltersThrottledEntries) {
667 TEST_F(SyncerTest, DISABLED_GetCommitIdsFiltersThrottledEntries) {
668 const ModelTypeSet throttled_types(BOOKMARKS); 658 const ModelTypeSet throttled_types(BOOKMARKS);
669 sync_pb::EntitySpecifics bookmark_data; 659 sync_pb::EntitySpecifics bookmark_data;
670 AddDefaultFieldValue(BOOKMARKS, &bookmark_data); 660 AddDefaultFieldValue(BOOKMARKS, &bookmark_data);
671 661
672 mock_server_->AddUpdateDirectory(1, 0, "A", 10, 10, 662 mock_server_->AddUpdateDirectory(1, 0, "A", 10, 10,
673 foreign_cache_guid(), "-1"); 663 foreign_cache_guid(), "-1");
674 SyncShareNudge(); 664 SyncShareNudge();
675 665
676 { 666 {
677 WriteTransaction wtrans(FROM_HERE, UNITTEST, directory()); 667 WriteTransaction wtrans(FROM_HERE, UNITTEST, directory());
678 MutableEntry A(&wtrans, GET_BY_ID, ids_.FromNumber(1)); 668 MutableEntry A(&wtrans, GET_BY_ID, ids_.FromNumber(1));
679 ASSERT_TRUE(A.good()); 669 ASSERT_TRUE(A.good());
680 A.Put(IS_UNSYNCED, true); 670 A.Put(IS_UNSYNCED, true);
681 A.Put(SPECIFICS, bookmark_data); 671 A.Put(SPECIFICS, bookmark_data);
682 A.Put(NON_UNIQUE_NAME, "bookmark"); 672 A.Put(NON_UNIQUE_NAME, "bookmark");
683 } 673 }
684 674
685 // Now set the throttled types. 675 // Now sync without enabling bookmarks.
686 // context_->throttled_data_type_tracker()->SetUnthrottleTime( 676 syncer_->NormalSyncShare(
687 // throttled_types, 677 session_.get(),
688 // base::TimeTicks::Now() + base::TimeDelta::FromSeconds(1200)); 678 Difference(GetRoutingInfoTypes(context_->routing_info()),
689 SyncShareNudge(); 679 ModelTypeSet(BOOKMARKS)),
680 nudge_tracker_);
690 681
691 { 682 {
692 // Nothing should have been committed as bookmarks is throttled. 683 // Nothing should have been committed as bookmarks is throttled.
693 syncable::ReadTransaction rtrans(FROM_HERE, directory()); 684 syncable::ReadTransaction rtrans(FROM_HERE, directory());
694 Entry entryA(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(1)); 685 Entry entryA(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(1));
695 ASSERT_TRUE(entryA.good()); 686 ASSERT_TRUE(entryA.good());
696 EXPECT_TRUE(entryA.Get(IS_UNSYNCED)); 687 EXPECT_TRUE(entryA.Get(IS_UNSYNCED));
697 } 688 }
698 689
699 // Now unthrottle. 690 // Sync again with bookmarks enabled.
700 // context_->throttled_data_type_tracker()->SetUnthrottleTime( 691 syncer_->NormalSyncShare(
701 // throttled_types, 692 session_.get(),
702 // base::TimeTicks::Now() - base::TimeDelta::FromSeconds(1200)); 693 GetRoutingInfoTypes(context_->routing_info()),
694 nudge_tracker_);
703 SyncShareNudge(); 695 SyncShareNudge();
704 { 696 {
705 // It should have been committed. 697 // It should have been committed.
706 syncable::ReadTransaction rtrans(FROM_HERE, directory()); 698 syncable::ReadTransaction rtrans(FROM_HERE, directory());
707 Entry entryA(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(1)); 699 Entry entryA(&rtrans, syncable::GET_BY_ID, ids_.FromNumber(1));
708 ASSERT_TRUE(entryA.good()); 700 ASSERT_TRUE(entryA.good());
709 EXPECT_FALSE(entryA.Get(IS_UNSYNCED)); 701 EXPECT_FALSE(entryA.Get(IS_UNSYNCED));
710 } 702 }
711 } 703 }
712 704
(...skipping 2493 matching lines...) Expand 10 before | Expand all | Expand 10 after
3206 local_cache_guid(), "-1"); 3198 local_cache_guid(), "-1");
3207 SyncShareNudge(); 3199 SyncShareNudge();
3208 { 3200 {
3209 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); 3201 WriteTransaction trans(FROM_HERE, UNITTEST, directory());
3210 MutableEntry entry( 3202 MutableEntry entry(
3211 &trans, CREATE, BOOKMARKS, trans.root_id(), "Copy of base"); 3203 &trans, CREATE, BOOKMARKS, trans.root_id(), "Copy of base");
3212 WriteTestDataToEntry(&trans, &entry); 3204 WriteTestDataToEntry(&trans, &entry);
3213 } 3205 }
3214 mock_server_->AddUpdateBookmark(1, 0, "Copy of base", 50, 50, 3206 mock_server_->AddUpdateBookmark(1, 0, "Copy of base", 50, 50,
3215 local_cache_guid(), "-1"); 3207 local_cache_guid(), "-1");
3216 SyncRepeatedlyToTriggerConflictResolution(session_.get()); 3208 SyncShareNudge();
3217 } 3209 }
3218 3210
3219 // In this test a long changelog contains a child at the start of the changelog 3211 // In this test a long changelog contains a child at the start of the changelog
3220 // and a parent at the end. While these updates are in progress the client would 3212 // and a parent at the end. While these updates are in progress the client would
3221 // appear stuck. 3213 // appear stuck.
3222 TEST_F(SyncerTest, LongChangelistWithApplicationConflict) { 3214 TEST_F(SyncerTest, LongChangelistWithApplicationConflict) {
3223 const int depth = 400; 3215 const int depth = 400;
3224 syncable::Id folder_id = ids_.FromNumber(1); 3216 syncable::Id folder_id = ids_.FromNumber(1);
3225 3217
3226 // First we an item in a folder in the root. However the folder won't come 3218 // First we an item in a folder in the root. However the folder won't come
3227 // till much later. 3219 // till much later.
3228 syncable::Id stuck_entry_id = TestIdFactory::FromNumber(99999); 3220 syncable::Id stuck_entry_id = TestIdFactory::FromNumber(99999);
3229 mock_server_->AddUpdateDirectory(stuck_entry_id, 3221 mock_server_->AddUpdateDirectory(stuck_entry_id,
3230 folder_id, "stuck", 1, 1, 3222 folder_id, "stuck", 1, 1,
3231 foreign_cache_guid(), "-99999"); 3223 foreign_cache_guid(), "-99999");
3232 mock_server_->SetChangesRemaining(depth - 1); 3224 mock_server_->SetChangesRemaining(depth - 1);
3233 SyncShareNudge(); 3225 SyncShareNudge();
3234 3226
3235 // Buffer up a very long series of downloads. 3227 // Buffer up a very long series of downloads.
3236 // We should never be stuck (conflict resolution shouldn't 3228 // We should never be stuck (conflict resolution shouldn't
3237 // kick in so long as we're making forward progress). 3229 // kick in so long as we're making forward progress).
3238 for (int i = 0; i < depth; i++) { 3230 for (int i = 0; i < depth; i++) {
3239 mock_server_->NextUpdateBatch(); 3231 mock_server_->NextUpdateBatch();
3240 mock_server_->SetNewTimestamp(i + 1); 3232 mock_server_->SetNewTimestamp(i + 1);
3241 mock_server_->SetChangesRemaining(depth - i); 3233 mock_server_->SetChangesRemaining(depth - i);
3242 } 3234 }
3243 3235
3244 syncer_->SyncShare(session_.get(), SYNCER_BEGIN, SYNCER_END); 3236 SyncShareNudge();
3245 3237
3246 // Ensure our folder hasn't somehow applied. 3238 // Ensure our folder hasn't somehow applied.
3247 { 3239 {
3248 syncable::ReadTransaction trans(FROM_HERE, directory()); 3240 syncable::ReadTransaction trans(FROM_HERE, directory());
3249 Entry child(&trans, GET_BY_ID, stuck_entry_id); 3241 Entry child(&trans, GET_BY_ID, stuck_entry_id);
3250 EXPECT_TRUE(child.good()); 3242 EXPECT_TRUE(child.good());
3251 EXPECT_TRUE(child.Get(IS_UNAPPLIED_UPDATE)); 3243 EXPECT_TRUE(child.Get(IS_UNAPPLIED_UPDATE));
3252 EXPECT_TRUE(child.Get(IS_DEL)); 3244 EXPECT_TRUE(child.Get(IS_DEL));
3253 EXPECT_FALSE(child.Get(IS_UNSYNCED)); 3245 EXPECT_FALSE(child.Get(IS_UNSYNCED));
3254 } 3246 }
(...skipping 26 matching lines...) Expand all
3281 SyncShareNudge(); 3273 SyncShareNudge();
3282 { 3274 {
3283 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); 3275 WriteTransaction trans(FROM_HERE, UNITTEST, directory());
3284 MutableEntry entry(&trans, GET_BY_ID, ids_.FromNumber(2)); 3276 MutableEntry entry(&trans, GET_BY_ID, ids_.FromNumber(2));
3285 ASSERT_TRUE(entry.good()); 3277 ASSERT_TRUE(entry.good());
3286 EXPECT_TRUE(entry.Put(NON_UNIQUE_NAME, "Copy of base")); 3278 EXPECT_TRUE(entry.Put(NON_UNIQUE_NAME, "Copy of base"));
3287 entry.Put(IS_UNSYNCED, true); 3279 entry.Put(IS_UNSYNCED, true);
3288 } 3280 }
3289 mock_server_->AddUpdateBookmark(1, 0, "Copy of base", 50, 50, 3281 mock_server_->AddUpdateBookmark(1, 0, "Copy of base", 50, 50,
3290 foreign_cache_guid(), "-1"); 3282 foreign_cache_guid(), "-1");
3291 SyncRepeatedlyToTriggerConflictResolution(session_.get()); 3283 SyncShareNudge();
3292 { 3284 {
3293 syncable::ReadTransaction trans(FROM_HERE, directory()); 3285 syncable::ReadTransaction trans(FROM_HERE, directory());
3294 Entry entry1(&trans, GET_BY_ID, ids_.FromNumber(1)); 3286 Entry entry1(&trans, GET_BY_ID, ids_.FromNumber(1));
3295 EXPECT_FALSE(entry1.Get(IS_UNAPPLIED_UPDATE)); 3287 EXPECT_FALSE(entry1.Get(IS_UNAPPLIED_UPDATE));
3296 EXPECT_FALSE(entry1.Get(IS_UNSYNCED)); 3288 EXPECT_FALSE(entry1.Get(IS_UNSYNCED));
3297 EXPECT_FALSE(entry1.Get(IS_DEL)); 3289 EXPECT_FALSE(entry1.Get(IS_DEL));
3298 Entry entry2(&trans, GET_BY_ID, ids_.FromNumber(2)); 3290 Entry entry2(&trans, GET_BY_ID, ids_.FromNumber(2));
3299 EXPECT_FALSE(entry2.Get(IS_UNAPPLIED_UPDATE)); 3291 EXPECT_FALSE(entry2.Get(IS_UNAPPLIED_UPDATE));
3300 EXPECT_TRUE(entry2.Get(IS_UNSYNCED)); 3292 EXPECT_TRUE(entry2.Get(IS_UNSYNCED));
3301 EXPECT_FALSE(entry2.Get(IS_DEL)); 3293 EXPECT_FALSE(entry2.Get(IS_DEL));
(...skipping 1524 matching lines...) Expand 10 before | Expand all | Expand 10 after
4826 EXPECT_EQ("xyz", final_monitor_records["xyz"].extension_id); 4818 EXPECT_EQ("xyz", final_monitor_records["xyz"].extension_id);
4827 EXPECT_EQ(2049U, final_monitor_records["ABC"].bookmark_write_count); 4819 EXPECT_EQ(2049U, final_monitor_records["ABC"].bookmark_write_count);
4828 EXPECT_EQ(4U, final_monitor_records["xyz"].bookmark_write_count); 4820 EXPECT_EQ(4U, final_monitor_records["xyz"].bookmark_write_count);
4829 } else { 4821 } else {
4830 EXPECT_TRUE(final_monitor_records.empty()) 4822 EXPECT_TRUE(final_monitor_records.empty())
4831 << "Should not restore records after successful bookmark commit."; 4823 << "Should not restore records after successful bookmark commit.";
4832 } 4824 }
4833 } 4825 }
4834 4826
4835 } // namespace syncer 4827 } // namespace syncer
OLDNEW
« no previous file with comments | « sync/engine/syncer.cc ('k') | sync/engine/syncer_util.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698