#include "ui/base/test/skia_gold_pixel_diff.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/scoped_environment_variable_override.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/test_switches.h"
#include "base/threading/thread.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "ui/base/test/skia_gold_matching_algorithm.h"
#include "ui/gfx/image/image.h"
using ::testing::_;
using ::testing::AllOf;
using ::testing::AnyNumber;
using ::testing::HasSubstr;
using ::testing::Property;
namespace ui {
namespace test {
class SkiaGoldPixelDiffTest : public ::testing::Test {
public:
using MockLaunchProcess =
testing::MockFunction<SkiaGoldPixelDiff::LaunchProcessCallback::RunType>;
SkiaGoldPixelDiffTest() {
auto* cmd_line = base::CommandLine::ForCurrentProcess();
cmd_line->AppendSwitchASCII("git-revision", "test");
CreateTestBitmap();
}
SkiaGoldPixelDiffTest(const SkiaGoldPixelDiffTest&) = delete;
SkiaGoldPixelDiffTest& operator=(const SkiaGoldPixelDiffTest&) = delete;
~SkiaGoldPixelDiffTest() override = default;
SkBitmap GetTestBitmap() { return test_bitmap_; }
void CreateTestBitmap() {
SkImageInfo info =
SkImageInfo::Make(10, 10, SkColorType::kBGRA_8888_SkColorType,
SkAlphaType::kPremul_SkAlphaType);
test_bitmap_.allocPixels(info, 10 * 4);
}
protected:
void SetUp() override {
session_cache_.emplace();
mock_launch_process_.emplace();
auto_reset_custom_launch_process_.emplace(
SkiaGoldPixelDiff::OverrideLaunchProcessForTesting(
base::BindLambdaForTesting(mock_launch_process_->AsStdFunction())));
}
void TearDown() override {
auto_reset_custom_launch_process_.reset();
mock_launch_process_.reset();
session_cache_.reset();
}
MockLaunchProcess& mock_launch_process() {
return mock_launch_process_.value();
}
private:
SkBitmap test_bitmap_;
std::optional<SkiaGoldPixelDiff::ScopedSessionCacheForTesting> session_cache_;
std::optional<MockLaunchProcess> mock_launch_process_;
std::optional<base::AutoReset<SkiaGoldPixelDiff::LaunchProcessCallback>>
auto_reset_custom_launch_process_;
};
TEST_F(SkiaGoldPixelDiffTest, CompareScreenshotBySkBitmap) {
EXPECT_CALL(mock_launch_process(), Call(_)).Times(3);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap());
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, BypassSkiaGoldFunctionality) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
"bypass-skia-gold-functionality");
EXPECT_CALL(mock_launch_process(), Call(_)).Times(0);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap());
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, LuciAuthSwitch) {
auto* cmd_line = base::CommandLine::ForCurrentProcess();
cmd_line->AppendSwitch(switches::kTestLauncherBotMode);
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_CALL(mock_launch_process(),
Call(AllOf(Property(&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL("--luci"))))))
.Times(1);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap());
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, NoLuciAuthSwitch) {
auto* cmd_line = base::CommandLine::ForCurrentProcess();
cmd_line->AppendSwitch("no-luci-auth");
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_CALL(
mock_launch_process(),
Call(AllOf(Property(&base::CommandLine::GetCommandLineString,
Not(HasSubstr(FILE_PATH_LITERAL("--luci")))))))
.Times(3);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap());
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, LocalNoLuciAuth) {
auto* cmd_line = base::CommandLine::ForCurrentProcess();
cmd_line->RemoveSwitch(switches::kTestLauncherBotMode);
base::ScopedEnvironmentVariableOverride env_override(
"CHROMIUM_TEST_LAUNCHER_BOT_MODE");
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_CALL(
mock_launch_process(),
Call(AllOf(Property(&base::CommandLine::GetCommandLineString,
Not(HasSubstr(FILE_PATH_LITERAL("--luci")))))))
.Times(3);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap());
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, FuzzyMatching) {
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_CALL(
mock_launch_process(),
Call(AllOf(
Property(
&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL(
"--add-test-optional-key=image_matching_algorithm:fuzzy"))),
Property(
&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL(
"--add-test-optional-key=fuzzy_max_different_pixels:1"))),
Property(
&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL(
"--add-test-optional-key=fuzzy_pixel_delta_threshold:2"))))))
.Times(1);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
FuzzySkiaGoldMatchingAlgorithm algorithm(1, 2);
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap(), &algorithm);
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, FuzzyMatchingWithIgnoredBorder) {
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_CALL(
mock_launch_process(),
Call(AllOf(
Property(
&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL(
"--add-test-optional-key=image_matching_algorithm:fuzzy"))),
Property(
&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL(
"--add-test-optional-key=fuzzy_max_different_pixels:1"))),
Property(
&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL(
"--add-test-optional-key=fuzzy_pixel_delta_threshold:2"))),
Property(
&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL("--add-test-optional-key=fuzzy_"
"ignored_border_thickness:3"))))))
.Times(1);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
FuzzySkiaGoldMatchingAlgorithm algorithm(1, 2, 3);
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap(), &algorithm);
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, SobelMatching) {
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_CALL(
mock_launch_process(),
Call(AllOf(
Property(
&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL(
"--add-test-optional-key=image_matching_algorithm:sobel"))),
Property(
&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL(
"--add-test-optional-key=fuzzy_max_different_pixels:1"))),
Property(
&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL(
"--add-test-optional-key=fuzzy_pixel_delta_threshold:2"))),
Property(&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL(
"--add-test-optional-key=sobel_edge_threshold:3"))),
Property(
&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL("--add-test-optional-key=fuzzy_"
"ignored_border_thickness:4"))))))
.Times(1);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
SobelSkiaGoldMatchingAlgorithm algorithm(1, 2, 3, 4);
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap(), &algorithm);
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, DefaultCorpus) {
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_CALL(mock_launch_process(),
Call(AllOf(Property(
&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL("--corpus=gtest-pixeltests"))))))
.Times(1);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap());
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, ExplicitCorpus) {
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_CALL(
mock_launch_process(),
Call(AllOf(Property(&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL("--corpus=corpus"))))))
.Times(1);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession("corpus");
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap());
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, DefaultCodeReviewSystem) {
auto* cmd_line = base::CommandLine::ForCurrentProcess();
cmd_line->AppendSwitchASCII("gerrit-issue", "1");
cmd_line->AppendSwitchASCII("gerrit-patchset", "2");
cmd_line->AppendSwitchASCII("buildbucket-id", "3");
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_CALL(
mock_launch_process(),
Call(AllOf(Property(&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL("--crs=gerrit"))))))
.Times(1);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap());
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, ExplicitCodeReviewSystem) {
auto* cmd_line = base::CommandLine::ForCurrentProcess();
cmd_line->AppendSwitchASCII("gerrit-issue", "1");
cmd_line->AppendSwitchASCII("gerrit-patchset", "2");
cmd_line->AppendSwitchASCII("buildbucket-id", "3");
cmd_line->AppendSwitchASCII("code-review-system", "new-crs");
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_CALL(
mock_launch_process(),
Call(AllOf(Property(&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL("--crs=new-crs"))),
Property(&base::CommandLine::GetCommandLineString,
Not(HasSubstr(FILE_PATH_LITERAL("gerrit")))))))
.Times(1);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap());
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, DryRunLocally) {
auto* cmd_line = base::CommandLine::ForCurrentProcess();
cmd_line->RemoveSwitch(switches::kTestLauncherBotMode);
base::ScopedEnvironmentVariableOverride env_override(
"CHROMIUM_TEST_LAUNCHER_BOT_MODE");
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_CALL(mock_launch_process(),
Call(AllOf(Property(&base::CommandLine::GetCommandLineString,
HasSubstr(FILE_PATH_LITERAL("--dryrun"))))))
.Times(1);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap());
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, NotDryRunOnBots) {
auto* cmd_line = base::CommandLine::ForCurrentProcess();
cmd_line->AppendSwitch(switches::kTestLauncherBotMode);
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_CALL(
mock_launch_process(),
Call(AllOf(Property(&base::CommandLine::GetCommandLineString,
Not(HasSubstr(FILE_PATH_LITERAL("--dryrun")))))))
.Times(3);
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap());
EXPECT_TRUE(ret);
}
TEST_F(SkiaGoldPixelDiffTest, ReuseSessionDefaultParameters) {
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
{
auto* instance = SkiaGoldPixelDiff::GetSession("corpus");
auto* instance2 = SkiaGoldPixelDiff::GetSession("corpus");
EXPECT_EQ(instance, instance2);
}
{
auto* instance = SkiaGoldPixelDiff::GetSession();
auto* instance2 = SkiaGoldPixelDiff::GetSession();
EXPECT_EQ(instance, instance2);
}
}
TEST_F(SkiaGoldPixelDiffTest, ReuseSession) {
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
const std::string corpus = "corpus";
const TestEnvironmentMap test_environment = {
{TestEnvironmentKey::kSystemVersion, "value"}};
auto* instance = SkiaGoldPixelDiff::GetSession(corpus, test_environment);
EXPECT_EQ(instance, SkiaGoldPixelDiff::GetSession(corpus, test_environment));
auto* instance_different_corpus = SkiaGoldPixelDiff::GetSession(
"different-corpus-for-testing", test_environment);
EXPECT_NE(instance, instance_different_corpus);
auto* instance_different_map = SkiaGoldPixelDiff::GetSession(
corpus, TestEnvironmentMap{{TestEnvironmentKey::kSystemVersion,
"different-value-for-testing"}});
EXPECT_NE(instance, instance_different_map);
EXPECT_EQ(instance, SkiaGoldPixelDiff::GetSession(corpus, test_environment));
EXPECT_EQ(instance_different_corpus,
SkiaGoldPixelDiff::GetSession("different-corpus-for-testing",
test_environment));
EXPECT_EQ(instance_different_map,
SkiaGoldPixelDiff::GetSession(
corpus, TestEnvironmentMap{{TestEnvironmentKey::kSystemVersion,
"different-value-for-testing"}}));
}
TEST_F(SkiaGoldPixelDiffTest, GetGoldenImageName) {
{
const std::string name =
SkiaGoldPixelDiff::GetGoldenImageName("Prefix", "TestName");
EXPECT_TRUE(name.find("Prefix") != std::string::npos);
EXPECT_TRUE(name.find("TestName") != std::string::npos);
}
{
const std::string name =
SkiaGoldPixelDiff::GetGoldenImageName("Prefix", "TestName", {"Suffix"});
EXPECT_TRUE(name.find("Prefix") != std::string::npos);
EXPECT_TRUE(name.find("TestName") != std::string::npos);
EXPECT_TRUE(name.find("Suffix") != std::string::npos);
}
}
TEST_F(SkiaGoldPixelDiffTest, GetGoldenImageNameParameterizedTest) {
{
const std::string name = SkiaGoldPixelDiff::GetGoldenImageName(
"TestSuiteName", "TestName/ParamValue");
EXPECT_TRUE(name.find("TestSuiteName") != std::string::npos);
EXPECT_TRUE(name.find("TestName") != std::string::npos);
EXPECT_TRUE(name.find("ParamValue") != std::string::npos);
}
{
const std::string name = SkiaGoldPixelDiff::GetGoldenImageName(
"ParamInstantiation/TestSuiteName", "TestName/ParamValue");
EXPECT_TRUE(name.find("ParamInstantiation") != std::string::npos);
EXPECT_TRUE(name.find("TestSuiteName") != std::string::npos);
EXPECT_TRUE(name.find("TestName") != std::string::npos);
EXPECT_TRUE(name.find("ParamValue") != std::string::npos);
}
{
const std::string name = SkiaGoldPixelDiff::GetGoldenImageName(
"ParamInstantiation/TestSuiteName", "TestName/ParamValue", {"Suffix"});
EXPECT_TRUE(name.find("ParamInstantiation") != std::string::npos);
EXPECT_TRUE(name.find("TestSuiteName") != std::string::npos);
EXPECT_TRUE(name.find("TestName") != std::string::npos);
EXPECT_TRUE(name.find("ParamValue") != std::string::npos);
EXPECT_TRUE(name.find("Suffix") != std::string::npos);
}
}
#if DCHECK_IS_ON() && defined(GTEST_HAS_DEATH_TEST)
TEST_F(SkiaGoldPixelDiffTest, MustBeUsedSequentially) {
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
auto* mock_pixel = SkiaGoldPixelDiff::GetSession();
bool ret = mock_pixel->CompareScreenshot("test", GetTestBitmap());
EXPECT_TRUE(ret);
auto thread = base::Thread("NonSequentialThread");
ASSERT_TRUE(thread.StartAndWaitForTesting());
thread.task_runner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
auto* mock_pixel2 = SkiaGoldPixelDiff::GetSession();
EXPECT_EQ(mock_pixel, mock_pixel2);
EXPECT_DCHECK_DEATH_WITH(
mock_pixel2->CompareScreenshot("test", GetTestBitmap()),
"CalledOnValidSequence");
}));
thread.FlushForTesting();
}
TEST_F(SkiaGoldPixelDiffTest, GoldenImageNameCannotHaveIllegalCharacters) {
EXPECT_CALL(mock_launch_process(), Call(_)).Times(AnyNumber());
EXPECT_DCHECK_DEATH_WITH(
SkiaGoldPixelDiff::GetSession()->CompareScreenshot(
"image_name_with space", GetTestBitmap()),
"a golden image name should not contain any space or back slash");
EXPECT_DCHECK_DEATH_WITH(
SkiaGoldPixelDiff::GetSession()->CompareScreenshot(
"image_name_with/slash", GetTestBitmap()),
"a golden image name should not contain any space or back slash");
}
#endif
}
}