// Copyright 2021 Benjamin Barenblat // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "goldfishterm/terminfo.h" #include #include #include #include #include #include #include #include #include #include #include "third_party/abseil/absl/cleanup/cleanup.h" #include "third_party/abseil/absl/strings/str_cat.h" #include "third_party/abseil/absl/types/optional.h" namespace goldfishterm { namespace { using namespace std::literals::string_literals; using ::testing::ElementsAre; using ::testing::Eq; using ::testing::IsEmpty; using ::testing::Optional; void SetEnv(const char* var, const char* val) { if (setenv(var, val, /*overwrite=*/1)) { throw std::system_error(errno, std::system_category(), "setenv"); } } // Creates a temporary directory for this test. std::string MakeTemporaryDirectory() noexcept { std::string dir = absl::StrCat(testing::TempDir(), "/terminfo_test.", getpid()); std::filesystem::create_directories(dir); return dir; } TEST(EntryParseTest, EmptyFails) { std::istringstream s(""); EXPECT_THROW(TerminfoEntry t(s), std::runtime_error); } TEST(EntryParseTest, BadMagicFails) { std::istringstream s("foobar"); EXPECT_THROW(TerminfoEntry t(s), std::runtime_error); } TEST(EntryParseTest, TooShortHeaderFails) { std::istringstream s("\x1a\x01"); EXPECT_THROW(TerminfoEntry t(s), std::runtime_error); } TEST(EntryParseTest, NegativeNamesLengthFails) { std::istringstream s("\x1a\x01\xff\xff"); EXPECT_THROW(TerminfoEntry t(s), std::runtime_error); } TEST(EntryParseTest, NegativeBooleansLengthFails) { std::istringstream s("\x1a\x01\0\0\xff\xff"s); EXPECT_THROW(TerminfoEntry t(s), std::runtime_error); } TEST(EntryParseTest, NegativeNumbersCountFails) { std::istringstream s("\x1a\x01\0\0\0\0\xff\xff"s); EXPECT_THROW(TerminfoEntry t(s), std::runtime_error); } TEST(EntryParseTest, NegativeStringsOffsetsCountFails) { std::istringstream s("\x1a\x01\0\0\0\0\0\0\xff\xff"s); EXPECT_THROW(TerminfoEntry t(s), std::runtime_error); } TEST(EntryParseTest, NegativeStringsLengthFails) { std::istringstream s("\x1a\x01\0\0\0\0\0\0\0\0\xff\xff"s); EXPECT_THROW(TerminfoEntry t(s), std::runtime_error); } TEST(EntryParseTest, MinimalTerminfo) { std::istringstream s("\x1a\x01\0\0\0\0\0\0\0\0\0\0"s); TerminfoEntry t(s); } TEST(EntryParseTest, NamesNotNullTerminated) { std::istringstream s("\x1a\x01\x04\0\0\0\0\0\0\0\0\0term"s); EXPECT_THROW(TerminfoEntry t(s), std::runtime_error); } TEST(EntryParseTest, Names) { std::istringstream s("\x1a\x01\x0f\0\0\0\0\0\0\0\0\0name|othername\0"s); EXPECT_THAT(TerminfoEntry(s).names(), ElementsAre("name", "othername")); } TEST(EntryParseTest, FirstName) { std::istringstream s("\x1a\x01\x0f\0\0\0\0\0\0\0\0\0name|othername\0"s); EXPECT_EQ(TerminfoEntry(s).name(), "name"); } TEST(EntryParseTest, Booleans) { std::istringstream s("\x1a\x01\x01\0\x04\0\0\0\0\0\0\0\0\x01\x00\x01\x00"s); TerminfoEntry t(s); EXPECT_TRUE(t.get(BooleanCapability::kAutoLeftMargin)); EXPECT_FALSE(t.get(BooleanCapability::kAutoRightMargin)); EXPECT_TRUE(t.get(BooleanCapability::kNoEscCtlc)); EXPECT_FALSE(t.get(BooleanCapability::kCeolStandoutGlitch)); } TEST(EntryParseTest, BooleanNegativeOneIsFalse) { std::istringstream s("\x1a\x01\x01\0\x01\0\0\0\0\0\0\0\0\xff"s); EXPECT_FALSE(TerminfoEntry(s).get(BooleanCapability::kAutoLeftMargin)); } TEST(EntryParseTest, BooleanNegativeTwoIsFalse) { std::istringstream s("\x1a\x01\x01\0\x01\0\0\0\0\0\0\0\0\xfe"s); EXPECT_FALSE(TerminfoEntry(s).get(BooleanCapability::kAutoLeftMargin)); } TEST(EntryParseTest, Numbers) { std::istringstream s("\x1a\x01\x01\0\0\0\x02\0\0\0\0\0\0\0\x01\x02\x03\x04"s); TerminfoEntry t(s); EXPECT_EQ(t.get(NumericCapability::kColumns), 0x0201); EXPECT_EQ(t.get(NumericCapability::kInitTabs), 0x0403); } TEST(EntryParseTest, PaddingBeforeNumbersForAlignment) { std::istringstream s("\x1a\x01\x01\0\0\0\x02\0\0\0\0\0\0\x01\x02\x03\x04"s); EXPECT_THROW(TerminfoEntry t(s), std::runtime_error); } TEST(EntryParseTest, NumberNegativeOneOkay) { std::istringstream s("\x1a\x01\x01\0\0\0\x01\0\0\0\0\0\0\0\xff\xff"s); TerminfoEntry t(s); } TEST(EntryParseTest, NumberNegativeTwoOkay) { std::istringstream s("\x1a\x01\x01\0\0\0\x01\0\0\0\0\0\0\0\xfe\xff"s); TerminfoEntry t(s); } TEST(EntryParseTest, NumberNegativeThreeFails) { std::istringstream s("\x1a\x01\x01\0\0\0\x01\0\0\0\0\0\0\0\xfd\xff"s); EXPECT_THROW(TerminfoEntry t(s), std::runtime_error); } TEST(EntryParseTest, ExtendedNumberFormat) { std::istringstream s("\x1e\x02\x01\0\0\0\x01\0\0\0\0\0\0\0\x01\x02\x03\x04"s); EXPECT_EQ(TerminfoEntry(s).get(NumericCapability::kColumns), 0x04030201); } TEST(EntryParseTest, StringsWithoutOffsets) { std::istringstream s("\x1a\x01\x01\0\0\0\0\0\0\0\x08\0\0\0foo\0bar\0"s); TerminfoEntry t(s); EXPECT_EQ(t.get(StringCapability::kBackTab), absl::nullopt); EXPECT_EQ(t.get(StringCapability::kBell), absl::nullopt); } TEST(EntryParseTest, ZeroOffsets) { std::istringstream s("\x1a\x01\x01\0\0\0\0\0\x02\0\0\0\0\0\0\0\0\0"s); TerminfoEntry t(s); EXPECT_THAT(t.get(StringCapability::kBackTab), Optional(IsEmpty())); EXPECT_THAT(t.get(StringCapability::kBell), Optional(IsEmpty())); } TEST(EntryParseTest, NegativeOneOffsetOkay) { std::istringstream s("\x1a\x01\x01\0\0\0\0\0\x01\0\0\0\0\0\xff\xff"s); EXPECT_EQ(TerminfoEntry(s).get(StringCapability::kBackTab), absl::nullopt); } TEST(EntryParseTest, NegativeTwoOffsetOkay) { std::istringstream s("\x1a\x01\x01\0\0\0\0\0\x01\0\0\0\0\0\xfe\xff"s); EXPECT_EQ(TerminfoEntry(s).get(StringCapability::kBackTab), absl::nullopt); } TEST(EntryParseTest, NegativeThreeOffsetFails) { std::istringstream s("\x1a\x01\x01\0\0\0\0\0\x01\0\0\0\0\0\xfd\xff"s); EXPECT_THROW(TerminfoEntry t(s), std::runtime_error); } TEST(EntryParseTest, Strings) { std::istringstream s( "\x1a\x01\x01\0\0\0\0\0\x02\0\x08\0\0\0\x04\0\0\0foo\0bar\0"s); TerminfoEntry t(s); EXPECT_THAT(t.get(StringCapability::kBackTab), Optional(Eq("bar"))); EXPECT_THAT(t.get(StringCapability::kBell), Optional(Eq("foo"))); } TEST(SystemTerminfoTest, NoSuchTerminal) { std::string tmp = MakeTemporaryDirectory(); absl::Cleanup remove_tmp = [&] { std::filesystem::remove_all(tmp); }; SetEnv("TERMINFO", tmp.c_str()); EXPECT_THROW(TerminfoEntry::FromSystemDatabase("vt100"), std::runtime_error); } TEST(SystemTerminfoTest, LooksUpAdm3a) { std::string tmp = MakeTemporaryDirectory(); absl::Cleanup remove_tmp = [&] { std::filesystem::remove_all(tmp); }; SetEnv("TERMINFO", tmp.c_str()); std::filesystem::create_directories(absl::StrCat(tmp, "/n")); std::string binary_terminfo = "\x1a\x01\x11\0\0\0\0\0\0\0\0\0notarealterminal\0"s; std::ofstream(std::string(absl::StrCat(tmp, "/n/notarealterminal"))) .write(binary_terminfo.data(), binary_terminfo.size()); EXPECT_EQ(TerminfoEntry::FromSystemDatabase("notarealterminal").name(), "notarealterminal"); } } // namespace } // namespace goldfishterm