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

Side by Side Diff: content/shell/webkit_test_runner_host.cc

Issue 10941011: Rewrite layout_browsertests to use content_shell --dump-render-tree to execute layout tests (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: updates Created 8 years, 3 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 "content/shell/webkit_test_runner_host.h" 5 #include "content/shell/webkit_test_runner_host.h"
6 6
7 #include <iostream>
8
7 #include "base/command_line.h" 9 #include "base/command_line.h"
8 #include "base/message_loop.h" 10 #include "base/message_loop.h"
9 #include "base/run_loop.h" 11 #include "base/run_loop.h"
10 #include "content/public/browser/navigation_controller.h" 12 #include "content/public/browser/navigation_controller.h"
11 #include "content/public/browser/render_view_host.h" 13 #include "content/public/browser/render_view_host.h"
12 #include "content/public/browser/web_contents.h" 14 #include "content/public/browser/web_contents.h"
13 #include "content/shell/shell.h" 15 #include "content/shell/shell.h"
14 #include "content/shell/shell_browser_context.h" 16 #include "content/shell/shell_browser_context.h"
15 #include "content/shell/shell_content_browser_client.h" 17 #include "content/shell/shell_content_browser_client.h"
16 #include "content/shell/shell_messages.h" 18 #include "content/shell/shell_messages.h"
17 #include "content/shell/shell_switches.h" 19 #include "content/shell/shell_switches.h"
18 #include "webkit/support/webkit_support_gfx.h" 20 #include "webkit/support/webkit_support_gfx.h"
19 21
20 namespace content { 22 namespace content {
21 23
22 namespace { 24 namespace {
23 const int kTestTimeoutMilliseconds = 30 * 1000; 25 const int kTestTimeoutMilliseconds = 30 * 1000;
24 } // namespace 26 } // namespace
25 27
26 // WebKitTestResultPrinter ---------------------------------------------------- 28 // WebKitTestResultPrinter ----------------------------------------------------
27 29
28 WebKitTestResultPrinter::WebKitTestResultPrinter() 30 WebKitTestResultPrinter::WebKitTestResultPrinter(
29 : state_(BEFORE_TEST) { 31 std::ostream* output, std::ostream* error)
32 : state_(BEFORE_TEST),
33 capture_text_only_(false),
34 output_(output),
35 error_(error) {
30 } 36 }
31 37
32 WebKitTestResultPrinter::~WebKitTestResultPrinter() { 38 WebKitTestResultPrinter::~WebKitTestResultPrinter() {
33 } 39 }
34 40
35 void WebKitTestResultPrinter::PrintTextHeader() { 41 void WebKitTestResultPrinter::PrintTextHeader() {
36 DCHECK_EQ(state_, BEFORE_TEST); 42 DCHECK_EQ(state_, BEFORE_TEST);
37 printf("Content-Type: text/plain\n"); 43 if (!capture_text_only_)
44 *output_ << "Content-Type: text/plain\n";
38 state_ = IN_TEXT_BLOCK; 45 state_ = IN_TEXT_BLOCK;
39 } 46 }
40 47
41 void WebKitTestResultPrinter::PrintTextBlock(const std::string& block) { 48 void WebKitTestResultPrinter::PrintTextBlock(const std::string& block) {
42 DCHECK_EQ(state_, IN_TEXT_BLOCK); 49 DCHECK_EQ(state_, IN_TEXT_BLOCK);
43 printf("%s", block.c_str()); 50 *output_ << block;
44 } 51 }
45 52
46 void WebKitTestResultPrinter::PrintTextFooter() { 53 void WebKitTestResultPrinter::PrintTextFooter() {
47 if (state_ != IN_TEXT_BLOCK) 54 if (state_ != IN_TEXT_BLOCK)
48 return; 55 return;
49 printf("#EOF\n"); 56 if (!capture_text_only_) {
50 fprintf(stderr, "#EOF\n"); 57 *output_ << "#EOF\n";
58 *error_ << "#EOF\n";
59 output_->flush();
60 error_->flush();
61 }
51 state_ = IN_IMAGE_BLOCK; 62 state_ = IN_IMAGE_BLOCK;
52 } 63 }
53 64
54 void WebKitTestResultPrinter::PrintImageHeader( 65 void WebKitTestResultPrinter::PrintImageHeader(
55 const std::string& actual_hash, 66 const std::string& actual_hash,
56 const std::string& expected_hash) { 67 const std::string& expected_hash) {
57 if (state_ != IN_IMAGE_BLOCK) 68 if (state_ != IN_IMAGE_BLOCK || capture_text_only_)
58 return; 69 return;
59 printf("\nActualHash: %s\n", actual_hash.c_str()); 70 *output_ << "\nActualHash: " << actual_hash << "%s\n";
60 if (!expected_hash.empty()) 71 if (!expected_hash.empty())
61 printf("\nExpectedHash: %s\n", expected_hash.c_str()); 72 *output_ << "\nExpectedHash: " << expected_hash << "%s\n";
62 } 73 }
63 74
64 void WebKitTestResultPrinter::PrintImageBlock( 75 void WebKitTestResultPrinter::PrintImageBlock(
65 const std::vector<unsigned char>& png_image) { 76 const std::vector<unsigned char>& png_image) {
66 if (state_ != IN_IMAGE_BLOCK) 77 if (state_ != IN_IMAGE_BLOCK || capture_text_only_)
67 return; 78 return;
68 printf("Content-Type: image/png\n"); 79 *output_ << "Content-Type: image/png\n";
69 printf("Content-Length: %u\n", static_cast<unsigned>(png_image.size())); 80 *output_ << "Content-Length: " << png_image.size() << "\n";
70 fwrite(&png_image[0], 1, png_image.size(), stdout); 81 output_->write(
82 reinterpret_cast<const char*>(&png_image[0]), png_image.size());
71 } 83 }
72 84
73 void WebKitTestResultPrinter::PrintImageFooter() { 85 void WebKitTestResultPrinter::PrintImageFooter() {
74 if (state_ != IN_IMAGE_BLOCK) 86 if (state_ != IN_IMAGE_BLOCK)
75 return; 87 return;
76 printf("#EOF\n"); 88 if (!capture_text_only_) {
89 *output_ << "#EOF\n";
90 output_->flush();
91 }
77 state_ = AFTER_TEST; 92 state_ = AFTER_TEST;
78 } 93 }
79 94
80 void WebKitTestResultPrinter::AddMessage(const std::string& message) { 95 void WebKitTestResultPrinter::AddMessage(const std::string& message) {
81 if (state_ != IN_TEXT_BLOCK) 96 if (state_ != IN_TEXT_BLOCK)
82 return; 97 return;
83 printf("%s\n", message.c_str()); 98 *output_ << message << "\n";
84 } 99 }
85 100
86 void WebKitTestResultPrinter::AddErrorMessage(const std::string& message) { 101 void WebKitTestResultPrinter::AddErrorMessage(const std::string& message) {
87 if (state_ != IN_TEXT_BLOCK) 102 if (state_ != IN_TEXT_BLOCK)
88 return; 103 return;
89 printf("%s\n", message.c_str()); 104 *output_ << message << "\n";
90 fprintf(stderr, "%s\n", message.c_str()); 105 if (!capture_text_only_)
106 *error_ << message << "\n";
91 PrintTextFooter(); 107 PrintTextFooter();
92 PrintImageFooter(); 108 PrintImageFooter();
93 MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); 109 MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
94 } 110 }
95 111
96 // WebKitTestController ------------------------------------------------------- 112 // WebKitTestController -------------------------------------------------------
97 113
98 WebKitTestController* WebKitTestController::instance_ = NULL; 114 WebKitTestController* WebKitTestController::instance_ = NULL;
99 115
100 // static 116 // static
101 WebKitTestController* WebKitTestController::Get() { 117 WebKitTestController* WebKitTestController::Get() {
102 DCHECK(!instance_ || instance_->CalledOnValidThread()); 118 DCHECK(!instance_ || instance_->CalledOnValidThread());
103 return instance_; 119 return instance_;
104 } 120 }
105 121
106 WebKitTestController::WebKitTestController() { 122 WebKitTestController::WebKitTestController() {
107 CHECK(!instance_); 123 CHECK(!instance_);
108 instance_ = this; 124 instance_ = this;
125 printer_.reset(new WebKitTestResultPrinter(&std::cout, &std::cerr));
109 126
110 content::ShellBrowserContext* browser_context = 127 content::ShellBrowserContext* browser_context =
111 static_cast<content::ShellContentBrowserClient*>( 128 static_cast<content::ShellContentBrowserClient*>(
112 content::GetContentClient()->browser())->browser_context(); 129 content::GetContentClient()->browser())->browser_context();
113 main_window_ = content::Shell::CreateNewWindow( 130 main_window_ = content::Shell::CreateNewWindow(
114 browser_context, 131 browser_context,
115 GURL("about:blank"), 132 GURL("about:blank"),
116 NULL, 133 NULL,
117 MSG_ROUTING_NONE, 134 MSG_ROUTING_NONE,
118 NULL); 135 NULL);
(...skipping 11 matching lines...) Expand all
130 147
131 bool WebKitTestController::PrepareForLayoutTest( 148 bool WebKitTestController::PrepareForLayoutTest(
132 const GURL& test_url, 149 const GURL& test_url,
133 bool enable_pixel_dumping, 150 bool enable_pixel_dumping,
134 const std::string& expected_pixel_hash) { 151 const std::string& expected_pixel_hash) {
135 DCHECK(CalledOnValidThread()); 152 DCHECK(CalledOnValidThread());
136 if (!main_window_) 153 if (!main_window_)
137 return false; 154 return false;
138 enable_pixel_dumping_ = enable_pixel_dumping; 155 enable_pixel_dumping_ = enable_pixel_dumping;
139 expected_pixel_hash_ = expected_pixel_hash; 156 expected_pixel_hash_ = expected_pixel_hash;
140 printer_.reset(); 157 printer_->reset();
141 printer_.PrintTextHeader(); 158 printer_->PrintTextHeader();
142 main_window_->LoadURL(test_url); 159 main_window_->LoadURL(test_url);
143 return true; 160 return true;
144 } 161 }
145 162
146 bool WebKitTestController::ResetAfterLayoutTest() { 163 bool WebKitTestController::ResetAfterLayoutTest() {
147 DCHECK(CalledOnValidThread()); 164 DCHECK(CalledOnValidThread());
148 printer_.PrintTextFooter(); 165 printer_->PrintTextFooter();
149 printer_.PrintImageFooter(); 166 printer_->PrintImageFooter();
150 pumping_messages_ = false; 167 pumping_messages_ = false;
151 enable_pixel_dumping_ = false; 168 enable_pixel_dumping_ = false;
152 expected_pixel_hash_.clear(); 169 expected_pixel_hash_.clear();
153 captured_dump_ = false; 170 captured_dump_ = false;
154 dump_as_text_ = false; 171 dump_as_text_ = false;
155 dump_child_frames_ = false; 172 dump_child_frames_ = false;
156 is_printing_ = false; 173 is_printing_ = false;
157 should_stay_on_page_after_handling_before_unload_ = false; 174 should_stay_on_page_after_handling_before_unload_ = false;
158 wait_until_done_ = false; 175 wait_until_done_ = false;
159 watchdog_.Cancel(); 176 watchdog_.Cancel();
160 if (!main_window_) 177 if (!main_window_)
161 return false; 178 return false;
162 if (main_window_->web_contents()->GetController().GetEntryCount() > 0) { 179 if (main_window_->web_contents()->GetController().GetEntryCount() > 0) {
163 // Reset the WebContents for the next test. 180 // Reset the WebContents for the next test.
164 // TODO(jochen): Reset it more thoroughly. 181 // TODO(jochen): Reset it more thoroughly.
165 main_window_->web_contents()->GetController().GoToIndex(0); 182 main_window_->web_contents()->GetController().GoToIndex(0);
166 } 183 }
167 renderer_crashed_ = false; 184 renderer_crashed_ = false;
168 // Wait for the main_window_ to finish loading. 185 // Wait for the main_window_ to finish loading.
169 pumping_messages_ = true; 186 pumping_messages_ = true;
170 base::RunLoop run_loop; 187 base::RunLoop run_loop;
171 run_loop.Run(); 188 run_loop.Run();
172 pumping_messages_ = false; 189 pumping_messages_ = false;
173 return !renderer_crashed_; 190 return !renderer_crashed_;
174 } 191 }
175 192
176 void WebKitTestController::RendererUnresponsive() { 193 void WebKitTestController::RendererUnresponsive() {
177 printer_.AddErrorMessage("#PROCESS UNRESPONSIVE - renderer"); 194 printer_->AddErrorMessage("#PROCESS UNRESPONSIVE - renderer");
178 } 195 }
179 196
180 void WebKitTestController::NotifyDone() { 197 void WebKitTestController::NotifyDone() {
181 if (!wait_until_done_) 198 if (!wait_until_done_)
182 return; 199 return;
183 watchdog_.Cancel(); 200 watchdog_.Cancel();
184 CaptureDump(); 201 CaptureDump();
185 } 202 }
186 203
187 void WebKitTestController::WaitUntilDone() { 204 void WebKitTestController::WaitUntilDone() {
188 if (wait_until_done_) 205 if (wait_until_done_)
189 return; 206 return;
190 if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoTimeout)) { 207 if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoTimeout)) {
191 watchdog_.Reset(base::Bind(&WebKitTestController::TimeoutHandler, 208 watchdog_.Reset(base::Bind(&WebKitTestController::TimeoutHandler,
192 base::Unretained(this))); 209 base::Unretained(this)));
193 MessageLoop::current()->PostDelayedTask( 210 MessageLoop::current()->PostDelayedTask(
194 FROM_HERE, 211 FROM_HERE,
195 watchdog_.callback(), 212 watchdog_.callback(),
196 base::TimeDelta::FromMilliseconds(kTestTimeoutMilliseconds)); 213 base::TimeDelta::FromMilliseconds(kTestTimeoutMilliseconds));
197 } 214 }
198 wait_until_done_ = true; 215 wait_until_done_ = true;
199 } 216 }
200 217
201 void WebKitTestController::NotImplemented( 218 void WebKitTestController::NotImplemented(
202 const std::string& object_name, 219 const std::string& object_name,
203 const std::string& property_name) { 220 const std::string& property_name) {
204 printer_.AddErrorMessage( 221 printer_->AddErrorMessage(
205 std::string("FAIL: NOT IMPLEMENTED: ") + 222 std::string("FAIL: NOT IMPLEMENTED: ") +
206 object_name + "." + property_name); 223 object_name + "." + property_name);
207 } 224 }
208 225
209 bool WebKitTestController::OnMessageReceived(const IPC::Message& message) { 226 bool WebKitTestController::OnMessageReceived(const IPC::Message& message) {
210 bool handled = true; 227 bool handled = true;
211 IPC_BEGIN_MESSAGE_MAP(WebKitTestController, message) 228 IPC_BEGIN_MESSAGE_MAP(WebKitTestController, message)
212 IPC_MESSAGE_HANDLER(ShellViewHostMsg_DidFinishLoad, OnDidFinishLoad) 229 IPC_MESSAGE_HANDLER(ShellViewHostMsg_DidFinishLoad, OnDidFinishLoad)
213 IPC_MESSAGE_HANDLER(ShellViewHostMsg_TextDump, OnTextDump) 230 IPC_MESSAGE_HANDLER(ShellViewHostMsg_TextDump, OnTextDump)
214 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ImageDump, OnImageDump) 231 IPC_MESSAGE_HANDLER(ShellViewHostMsg_ImageDump, OnImageDump)
215 IPC_MESSAGE_UNHANDLED(handled = false) 232 IPC_MESSAGE_UNHANDLED(handled = false)
216 IPC_END_MESSAGE_MAP() 233 IPC_END_MESSAGE_MAP()
217 234
218 return handled; 235 return handled;
219 } 236 }
220 237
221 void WebKitTestController::PluginCrashed(const FilePath& plugin_path) { 238 void WebKitTestController::PluginCrashed(const FilePath& plugin_path) {
222 printer_.AddErrorMessage("#CRASHED - plugin"); 239 printer_->AddErrorMessage("#CRASHED - plugin");
223 } 240 }
224 241
225 void WebKitTestController::RenderViewGone(base::TerminationStatus status) { 242 void WebKitTestController::RenderViewGone(base::TerminationStatus status) {
226 renderer_crashed_ = true; 243 renderer_crashed_ = true;
227 printer_.AddErrorMessage("#CRASHED - renderer"); 244 printer_->AddErrorMessage("#CRASHED - renderer");
228 } 245 }
229 246
230 void WebKitTestController::WebContentsDestroyed(WebContents* web_contents) { 247 void WebKitTestController::WebContentsDestroyed(WebContents* web_contents) {
231 main_window_ = NULL; 248 main_window_ = NULL;
232 printer_.AddErrorMessage("FAIL: main window was destroyed"); 249 printer_->AddErrorMessage("FAIL: main window was destroyed");
233 } 250 }
234 251
235 void WebKitTestController::CaptureDump() { 252 void WebKitTestController::CaptureDump() {
236 if (captured_dump_ || !main_window_ || !printer_.in_text_block()) 253 if (captured_dump_ || !main_window_ || !printer_->in_text_block())
237 return; 254 return;
238 captured_dump_ = true; 255 captured_dump_ = true;
239 256
240 RenderViewHost* render_view_host = 257 RenderViewHost* render_view_host =
241 main_window_->web_contents()->GetRenderViewHost(); 258 main_window_->web_contents()->GetRenderViewHost();
242 259
243 render_view_host->Send(new ShellViewMsg_CaptureTextDump( 260 render_view_host->Send(new ShellViewMsg_CaptureTextDump(
244 render_view_host->GetRoutingID(), 261 render_view_host->GetRoutingID(),
245 dump_as_text_, 262 dump_as_text_,
246 is_printing_, 263 is_printing_,
247 dump_child_frames_)); 264 dump_child_frames_));
248 if (!dump_as_text_ && enable_pixel_dumping_) { 265 if (!dump_as_text_ && enable_pixel_dumping_) {
249 render_view_host->Send(new ShellViewMsg_CaptureImageDump( 266 render_view_host->Send(new ShellViewMsg_CaptureImageDump(
250 render_view_host->GetRoutingID(), 267 render_view_host->GetRoutingID(),
251 expected_pixel_hash_)); 268 expected_pixel_hash_));
252 } 269 }
253 } 270 }
254 271
255 void WebKitTestController::TimeoutHandler() { 272 void WebKitTestController::TimeoutHandler() {
256 printer_.AddErrorMessage( 273 printer_->AddErrorMessage(
257 "FAIL: Timed out waiting for notifyDone to be called"); 274 "FAIL: Timed out waiting for notifyDone to be called");
258 } 275 }
259 276
260 void WebKitTestController::OnDidFinishLoad() { 277 void WebKitTestController::OnDidFinishLoad() {
261 if (pumping_messages_) { 278 if (pumping_messages_) {
262 MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); 279 MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
263 return; 280 return;
264 } 281 }
265 if (wait_until_done_) 282 if (wait_until_done_)
266 return; 283 return;
267 CaptureDump(); 284 CaptureDump();
268 } 285 }
269 286
270 void WebKitTestController::OnImageDump( 287 void WebKitTestController::OnImageDump(
271 const std::string& actual_pixel_hash, 288 const std::string& actual_pixel_hash,
272 const SkBitmap& image) { 289 const SkBitmap& image) {
273 SkAutoLockPixels image_lock(image); 290 SkAutoLockPixels image_lock(image);
274 291
275 printer_.PrintImageHeader(actual_pixel_hash, expected_pixel_hash_); 292 printer_->PrintImageHeader(actual_pixel_hash, expected_pixel_hash_);
276 293
277 // Only encode and dump the png if the hashes don't match. Encoding the 294 // Only encode and dump the png if the hashes don't match. Encoding the
278 // image is really expensive. 295 // image is really expensive.
279 if (actual_pixel_hash != expected_pixel_hash_) { 296 if (actual_pixel_hash != expected_pixel_hash_) {
280 std::vector<unsigned char> png; 297 std::vector<unsigned char> png;
281 298
282 // Only the expected PNGs for Mac have a valid alpha channel. 299 // Only the expected PNGs for Mac have a valid alpha channel.
283 #if defined(OS_MACOSX) 300 #if defined(OS_MACOSX)
284 bool discard_transparency = false; 301 bool discard_transparency = false;
285 #else 302 #else
(...skipping 14 matching lines...) Expand all
300 success = webkit_support::EncodeBGRAPNGWithChecksum( 317 success = webkit_support::EncodeBGRAPNGWithChecksum(
301 reinterpret_cast<const unsigned char*>(image.getPixels()), 318 reinterpret_cast<const unsigned char*>(image.getPixels()),
302 image.width(), 319 image.width(),
303 image.height(), 320 image.height(),
304 static_cast<int>(image.rowBytes()), 321 static_cast<int>(image.rowBytes()),
305 discard_transparency, 322 discard_transparency,
306 actual_pixel_hash, 323 actual_pixel_hash,
307 &png); 324 &png);
308 #endif 325 #endif
309 if (success) 326 if (success)
310 printer_.PrintImageBlock(png); 327 printer_->PrintImageBlock(png);
311 } 328 }
312 printer_.PrintImageFooter(); 329 printer_->PrintImageFooter();
313 MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); 330 MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
314 } 331 }
315 332
316 void WebKitTestController::OnTextDump(const std::string& dump) { 333 void WebKitTestController::OnTextDump(const std::string& dump) {
317 printer_.PrintTextBlock(dump); 334 printer_->PrintTextBlock(dump);
318 printer_.PrintTextFooter(); 335 printer_->PrintTextFooter();
319 if (dump_as_text_ || !enable_pixel_dumping_) { 336 if (dump_as_text_ || !enable_pixel_dumping_) {
320 printer_.PrintImageFooter(); 337 printer_->PrintImageFooter();
321 MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); 338 MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
322 } 339 }
323 } 340 }
324 341
325 // WebKitTestRunnerHost ------------------------------------------------------- 342 // WebKitTestRunnerHost -------------------------------------------------------
326 343
327 WebKitTestRunnerHost::WebKitTestRunnerHost( 344 WebKitTestRunnerHost::WebKitTestRunnerHost(
328 RenderViewHost* render_view_host) 345 RenderViewHost* render_view_host)
329 : RenderViewHostObserver(render_view_host) { 346 : RenderViewHostObserver(render_view_host) {
330 } 347 }
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
379 WebKitTestController::Get()->WaitUntilDone(); 396 WebKitTestController::Get()->WaitUntilDone();
380 } 397 }
381 398
382 void WebKitTestRunnerHost::OnNotImplemented( 399 void WebKitTestRunnerHost::OnNotImplemented(
383 const std::string& object_name, 400 const std::string& object_name,
384 const std::string& property_name) { 401 const std::string& property_name) {
385 WebKitTestController::Get()->NotImplemented(object_name, property_name); 402 WebKitTestController::Get()->NotImplemented(object_name, property_name);
386 } 403 }
387 404
388 } // namespace content 405 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698