Files
carbon-lang/toolchain/driver/driver_test.cpp
T
Chandler Carruth 5f67029479 Use upstream GoogleTest and add related test utils. (#876)
This moves over to the vanilla upstream GoogleTest pulled in the more
expected manner with Bazel. It also adds Abseil and Google Benchmark
libraries in the same fashion (there are cross dependencies here).

As part of this, also introduce a dependency check test that can enforce
basic layering of dependencies. For example, this lets us ensure that
non-test Carbon code only depends on LLVM and Clang despite having other
libraries available. There remains some cleanup to improve the way these
dependency tests work, but this at least ensures we don't regress.

I've also provided workarounds to allow both Carbon code and LLVM code
to freely be used with GoogleTest (and other `std::ostream` based
output code). This is done by extending the code in
`//common/ostream.h`. One downside is that it requires opening the
`llvm` namespace and adding an ADL_found overload there. I think on
balance this is still a win and doesn't make me too nervous.

The new version of GoogleTest requires printing more often from matchers
and so I've also added several printing routines to types that
previously didn't require them. Otherwise, most of the updates are just
using the more conventional upstream style of including the headers and
adding `ostream.h` where it is needed.

I did consider moving code over to use `std::ostream` instead of LLVM's
`raw_ostream`, but the advantages of not doing virtual dispatch still
seem significant, and it also seems good to retain access to LLVM's
formatting utilities built around `raw_ostream` given that we can't pull
arbitrary dependencies into Carbon code outside of test code.

All of this was slightly motivated by requests for newer features in
GoogleTest, but much more-so by my desire to have access to Google
Benchmark and Abseil when writing benchmarks. For example, using
Abseil's random number generator seems extremely helpful when generating
inputs for benchmarks. The growing dependencies between these packages
further motivated me to just pull them all in and ensure they worked
well.
2021-11-02 20:14:12 -07:00

240 lines
9.1 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/driver/driver.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/SourceMgr.h"
#include "toolchain/common/yaml_test_helpers.h"
namespace Carbon {
namespace {
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::NotNull;
using ::testing::StrEq;
namespace Yaml = Carbon::Testing::Yaml;
/// A raw_ostream that makes it easy to repeatedly check streamed output.
class RawTestOstream : public llvm::raw_ostream {
std::string buffer;
void write_impl(const char* ptr, size_t size) override {
buffer.append(ptr, ptr + size);
}
[[nodiscard]] auto current_pos() const -> uint64_t override {
return buffer.size();
}
public:
~RawTestOstream() override {
flush();
if (!buffer.empty()) {
ADD_FAILURE() << "Unchecked output:\n" << buffer;
}
}
/// Flushes the stream and returns the contents so far, clearing the stream
/// back to empty.
auto TakeStr() -> std::string {
flush();
std::string result = std::move(buffer);
buffer.clear();
return result;
}
};
TEST(DriverTest, FullCommandErrors) {
RawTestOstream test_output_stream;
RawTestOstream test_error_stream;
Driver driver = Driver(test_output_stream, test_error_stream);
EXPECT_FALSE(driver.RunFullCommand({}));
EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
EXPECT_FALSE(driver.RunFullCommand({"foo"}));
EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
EXPECT_FALSE(driver.RunFullCommand({"foo --bar --baz"}));
EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
}
TEST(DriverTest, Help) {
RawTestOstream test_output_stream;
RawTestOstream test_error_stream;
Driver driver = Driver(test_output_stream, test_error_stream);
EXPECT_TRUE(driver.RunHelpSubcommand({}));
EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
auto help_text = test_output_stream.TakeStr();
// Help text should mention each subcommand.
#define CARBON_SUBCOMMAND(Name, Spelling, ...) \
EXPECT_THAT(help_text, HasSubstr(Spelling));
#include "toolchain/driver/flags.def"
// Check that the subcommand dispatch works.
EXPECT_TRUE(driver.RunFullCommand({"help"}));
EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
EXPECT_THAT(test_output_stream.TakeStr(), StrEq(help_text));
}
TEST(DriverTest, HelpErrors) {
RawTestOstream test_output_stream;
RawTestOstream test_error_stream;
Driver driver = Driver(test_output_stream, test_error_stream);
EXPECT_FALSE(driver.RunHelpSubcommand({"foo"}));
EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
EXPECT_FALSE(driver.RunHelpSubcommand({"help"}));
EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
EXPECT_FALSE(driver.RunHelpSubcommand({"--xyz"}));
EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
}
auto CreateTestFile(llvm::StringRef text) -> std::string {
int fd = -1;
llvm::SmallString<1024> path;
auto ec = llvm::sys::fs::createTemporaryFile("test_file", ".txt", fd, path);
if (ec) {
llvm::report_fatal_error(llvm::Twine("Failed to create temporary file: ") +
ec.message());
}
llvm::raw_fd_ostream s(fd, /*shouldClose=*/true);
s << text;
s.close();
return path.str().str();
}
TEST(DriverTest, DumpTokens) {
RawTestOstream test_output_stream;
RawTestOstream test_error_stream;
Driver driver = Driver(test_output_stream, test_error_stream);
auto test_file_path = CreateTestFile("Hello World");
EXPECT_TRUE(driver.RunDumpTokensSubcommand({test_file_path}));
EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
auto tokenized_text = test_output_stream.TakeStr();
EXPECT_THAT(Yaml::Value::FromText(tokenized_text),
ElementsAre(Yaml::MappingValue{
{"token", Yaml::MappingValue{{"index", "0"},
{"kind", "Identifier"},
{"line", "1"},
{"column", "1"},
{"indent", "1"},
{"spelling", "Hello"},
{"identifier", "0"},
{"has_trailing_space", "true"}}},
{"token", Yaml::MappingValue{{"index", "1"},
{"kind", "Identifier"},
{"line", "1"},
{"column", "7"},
{"indent", "1"},
{"spelling", "World"},
{"identifier", "1"},
{"has_trailing_space", "true"}}},
{"token", Yaml::MappingValue{{"index", "2"},
{"kind", "EndOfFile"},
{"line", "1"},
{"column", "12"},
{"indent", "1"},
{"spelling", ""}}}}));
// Check that the subcommand dispatch works.
EXPECT_TRUE(driver.RunFullCommand({"dump-tokens", test_file_path}));
EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
EXPECT_THAT(test_output_stream.TakeStr(), StrEq(tokenized_text));
}
TEST(DriverTest, DumpTokenErrors) {
RawTestOstream test_output_stream;
RawTestOstream test_error_stream;
Driver driver = Driver(test_output_stream, test_error_stream);
EXPECT_FALSE(driver.RunDumpTokensSubcommand({}));
EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
EXPECT_FALSE(driver.RunDumpTokensSubcommand({"--xyz"}));
EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
EXPECT_FALSE(driver.RunDumpTokensSubcommand({"/not/a/real/file/name"}));
EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
}
TEST(DriverTest, DumpParseTree) {
RawTestOstream test_output_stream;
RawTestOstream test_error_stream;
Driver driver = Driver(test_output_stream, test_error_stream);
auto test_file_path = CreateTestFile("var v: Int = 42;");
EXPECT_TRUE(driver.RunDumpParseTreeSubcommand({test_file_path}));
EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
auto tokenized_text = test_output_stream.TakeStr();
EXPECT_THAT(
Yaml::Value::FromText(tokenized_text),
ElementsAre(Yaml::SequenceValue{
Yaml::MappingValue{
{"node_index", "6"},
{"kind", "VariableDeclaration"},
{"text", "var"},
{"subtree_size", "7"},
{"children",
Yaml::SequenceValue{
Yaml::MappingValue{
{"node_index", "2"},
{"kind", "PatternBinding"},
{"text", ":"},
{"subtree_size", "3"},
{"children",
Yaml::SequenceValue{
Yaml::MappingValue{{"node_index", "0"},
{"kind", "DeclaredName"},
{"text", "v"}},
Yaml::MappingValue{{"node_index", "1"},
{"kind", "NameReference"},
{"text", "Int"}}}}},
Yaml::MappingValue{{"node_index", "4"},
{"kind", "VariableInitializer"},
{"text", "="},
{"subtree_size", "2"},
{"children", //
Yaml::SequenceValue{Yaml::MappingValue{
{"node_index", "3"},
{"kind", "Literal"},
{"text", "42"}}}}},
Yaml::MappingValue{{"node_index", "5"},
{"kind", "DeclarationEnd"},
{"text", ";"}}}}},
Yaml::MappingValue{{"node_index", "7"}, //
{"kind", "FileEnd"},
{"text", ""}}}));
// Check that the subcommand dispatch works.
EXPECT_TRUE(driver.RunFullCommand({"dump-parse-tree", test_file_path}));
EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
EXPECT_THAT(test_output_stream.TakeStr(), StrEq(tokenized_text));
}
} // namespace
} // namespace Carbon