blob: ec42a70f91a4a4b7bf94d383a2cade1953478711 [file] [log] [blame]
// Copyright 2022 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 "src/developer/debug/e2e_tests/script_test.h"
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <string>
#include <string_view>
#include <gtest/gtest.h>
#include "src/developer/debug/e2e_tests/fuzzy_matcher.h"
#include "src/developer/debug/shared/string_util.h"
#include "src/developer/debug/zxdb/common/host_util.h"
#include "src/lib/fxl/strings/trim.h"
namespace zxdb {
namespace {
constexpr uint64_t kDefaultTimeout = 3; // in seconds
constexpr std::string_view kBuildType = ZXDB_E2E_TESTS_BUILD_TYPE;
} // namespace
void ScriptTest::TestBody() {
script_file_ = std::ifstream(script_path_);
ASSERT_TRUE(script_file_) << "Fail to open " << script_path_;
// Process directives first.
uint64_t timeout = kDefaultTimeout;
std::string line;
while (std::getline(script_file_, line)) {
line_number_++;
if (line.empty())
continue;
if (debug::StringStartsWith(line, "##")) {
std::string directive = std::string(fxl::TrimString(line.substr(2), " "));
if (debug::StringStartsWith(directive, "require ")) {
std::string requirement = directive.substr(8);
if (kBuildType.find(requirement) == std::string::npos) {
GTEST_SKIP() << "Skipped because of unmet requirement " << requirement;
}
} else if (debug::StringStartsWith(directive, "set timeout ")) {
timeout = std::stoul(directive.substr(12));
} else {
GTEST_FAIL() << "Unknown directive: " << directive;
}
} else if (debug::StringStartsWith(line, "#")) {
continue;
} else {
// Put the line back.
script_file_.seekg(-static_cast<int>(line.size() + 1), std::ios::cur);
line_number_--;
break;
}
}
// Adjust timeout when running on bots so we're less likely to flake.
if (std::getenv("BUILDBUCKET_ID")) {
timeout *= 5;
}
std::string error_msg;
loop().PostTimer(FROM_HERE, timeout * 1000, [&]() {
error_msg = "Failed to find pattern \"" + expected_output_pattern_ +
"\" in the output:\n"
"============================= BEGIN OUTPUT =============================\n" +
output_for_debug_ +
"============================== END OUTPUT ==============================";
loop().QuitNow();
});
console().output_observers().AddObserver(this);
ProcessUntilNextOutput();
loop().Run();
console().output_observers().RemoveObserver(this);
if (!error_msg.empty()) {
ADD_FAILURE_AT(script_path_.c_str(), line_number_) << error_msg;
}
}
void ScriptTest::OnOutput(const OutputBuffer& output) {
output_for_debug_ += output.AsString();
if (!output_for_debug_.empty() && output_for_debug_.back() != '\n') {
output_for_debug_ += '\n';
}
FuzzyMatcher matcher(output.AsString());
while (matcher.MatchesLine(expected_output_pattern_)) {
ProcessUntilNextOutput();
}
}
void ScriptTest::ProcessUntilNextOutput() {
std::string line;
while (std::getline(script_file_, line)) {
line_number_++;
if (line.empty() || debug::StringStartsWith(line, "#")) {
continue;
}
// Inputs
if (debug::StringStartsWith(line, "[zxdb]")) {
std::string command = std::string(fxl::TrimString(line.substr(6), " "));
// Defer ProcessInputLine because it will trigger OnOutput synchronously.
loop().PostTask(FROM_HERE,
[this, command]() { console().ProcessInputLine(std::string(command)); });
output_for_debug_.clear();
continue;
}
// Expected outputs
expected_output_pattern_ = line;
return;
}
// Done
loop().QuitNow();
}
void ScriptTest::OnTestExited(const std::string& url) {
// Insert a definitive marker for a test component being completed. Scripts that use `run-test`
// will want to depend on this output so we remain listening for test_runner messages until it has
// completely shutdown.
loop().PostTask(FROM_HERE, [this, url]() { console().Output("Test Done: " + url, false); });
}
void ScriptTest::RegisterScriptTests() {
std::filesystem::path test_scripts_dir =
(std::filesystem::path(GetSelfPath()).parent_path() / ZXDB_E2E_TESTS_SCRIPTS_DIR)
.lexically_normal();
for (const auto& entry : std::filesystem::directory_iterator(test_scripts_dir)) {
if (entry.path().extension() == ".script") {
::testing::RegisterTest("ScriptTest", entry.path().stem().c_str(), nullptr, nullptr,
entry.path().c_str(), 0,
[=]() { return new ScriptTest(entry.path()); });
}
}
}
} // namespace zxdb