// 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 "driver/driver.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "lexer/tokenized_buffer_test_helpers.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/YAMLParser.h" namespace Carbon { namespace { using Carbon::Testing::IsKeyValueScalars; using ::testing::Eq; using ::testing::HasSubstr; using ::testing::NotNull; using ::testing::StrEq; /// 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 "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(); // Parse the output into a YAML stream. This will print errors to stderr and // is the most stable view of the textual dumping API. llvm::SourceMgr sm; llvm::yaml::Stream yaml_stream(tokenized_text, sm); auto yaml_it = yaml_stream.begin(); auto* root_node = llvm::dyn_cast(yaml_it->getRoot()); ASSERT_THAT(root_node, NotNull()); // Walk the top-level mapping of tokens, dig out the sub-mapping of data for // each taken, and then verify those entries. auto mapping_it = llvm::cast(root_node)->begin(); auto* token_node = llvm::dyn_cast(&*mapping_it); ASSERT_THAT(token_node, NotNull()); auto* token_key_node = llvm::dyn_cast(token_node->getKey()); ASSERT_THAT(token_key_node, NotNull()); EXPECT_THAT(token_key_node->getRawValue(), StrEq("token")); auto* token_value_node = llvm::dyn_cast(token_node->getValue()); ASSERT_THAT(token_value_node, NotNull()); auto token_it = token_value_node->begin(); EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "0")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "Identifier")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "1")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "1")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "1")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", "Hello")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("identifier", "0")); EXPECT_THAT(++token_it, Eq(token_value_node->end())); ++mapping_it; token_node = llvm::dyn_cast(&*mapping_it); ASSERT_THAT(token_node, NotNull()); token_key_node = llvm::dyn_cast(token_node->getKey()); ASSERT_THAT(token_key_node, NotNull()); EXPECT_THAT(token_key_node->getRawValue(), StrEq("token")); token_value_node = llvm::dyn_cast(token_node->getValue()); ASSERT_THAT(token_value_node, NotNull()); token_it = token_value_node->begin(); EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "1")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "Identifier")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "1")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "7")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "1")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", "World")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("identifier", "1")); EXPECT_THAT(++token_it, Eq(token_value_node->end())); ++mapping_it; token_node = llvm::dyn_cast(&*mapping_it); ASSERT_THAT(token_node, NotNull()); token_key_node = llvm::dyn_cast(token_node->getKey()); ASSERT_THAT(token_key_node, NotNull()); EXPECT_THAT(token_key_node->getRawValue(), StrEq("token")); token_value_node = llvm::dyn_cast(token_node->getValue()); ASSERT_THAT(token_value_node, NotNull()); token_it = token_value_node->begin(); EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "2")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "EndOfFile")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "1")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "12")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "1")); ++token_it; EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", "")); EXPECT_THAT(++token_it, Eq(token_value_node->end())); ASSERT_THAT(++mapping_it, Eq(root_node->end())); ASSERT_THAT(++yaml_it, Eq(yaml_stream.end())); // 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")); } } // namespace } // namespace Carbon