diff options
Diffstat (limited to 'goldfishterm/terminfo_test.cc')
-rw-r--r-- | goldfishterm/terminfo_test.cc | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/goldfishterm/terminfo_test.cc b/goldfishterm/terminfo_test.cc new file mode 100644 index 0000000..c50cb90 --- /dev/null +++ b/goldfishterm/terminfo_test.cc @@ -0,0 +1,228 @@ +// 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 <errno.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <unistd.h> + +#include <filesystem> +#include <fstream> +#include <sstream> +#include <stdexcept> +#include <string> +#include <system_error> + +#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::generic_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 |