blob: 740b91db7b5a2a0543827bf3537ff74490a6cbc3 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 <iomanip>
#include <map>
#include <string>
#include <vector>
#include <fuchsia/sys/cpp/fidl.h>
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/policy/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <gtest/gtest.h>
#include <lib/component/cpp/startup_context.h>
#include <lib/fdio/spawn.h>
#include <lib/fit/function.h>
#include <lib/fsl/vmo/vector.h>
#include <src/lib/fxl/logging.h>
#include <src/lib/fxl/strings/string_printf.h>
#include <lib/gtest/real_loop_fixture.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <lib/zx/time.h>
#include <src/lib/files/file.h>
#include <zircon/status.h>
#include "topaz/tests/web_runner_tests/chromium_context.h"
#include "topaz/tests/web_runner_tests/test_server.h"
namespace {
// Max time to wait in failure cases before bailing.
constexpr zx::duration kTimeout = zx::sec(15);
constexpr uint32_t kBlankColor = 0x00000000;
std::map<uint32_t, size_t> Histogram(
const fuchsia::ui::scenic::ScreenshotData& screenshot) {
EXPECT_GT(screenshot.info.width, 0u);
EXPECT_GT(screenshot.info.height, 0u);
std::vector<uint8_t> data;
EXPECT_TRUE(fsl::VectorFromVmo(screenshot.data, &data))
<< "Failed to read screenshot";
std::map<uint32_t, size_t> histogram;
const uint32_t* bitmap = reinterpret_cast<const uint32_t*>(data.data());
const size_t size = screenshot.info.width * screenshot.info.height;
EXPECT_EQ(size * sizeof(uint32_t), data.size());
for (size_t i = 0; i < size; ++i) {
++histogram[bitmap[i]];
}
return histogram;
}
// Responds to a GET request, testing that the request looks as expected.
void MockHttpGetResponse(web_runner_tests::TestServer* server,
const char* resource) {
const std::string expected_prefix =
fxl::StringPrintf("GET /%s HTTP", resource);
// |Read| requires preallocate (see sys/socket.h: read)
std::string buf(expected_prefix.size(), 0);
EXPECT_TRUE(server->Read(&buf));
EXPECT_EQ(expected_prefix, buf);
std::string content;
FXL_CHECK(files::ReadFileToString(fxl::StringPrintf("/pkg/data/%s", resource),
&content));
FXL_CHECK(server->WriteContent(content));
}
// Invokes the input tool for input injection.
// See garnet/bin/ui/input/README.md or `input --help` for usage details.
// Commands used here:
// * tap <x> <y> (scaled out of 1000)
// * text <text>
// TODO(SCN-1262): Expose as a FIDL service.
void Input(std::vector<const char*> args) {
// start with proc name, end with nullptr
args.insert(args.begin(), "input");
args.push_back(nullptr);
zx_handle_t proc;
zx_status_t status = fdio_spawn(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL,
"/bin/input", args.data(), &proc);
FXL_CHECK(status == ZX_OK) << "fdio_spawn: " << zx_status_get_string(status);
status = zx_object_wait_one(proc, ZX_PROCESS_TERMINATED,
(zx::clock::get_monotonic() + kTimeout).get(),
nullptr);
FXL_CHECK(status == ZX_OK)
<< "zx_object_wait_one: " << zx_status_get_string(status);
zx_info_process_t info;
status = zx_object_get_info(proc, ZX_INFO_PROCESS, &info, sizeof(info),
nullptr, nullptr);
FXL_CHECK(status == ZX_OK)
<< "zx_object_get_info: " << zx_status_get_string(status);
FXL_CHECK(info.return_code == 0) << info.return_code;
}
// Base fixture for pixel tests, containing Scenic and presentation setup, and
// screenshot utilities.
class PixelTest : public gtest::RealLoopFixture {
protected:
PixelTest() : context_(component::StartupContext::CreateFromStartupInfo()) {
scenic_ =
context_->ConnectToEnvironmentService<fuchsia::ui::scenic::Scenic>();
scenic_.set_error_handler([](zx_status_t status) {
FAIL() << "Lost connection to Scenic: " << zx_status_get_string(status);
});
// FLK-49
//
// These tests can flake when a screenshot captures a frame from the
// previous test, which can advance the test logic early. This is a
// temporary solution that waits for a blank on setup. Better solutions
// include hermetic Scenic (complicated by CF-605) or refactoring to use
// view state events (probably the best solution; greatly improves
// determinism at the expense of added harness complexity).
FXL_CHECK(WaitForBlank());
}
component::StartupContext* context() { return context_.get(); }
// Gets a view token for presentation by |RootPresenter|. See also
// garnet/examples/ui/hello_base_view
fuchsia::ui::views::ViewToken CreatePresentationViewToken() {
auto [view_token, view_holder_token] = scenic::NewViewTokenPair();
auto presenter =
context_->ConnectToEnvironmentService<fuchsia::ui::policy::Presenter>();
presenter.set_error_handler([](zx_status_t status) {
FAIL() << "presenter: " << zx_status_get_string(status);
});
presenter->PresentView(std::move(view_holder_token), nullptr);
return std::move(view_token);
}
bool ScreenshotUntil(
fit::function<bool(fuchsia::ui::scenic::ScreenshotData, bool)> condition,
zx::duration timeout = kTimeout) {
zx::time start = zx::clock::get_monotonic();
while (zx::clock::get_monotonic() - start <= timeout) {
fuchsia::ui::scenic::ScreenshotData screenshot;
bool ok;
scenic_->TakeScreenshot(
[this, &screenshot, &ok](
fuchsia::ui::scenic::ScreenshotData screenshot_in, bool status) {
ok = status;
screenshot = std::move(screenshot_in);
QuitLoop();
});
if (!RunLoopWithTimeout(timeout) &&
condition(std::move(screenshot), ok)) {
return true;
}
}
return false;
}
// Blank can manifest as invalid screenshots or blackness.
bool WaitForBlank() {
return ScreenshotUntil(
[](fuchsia::ui::scenic::ScreenshotData screenshot, bool status) {
return !status || Histogram(screenshot)[kBlankColor] > 0u;
});
}
void ExpectSolidColor(uint32_t argb) {
std::map<uint32_t, size_t> histogram;
FXL_LOG(INFO) << "Looking for color " << std::hex << argb;
EXPECT_TRUE(ScreenshotUntil(
[argb, &histogram](fuchsia::ui::scenic::ScreenshotData screenshot,
bool status) {
if (!status)
return false;
histogram = Histogram(screenshot);
FXL_LOG(INFO) << histogram[argb] << " px";
return histogram[argb] > 0u;
}));
histogram.erase(argb);
EXPECT_EQ((std::map<uint32_t, size_t>){}, histogram) << "Unexpected colors";
}
void ExpectPrimaryColor(uint32_t color) {
std::multimap<size_t, uint32_t> inverse_histogram;
FXL_LOG(INFO) << "Looking for color " << std::hex << color;
EXPECT_TRUE(ScreenshotUntil(
[color, &inverse_histogram](
fuchsia::ui::scenic::ScreenshotData screenshot, bool status) {
if (!status)
return false;
std::map<uint32_t, size_t> histogram = Histogram(screenshot);
FXL_LOG(INFO) << histogram[color] << " px";
inverse_histogram.clear();
for (const auto entry : histogram) {
inverse_histogram.emplace(entry.second, entry.first);
}
return (--inverse_histogram.end())->second == color;
}))
<< "Primary color: " << std::hex << (--inverse_histogram.end())->second;
}
private:
std::unique_ptr<component::StartupContext> context_;
fuchsia::sys::ComponentControllerPtr runner_ctrl_;
fuchsia::ui::scenic::ScenicPtr scenic_;
};
using WebRunnerPixelTest = PixelTest;
// Loads a static page with a solid color via the component framework and
// verifies that the color is the only color onscreen.
TEST_F(WebRunnerPixelTest, Static) {
static constexpr uint32_t kTargetColor = 0xffff00ff;
web_runner_tests::TestServer server;
FXL_CHECK(server.FindAndBindPort());
// Chromium and the Fuchsia network package loader both send us requests. This
// may go away after MI4-1807; although the race seems to be in Modular, the
// fix may remove the unnecessary net request in component framework.
auto serve = server.ServeAsync([&server] {
while (server.Accept()) {
MockHttpGetResponse(&server, "static.html");
}
});
component::Services services;
fuchsia::sys::ComponentControllerPtr controller;
context()->launcher()->CreateComponent(
{.url =
fxl::StringPrintf("http://localhost:%d/static.html", server.port()),
.directory_request = services.NewRequest()},
controller.NewRequest());
// Present the view.
services.ConnectToService<fuchsia::ui::app::ViewProvider>()->CreateView(
CreatePresentationViewToken().value, nullptr, nullptr);
ExpectSolidColor(kTargetColor);
}
// This fixture uses chromium.web FIDL services to interact with Chromium.
class ChromiumPixelTest : public PixelTest {
protected:
ChromiumPixelTest() : chromium_(context()) {
// And create a view for the frame.
chromium_.frame()->CreateView2(CreatePresentationViewToken().value, nullptr,
nullptr);
}
ChromiumContext* chromium() { return &chromium_; }
private:
ChromiumContext chromium_;
};
// Loads a static page with a solid color via chromium.web interfaces and
// verifies that the color is the only color onscreen.
TEST_F(ChromiumPixelTest, Static) {
static constexpr uint32_t kTargetColor = 0xffff00ff;
web_runner_tests::TestServer server;
FXL_CHECK(server.FindAndBindPort());
auto serve = server.ServeAsync([&server] {
FXL_LOG(INFO) << "Waiting for HTTP request from Chromium";
ASSERT_TRUE(server.Accept())
<< "Did not receive HTTP request from Chromium";
MockHttpGetResponse(&server, "static.html");
});
chromium()->Navigate(
fxl::StringPrintf("http://localhost:%d/static.html", server.port()));
ExpectSolidColor(kTargetColor);
}
// Loads a dynamic page that starts with a Fuchsia background and has a large
// text box, with Javascript to change the background to the color typed in the
// text box. This test verifies the initial color, taps on the text box (top
// quarter of the screen), types a new color, and verifies the changed color.
TEST_F(ChromiumPixelTest, Dynamic) {
static constexpr char kInput[] = "#40e0d0";
static constexpr uint32_t kBeforeColor = 0xffff00ff;
static constexpr uint32_t kAfterColor = 0xff40e0d0;
web_runner_tests::TestServer server;
FXL_CHECK(server.FindAndBindPort());
auto serve = server.ServeAsync([&server] {
ASSERT_TRUE(server.Accept());
MockHttpGetResponse(&server, "dynamic.html");
});
chromium()->Navigate(
fxl::StringPrintf("http://localhost:%d/dynamic.html", server.port()));
ExpectPrimaryColor(kBeforeColor);
Input({"tap", "500", "125"}); // centered in top quarter of screen
Input({"text", kInput});
ExpectPrimaryColor(kAfterColor);
}
} // namespace