blob: 3c6ed5a59bdaf6c538f3d058f99fbc52ee8997ce [file] [log] [blame]
// Copyright 2018 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 <fuchsia/inspect/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <lib/async/default.h>
#include <lib/fdio/spawn.h>
#include <lib/fdio/util.h>
#include "gmock/gmock.h"
#include "lib/component/cpp/environment_services_helper.h"
#include "lib/component/cpp/testing/test_util.h"
#include "lib/component/cpp/testing/test_with_environment.h"
#include "lib/fxl/files/file.h"
#include "lib/fxl/files/glob.h"
#include "lib/fxl/files/path.h"
#include "lib/fxl/strings/split_string.h"
#include "lib/fxl/strings/string_printf.h"
#include "lib/fxl/strings/substitute.h"
namespace {
constexpr char kGoldenPath[] = "/pkg/data/iquery_goldens";
using component::testing::EnclosingEnvironment;
using ::testing::StartsWith;
class IqueryGoldenTest : public component::testing::TestWithEnvironment,
public testing::WithParamInterface<std::string> {
protected:
IqueryGoldenTest() {
// Create a new enclosing environment and create the example component in
// it.
environment_ = CreateNewEnclosingEnvironment("test", CreateServices());
WaitForEnclosingEnvToStart(environment_.get());
fuchsia::sys::LaunchInfo launch_info;
launch_info.url =
"fuchsia-pkg://fuchsia.com/iquery_golden_test#meta/"
"iquery_example_component.cmx";
launch_info.arguments.push_back("--rows=5");
launch_info.arguments.push_back("--columns=3");
controller_ = environment_->CreateComponent(std::move(launch_info));
// Wait until the component's output directory shows up, and save the path
// to it.
bool ready = false;
controller_.events().OnDirectoryReady = [&ready] { ready = true; };
RunLoopWithTimeoutOrUntil([&ready] { return ready; }, zx::sec(10));
SetUpHubPath();
}
void SetUpHubPath() {
auto glob = GetGlob("/hub/r/test/*/c/iquery_example_component.cmx/*/out");
ASSERT_EQ(1u, glob.size());
hub_directory_path_ = glob[0];
}
std::vector<std::string> GetGlob(const std::string& path) {
files::Glob glob(path);
return std::vector<std::string>{glob.begin(), glob.end()};
}
// Format the output with visible delimiters so we can easily copy and paste
// to update goldens.
std::string CopyableOutput(const std::string& output) {
return fxl::Substitute(
"\n======= COPYABLE OUTPUT =======\n$0\n======= END COPYABLE OUTPUT "
"=======\n",
output);
}
void RunTestCase(const std::string& filepath) {
std::string golden_file;
ASSERT_TRUE(files::ReadFileToString(filepath, &golden_file))
<< "Failed to open " << filepath;
auto golden_lines = fxl::SplitStringCopy(
golden_file, "\n", fxl::kKeepWhitespace, fxl::kSplitWantAll);
ASSERT_GE(golden_lines.size(), 1u);
std::string command_line;
auto golden_it = golden_lines.begin();
int line = 1;
// Skip leading comment lines.
while (golden_it->find("#") == 0) {
++golden_it;
++line;
}
// Get the command line and put iterator to the first comparison line.
command_line = *golden_it++;
++line;
ASSERT_THAT(command_line, StartsWith("iquery "))
<< "We only support testing iquery goldens right now.";
// Create a temporary output file, and prepare it to become STDOUT of the
// new process. Use the same stderr and stdin as this process so error
// output goes to the terminal.
std::FILE* outf = std::tmpfile();
int out_fd = fileno(outf);
fdio_spawn_action_t actions[] = {
{.action = FDIO_SPAWN_ACTION_CLONE_FD,
.fd = {.local_fd = STDIN_FILENO, .target_fd = STDIN_FILENO}},
{.action = FDIO_SPAWN_ACTION_CLONE_FD,
.fd = {.local_fd = dup(out_fd), .target_fd = STDOUT_FILENO}},
{.action = FDIO_SPAWN_ACTION_CLONE_FD,
.fd = {.local_fd = STDERR_FILENO, .target_fd = STDERR_FILENO}},
};
// Run:
// /boot/bin/sh -c "cd <hub>; /system/bin/iquery <args>"
command_line = fxl::Substitute("cd $0; /bin/$1", hub_directory_path_,
command_line);
const char* argv[] = {"sh", "-c", command_line.c_str(), nullptr};
zx_handle_t proc = ZX_HANDLE_INVALID;
auto status =
fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, "/boot/bin/sh",
argv, NULL, 2, actions, &proc, nullptr);
ASSERT_EQ(status, ZX_OK);
zx_signals_t observed{};
zx_object_wait_one(proc, ZX_PROCESS_TERMINATED,
zx::deadline_after(zx::sec(10)).get(), &observed);
ASSERT_TRUE(observed & ZX_PROCESS_TERMINATED);
std::string output;
ASSERT_TRUE(files::ReadFileDescriptorToString(out_fd, &output));
auto output_lines = fxl::SplitStringCopy(output, "\n", fxl::kKeepWhitespace,
fxl::kSplitWantAll);
// Compare golden with output line by line.
auto output_it = output_lines.begin();
while (golden_it != golden_lines.end() && output_it != output_lines.end()) {
ASSERT_EQ(std::string(output_it->data()), std::string(golden_it->data()))
<< CopyableOutput(output)
<< fxl::StringPrintf(
"%s:%d First difference:\nINPUT : %s\nGOLDEN: %s",
filepath.c_str(), line, output_it->c_str(),
golden_it->c_str());
++output_it;
++golden_it;
++line;
}
// Make sure both files are at the end.
// Allow for the golden file to end with a blank line.
ASSERT_TRUE(golden_it == golden_lines.end() ||
*golden_it == "" && ++golden_it == golden_lines.end())
<< CopyableOutput(output)
<< "Golden file had extra lines starting at line " << line << "\n"
<< *golden_it;
ASSERT_EQ(output_it, output_lines.end())
<< CopyableOutput(output) << "Output had extra lines starting at line "
<< line << "\n"
<< *output_it;
}
private:
std::string hub_directory_path_;
std::unique_ptr<EnclosingEnvironment> environment_;
fuchsia::sys::ComponentControllerPtr controller_;
};
std::vector<std::string> GetGoldenFiles() {
std::vector<std::string> ret;
for (const auto& filename :
files::Glob(fxl::Substitute("$0/*", kGoldenPath))) {
ret.push_back(filename);
}
return ret;
}
TEST_P(IqueryGoldenTest, MatchesGolden) { RunTestCase(GetParam()); }
// Nicely format a parameter name (file path) as a camel-case name. Stripping
// all non-alphanumeric characters, path prefix, and extension.
// Example: /data/pkg/my-file-name10.txt -> "MyFileName10".
std::string OutputTestName(const ::testing::TestParamInfo<std::string>& info) {
auto param = files::GetBaseName(info.param);
std::string out;
bool cap = true;
for (char c : param) {
if (c == '.') {
break;
}
if (!isalnum(c)) {
cap = true;
} else {
if (cap) {
c = toupper(c);
cap = false;
}
out.append(1, c);
}
}
return out;
};
INSTANTIATE_TEST_CASE_P(AllFiles, IqueryGoldenTest,
::testing::ValuesIn(GetGoldenFiles()), OutputTestName);
} // namespace