blob: d6787757957721985ef9f954545f4c8ae0a743c3 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/test/pixel/skia_gold_pixel_diff.h"
#include "build/build_config.h"
#if defined(OS_WIN)
#include <windows.h>
#endif
#include "third_party/skia/include/core/SkBitmap.h"
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/process/process.h"
#include "base/process/launch.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "chrome/browser/ui/browser_window.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image.h"
#include "ui/snapshot/snapshot.h"
#include "ui/views/view.h"
// This may change to a different bucket in the future.
std::string kSkiaGoldInstance = "chrome-gpu";
#if defined(OS_WIN)
base::string16 kSkiaGoldCtl = L"tools/skia_goldctl/goldctl.exe";
#else
std::string kSkiaGoldCtl = "tools/skia_goldctl/goldctl";
#endif
std::string kBuildRevisionKey = "build-revision";
SkiaGoldPixelDiff::SkiaGoldPixelDiff() = default;
SkiaGoldPixelDiff::~SkiaGoldPixelDiff() = default;
void SkiaGoldPixelDiff::Init(BrowserWindow* browser,
const std::string& screenshot_prefix) {
auto* cmd_line = base::CommandLine::ForCurrentProcess();
ASSERT_TRUE(cmd_line->HasSwitch(kBuildRevisionKey))
<< "Missing switch " << kBuildRevisionKey;
build_revision_ = cmd_line->GetSwitchValueASCII(kBuildRevisionKey);
initialized_ = true;
prefix_ = screenshot_prefix;
browser_ = browser;
base::CreateNewTempDirectory(
FILE_PATH_LITERAL("SkiaGoldTemp"), &working_dir_);
}
// Fill in test environment to the keys_file. The format is json.
// We need the system information to determine whether a new screenshot
// is good or not. All the information that can affect the output of pixels
// should be filled in. Eg: operating system, graphics card, processor
// architecture, screen resolution, etc.
bool FillInTestEnvironment(const base::FilePath& keys_file) {
std::string system = "unknown";
std::string processor = "unknown";
#if defined(OS_WIN)
system = "windows";
SYSTEM_INFO system_info;
GetSystemInfo(&system_info);
switch (system_info.wProcessorArchitecture) {
case PROCESSOR_ARCHITECTURE_INTEL:
processor = "x86";
break;
case PROCESSOR_ARCHITECTURE_AMD64:
processor = "x86_64";
break;
case PROCESSOR_ARCHITECTURE_IA64:
processor = "ia_64";
break;
case PROCESSOR_ARCHITECTURE_ARM:
processor = "arm";
break;
}
#else
LOG(WARNING) << "Other OS not implemented.";
#endif
base::Value::DictStorage ds;
ds["system"] = std::make_unique<base::Value>(system);
ds["processor"] = std::make_unique<base::Value>(processor);
base::Value root(std::move(ds));
std::string content;
base::JSONWriter::Write(root, &content);
base::ScopedAllowBlockingForTesting allow_blocking;
base::File file(keys_file,
base::File::Flags::FLAG_CREATE_ALWAYS | base::File::Flags::FLAG_WRITE);
int ret_code = file.Write(0, content.c_str(), content.size());
file.Close();
if (ret_code <= 0) {
LOG(ERROR) << "Writing the keys file to temporary file failed."
<< "File path:" << keys_file.AsUTF8Unsafe()
<< ". Return code: " << ret_code;
return false;
}
return true;
}
bool SkiaGoldPixelDiff::UploadToSkiaGoldServer(
const base::FilePath& local_file_path,
const std::string& remote_golden_image_name) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath json_temp_file = working_dir_.Append(
FILE_PATH_LITERAL("keys_file.txt"));
FillInTestEnvironment(json_temp_file);
base::FilePath root_path;
base::PathService::Get(base::BasePathKey::DIR_SOURCE_ROOT, &root_path);
base::FilePath goldctl = base::MakeAbsoluteFilePath(
root_path.Append(kSkiaGoldCtl));
base::CommandLine cmd(goldctl);
cmd.AppendArg("imgtest");
cmd.AppendArg("add");
cmd.AppendSwitchASCII("test-name", remote_golden_image_name);
cmd.AppendSwitchASCII("instance", kSkiaGoldInstance);
cmd.AppendSwitchASCII("keys-file", json_temp_file.AsUTF8Unsafe());
cmd.AppendSwitchPath("png-file", local_file_path);
cmd.AppendSwitchASCII("work-dir", working_dir_.AsUTF8Unsafe());
cmd.AppendSwitchASCII("failure-file", "failure.log");
cmd.AppendSwitch("passfail");
cmd.AppendSwitchASCII("commit", build_revision_);
LOG(INFO) << "Skia Gold Commandline: " << cmd.GetCommandLineString();
base::Process sub_process = base::LaunchProcess(
cmd, base::LaunchOptionsForTest());
int exit_code;
sub_process.WaitForExit(&exit_code);
LOG(INFO) << "exit code" <<exit_code;
// TODO(svenzheng): return correct value when this function can
// correctly compare images.
return true;
}
bool SkiaGoldPixelDiff::GrabWindowSnapshotInternal(gfx::NativeWindow window,
const gfx::Rect& snapshot_bounds, gfx::Image* image) {
bool ret = ui::GrabWindowSnapshot(window, snapshot_bounds, image);
if (!ret) {
LOG(WARNING) << "Grab snapshot failed";
return false;
}
return true;
}
bool SkiaGoldPixelDiff::CompareScreenshot(
const std::string& screenshot_name,
const views::View* view) {
if (!initialized_) {
LOG(ERROR) << "Initialize the class before using this method.";
return false;
}
gfx::NativeWindow nw = browser_->GetNativeWindow();
gfx::Rect nw_bounds = nw->GetBoundsInScreen();
gfx::Rect nw_root_bounds = nw->GetBoundsInRootWindow();
gfx::Rect rc = view->GetBoundsInScreen();
rc.Offset(nw_root_bounds.x() - nw_bounds.x(),
nw_root_bounds.y() - nw_bounds.y());
gfx::Image image;
bool ret = GrabWindowSnapshotInternal(nw, rc, &image);
if (!ret) {
return false;
}
return CompareScreenshot(screenshot_name, *image.ToSkBitmap());
}
bool SkiaGoldPixelDiff::CompareScreenshot(
const std::string& screenshot_name,
const SkBitmap& bitmap) {
if (!initialized_) {
LOG(ERROR) << "Initialize the class before using this method.";
return false;
}
std::vector<unsigned char> output;
bool ret = gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, true, &output);
if (!ret) {
LOG(ERROR) << "Encoding SkBitmap to PNG format failed.";
return false;
}
// The golden image name should be unique on GCS. And also the name
// should be valid across all systems.
std::string name = prefix_ + "_" + screenshot_name;
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath temporary_path = working_dir_.Append(
base::FilePath::FromUTF8Unsafe(name+".png"));
base::File file(temporary_path,
base::File::Flags::FLAG_CREATE_ALWAYS | base::File::Flags::FLAG_WRITE);
int ret_code = file.Write(0, (char*)output.data(), output.size());
file.Close();
if (ret_code <= 0) {
LOG(ERROR) << "Writing the PNG image to temporary file failed."
<< "File path:" << temporary_path.AsUTF8Unsafe()
<< ". Return code: " << ret_code;
return false;
}
UploadToSkiaGoldServer(temporary_path, name);
return true;
}