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

Side by Side Diff: chrome/browser/sync/glue/chrome_sync_notification_bridge_unittest.cc

Issue 10808040: [Sync] Avoid ObserverListThreadSafe in ChromeSyncNotificationBridge (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address comments Created 8 years, 5 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
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 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 #include "chrome/browser/sync/glue/chrome_sync_notification_bridge.h" 5 #include "chrome/browser/sync/glue/chrome_sync_notification_bridge.h"
6 6
7 #include "base/compiler_specific.h" 7 #include "base/compiler_specific.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/memory/ref_counted.h"
8 #include "base/memory/weak_ptr.h" 10 #include "base/memory/weak_ptr.h"
9 #include "base/message_loop.h" 11 #include "base/message_loop.h"
12 #include "base/sequenced_task_runner.h"
10 #include "base/synchronization/waitable_event.h" 13 #include "base/synchronization/waitable_event.h"
11 #include "base/test/test_timeouts.h" 14 #include "base/test/test_timeouts.h"
12 #include "base/threading/thread.h" 15 #include "base/threading/thread.h"
13 #include "chrome/common/chrome_notification_types.h" 16 #include "chrome/common/chrome_notification_types.h"
14 #include "chrome/test/base/profile_mock.h" 17 #include "chrome/test/base/profile_mock.h"
15 #include "content/public/browser/notification_details.h" 18 #include "content/public/browser/notification_details.h"
16 #include "content/public/browser/notification_service.h" 19 #include "content/public/browser/notification_service.h"
17 #include "content/public/test/test_browser_thread.h" 20 #include "content/public/test/test_browser_thread.h"
18 #include "sync/internal_api/public/base/model_type.h" 21 #include "sync/internal_api/public/base/model_type.h"
19 #include "sync/internal_api/public/base/model_type_payload_map.h" 22 #include "sync/internal_api/public/base/model_type_payload_map.h"
20 #include "sync/notifier/mock_sync_notifier_observer.h"
21 #include "sync/notifier/sync_notifier_observer.h" 23 #include "sync/notifier/sync_notifier_observer.h"
22 #include "testing/gmock/include/gmock/gmock.h" 24 #include "testing/gmock/include/gmock/gmock.h"
23 #include "testing/gtest/include/gtest/gtest.h" 25 #include "testing/gtest/include/gtest/gtest.h"
24 26
25 namespace browser_sync { 27 namespace browser_sync {
26 namespace { 28 namespace {
27 29
28 using ::testing::Mock; 30 using ::testing::Mock;
29 using ::testing::NiceMock; 31 using ::testing::NiceMock;
30 using ::testing::StrictMock; 32 using ::testing::StrictMock;
31 using content::BrowserThread; 33 using content::BrowserThread;
32 34
33 // Receives a ChromeSyncNotificationBridge to register to, and an expected 35 // Receives a ChromeSyncNotificationBridge to register to, and an expected
34 // ModelTypePayloadMap. ReceivedProperNotification() will return true only 36 // ModelTypePayloadMap. ReceivedProperNotification() will return true only
35 // if the observer has received a notification with the proper source and 37 // if the observer has received a notification with the proper source and
36 // payload. 38 // payload.
37 // Note: Because this object lives on the IO thread, we use a fake (vs a mock) 39 // Note: Because this object lives on the sync thread, we use a fake
38 // so we don't have to worry about possible thread safety issues within 40 // (vs a mock) so we don't have to worry about possible thread safety
39 // GTest/GMock. 41 // issues within GTest/GMock.
40 class FakeSyncNotifierObserverIO 42 class FakeSyncNotifierObserver : public syncer::SyncNotifierObserver {
41 : public syncer::SyncNotifierObserver {
42 public: 43 public:
43 FakeSyncNotifierObserverIO( 44 FakeSyncNotifierObserver(
45 const scoped_refptr<base::SequencedTaskRunner>& sync_task_runner,
44 ChromeSyncNotificationBridge* bridge, 46 ChromeSyncNotificationBridge* bridge,
45 const syncer::ModelTypePayloadMap& expected_payloads) 47 const syncer::ModelTypePayloadMap& expected_payloads,
46 : bridge_(bridge), 48 syncer::IncomingNotificationSource expected_source)
49 : sync_task_runner_(sync_task_runner),
50 bridge_(bridge),
47 received_improper_notification_(false), 51 received_improper_notification_(false),
48 notification_count_(0), 52 notification_count_(0),
49 expected_payloads_(expected_payloads) { 53 expected_payloads_(expected_payloads),
50 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 54 expected_source_(expected_source) {
55 DCHECK(sync_task_runner_->RunsTasksOnCurrentThread());
51 bridge_->AddObserver(this); 56 bridge_->AddObserver(this);
52 } 57 }
53 virtual ~FakeSyncNotifierObserverIO() { 58
54 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 59 virtual ~FakeSyncNotifierObserver() {
60 DCHECK(sync_task_runner_->RunsTasksOnCurrentThread());
55 bridge_->RemoveObserver(this); 61 bridge_->RemoveObserver(this);
56 } 62 }
57 63
58 // SyncNotifierObserver implementation. 64 // SyncNotifierObserver implementation.
59 virtual void OnIncomingNotification( 65 virtual void OnIncomingNotification(
60 const syncer::ModelTypePayloadMap& type_payloads, 66 const syncer::ModelTypePayloadMap& type_payloads,
61 syncer::IncomingNotificationSource source) OVERRIDE { 67 syncer::IncomingNotificationSource source) OVERRIDE {
62 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 68 DCHECK(sync_task_runner_->RunsTasksOnCurrentThread());
63 notification_count_++; 69 notification_count_++;
64 if (source != syncer::LOCAL_NOTIFICATION) { 70 if (source != expected_source_) {
65 LOG(ERROR) << "Received notification with wrong source."; 71 LOG(ERROR) << "Received notification with wrong source";
66 received_improper_notification_ = true; 72 received_improper_notification_ = true;
67 } 73 }
68 if (expected_payloads_ != type_payloads) { 74 if (expected_payloads_ != type_payloads) {
69 LOG(ERROR) << "Received wrong payload."; 75 LOG(ERROR) << "Received wrong payload";
70 received_improper_notification_ = true; 76 received_improper_notification_ = true;
71 } 77 }
72 } 78 }
73 virtual void OnNotificationsEnabled() OVERRIDE { 79 virtual void OnNotificationsEnabled() OVERRIDE {
74 NOTREACHED(); 80 NOTREACHED();
75 } 81 }
76 virtual void OnNotificationsDisabled( 82 virtual void OnNotificationsDisabled(
77 syncer::NotificationsDisabledReason reason) OVERRIDE { 83 syncer::NotificationsDisabledReason reason) OVERRIDE {
78 NOTREACHED(); 84 NOTREACHED();
79 } 85 }
80 86
81 bool ReceivedProperNotification() const { 87 bool ReceivedProperNotification() const {
88 DCHECK(sync_task_runner_->RunsTasksOnCurrentThread());
82 return (notification_count_ == 1) && !received_improper_notification_; 89 return (notification_count_ == 1) && !received_improper_notification_;
83 } 90 }
84 91
85 private: 92 private:
86 ChromeSyncNotificationBridge* bridge_; 93 const scoped_refptr<base::SequencedTaskRunner> sync_task_runner_;
94 ChromeSyncNotificationBridge* const bridge_;
87 bool received_improper_notification_; 95 bool received_improper_notification_;
88 size_t notification_count_; 96 size_t notification_count_;
89 syncer::ModelTypePayloadMap expected_payloads_; 97 const syncer::ModelTypePayloadMap expected_payloads_;
98 const syncer::IncomingNotificationSource expected_source_;
90 }; 99 };
91 100
92 class ChromeSyncNotificationBridgeTest : public testing::Test { 101 class ChromeSyncNotificationBridgeTest : public testing::Test {
93 public: 102 public:
94 ChromeSyncNotificationBridgeTest() 103 ChromeSyncNotificationBridgeTest()
95 : ui_thread_(BrowserThread::UI, &ui_loop_), 104 : ui_thread_(BrowserThread::UI),
96 io_thread_(BrowserThread::IO), 105 sync_thread_("Sync thread"),
97 io_observer_(NULL), 106 sync_observer_(NULL),
98 io_observer_notification_failure_(false), 107 sync_observer_notification_failure_(false),
99 bridge_(&mock_profile_), 108 done_(true, false) {}
100 done_(true, false) { 109
101 io_thread_.StartIOThread();
102 }
103 virtual ~ChromeSyncNotificationBridgeTest() {} 110 virtual ~ChromeSyncNotificationBridgeTest() {}
104 111
105 protected: 112 protected:
106 virtual void TearDown() OVERRIDE { 113 virtual void SetUp() OVERRIDE {
107 io_thread_.Stop(); 114 ASSERT_TRUE(sync_thread_.Start());
108 ui_loop_.RunAllPending(); 115 bridge_.reset(
109 EXPECT_EQ(NULL, io_observer_); 116 new ChromeSyncNotificationBridge(
110 if (io_observer_notification_failure_) 117 &mock_profile_, sync_thread_.message_loop_proxy()));
111 ADD_FAILURE() << "IO Observer did not receive proper notification.";
112 } 118 }
113 119
114 void VerifyAndDestroyObserverOnIOThread() { 120 virtual void TearDown() OVERRIDE {
115 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 121 sync_thread_.Stop();
116 if (!io_observer_) { 122 // Must be reset only after the sync thread is stopped.
117 io_observer_notification_failure_ = true; 123 bridge_.reset();
124 EXPECT_EQ(NULL, sync_observer_);
125 if (sync_observer_notification_failure_)
126 ADD_FAILURE() << "Sync Observer did not receive proper notification.";
127 }
128
129 void VerifyAndDestroyObserverOnSyncThread() {
130 DCHECK(sync_thread_.message_loop_proxy()->RunsTasksOnCurrentThread());
131 if (!sync_observer_) {
132 sync_observer_notification_failure_ = true;
118 } else { 133 } else {
119 io_observer_notification_failure_ = 134 sync_observer_notification_failure_ =
120 !io_observer_->ReceivedProperNotification(); 135 !sync_observer_->ReceivedProperNotification();
121 delete io_observer_; 136 delete sync_observer_;
122 io_observer_ = NULL; 137 sync_observer_ = NULL;
123 } 138 }
124 } 139 }
125 140
126 void VerifyAndDestroyObserver() { 141 void VerifyAndDestroyObserver() {
127 ASSERT_TRUE(BrowserThread::PostTask( 142 ASSERT_TRUE(sync_thread_.message_loop_proxy()->PostTask(
128 BrowserThread::IO,
129 FROM_HERE, 143 FROM_HERE,
130 base::Bind(&ChromeSyncNotificationBridgeTest:: 144 base::Bind(&ChromeSyncNotificationBridgeTest::
131 VerifyAndDestroyObserverOnIOThread, 145 VerifyAndDestroyObserverOnSyncThread,
132 base::Unretained(this)))); 146 base::Unretained(this))));
147 BlockForSyncThread();
133 } 148 }
134 149
135 void CreateObserverOnIOThread( 150 void CreateObserverOnSyncThread(
136 syncer::ModelTypePayloadMap expected_payloads) { 151 syncer::ModelTypePayloadMap expected_payloads,
137 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 152 syncer::IncomingNotificationSource expected_source) {
138 io_observer_ = new FakeSyncNotifierObserverIO(&bridge_, 153 DCHECK(sync_thread_.message_loop_proxy()->RunsTasksOnCurrentThread());
139 expected_payloads); 154 sync_observer_ = new FakeSyncNotifierObserver(
155 sync_thread_.message_loop_proxy(),
156 bridge_.get(),
157 expected_payloads,
158 expected_source);
140 } 159 }
141 160
142 void CreateObserverWithExpectedPayload( 161 void CreateObserverWithExpectations(
143 syncer::ModelTypePayloadMap expected_payloads) { 162 syncer::ModelTypePayloadMap expected_payloads,
144 ASSERT_TRUE(BrowserThread::PostTask( 163 syncer::IncomingNotificationSource expected_source) {
145 BrowserThread::IO, 164 ASSERT_TRUE(sync_thread_.message_loop_proxy()->PostTask(
146 FROM_HERE, 165 FROM_HERE,
147 base::Bind(&ChromeSyncNotificationBridgeTest::CreateObserverOnIOThread, 166 base::Bind(
148 base::Unretained(this), 167 &ChromeSyncNotificationBridgeTest::CreateObserverOnSyncThread,
149 expected_payloads))); 168 base::Unretained(this),
150 BlockForIOThread(); 169 expected_payloads,
170 expected_source)));
171 BlockForSyncThread();
151 } 172 }
152 173
153 void SignalOnIOThread() { 174 void SignalOnSyncThread() {
154 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 175 DCHECK(sync_thread_.message_loop_proxy()->RunsTasksOnCurrentThread());
155 done_.Signal(); 176 done_.Signal();
156 } 177 }
157 178
158 void BlockForIOThread() { 179 void BlockForSyncThread() {
159 done_.Reset(); 180 done_.Reset();
160 BrowserThread::PostTask( 181 ASSERT_TRUE(sync_thread_.message_loop_proxy()->PostTask(
161 BrowserThread::IO,
162 FROM_HERE, 182 FROM_HERE,
163 base::Bind(&ChromeSyncNotificationBridgeTest::SignalOnIOThread, 183 base::Bind(&ChromeSyncNotificationBridgeTest::SignalOnSyncThread,
164 base::Unretained(this))); 184 base::Unretained(this))));
165 done_.TimedWait(TestTimeouts::action_timeout()); 185 done_.TimedWait(TestTimeouts::action_timeout());
166 if (!done_.IsSignaled()) 186 if (!done_.IsSignaled())
167 ADD_FAILURE() << "Timed out waiting for IO thread."; 187 ADD_FAILURE() << "Timed out waiting for IO thread.";
168 } 188 }
169 189
170 void TriggerRefreshNotification( 190 void TriggerRefreshNotification(
171 int type, 191 int type,
172 const syncer::ModelTypePayloadMap& payload_map) { 192 const syncer::ModelTypePayloadMap& payload_map) {
173 content::NotificationService::current()->Notify( 193 content::NotificationService::current()->Notify(
174 type, 194 type,
175 content::Source<Profile>(&mock_profile_), 195 content::Source<Profile>(&mock_profile_),
176 content::Details<const syncer::ModelTypePayloadMap>(&payload_map)); 196 content::Details<const syncer::ModelTypePayloadMap>(&payload_map));
177 } 197 }
178 198
179 MessageLoop ui_loop_; 199 private:
180 content::TestBrowserThread ui_thread_; 200 content::TestBrowserThread ui_thread_;
181 content::TestBrowserThread io_thread_; 201 base::Thread sync_thread_;
182 NiceMock<ProfileMock> mock_profile_; 202 NiceMock<ProfileMock> mock_profile_;
183 // Created/used/destroyed on I/O thread. 203 // Created/used/destroyed on sync thread.
184 FakeSyncNotifierObserverIO* io_observer_; 204 FakeSyncNotifierObserver* sync_observer_;
185 bool io_observer_notification_failure_; 205 bool sync_observer_notification_failure_;
186 ChromeSyncNotificationBridge bridge_; 206 scoped_ptr<ChromeSyncNotificationBridge> bridge_;
187 base::WaitableEvent done_; 207 base::WaitableEvent done_;
188 }; 208 };
189 209
190 // Adds an observer on the UI thread, triggers a local refresh notification, and 210 // Adds an observer on the sync thread, triggers a local refresh
191 // ensures the bridge posts a LOCAL_NOTIFICATION with the proper payload to it. 211 // notification, and ensures the bridge posts a LOCAL_NOTIFICATION
212 // with the proper payload to it.
192 TEST_F(ChromeSyncNotificationBridgeTest, LocalNotification) { 213 TEST_F(ChromeSyncNotificationBridgeTest, LocalNotification) {
193 syncer::ModelTypePayloadMap payload_map; 214 syncer::ModelTypePayloadMap payload_map;
194 payload_map[syncer::SESSIONS] = ""; 215 payload_map[syncer::SESSIONS] = "";
195 StrictMock<syncer::MockSyncNotifierObserver> observer; 216 CreateObserverWithExpectations(payload_map, syncer::LOCAL_NOTIFICATION);
196 EXPECT_CALL(observer,
197 OnIncomingNotification(payload_map,
198 syncer::LOCAL_NOTIFICATION));
199 bridge_.AddObserver(&observer);
200 TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_LOCAL, 217 TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
201 payload_map); 218 payload_map);
202 ui_loop_.RunAllPending(); 219 VerifyAndDestroyObserver();
203 Mock::VerifyAndClearExpectations(&observer);
204 } 220 }
205 221
206 // Adds an observer on the UI thread, triggers a remote refresh notification, 222 // Adds an observer on the sync thread, triggers a remote refresh
207 // and ensures the bridge posts a REMOTE_NOTIFICATION with the proper payload 223 // notification, and ensures the bridge posts a REMOTE_NOTIFICATION
208 // to it. 224 // with the proper payload to it.
209 TEST_F(ChromeSyncNotificationBridgeTest, RemoteNotification) { 225 TEST_F(ChromeSyncNotificationBridgeTest, RemoteNotification) {
210 syncer::ModelTypePayloadMap payload_map; 226 syncer::ModelTypePayloadMap payload_map;
211 payload_map[syncer::BOOKMARKS] = ""; 227 payload_map[syncer::SESSIONS] = "";
212 StrictMock<syncer::MockSyncNotifierObserver> observer; 228 CreateObserverWithExpectations(payload_map, syncer::REMOTE_NOTIFICATION);
213 EXPECT_CALL(observer,
214 OnIncomingNotification(payload_map,
215 syncer::REMOTE_NOTIFICATION));
216 bridge_.AddObserver(&observer);
217 TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_REMOTE, 229 TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_REMOTE,
218 payload_map); 230 payload_map);
219 ui_loop_.RunAllPending(); 231 VerifyAndDestroyObserver();
220 Mock::VerifyAndClearExpectations(&observer);
221 } 232 }
222 233
223 // Adds an observer on the UI thread, triggers a local refresh notification 234 // Adds an observer on the sync thread, triggers a local refresh
224 // with empty payload map and ensures the bridge posts a 235 // notification with empty payload map and ensures the bridge posts a
225 // LOCAL_NOTIFICATION with the proper payload to it. 236 // LOCAL_NOTIFICATION with the proper payload to it.
226 TEST_F(ChromeSyncNotificationBridgeTest, LocalNotificationEmptyPayloadMap) { 237 TEST_F(ChromeSyncNotificationBridgeTest, LocalNotificationEmptyPayloadMap) {
227 const syncer::ModelTypeSet enabled_types( 238 const syncer::ModelTypeSet enabled_types(
228 syncer::BOOKMARKS, syncer::PASSWORDS); 239 syncer::BOOKMARKS, syncer::PASSWORDS);
229 const syncer::ModelTypePayloadMap enabled_types_payload_map = 240 const syncer::ModelTypePayloadMap enabled_types_payload_map =
230 syncer::ModelTypePayloadMapFromEnumSet(enabled_types, std::string()); 241 syncer::ModelTypePayloadMapFromEnumSet(enabled_types, std::string());
231 242 CreateObserverWithExpectations(
232 StrictMock<syncer::MockSyncNotifierObserver> observer; 243 enabled_types_payload_map, syncer::LOCAL_NOTIFICATION);
233 EXPECT_CALL(observer,
234 OnIncomingNotification(enabled_types_payload_map,
235 syncer::LOCAL_NOTIFICATION));
236 bridge_.AddObserver(&observer);
237 // Set enabled types on the bridge.
238 bridge_.UpdateEnabledTypes(enabled_types);
239 TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_LOCAL, 244 TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
240 syncer::ModelTypePayloadMap()); 245 enabled_types_payload_map);
241 ui_loop_.RunAllPending(); 246 VerifyAndDestroyObserver();
242 Mock::VerifyAndClearExpectations(&observer);
243 } 247 }
244 248
245 // Adds an observer on the UI thread, triggers a remote refresh notification 249 // Adds an observer on the sync thread, triggers a remote refresh
246 // with empty payload map and ensures the bridge posts a 250 // notification with empty payload map and ensures the bridge posts a
247 // REMOTE_NOTIFICATION with the proper payload to it. 251 // REMOTE_NOTIFICATION with the proper payload to it.
248 TEST_F(ChromeSyncNotificationBridgeTest, RemoteNotificationEmptyPayloadMap) { 252 TEST_F(ChromeSyncNotificationBridgeTest, RemoteNotificationEmptyPayloadMap) {
249 const syncer::ModelTypeSet enabled_types( 253 const syncer::ModelTypeSet enabled_types(
250 syncer::BOOKMARKS, syncer::TYPED_URLS); 254 syncer::BOOKMARKS, syncer::TYPED_URLS);
251 const syncer::ModelTypePayloadMap enabled_types_payload_map = 255 const syncer::ModelTypePayloadMap enabled_types_payload_map =
252 syncer::ModelTypePayloadMapFromEnumSet(enabled_types, std::string()); 256 syncer::ModelTypePayloadMapFromEnumSet(enabled_types, std::string());
253 257 CreateObserverWithExpectations(
254 StrictMock<syncer::MockSyncNotifierObserver> observer; 258 enabled_types_payload_map, syncer::REMOTE_NOTIFICATION);
255 EXPECT_CALL(observer,
256 OnIncomingNotification(enabled_types_payload_map,
257 syncer::REMOTE_NOTIFICATION));
258 bridge_.AddObserver(&observer);
259 // Set enabled types on the bridge.
260 bridge_.UpdateEnabledTypes(enabled_types);
261 TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_REMOTE, 259 TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_REMOTE,
262 syncer::ModelTypePayloadMap()); 260 enabled_types_payload_map);
263 ui_loop_.RunAllPending();
264 Mock::VerifyAndClearExpectations(&observer);
265 }
266
267 // Adds an observer on the I/O thread. Then triggers a refresh notification on
268 // the UI thread. We finally verify the proper notification was received by the
269 // observer and destroy it on the I/O thread.
270 TEST_F(ChromeSyncNotificationBridgeTest, BasicThreaded) {
271 syncer::ModelTypePayloadMap payload_map;
272 payload_map[syncer::SESSIONS] = "";
273 CreateObserverWithExpectedPayload(payload_map);
274 TriggerRefreshNotification(chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
275 payload_map);
276 VerifyAndDestroyObserver(); 261 VerifyAndDestroyObserver();
277 } 262 }
278 263
279 } // namespace 264 } // namespace
280 } // namespace browser_sync 265 } // namespace browser_sync
OLDNEW
« no previous file with comments | « chrome/browser/sync/glue/chrome_sync_notification_bridge.cc ('k') | chrome/browser/sync/glue/sync_backend_host.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698