1 // Copyright (c) 2013 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 "base/command_line.h" 6 #include "base/file_util.h" 7 #include "base/files/file_enumerator.h" 8 #include "base/files/file_path.h" 9 #include "base/path_service.h" 10 #include "base/strings/string_number_conversions.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/stringprintf.h" 13 #include "content/public/browser/render_view_host.h" 14 #include "content/public/browser/render_widget_host_view.h" 15 #include "content/public/browser/web_contents.h" 16 #include "content/public/common/content_paths.h" 17 #include "content/public/common/content_switches.h" 18 #include "content/public/test/browser_test_utils.h" 19 #include "content/shell/shell.h" 20 #include "content/test/content_browser_test.h" 21 #include "content/test/content_browser_test_utils.h" 22 #include "gpu/config/gpu_test_config.h" 23 #include "net/base/net_util.h" 24 #include "testing/gtest/include/gtest/gtest.h" 25 #include "third_party/skia/include/core/SkBitmap.h" 26 #include "third_party/skia/include/core/SkColor.h" 27 #include "ui/gfx/codec/png_codec.h" 28 #include "ui/gfx/size.h" 29 #include "ui/gl/gl_switches.h" 30 #include "ui/snapshot/snapshot.h" 31 32 namespace { 33 34 enum ReferenceImageOption { 35 kReferenceImageLocal, 36 kReferenceImageCheckedIn, 37 kReferenceImageNone // Only check a few key pixels. 38 }; 39 40 struct ReferencePixel { 41 int x, y; 42 unsigned char r, g, b; 43 }; 44 45 // Command line flag for overriding the default location for putting generated 46 // test images that do not match references. 47 const char kGeneratedDir[] = "generated-dir"; 48 // Command line flag for overriding the default location for reference images. 49 const char kReferenceDir[] = "reference-dir"; 50 // Command line flag for Chromium build revision. 51 const char kBuildRevision[] = "build-revision"; 52 53 // Reads and decodes a PNG image to a bitmap. Returns true on success. The PNG 54 // should have been encoded using |gfx::PNGCodec::Encode|. 55 bool ReadPNGFile(const base::FilePath& file_path, SkBitmap* bitmap) { 56 DCHECK(bitmap); 57 base::FilePath abs_path(base::MakeAbsoluteFilePath(file_path)); 58 if (abs_path.empty()) 59 return false; 60 61 std::string png_data; 62 return file_util::ReadFileToString(abs_path, &png_data) && 63 gfx::PNGCodec::Decode(reinterpret_cast<unsigned char*>(&png_data[0]), 64 png_data.length(), 65 bitmap); 66 } 67 68 // Encodes a bitmap into a PNG and write to disk. Returns true on success. The 69 // parent directory does not have to exist. 70 bool WritePNGFile(const SkBitmap& bitmap, const base::FilePath& file_path) { 71 std::vector<unsigned char> png_data; 72 if (gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, true, &png_data) && 73 file_util::CreateDirectory(file_path.DirName())) { 74 int bytes_written = file_util::WriteFile( 75 file_path, reinterpret_cast<char*>(&png_data[0]), png_data.size()); 76 if (bytes_written == static_cast<int>(png_data.size())) 77 return true; 78 } 79 return false; 80 } 81 82 // Write an empty file, whose name indicates the chrome revision when the ref 83 // image was generated. 84 bool WriteREVFile(const base::FilePath& file_path) { 85 if (file_util::CreateDirectory(file_path.DirName())) { 86 char one_byte = 0; 87 int bytes_written = file_util::WriteFile(file_path, &one_byte, 1); 88 if (bytes_written == 1) 89 return true; 90 } 91 return false; 92 } 93 94 } // namespace anonymous 95 96 namespace content { 97 98 // Test fixture for GPU image comparison tests. 99 // TODO(kkania): Document how to add to/modify these tests. 100 class GpuPixelBrowserTest : public ContentBrowserTest { 101 public: 102 GpuPixelBrowserTest() 103 : ref_img_revision_(0), 104 ref_img_revision_no_older_than_(0), 105 ref_img_option_(kReferenceImageNone) { 106 } 107 108 virtual void SetUp() { 109 // We expect real pixel output for these tests. 110 UseRealGLContexts(); 111 112 ContentBrowserTest::SetUp(); 113 } 114 115 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 116 command_line->AppendSwitchASCII(switches::kTestGLLib, 117 "libllvmpipe.so"); 118 } 119 120 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { 121 ContentBrowserTest::SetUpInProcessBrowserTestFixture(); 122 123 CommandLine* command_line = CommandLine::ForCurrentProcess(); 124 if (command_line->HasSwitch(switches::kUseGpuInTests)) 125 ref_img_option_ = kReferenceImageLocal; 126 127 if (command_line->HasSwitch(kBuildRevision)) 128 build_revision_ = command_line->GetSwitchValueASCII(kBuildRevision); 129 130 ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &test_data_dir_)); 131 test_data_dir_ = test_data_dir_.AppendASCII("gpu"); 132 133 if (command_line->HasSwitch(kGeneratedDir)) 134 generated_img_dir_ = command_line->GetSwitchValuePath(kGeneratedDir); 135 else 136 generated_img_dir_ = test_data_dir_.AppendASCII("generated"); 137 138 switch (ref_img_option_) { 139 case kReferenceImageLocal: 140 if (command_line->HasSwitch(kReferenceDir)) 141 ref_img_dir_ = command_line->GetSwitchValuePath(kReferenceDir); 142 else 143 ref_img_dir_ = test_data_dir_.AppendASCII("gpu_reference"); 144 break; 145 case kReferenceImageCheckedIn: 146 ref_img_dir_ = test_data_dir_.AppendASCII("llvmpipe_reference"); 147 break; 148 default: 149 break; 150 } 151 152 test_name_ = testing::UnitTest::GetInstance()->current_test_info()->name(); 153 const char* test_status_prefixes[] = { 154 "DISABLED_", "FLAKY_", "FAILS_", "MANUAL_"}; 155 for (size_t i = 0; i < arraysize(test_status_prefixes); ++i) { 156 ReplaceFirstSubstringAfterOffset( 157 &test_name_, 0, test_status_prefixes[i], std::string()); 158 } 159 } 160 161 // If the existing ref image was saved from an revision older than the 162 // ref_img_update_revision, refresh the ref image. 163 void RunPixelTest(const gfx::Size& tab_container_size, 164 const base::FilePath& url, 165 int64 ref_img_update_revision, 166 const ReferencePixel* ref_pixels, 167 size_t ref_pixel_count) { 168 if (ref_img_option_ == kReferenceImageLocal) { 169 ref_img_revision_no_older_than_ = ref_img_update_revision; 170 ObtainLocalRefImageRevision(); 171 } 172 173 DOMMessageQueue message_queue; 174 NavigateToURL(shell(), net::FilePathToFileURL(url)); 175 176 std::string message; 177 // Wait for notification that page is loaded. 178 ASSERT_TRUE(message_queue.WaitForMessage(&message)); 179 EXPECT_STREQ("\"SUCCESS\"", message.c_str()) << message; 180 181 SkBitmap bitmap; 182 ASSERT_TRUE(TabSnapShotToImage(&bitmap, tab_container_size)); 183 bool same_pixels = true; 184 if (ref_img_option_ == kReferenceImageNone && ref_pixels && ref_pixel_count) 185 same_pixels = ComparePixels(bitmap, ref_pixels, ref_pixel_count); 186 else 187 same_pixels = CompareImages(bitmap); 188 EXPECT_TRUE(same_pixels); 189 } 190 191 const base::FilePath& test_data_dir() const { 192 return test_data_dir_; 193 } 194 195 private: 196 base::FilePath test_data_dir_; 197 base::FilePath generated_img_dir_; 198 base::FilePath ref_img_dir_; 199 int64 ref_img_revision_; 200 std::string build_revision_; 201 // The name of the test, with any special prefixes dropped. 202 std::string test_name_; 203 204 // Any local ref image generated from older revision is ignored. 205 int64 ref_img_revision_no_older_than_; 206 207 // Whether use locally generated ref images, or checked in ref images, or 208 // simply check a few key pixels. 209 ReferenceImageOption ref_img_option_; 210 211 // Compares the generated bitmap with the appropriate reference image on disk. 212 // Returns true iff the images were the same. 213 // 214 // If no valid reference image exists, save the generated bitmap to the disk. 215 // The image format is: 216 // <test_name>_<revision>.png 217 // E.g., 218 // WebGLTeapot_19762.png 219 // The number is the chromium revision that generated the image. 220 // 221 // On failure or on ref image generation, the image and diff image will be 222 // written to disk. The formats are: 223 // FAIL_<ref_image_name>, DIFF_<ref_image_name> 224 // E.g., 225 // FAIL_WebGLTeapot_19762.png, DIFF_WebGLTeapot_19762.png 226 bool CompareImages(const SkBitmap& gen_bmp) { 227 SkBitmap ref_bmp_on_disk; 228 229 base::FilePath img_path = ref_img_dir_.AppendASCII(test_name_ + ".png"); 230 bool found_ref_img = ReadPNGFile(img_path, &ref_bmp_on_disk); 231 232 if (!found_ref_img && ref_img_option_ == kReferenceImageCheckedIn) { 233 LOG(ERROR) << "Couldn't find reference image: " 234 << img_path.value(); 235 // No image to compare to, exit early. 236 return false; 237 } 238 239 const SkBitmap* ref_bmp; 240 bool save_gen = false; 241 bool save_diff = true; 242 bool rt = true; 243 244 if ((ref_img_revision_ <= 0 && ref_img_option_ == kReferenceImageLocal) || 245 !found_ref_img) { 246 base::FilePath rev_path = ref_img_dir_.AppendASCII( 247 test_name_ + "_" + build_revision_ + ".rev"); 248 if (!WritePNGFile(gen_bmp, img_path)) { 249 LOG(ERROR) << "Can't save generated image to: " 250 << img_path.value() 251 << " as future reference."; 252 rt = false; 253 } else { 254 LOG(INFO) << "Saved reference image to: " 255 << img_path.value(); 256 } 257 if (rt) { 258 if (!WriteREVFile(rev_path)) { 259 LOG(ERROR) << "Can't save revision file to: " 260 << rev_path.value(); 261 rt = false; 262 base::DeleteFile(img_path, false); 263 } else { 264 LOG(INFO) << "Saved revision file to: " 265 << rev_path.value(); 266 } 267 } 268 if (ref_img_revision_ > 0) { 269 LOG(ERROR) << "Can't read the local ref image: " 270 << img_path.value() 271 << ", reset it."; 272 rt = false; 273 } 274 // If we re-generate the ref image, we save the gen and diff images so 275 // the ref image can be uploaded to the server and be viewed later. 276 save_gen = true; 277 save_diff = true; 278 ref_bmp = &gen_bmp; 279 } else { 280 ref_bmp = &ref_bmp_on_disk; 281 } 282 283 SkBitmap diff_bmp; 284 if (ref_bmp->width() != gen_bmp.width() || 285 ref_bmp->height() != gen_bmp.height()) { 286 LOG(ERROR) 287 << "Dimensions do not match (Expected) vs (Actual):" 288 << "(" << ref_bmp->width() << "x" << ref_bmp->height() 289 << ") vs. " 290 << "(" << gen_bmp.width() << "x" << gen_bmp.height() << ")"; 291 if (ref_img_option_ == kReferenceImageLocal) 292 save_gen = true; 293 rt = false; 294 } else { 295 // Compare pixels and create a simple diff image. 296 int diff_pixels_count = 0; 297 diff_bmp.setConfig(SkBitmap::kARGB_8888_Config, 298 gen_bmp.width(), gen_bmp.height()); 299 diff_bmp.allocPixels(); 300 diff_bmp.eraseColor(SK_ColorWHITE); 301 SkAutoLockPixels lock_bmp(gen_bmp); 302 SkAutoLockPixels lock_ref_bmp(*ref_bmp); 303 SkAutoLockPixels lock_diff_bmp(diff_bmp); 304 // The reference images were saved with no alpha channel. Use the mask to 305 // set alpha to 0. 306 uint32_t kAlphaMask = 0x00FFFFFF; 307 for (int x = 0; x < gen_bmp.width(); ++x) { 308 for (int y = 0; y < gen_bmp.height(); ++y) { 309 if ((*gen_bmp.getAddr32(x, y) & kAlphaMask) != 310 (*ref_bmp->getAddr32(x, y) & kAlphaMask)) { 311 ++diff_pixels_count; 312 *diff_bmp.getAddr32(x, y) = 192 << 16; // red 313 } 314 } 315 } 316 if (diff_pixels_count > 0) { 317 LOG(ERROR) << diff_pixels_count 318 << " pixels do not match."; 319 if (ref_img_option_ == kReferenceImageLocal) { 320 save_gen = true; 321 save_diff = true; 322 } 323 rt = false; 324 } 325 } 326 327 std::string ref_img_filename = img_path.BaseName().MaybeAsASCII(); 328 if (save_gen) { 329 base::FilePath img_fail_path = generated_img_dir_.AppendASCII( 330 "FAIL_" + ref_img_filename); 331 if (!WritePNGFile(gen_bmp, img_fail_path)) { 332 LOG(ERROR) << "Can't save generated image to: " 333 << img_fail_path.value(); 334 } else { 335 LOG(INFO) << "Saved generated image to: " 336 << img_fail_path.value(); 337 } 338 } 339 if (save_diff) { 340 base::FilePath img_diff_path = generated_img_dir_.AppendASCII( 341 "DIFF_" + ref_img_filename); 342 if (!WritePNGFile(diff_bmp, img_diff_path)) { 343 LOG(ERROR) << "Can't save generated diff image to: " 344 << img_diff_path.value(); 345 } else { 346 LOG(INFO) << "Saved difference image to: " 347 << img_diff_path.value(); 348 } 349 } 350 return rt; 351 } 352 353 bool ComparePixels(const SkBitmap& gen_bmp, 354 const ReferencePixel* ref_pixels, 355 size_t ref_pixel_count) { 356 SkAutoLockPixels lock_bmp(gen_bmp); 357 358 for (size_t i = 0; i < ref_pixel_count; ++i) { 359 int x = ref_pixels[i].x; 360 int y = ref_pixels[i].y; 361 unsigned char r = ref_pixels[i].r; 362 unsigned char g = ref_pixels[i].g; 363 unsigned char b = ref_pixels[i].b; 364 365 DCHECK(x >= 0 && x < gen_bmp.width() && y >= 0 && y < gen_bmp.height()); 366 367 unsigned char* rgba = reinterpret_cast<unsigned char*>( 368 gen_bmp.getAddr32(x, y)); 369 DCHECK(rgba); 370 if (rgba[0] != b || rgba[1] != g || rgba[2] != r) { 371 std::string error_message = base::StringPrintf( 372 "pixel(%d,%d) expects [%u,%u,%u], but gets [%u,%u,%u] instead", 373 x, y, r, g, b, rgba[0], rgba[1], rgba[2]); 374 LOG(ERROR) << error_message.c_str(); 375 return false; 376 } 377 } 378 return true; 379 } 380 381 // Take snapshot of the tab, encode it as PNG, and save to a SkBitmap. 382 bool TabSnapShotToImage(SkBitmap* bitmap, const gfx::Size& size) { 383 CHECK(bitmap); 384 std::vector<unsigned char> png; 385 386 gfx::Rect snapshot_bounds(size); 387 RenderViewHost* view_host = shell()->web_contents()->GetRenderViewHost(); 388 if (!ui::GrabViewSnapshot(view_host->GetView()->GetNativeView(), 389 &png, snapshot_bounds)) { 390 LOG(ERROR) << "ui::GrabViewSnapShot() failed"; 391 return false; 392 } 393 394 if (!gfx::PNGCodec::Decode(reinterpret_cast<unsigned char*>(&*png.begin()), 395 png.size(), bitmap)) { 396 LOG(ERROR) << "Decode PNG to a SkBitmap failed"; 397 return false; 398 } 399 return true; 400 } 401 402 // If no valid local revision file is located, the ref_img_revision_ is 0. 403 void ObtainLocalRefImageRevision() { 404 base::FilePath filter; 405 filter = filter.AppendASCII(test_name_ + "_*.rev"); 406 base::FileEnumerator locator(ref_img_dir_, 407 false, // non recursive 408 base::FileEnumerator::FILES, 409 filter.value()); 410 int64 max_revision = 0; 411 std::vector<base::FilePath> outdated_revs; 412 for (base::FilePath full_path = locator.Next(); 413 !full_path.empty(); 414 full_path = locator.Next()) { 415 std::string filename = 416 full_path.BaseName().RemoveExtension().MaybeAsASCII(); 417 std::string revision_string = 418 filename.substr(test_name_.length() + 1); 419 int64 revision = 0; 420 bool converted = base::StringToInt64(revision_string, &revision); 421 if (!converted) 422 continue; 423 if (revision < ref_img_revision_no_older_than_ || 424 revision < max_revision) { 425 outdated_revs.push_back(full_path); 426 continue; 427 } 428 max_revision = revision; 429 } 430 ref_img_revision_ = max_revision; 431 for (size_t i = 0; i < outdated_revs.size(); ++i) 432 base::DeleteFile(outdated_revs[i], false); 433 } 434 435 DISALLOW_COPY_AND_ASSIGN(GpuPixelBrowserTest); 436 }; 437 438 IN_PROC_BROWSER_TEST_F(GpuPixelBrowserTest, MANUAL_WebGLGreenTriangle) { 439 // If test baseline needs to be updated after a given revision, update the 440 // following number. If no revision requirement, then 0. 441 const int64 ref_img_revision_update = 123489; 442 443 const ReferencePixel ref_pixels[] = { 444 // x, y, r, g, b 445 {50, 100, 0, 0, 0}, 446 {100, 100, 0, 255, 0}, 447 {150, 100, 0, 0, 0}, 448 {50, 150, 0, 255, 0}, 449 {100, 150, 0, 255, 0}, 450 {150, 150, 0, 255, 0} 451 }; 452 const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel); 453 454 gfx::Size container_size(400, 300); 455 base::FilePath url = 456 test_data_dir().AppendASCII("pixel_webgl.html"); 457 RunPixelTest(container_size, url, ref_img_revision_update, 458 ref_pixels, ref_pixel_count); 459 } 460 461 IN_PROC_BROWSER_TEST_F(GpuPixelBrowserTest, MANUAL_CSS3DBlueBox) { 462 // If test baseline needs to be updated after a given revision, update the 463 // following number. If no revision requirement, then 0. 464 const int64 ref_img_revision_update = 209827; 465 466 const ReferencePixel ref_pixels[] = { 467 // x, y, r, g, b 468 {70, 50, 0, 0, 255}, 469 {150, 50, 0, 0, 0}, 470 {70, 90, 0, 0, 255}, 471 {150, 90, 0, 0, 255}, 472 {70, 125, 0, 0, 255}, 473 {150, 125, 0, 0, 0} 474 }; 475 const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel); 476 477 gfx::Size container_size(400, 300); 478 base::FilePath url = 479 test_data_dir().AppendASCII("pixel_css3d.html"); 480 RunPixelTest(container_size, url, ref_img_revision_update, 481 ref_pixels, ref_pixel_count); 482 } 483 484 IN_PROC_BROWSER_TEST_F(GpuPixelBrowserTest, MANUAL_Canvas2DRedBoxHD) { 485 // If test baseline needs to be updated after a given revision, update the 486 // following number. If no revision requirement, then 0. 487 const int64 ref_img_revision_update = 123489; 488 489 const ReferencePixel ref_pixels[] = { 490 // x, y, r, g, b 491 {40, 100, 0, 0, 0}, 492 {60, 100, 127, 0, 0}, 493 {140, 100, 127, 0, 0}, 494 {160, 100, 0, 0, 0} 495 }; 496 const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel); 497 498 gfx::Size container_size(400, 300); 499 base::FilePath url = 500 test_data_dir().AppendASCII("pixel_canvas2d.html"); 501 RunPixelTest(container_size, url, ref_img_revision_update, 502 ref_pixels, ref_pixel_count); 503 } 504 505 class GpuPixelTestCanvas2DSD : public GpuPixelBrowserTest { 506 public: 507 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 508 GpuPixelBrowserTest::SetUpCommandLine(command_line); 509 command_line->AppendSwitch(switches::kDisableAccelerated2dCanvas); 510 } 511 }; 512 513 IN_PROC_BROWSER_TEST_F(GpuPixelTestCanvas2DSD, MANUAL_Canvas2DRedBoxSD) { 514 // If test baseline needs to be updated after a given revision, update the 515 // following number. If no revision requirement, then 0. 516 const int64 ref_img_revision_update = 123489; 517 518 const ReferencePixel ref_pixels[] = { 519 // x, y, r, g, b 520 {40, 100, 0, 0, 0}, 521 {60, 100, 127, 0, 0}, 522 {140, 100, 127, 0, 0}, 523 {160, 100, 0, 0, 0} 524 }; 525 const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel); 526 527 gfx::Size container_size(400, 300); 528 base::FilePath url = 529 test_data_dir().AppendASCII("pixel_canvas2d.html"); 530 RunPixelTest(container_size, url, ref_img_revision_update, 531 ref_pixels, ref_pixel_count); 532 } 533 534 class GpuPixelTestBrowserPlugin : public GpuPixelBrowserTest { 535 public: 536 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 537 GpuPixelBrowserTest::SetUpCommandLine(command_line); 538 command_line->AppendSwitch(switches::kEnableBrowserPluginForAllViewTypes); 539 } 540 }; 541 542 // TODO(fsamuel): re-enable as MANUAL_BrowserPluginBlueBox: crbug.com/166165 543 IN_PROC_BROWSER_TEST_F(GpuPixelTestBrowserPlugin, 544 DISABLED_BrowserPluginBlueBox) { 545 // If test baseline needs to be updated after a given revision, update the 546 // following number. If no revision requirement, then 0. 547 const int64 ref_img_revision_update = 209445; 548 549 const ReferencePixel ref_pixels[] = { 550 // x, y, r, g, b 551 {70, 50, 0, 0, 255}, 552 {150, 50, 0, 0, 0}, 553 {70, 90, 0, 0, 255}, 554 {150, 90, 0, 0, 255}, 555 {70, 125, 0, 0, 255}, 556 {150, 125, 0, 0, 0} 557 }; 558 const size_t ref_pixel_count = sizeof(ref_pixels) / sizeof(ReferencePixel); 559 560 gfx::Size container_size(400, 300); 561 base::FilePath url = 562 test_data_dir().AppendASCII("pixel_browser_plugin.html"); 563 RunPixelTest(container_size, url, ref_img_revision_update, 564 ref_pixels, ref_pixel_count); 565 } 566 567 } // namespace content 568 569