Elim cr-checkbox
[chromium-blink-merge.git] / chrome / browser / chromeos / login / screenshot_testing / screenshot_tester.cc
blob0f7c59151af14366ee51714d8951e6fb4acaff92
1 // Copyright 2014 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 "chrome/browser/chromeos/login/screenshot_testing/screenshot_tester.h"
7 #include "ash/shell.h"
8 #include "base/command_line.h"
9 #include "base/files/file_util.h"
10 #include "chrome/browser/chromeos/login/screenshot_testing/SkDiffPixelsMetric.h"
11 #include "chrome/browser/chromeos/login/screenshot_testing/SkImageDiffer.h"
12 #include "chrome/browser/chromeos/login/screenshot_testing/SkPMetric.h"
13 #include "chromeos/chromeos_switches.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "third_party/skia/include/core/SkCanvas.h"
17 #include "ui/compositor/compositor_switches.h"
18 #include "ui/gfx/codec/png_codec.h"
19 #include "ui/gfx/image/image.h"
20 #include "ui/snapshot/snapshot.h"
22 namespace {
24 // Sets test mode for screenshot testing, using regular comparison.
25 const char kTestMode[] = "test";
27 // Sets update mode for screenshot testing.
28 const char kUpdateMode[] = "update";
30 // Sets test mode for screenshot testing, using PerceptualDiff as comparison.
31 const char kPdiffTestMode[] = "pdiff-test";
33 } // namespace
35 namespace chromeos {
37 ScreenshotTester::ScreenshotTester()
38 : test_mode_(false), pdiff_enabled_(false), weak_factory_(this) {
41 ScreenshotTester::~ScreenshotTester() {
44 ScreenshotTester::Result::Result() {
47 ScreenshotTester::Result::~Result() {
50 bool ScreenshotTester::TryInitialize() {
51 base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
52 if (!command_line.HasSwitch(switches::kEnableScreenshotTestingWithMode))
53 return false;
55 std::string mode = command_line.GetSwitchValueASCII(
56 switches::kEnableScreenshotTestingWithMode);
57 CHECK(mode == kUpdateMode || mode == kTestMode || mode == kPdiffTestMode)
58 << "Invalid mode for screenshot testing: " << mode;
60 CHECK(command_line.HasSwitch(chromeos::switches::kGoldenScreenshotsDir))
61 << "No directory with golden screenshots specified, use "
62 "--golden-screenshots-dir";
64 golden_screenshots_dir_ =
65 command_line.GetSwitchValuePath(switches::kGoldenScreenshotsDir);
67 if (mode == kTestMode || mode == kPdiffTestMode) {
68 test_mode_ = true;
69 generate_artifacts_ = command_line.HasSwitch(switches::kArtifactsDir);
70 if (generate_artifacts_) {
71 artifacts_dir_ = command_line.GetSwitchValuePath(switches::kArtifactsDir);
74 if (mode == kPdiffTestMode) {
75 pdiff_enabled_ = true;
77 return true;
80 std::string ScreenshotTester::GetImageFileName(
81 const std::string& file_name_prefix,
82 ImageCategories category) {
83 std::string file_name = file_name_prefix + "_";
84 switch (category) {
85 case kGoldenScreenshot: {
86 file_name += "golden_screenshot";
87 break;
89 case kFailedScreenshot: {
90 file_name += "failed_screenshot";
91 break;
93 case kDifferenceImage: {
94 file_name += "difference";
95 break;
98 return file_name + ".png";
101 base::FilePath ScreenshotTester::GetImageFilePath(
102 const std::string& file_name_prefix,
103 ImageCategories category) {
104 std::string file_name = GetImageFileName(file_name_prefix, category);
105 base::FilePath file_path;
106 if (category == kGoldenScreenshot) {
107 file_path = golden_screenshots_dir_.AppendASCII(file_name);
108 } else {
109 file_path = artifacts_dir_.AppendASCII(file_name);
111 return file_path;
114 void ScreenshotTester::Run(const std::string& test_name) {
115 PNGFile current_screenshot = TakeScreenshot();
116 base::FilePath golden_screenshot_path =
117 GetImageFilePath(test_name, kGoldenScreenshot);
118 PNGFile golden_screenshot = LoadGoldenScreenshot(golden_screenshot_path);
119 if (test_mode_) {
120 CHECK(golden_screenshot.get())
121 << "A golden screenshot is required for screenshot testing";
122 VLOG(0) << "Loaded golden screenshot";
123 Result result = CompareScreenshots(golden_screenshot, current_screenshot);
124 VLOG(0) << "Compared";
125 LogComparisonResults(result);
126 if (!result.screenshots_match && generate_artifacts_) {
127 // Saving diff imag
128 if (!pdiff_enabled_) {
129 base::FilePath difference_image_path =
130 GetImageFilePath(test_name, kDifferenceImage);
131 CHECK(SaveImage(difference_image_path, result.diff_image));
134 // Saving failed screenshot
135 base::FilePath failed_screenshot_path =
136 GetImageFilePath(test_name, kFailedScreenshot);
137 CHECK(SaveImage(failed_screenshot_path, current_screenshot));
139 ASSERT_TRUE(result.screenshots_match);
140 } else {
141 bool golden_screenshot_needs_update;
142 if (golden_screenshot.get()) {
143 // There is a golden screenshot, so we need to check it first.
144 Result result = CompareScreenshots(golden_screenshot, current_screenshot);
145 golden_screenshot_needs_update = (!result.screenshots_match);
146 } else {
147 // There is no golden screenshot for this test at all.
148 golden_screenshot_needs_update = true;
150 if (golden_screenshot_needs_update) {
151 bool golden_screenshot_saved =
152 SaveImage(golden_screenshot_path, current_screenshot);
153 CHECK(golden_screenshot_saved);
154 } else {
155 VLOG(0) << "Golden screenshot does not differ from the current one, no "
156 "need to update";
161 void ScreenshotTester::IgnoreArea(const SkIRect& area) {
162 ignored_areas_.push_back(area);
165 void ScreenshotTester::EraseIgnoredAreas(SkBitmap& bitmap) {
166 for (std::vector<SkIRect>::iterator it = ignored_areas_.begin();
167 it != ignored_areas_.end();
168 ++it) {
169 bitmap.eraseArea((*it), SK_ColorWHITE);
173 bool ScreenshotTester::SaveImage(const base::FilePath& image_path,
174 PNGFile png_data) {
175 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
176 if (!png_data.get()) {
177 LOG(ERROR) << "There is no png data";
178 return false;
180 if (!base::CreateDirectory(image_path.DirName())) {
181 LOG(ERROR) << "Can't create directory" << image_path.DirName().value();
182 return false;
184 if (static_cast<size_t>(
185 base::WriteFile(image_path,
186 reinterpret_cast<char*>(&(png_data->data()[0])),
187 png_data->size())) != png_data->size()) {
188 LOG(ERROR) << "Can't save screenshot " << image_path.BaseName().value()
189 << ".";
190 return false;
192 VLOG(0) << "Screenshot " << image_path.BaseName().value() << " saved to "
193 << image_path.DirName().value() << ".";
194 return true;
197 void ScreenshotTester::ReturnScreenshot(const PNGFile& screenshot,
198 PNGFile png_data) {
199 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
200 screenshot->data() = png_data->data();
201 content::BrowserThread::PostTask(
202 content::BrowserThread::UI, FROM_HERE, run_loop_quitter_);
205 ScreenshotTester::PNGFile ScreenshotTester::TakeScreenshot() {
206 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
207 aura::Window* primary_window = ash::Shell::GetPrimaryRootWindow();
208 gfx::Rect rect = primary_window->bounds();
209 PNGFile screenshot = new base::RefCountedBytes;
210 ui::GrabWindowSnapshotAsync(primary_window,
211 rect,
212 content::BrowserThread::GetBlockingPool(),
213 base::Bind(&ScreenshotTester::ReturnScreenshot,
214 weak_factory_.GetWeakPtr(),
215 screenshot));
216 base::RunLoop run_loop;
217 run_loop_quitter_ = run_loop.QuitClosure();
218 run_loop.Run();
219 return screenshot;
222 ScreenshotTester::PNGFile ScreenshotTester::LoadGoldenScreenshot(
223 base::FilePath image_path) {
224 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
226 if (!base::PathExists(image_path)) {
227 LOG(WARNING) << "Can't find a golden screenshot for this test";
228 return 0;
231 int64 golden_screenshot_size;
232 base::GetFileSize(image_path, &golden_screenshot_size);
234 if (golden_screenshot_size == -1) {
235 CHECK(false) << "Can't get golden screenshot size";
237 PNGFile png_data = new base::RefCountedBytes;
238 png_data->data().resize(golden_screenshot_size);
239 base::ReadFile(image_path,
240 reinterpret_cast<char*>(&(png_data->data()[0])),
241 golden_screenshot_size);
243 return png_data;
246 SkBitmap ScreenshotTester::ProcessImageForComparison(const PNGFile& image) {
247 CHECK(image.get());
248 SkBitmap current_bitmap;
249 gfx::PNGCodec::Decode(reinterpret_cast<unsigned char*>(&(image->data()[0])),
250 image->data().size(),
251 &current_bitmap);
252 EraseIgnoredAreas(current_bitmap);
253 return current_bitmap;
256 ScreenshotTester::Result ScreenshotTester::CompareScreenshots(
257 const PNGFile& model,
258 const PNGFile& sample) {
259 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
261 SkBitmap model_bitmap;
262 SkBitmap sample_bitmap;
264 model_bitmap = ProcessImageForComparison(model);
265 sample_bitmap = ProcessImageForComparison(sample);
267 if (pdiff_enabled_) {
268 return CompareScreenshotsPerceptually(model_bitmap, sample_bitmap);
269 } else {
270 return CompareScreenshotsRegularly(model_bitmap, sample_bitmap);
274 void ScreenshotTester::LogSimilarity(double similarity,
275 bool screenshots_match) {
276 VLOG(0) << "Screenshots similarity: " << std::setprecision(5)
277 << similarity * 100 << "\%";
278 if (!pdiff_enabled_ && screenshots_match) {
279 if (similarity == 1) { // 100%
280 VLOG(0) << "Screenshots match perfectly";
281 } else {
282 VLOG(0) << "Screenshots differ slightly, but it is still a match";
287 void ScreenshotTester::LogComparisonResults(
288 const ScreenshotTester::Result& result) {
289 std::string comparison_type = pdiff_enabled_ ? "PerceptualDiff" : "regular";
290 if (result.screenshots_match) {
291 VLOG(0) << "Screenshot testing passed using " << comparison_type
292 << " comparison";
293 } else {
294 LOG(ERROR) << "Screenshot testing failed using " << comparison_type
295 << " comparison";
296 if (!pdiff_enabled_) {
297 VLOG(0) << "(HINT): Result may be false negative. Try using "
298 "PerceptualDiff comparison (use pdiff-test mode instead of "
299 "test)";
302 LogSimilarity(result.similarity, result.screenshots_match);
305 ScreenshotTester::Result ScreenshotTester::CompareScreenshotsRegularly(
306 SkBitmap model_bitmap,
307 SkBitmap sample_bitmap) {
308 SkDifferentPixelsMetric differ;
310 SkImageDiffer::BitmapsToCreate diff_parameters;
311 diff_parameters.rgbDiff = true;
312 SkImageDiffer::Result result;
314 differ.diff(&model_bitmap, &sample_bitmap, diff_parameters, &result);
316 Result testing_result;
318 testing_result.screenshots_match =
319 (result.result >= kPrecision &&
320 result.maxRedDiff <= kMaxAllowedColorDifference &&
321 result.maxGreenDiff <= kMaxAllowedColorDifference &&
322 result.maxBlueDiff <= kMaxAllowedColorDifference);
324 testing_result.similarity = result.result;
326 testing_result.diff_image = new base::RefCountedBytes;
327 testing_result.diff_image->data().resize(result.rgbDiffBitmap.getSize());
328 CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(
329 result.rgbDiffBitmap, false, &testing_result.diff_image->data()))
330 << "Could not encode difference to PNG";
332 return testing_result;
335 ScreenshotTester::Result ScreenshotTester::CompareScreenshotsPerceptually(
336 SkBitmap model_bitmap,
337 SkBitmap sample_bitmap) {
338 SkPMetric differ;
339 SkImageDiffer::BitmapsToCreate diff_parameters;
340 SkImageDiffer::Result result;
342 differ.diff(&model_bitmap, &sample_bitmap, diff_parameters, &result);
344 ScreenshotTester::Result testing_result;
345 testing_result.similarity = result.result;
346 testing_result.screenshots_match =
347 (result.result == SkImageDiffer::RESULT_CORRECT);
349 return testing_result;
352 } // namespace chromeos