Extract SIGPIPE ignoring code to a common place.
[chromium-blink-merge.git] / chrome / test / gpu / gpu_pixel_browsertest.cc
blob8121b38ea1284450c5b0d05cbbcf1f3013131a07
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
3 // found in the LICENSE file.
5 #include "base/command_line.h"
6 #include "base/file_path.h"
7 #include "base/file_util.h"
8 #include "base/path_service.h"
9 #include "base/string_number_conversions.h"
10 #include "base/string_util.h"
11 #include "base/stringprintf.h"
12 #include "chrome/browser/ui/browser.h"
13 #include "chrome/browser/ui/browser_tabstrip.h"
14 #include "chrome/browser/ui/browser_window.h"
15 #include "chrome/browser/ui/window_snapshot/window_snapshot.h"
16 #include "chrome/common/chrome_paths.h"
17 #include "chrome/common/chrome_switches.h"
18 #include "chrome/common/chrome_version_info.h"
19 #include "chrome/test/base/in_process_browser_test.h"
20 #include "chrome/test/base/test_launcher_utils.h"
21 #include "chrome/test/base/tracing.h"
22 #include "chrome/test/base/ui_test_utils.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/common/content_switches.h"
26 #include "content/public/test/browser_test_utils.h"
27 #include "googleurl/src/gurl.h"
28 #include "net/base/net_util.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #include "third_party/skia/include/core/SkBitmap.h"
31 #include "third_party/skia/include/core/SkColor.h"
32 #include "ui/compositor/compositor_setup.h"
33 #include "ui/gfx/codec/png_codec.h"
34 #include "ui/gfx/size.h"
35 #include "ui/gl/gl_switches.h"
37 namespace {
39 enum ReferenceImageOption {
40 kReferenceImageLocal,
41 kReferenceImageCheckedIn,
42 kReferenceImageNone // Only check a few key pixels.
45 struct ReferencePixel {
46 int x, y;
47 unsigned char r, g, b;
50 // Command line flag for overriding the default location for putting generated
51 // test images that do not match references.
52 const char kGeneratedDir[] = "generated-dir";
53 // Command line flag for overriding the default location for reference images.
54 const char kReferenceDir[] = "reference-dir";
56 // Corner shadow size.
57 const int kCornerDecorationSize = 15;
58 // Side shadow size.
59 const int kSideDecorationSize = 2;
61 // Reads and decodes a PNG image to a bitmap. Returns true on success. The PNG
62 // should have been encoded using |gfx::PNGCodec::Encode|.
63 bool ReadPNGFile(const FilePath& file_path, SkBitmap* bitmap) {
64 DCHECK(bitmap);
65 std::string png_data;
66 return file_util::ReadFileToString(file_path, &png_data) &&
67 gfx::PNGCodec::Decode(reinterpret_cast<unsigned char*>(&png_data[0]),
68 png_data.length(),
69 bitmap);
72 // Encodes a bitmap into a PNG and write to disk. Returns true on success. The
73 // parent directory does not have to exist.
74 bool WritePNGFile(const SkBitmap& bitmap, const FilePath& file_path) {
75 std::vector<unsigned char> png_data;
76 if (gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, true, &png_data) &&
77 file_util::CreateDirectory(file_path.DirName())) {
78 int bytes_written = file_util::WriteFile(
79 file_path, reinterpret_cast<char*>(&png_data[0]), png_data.size());
80 if (bytes_written == static_cast<int>(png_data.size()))
81 return true;
83 return false;
86 // Write an empty file, whose name indicates the chrome revision when the ref
87 // image was generated.
88 bool WriteREVFile(const FilePath& file_path) {
89 if (file_util::CreateDirectory(file_path.DirName())) {
90 char one_byte = 0;
91 int bytes_written = file_util::WriteFile(file_path, &one_byte, 1);
92 if (bytes_written == 1)
93 return true;
95 return false;
98 } // namespace
100 // Test fixture for GPU image comparison tests.
101 // TODO(kkania): Document how to add to/modify these tests.
102 class GpuPixelBrowserTest : public InProcessBrowserTest {
103 public:
104 GpuPixelBrowserTest()
105 : ref_img_revision_(0),
106 ref_img_revision_no_older_than_(0),
107 ref_img_option_(kReferenceImageNone) {
110 virtual void SetUpCommandLine(CommandLine* command_line) {
111 command_line->AppendSwitchASCII(switches::kTestGLLib,
112 "libllvmpipe.so");
115 virtual void SetUpInProcessBrowserTestFixture() {
116 InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
118 CommandLine* command_line = CommandLine::ForCurrentProcess();
119 if (command_line->HasSwitch(switches::kUseGpuInTests))
120 ref_img_option_ = kReferenceImageLocal;
122 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_));
123 test_data_dir_ = test_data_dir_.AppendASCII("gpu");
125 if (command_line->HasSwitch(kGeneratedDir))
126 generated_img_dir_ = command_line->GetSwitchValuePath(kGeneratedDir);
127 else
128 generated_img_dir_ = test_data_dir_.AppendASCII("generated");
130 switch (ref_img_option_) {
131 case kReferenceImageLocal:
132 if (command_line->HasSwitch(kReferenceDir))
133 ref_img_dir_ = command_line->GetSwitchValuePath(kReferenceDir);
134 else
135 ref_img_dir_ = test_data_dir_.AppendASCII("gpu_reference");
136 break;
137 case kReferenceImageCheckedIn:
138 ref_img_dir_ = test_data_dir_.AppendASCII("llvmpipe_reference");
139 break;
140 default:
141 break;
144 test_name_ = testing::UnitTest::GetInstance()->current_test_info()->name();
145 const char* test_status_prefixes[] = {"DISABLED_", "FLAKY_", "FAILS_"};
146 for (size_t i = 0; i < arraysize(test_status_prefixes); ++i) {
147 ReplaceFirstSubstringAfterOffset(
148 &test_name_, 0, test_status_prefixes[i], "");
151 ui::DisableTestCompositor();
154 // If the existing ref image was saved from an revision older than the
155 // ref_img_update_revision, refresh the ref image.
156 void RunPixelTest(const gfx::Size& tab_container_size,
157 const FilePath& url,
158 int64 ref_img_update_revision,
159 const ReferencePixel* ref_pixels,
160 size_t ref_pixel_count) {
161 if (ref_img_option_ == kReferenceImageLocal) {
162 ref_img_revision_no_older_than_ = ref_img_update_revision;
163 ObtainLocalRefImageRevision();
166 #if defined(OS_WIN)
167 ASSERT_TRUE(tracing::BeginTracing("-test_*"));
168 #endif
170 ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
172 content::DOMMessageQueue message_queue;
173 ui_test_utils::NavigateToURL(browser(), net::FilePathToFileURL(url));
175 // Wait for notification that page is loaded.
176 ASSERT_TRUE(message_queue.WaitForMessage(NULL));
177 message_queue.ClearQueue();
179 gfx::Rect new_bounds = GetNewTabContainerBounds(tab_container_size);
181 std::wostringstream js_call;
182 js_call << "preCallResizeInChromium(";
183 js_call << new_bounds.width() << ", " << new_bounds.height();
184 js_call << ");";
186 ASSERT_TRUE(content::ExecuteJavaScript(
187 chrome::GetActiveWebContents(browser())->GetRenderViewHost(),
188 L"", js_call.str()));
190 std::string message;
191 ASSERT_TRUE(message_queue.WaitForMessage(&message));
192 message_queue.ClearQueue();
193 browser()->window()->SetBounds(new_bounds);
195 // Wait for message from test page indicating the rendering is done.
196 while (message.compare("\"resized\"")) {
197 ASSERT_TRUE(message_queue.WaitForMessage(&message));
198 message_queue.ClearQueue();
201 bool ignore_bottom_corners = false;
202 #if defined(OS_MACOSX)
203 // On Mac Lion, bottom corners have shadows with random pixels.
204 ignore_bottom_corners = true;
205 #endif
207 SkBitmap bitmap;
208 ASSERT_TRUE(TabSnapShotToImage(&bitmap));
209 bool same_pixels = true;
210 if (ref_img_option_ == kReferenceImageNone && ref_pixels && ref_pixel_count)
211 same_pixels = ComparePixels(bitmap, ref_pixels, ref_pixel_count);
212 else
213 same_pixels = CompareImages(bitmap, ignore_bottom_corners);
214 EXPECT_TRUE(same_pixels);
216 #if defined(OS_WIN)
217 // For debugging the flaky test, this prints out a trace of what happened on
218 // failure.
219 std::string trace_events;
220 ASSERT_TRUE(tracing::EndTracing(&trace_events));
221 if (!same_pixels)
222 fprintf(stderr, "\n\nTRACE JSON:\n\n%s\n\n", trace_events.c_str());
223 #endif
226 const FilePath& test_data_dir() const {
227 return test_data_dir_;
230 private:
231 FilePath test_data_dir_;
232 FilePath generated_img_dir_;
233 FilePath ref_img_dir_;
234 int64 ref_img_revision_;
235 // The name of the test, with any special prefixes dropped.
236 std::string test_name_;
238 // Any local ref image generated from older revision is ignored.
239 int64 ref_img_revision_no_older_than_;
241 // Whether use locally generated ref images, or checked in ref images, or
242 // simply check a few key pixels.
243 ReferenceImageOption ref_img_option_;
245 // Compares the generated bitmap with the appropriate reference image on disk.
246 // Returns true iff the images were the same.
248 // If no valid reference image exists, save the generated bitmap to the disk.
249 // The image format is:
250 // <test_name>_<revision>.png
251 // E.g.,
252 // WebGLTeapot_19762.png
253 // The number is the chromium revision that generated the image.
255 // On failure or on ref image generation, the image and diff image will be
256 // written to disk. The formats are:
257 // FAIL_<ref_image_name>, DIFF_<ref_image_name>
258 // E.g.,
259 // FAIL_WebGLTeapot_19762.png, DIFF_WebGLTeapot_19762.png
260 bool CompareImages(const SkBitmap& gen_bmp, bool skip_bottom_corners) {
261 SkBitmap ref_bmp_on_disk;
263 FilePath img_path = ref_img_dir_.AppendASCII(test_name_ + ".png");
264 bool found_ref_img = ReadPNGFile(img_path, &ref_bmp_on_disk);
266 if (!found_ref_img && ref_img_option_ == kReferenceImageCheckedIn) {
267 LOG(ERROR) << "Couldn't find reference image: "
268 << img_path.value();
269 // No image to compare to, exit early.
270 return false;
273 const SkBitmap* ref_bmp;
274 bool save_gen = false;
275 bool save_diff = true;
276 bool rt = true;
278 if ((ref_img_revision_ <= 0 && ref_img_option_ == kReferenceImageLocal) ||
279 !found_ref_img) {
280 chrome::VersionInfo chrome_version_info;
281 FilePath rev_path = ref_img_dir_.AppendASCII(
282 test_name_ + "_" + chrome_version_info.LastChange() + ".rev");
283 if (!WritePNGFile(gen_bmp, img_path)) {
284 LOG(ERROR) << "Can't save generated image to: "
285 << img_path.value()
286 << " as future reference.";
287 rt = false;
288 } else {
289 LOG(INFO) << "Saved reference image to: "
290 << img_path.value();
292 if (rt) {
293 if (!WriteREVFile(rev_path)) {
294 LOG(ERROR) << "Can't save revision file to: "
295 << rev_path.value();
296 rt = false;
297 file_util::Delete(img_path, false);
298 } else {
299 LOG(INFO) << "Saved revision file to: "
300 << rev_path.value();
303 if (ref_img_revision_ > 0) {
304 LOG(ERROR) << "Can't read the local ref image: "
305 << img_path.value()
306 << ", reset it.";
307 rt = false;
309 // If we re-generate the ref image, we save the gen and diff images so
310 // the ref image can be uploaded to the server and be viewed later.
311 save_gen = true;
312 save_diff = true;
313 ref_bmp = &gen_bmp;
314 } else {
315 ref_bmp = &ref_bmp_on_disk;
318 SkBitmap diff_bmp;
319 if (ref_bmp->width() != gen_bmp.width() ||
320 ref_bmp->height() != gen_bmp.height()) {
321 LOG(ERROR)
322 << "Dimensions do not match (Expected) vs (Actual):"
323 << "(" << ref_bmp->width() << "x" << ref_bmp->height()
324 << ") vs. "
325 << "(" << gen_bmp.width() << "x" << gen_bmp.height() << ")";
326 if (ref_img_option_ == kReferenceImageLocal)
327 save_gen = true;
328 rt = false;
329 } else {
330 // Compare pixels and create a simple diff image.
331 int diff_pixels_count = 0;
332 diff_bmp.setConfig(SkBitmap::kARGB_8888_Config,
333 gen_bmp.width(), gen_bmp.height());
334 diff_bmp.allocPixels();
335 diff_bmp.eraseColor(SK_ColorWHITE);
336 SkAutoLockPixels lock_bmp(gen_bmp);
337 SkAutoLockPixels lock_ref_bmp(*ref_bmp);
338 SkAutoLockPixels lock_diff_bmp(diff_bmp);
339 // The reference images were saved with no alpha channel. Use the mask to
340 // set alpha to 0.
341 uint32_t kAlphaMask = 0x00FFFFFF;
342 for (int x = 0; x < gen_bmp.width(); ++x) {
343 for (int y = 0; y < gen_bmp.height(); ++y) {
344 if (skip_bottom_corners &&
345 (((x < kCornerDecorationSize ||
346 x >= gen_bmp.width() - kCornerDecorationSize) &&
347 y >= gen_bmp.height() - kCornerDecorationSize) ||
348 (x < kSideDecorationSize ||
349 x >= gen_bmp.width() - kSideDecorationSize)))
350 continue;
351 if ((*gen_bmp.getAddr32(x, y) & kAlphaMask) !=
352 (*ref_bmp->getAddr32(x, y) & kAlphaMask)) {
353 ++diff_pixels_count;
354 *diff_bmp.getAddr32(x, y) = 192 << 16; // red
358 if (diff_pixels_count > 0) {
359 LOG(ERROR) << diff_pixels_count
360 << " pixels do not match.";
361 if (ref_img_option_ == kReferenceImageLocal) {
362 save_gen = true;
363 save_diff = true;
365 rt = false;
369 std::string ref_img_filename = img_path.BaseName().MaybeAsASCII();
370 if (save_gen) {
371 FilePath img_fail_path = generated_img_dir_.AppendASCII(
372 "FAIL_" + ref_img_filename);
373 if (!WritePNGFile(gen_bmp, img_fail_path)) {
374 LOG(ERROR) << "Can't save generated image to: "
375 << img_fail_path.value();
376 } else {
377 LOG(INFO) << "Saved generated image to: "
378 << img_fail_path.value();
381 if (save_diff) {
382 FilePath img_diff_path = generated_img_dir_.AppendASCII(
383 "DIFF_" + ref_img_filename);
384 if (!WritePNGFile(diff_bmp, img_diff_path)) {
385 LOG(ERROR) << "Can't save generated diff image to: "
386 << img_diff_path.value();
387 } else {
388 LOG(INFO) << "Saved difference image to: "
389 << img_diff_path.value();
392 return rt;
395 bool ComparePixels(const SkBitmap& gen_bmp,
396 const ReferencePixel* ref_pixels,
397 size_t ref_pixel_count) {
398 SkAutoLockPixels lock_bmp(gen_bmp);
400 for (size_t i = 0; i < ref_pixel_count; ++i) {
401 int x = ref_pixels[i].x;
402 int y = ref_pixels[i].y;
403 unsigned char r = ref_pixels[i].r;
404 unsigned char g = ref_pixels[i].g;
405 unsigned char b = ref_pixels[i].b;
407 DCHECK(x >= 0 && x < gen_bmp.width() && y >= 0 && y < gen_bmp.height());
409 unsigned char* rgba = reinterpret_cast<unsigned char*>(
410 gen_bmp.getAddr32(x, y));
411 DCHECK(rgba);
412 if (rgba[0] != b || rgba[1] != g || rgba[2] != r) {
413 std::string error_message = base::StringPrintf(
414 "pixel(%d,%d) expects [%u,%u,%u], but gets [%u,%u,%u] instead",
415 x, y, r, g, b, rgba[0], rgba[1], rgba[2]);
416 LOG(ERROR) << error_message.c_str();
417 return false;
420 return true;
423 // Returns a gfx::Rect representing the bounds that the browser window should
424 // have if the tab contents have the desired size.
425 gfx::Rect GetNewTabContainerBounds(const gfx::Size& desired_size) {
426 gfx::Rect container_rect;
427 chrome::GetActiveWebContents(browser())->GetContainerBounds(&container_rect);
428 // Size cannot be negative, so use a point.
429 gfx::Point correction(
430 desired_size.width() - container_rect.size().width(),
431 desired_size.height() - container_rect.size().height());
433 gfx::Rect window_rect = browser()->window()->GetRestoredBounds();
434 gfx::Size new_size = window_rect.size();
435 new_size.Enlarge(correction.x(), correction.y());
436 window_rect.set_size(new_size);
437 return window_rect;
440 // Take snapshot of the current tab, encode it as PNG, and save to a SkBitmap.
441 bool TabSnapShotToImage(SkBitmap* bitmap) {
442 CHECK(bitmap);
443 std::vector<unsigned char> png;
445 gfx::Rect root_bounds = browser()->window()->GetBounds();
446 gfx::Rect tab_contents_bounds;
447 chrome::GetActiveWebContents(browser())->GetContainerBounds(
448 &tab_contents_bounds);
450 gfx::Rect snapshot_bounds(tab_contents_bounds.x() - root_bounds.x(),
451 tab_contents_bounds.y() - root_bounds.y(),
452 tab_contents_bounds.width(),
453 tab_contents_bounds.height());
455 gfx::NativeWindow native_window = browser()->window()->GetNativeWindow();
456 if (!chrome::GrabWindowSnapshotForUser(native_window, &png,
457 snapshot_bounds)) {
458 LOG(ERROR) << "browser::GrabWindowSnapShot() failed";
459 return false;
462 if (!gfx::PNGCodec::Decode(reinterpret_cast<unsigned char*>(&*png.begin()),
463 png.size(), bitmap)) {
464 LOG(ERROR) << "Decode PNG to a SkBitmap failed";
465 return false;
467 return true;
470 // If no valid local revision file is located, the ref_img_revision_ is 0.
471 void ObtainLocalRefImageRevision() {
472 FilePath filter;
473 filter = filter.AppendASCII(test_name_ + "_*.rev");
474 file_util::FileEnumerator locator(ref_img_dir_,
475 false, // non recursive
476 file_util::FileEnumerator::FILES,
477 filter.value());
478 int64 max_revision = 0;
479 std::vector<FilePath> outdated_revs;
480 for (FilePath full_path = locator.Next();
481 !full_path.empty();
482 full_path = locator.Next()) {
483 std::string filename =
484 full_path.BaseName().RemoveExtension().MaybeAsASCII();
485 std::string revision_string =
486 filename.substr(test_name_.length() + 1);
487 int64 revision = 0;
488 bool converted = base::StringToInt64(revision_string, &revision);
489 if (!converted)
490 continue;
491 if (revision < ref_img_revision_no_older_than_ ||
492 revision < max_revision) {
493 outdated_revs.push_back(full_path);
494 continue;
496 max_revision = revision;
498 ref_img_revision_ = max_revision;
499 for (size_t i = 0; i < outdated_revs.size(); ++i)
500 file_util::Delete(outdated_revs[i], false);
503 DISALLOW_COPY_AND_ASSIGN(GpuPixelBrowserTest);
506 IN_PROC_BROWSER_TEST_F(GpuPixelBrowserTest, WebGLGreenTriangle) {
507 // If test baseline needs to be updated after a given revision, update the
508 // following number. If no revision requirement, then 0.
509 const int64 ref_img_revision_update = 123489;
511 const ReferencePixel ref_pixels[] = {
512 // x, y, r, g, b
513 {50, 100, 0, 0, 0},
514 {100, 100, 0, 255, 0},
515 {150, 100, 0, 0, 0},
516 {50, 150, 0, 255, 0},
517 {100, 150, 0, 255, 0},
518 {150, 150, 0, 255, 0}
520 const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel);
522 gfx::Size container_size(400, 300);
523 FilePath url =
524 test_data_dir().AppendASCII("pixel_webgl.html");
525 RunPixelTest(container_size, url, ref_img_revision_update,
526 ref_pixels, ref_pixel_count);
529 IN_PROC_BROWSER_TEST_F(GpuPixelBrowserTest, CSS3DBlueBox) {
530 // If test baseline needs to be updated after a given revision, update the
531 // following number. If no revision requirement, then 0.
532 const int64 ref_img_revision_update = 123489;
534 const ReferencePixel ref_pixels[] = {
535 // x, y, r, g, b
536 {70, 50, 0, 0, 255},
537 {150, 50, 0, 0, 0},
538 {70, 90, 0, 0, 255},
539 {150, 90, 0, 0, 255},
540 {70, 125, 0, 0, 255},
541 {150, 125, 0, 0, 0}
543 const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel);
545 gfx::Size container_size(400, 300);
546 FilePath url =
547 test_data_dir().AppendASCII("pixel_css3d.html");
548 RunPixelTest(container_size, url, ref_img_revision_update,
549 ref_pixels, ref_pixel_count);
552 IN_PROC_BROWSER_TEST_F(GpuPixelBrowserTest, Canvas2DRedBoxHD) {
553 // If test baseline needs to be updated after a given revision, update the
554 // following number. If no revision requirement, then 0.
555 const int64 ref_img_revision_update = 123489;
557 const ReferencePixel ref_pixels[] = {
558 // x, y, r, g, b
559 {40, 100, 0, 0, 0},
560 {60, 100, 127, 0, 0},
561 {140, 100, 127, 0, 0},
562 {160, 100, 0, 0, 0}
564 const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel);
566 gfx::Size container_size(400, 300);
567 FilePath url =
568 test_data_dir().AppendASCII("pixel_canvas2d.html");
569 RunPixelTest(container_size, url, ref_img_revision_update,
570 ref_pixels, ref_pixel_count);
573 class Canvas2DPixelTestSD : public GpuPixelBrowserTest {
574 public:
575 virtual void SetUpCommandLine(CommandLine* command_line) {
576 GpuPixelBrowserTest::SetUpCommandLine(command_line);
577 command_line->AppendSwitch(switches::kDisableAccelerated2dCanvas);
581 IN_PROC_BROWSER_TEST_F(Canvas2DPixelTestSD, Canvas2DRedBoxSD) {
582 // If test baseline needs to be updated after a given revision, update the
583 // following number. If no revision requirement, then 0.
584 const int64 ref_img_revision_update = 123489;
586 const ReferencePixel ref_pixels[] = {
587 // x, y, r, g, b
588 {40, 100, 0, 0, 0},
589 {60, 100, 127, 0, 0},
590 {140, 100, 127, 0, 0},
591 {160, 100, 0, 0, 0}
593 const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel);
595 gfx::Size container_size(400, 300);
596 FilePath url =
597 test_data_dir().AppendASCII("pixel_canvas2d.html");
598 RunPixelTest(container_size, url, ref_img_revision_update,
599 ref_pixels, ref_pixel_count);