blob: 0027d5df655c3d4b5ac14ddd0f05fd46d3c2e59d [file]
// Copyright 2013 The Flutter 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 "impeller/playground/playground_test.h"
#ifndef FML_OS_WIN
#include <wordexp.h>
#endif
#include "flutter/fml/file.h"
#include "flutter/fml/time/time_point.h"
#include "impeller/base/timing.h"
#include "impeller/base/validation.h"
#include "impeller/playground/playground_impl.h"
#include "impeller/testing/golden_digest_manager.h"
namespace impeller {
PlaygroundTest::PlaygroundTest()
: Playground(GetParam(),
PlaygroundSwitches{flutter::testing::GetArgsForProcess()}) {
ImpellerValidationErrorsSetCallback(
[](const char* message, const char* file, int line) -> bool {
// GTEST_MESSAGE_AT_ can only be used in a function that returns void.
// Hence the goofy lambda. The failure message and location will still
// be correct however.
//
// https://google.github.io/googletest/advanced.html#assertion-placement
[message, file, line]() -> void {
GTEST_MESSAGE_AT_(file, line, "Impeller Validation Error",
::testing::TestPartResult::kFatalFailure)
<< message;
}();
return true;
});
}
PlaygroundTest::~PlaygroundTest() {
ImpellerValidationErrorsSetCallback(nullptr);
}
void PlaygroundTest::SetUp() {
if (!Playground::SupportsBackend(GetParam())) {
GTEST_SKIP() << "Playground doesn't support this backend type.";
return;
}
if (!Playground::ShouldOpenNewPlaygrounds()) {
GTEST_SKIP() << "Skipping due to user action.";
return;
}
SetEnableWriteGolden(IsGoldenTestSuite());
}
void PlaygroundTest::TearDown() {
Playground::TearDownContextData();
}
namespace {
class PlaygroundTestEnvironment : public ::testing::Environment {
public:
static std::optional<std::string> ValidateGoldenDirectory(
const std::string& dir) {
#ifdef FML_OS_WIN
return dir;
#else // FML_OS_WIN
wordexp_t wordexp_result;
int code = wordexp(dir.c_str(), &wordexp_result, 0);
FML_CHECK(code == 0) << "Could not parse golden output directory: " << dir;
FML_CHECK(wordexp_result.we_wordc == 1u)
<< "Exactly one directory must be specified for Golden image output: "
<< dir;
std::optional<std::string> working_dir = wordexp_result.we_wordv[0];
wordfree(&wordexp_result);
FML_CHECK(working_dir) << "Unrecognized golden output directory: " << dir;
fml::UniqueFD directory = fml::OpenDirectory(
working_dir->c_str(), false, fml::FilePermission::kReadWrite);
FML_CHECK(fml::IsDirectory(directory))
<< "Golden output directory must be a directory with read/write"
<< " permissions: " << dir;
return working_dir;
#endif // FML_OS_WIN
}
void SetUp() override {
const fml::CommandLine& args = ::flutter::testing::GetArgsForProcess();
std::string golden_output_dir;
if (args.GetOptionValue("golden_output_dir", &golden_output_dir)) {
const std::optional<std::string> validated_dir =
ValidateGoldenDirectory(golden_output_dir);
if (validated_dir) {
golden_manager_.emplace(*validated_dir);
} else {
FML_CHECK(validated_dir)
<< "Did not recognize golden output directory: "
<< golden_output_dir;
}
}
}
void TearDown() override {
if (golden_manager_) {
if (::testing::UnitTest::GetInstance()->Passed()) {
golden_manager_->Write();
} else {
FML_LOG(ERROR)
<< ::testing::UnitTest::GetInstance()->failed_test_count()
<< " tests failed, golden digest will not be written";
golden_manager_->ClearDigestData();
}
golden_manager_.reset();
}
PlaygroundImpl::OnTearDownTestEnvironment();
Playground::OnTearDownTestEnvironment();
}
static testing::GoldenDigestManager* GetGoldenDigestManager() {
return golden_manager_ ? &golden_manager_.value() : nullptr;
}
private:
static std::optional<testing::GoldenDigestManager> golden_manager_;
};
std::optional<testing::GoldenDigestManager>
PlaygroundTestEnvironment::golden_manager_;
} // namespace
// Change these declarations to #defines to enable swiftshader or metal
// validation.
#undef APPLY_METAL_VALIDATION
#undef ENABLE_VK_SWIFTSHADER
void PlaygroundTest::SetupTestEnvironment() {
#ifdef ENABLE_VK_SWIFTSHADER
// Make sure environment is set up for VK swiftshader
std::filesystem::path testing_assets_path =
flutter::testing::GetTestingAssetsPath();
std::filesystem::path target_path = testing_assets_path.parent_path()
.parent_path()
.parent_path()
.parent_path();
std::filesystem::path icd_path = target_path / "vk_swiftshader_icd.json";
setenv("VK_ICD_FILENAMES", icd_path.c_str(), 1);
#endif
#ifdef APPLY_METAL_VALIDATION
// https://developer.apple.com/documentation/metal/diagnosing_metal_programming_issues_early?language=objc
// Enables all shader validation tests.
setenv("MTL_SHADER_VALIDATION", "1", true);
// Validates accesses to device and constant memory.
setenv("MTL_SHADER_VALIDATION_GLOBAL_MEMORY", "1", true);
// Validates accesses to threadgroup memory.
setenv("MTL_SHADER_VALIDATION_THREADGROUP_MEMORY", "1", true);
// Validates that texture references are not nil.
setenv("MTL_SHADER_VALIDATION_TEXTURE_USAGE", "1", true);
// Enables metal validation.
setenv("METAL_DEBUG_ERROR_MODE", "0", true);
// Enables metal validation.
setenv("METAL_DEVICE_WRAPPER_TYPE", "1", true);
#endif
::testing::AddGlobalTestEnvironment(new PlaygroundTestEnvironment());
}
impeller::testing::GoldenDigestManager* PlaygroundTest::GetGoldenDigestManager()
const {
return PlaygroundTestEnvironment::GetGoldenDigestManager();
}
bool PlaygroundTest::IsGoldenTestSuite() const {
return false;
}
PlaygroundBackend PlaygroundTest::GetBackend() const {
return GetParam();
}
// |Playground|
std::unique_ptr<fml::Mapping> PlaygroundTest::OpenAssetAsMapping(
std::string asset_name) const {
return flutter::testing::OpenFixtureAsMapping(asset_name);
}
absl::StatusOr<RuntimeStage::Map> PlaygroundTest::OpenAssetAsRuntimeStage(
const char* asset_name) const {
const std::shared_ptr<fml::Mapping> fixture =
flutter::testing::OpenFixtureAsMapping(asset_name);
if (!fixture || fixture->GetSize() == 0) {
return absl::NotFoundError("Asset not found or empty.");
}
return RuntimeStage::DecodeRuntimeStages(fixture);
}
// |Playground|
std::string PlaygroundTest::GetWindowTitle() const {
std::stringstream stream;
stream << "Impeller Playground for '"
<< flutter::testing::GetCurrentTestName() << "' ";
switch (GetBackend()) {
case PlaygroundBackend::kMetal:
case PlaygroundBackend::kMetalSDF:
break;
case PlaygroundBackend::kOpenGLES:
case PlaygroundBackend::kOpenGLESSDF:
if (GetSwitches().use_angle) {
stream << " (Angle) ";
}
break;
case PlaygroundBackend::kVulkan:
if (GetSwitches().use_swiftshader) {
stream << " (SwiftShader) ";
}
break;
}
stream << " (Press ESC to quit)";
return stream.str();
}
// |Playground|
bool PlaygroundTest::ShouldKeepRendering() const {
if (!GetSwitches().timeout.has_value()) {
return true;
}
if (SecondsF{GetSecondsElapsed()} > GetSwitches().timeout.value()) {
return false;
}
return true;
}
} // namespace impeller