| // Copyright 2017-2020 The Verible Authors. |
| // |
| // 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 |
| // |
| // http://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. |
| |
| // Unit tests for TokenInfo |
| |
| #include "common/text/token_info.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "gtest/gtest.h" |
| #include "absl/base/macros.h" |
| #include "absl/strings/string_view.h" |
| #include "common/text/constants.h" |
| #include "common/util/range.h" |
| |
| namespace verible { |
| namespace { |
| |
| // Test construction with a token enum and text. |
| TEST(TokenInfoTest, EnumTextConstruction) { |
| constexpr absl::string_view text("string of length 19"); |
| TokenInfo token_info(143, text); |
| EXPECT_EQ(token_info.token_enum(), 143); |
| EXPECT_EQ(token_info.left(text), 0); |
| EXPECT_EQ(token_info.right(text), 19); |
| EXPECT_EQ(token_info.text(), text); |
| } |
| |
| // Test updating text. |
| TEST(TokenInfoTest, AdvanceText) { |
| constexpr absl::string_view text = "This quick brown fox..."; |
| TokenInfo token_info(1, text.substr(0, 0)); |
| EXPECT_TRUE(BoundsEqual(token_info.text(), text.substr(0, 0))); |
| token_info.AdvanceText(3); |
| EXPECT_TRUE(BoundsEqual(token_info.text(), text.substr(0, 3))); |
| EXPECT_EQ(token_info.text(), "Thi"); |
| token_info.AdvanceText(4); |
| EXPECT_TRUE(BoundsEqual(token_info.text(), text.substr(3, 4))); |
| EXPECT_EQ(token_info.text(), "s qu"); |
| } |
| |
| // Test operator ==. |
| TEST(TokenInfoTest, Equality) { |
| constexpr char text[] = "string of length 21"; |
| TokenInfo token_info_1(143, text); |
| TokenInfo token_info_2(143, text); |
| EXPECT_EQ(token_info_1, token_info_2); |
| EXPECT_EQ(token_info_2, token_info_1); |
| } |
| |
| // Test operator == EOF. |
| TEST(TokenInfoTest, EOFEquality) { |
| TokenInfo token_info_0 = TokenInfo::EOFToken(); |
| TokenInfo token_info_1(TK_EOF, "foo"); |
| TokenInfo token_info_2(TK_EOF, "bar"); |
| EXPECT_EQ(token_info_0, token_info_1); |
| EXPECT_EQ(token_info_1, token_info_0); |
| EXPECT_EQ(token_info_0, token_info_2); |
| EXPECT_EQ(token_info_2, token_info_0); |
| EXPECT_EQ(token_info_1, token_info_2); |
| EXPECT_EQ(token_info_2, token_info_1); |
| } |
| |
| TEST(TokenInfoTest, EOFWithBuffer) { |
| constexpr absl::string_view text("string of length 21"); |
| TokenInfo token_info = TokenInfo::EOFToken(text); |
| EXPECT_EQ(token_info.token_enum(), TK_EOF); |
| EXPECT_EQ(token_info.text().begin(), text.end()); |
| EXPECT_EQ(token_info.text().end(), text.end()); |
| } |
| |
| // Test operator !=. |
| TEST(TokenInfoTest, Inequality) { |
| constexpr absl::string_view text("string of length 21"); |
| const std::vector<TokenInfo> token_infos = { |
| TokenInfo(143, text), |
| TokenInfo(43, text), |
| TokenInfo(143, text.substr(0, 1)), // substring length 1 |
| TokenInfo(143, "different string"), |
| }; |
| const auto N = token_infos.size(); |
| for (size_t i = 0; i < N - 1; ++i) { |
| for (size_t j = i + 1; j < N; ++j) { |
| EXPECT_NE(token_infos[i], token_infos[j]); |
| EXPECT_NE(token_infos[j], token_infos[i]); |
| } |
| } |
| } |
| |
| TEST(TokenInfoTest, EquivalentWithoutLocation) { |
| const absl::string_view foo1("foo"), foo2("foo"); |
| TokenInfo token_0(1, foo1); // reference token |
| TokenInfo token_1(1, "bar"); // different text |
| TokenInfo token_2(1, foo2); // different location |
| TokenInfo token_3(2, foo1); // different enum |
| EXPECT_FALSE(token_0.EquivalentWithoutLocation(token_1)); |
| EXPECT_FALSE(token_1.EquivalentWithoutLocation(token_0)); |
| EXPECT_TRUE(token_0.EquivalentWithoutLocation(token_2)); |
| EXPECT_TRUE(token_2.EquivalentWithoutLocation(token_0)); |
| EXPECT_FALSE(token_0.EquivalentWithoutLocation(token_3)); |
| EXPECT_FALSE(token_3.EquivalentWithoutLocation(token_0)); |
| } |
| |
| TEST(TokenInfoTest, EquivalentEOF) { |
| TokenInfo token_0(TK_EOF, "foo"); // reference token |
| TokenInfo token_1(TK_EOF, "bar"); // different text |
| TokenInfo token_2(TK_EOF, "foo"); // different location |
| EXPECT_TRUE(token_0.EquivalentWithoutLocation(token_1)); |
| EXPECT_TRUE(token_1.EquivalentWithoutLocation(token_0)); |
| EXPECT_TRUE(token_0.EquivalentWithoutLocation(token_2)); |
| EXPECT_TRUE(token_2.EquivalentWithoutLocation(token_0)); |
| } |
| |
| TEST(TokenInfoTest, EquivalentBySpaceEmpty) { |
| TokenInfo t0(1, ""); |
| TokenInfo t1(1, ""); |
| EXPECT_TRUE(t0.EquivalentBySpace(t1)); |
| EXPECT_TRUE(t1.EquivalentBySpace(t0)); |
| } |
| |
| TEST(TokenInfoTest, EquivalentBySpaceDiffEnum) { |
| TokenInfo t0(1, ""); |
| TokenInfo t1(2, ""); |
| EXPECT_FALSE(t0.EquivalentBySpace(t1)); |
| EXPECT_FALSE(t1.EquivalentBySpace(t0)); |
| } |
| |
| TEST(TokenInfoTest, EquivalentBySpaceDiffLengthText) { |
| TokenInfo t0(1, "a"); |
| TokenInfo t1(1, "aa"); |
| EXPECT_FALSE(t0.EquivalentBySpace(t1)); |
| EXPECT_FALSE(t1.EquivalentBySpace(t0)); |
| } |
| |
| TEST(TokenInfoTest, EquivalentBySpaceDiffTextEqualLength) { |
| TokenInfo t0(1, "bb"); |
| TokenInfo t1(1, "aa"); |
| EXPECT_TRUE(t0.EquivalentBySpace(t1)); |
| EXPECT_TRUE(t1.EquivalentBySpace(t0)); |
| } |
| |
| TEST(TokenInfoTest, EquivalentBySpaceEOF) { |
| // text is ignored in EOF case |
| TokenInfo t0(TK_EOF, "xyz"); |
| TokenInfo t1(TK_EOF, "12345"); |
| EXPECT_TRUE(t0.EquivalentBySpace(t1)); |
| EXPECT_TRUE(t1.EquivalentBySpace(t0)); |
| } |
| |
| // This test verifies comparison against EOF. |
| TEST(TokenInfoTest, IsEOF) { |
| TokenInfo token_info = TokenInfo::EOFToken(); |
| EXPECT_TRUE(token_info.isEOF()); |
| } |
| |
| TEST(TokenInfoTest, IsEOFAnyString) { |
| TokenInfo token_info(TK_EOF, "does not matter"); |
| EXPECT_TRUE(token_info.isEOF()); |
| } |
| |
| // Test string representation of token_info. |
| TEST(TokenInfoTest, ToStringEOF) { |
| const absl::string_view base; // empty |
| const TokenInfo::Context context(base); |
| TokenInfo token_info(TK_EOF, base); |
| EXPECT_EQ(token_info.ToString(context), "(#0 @0-0: \"\")"); |
| } |
| |
| TEST(TokenInfoTest, ToStringWithBase) { |
| const absl::string_view base("basement cat"); |
| const TokenInfo::Context context(base); |
| TokenInfo token_info(7, base.substr(9, 3)); |
| EXPECT_EQ(token_info.ToString(context), "(#7 @9-12: \"cat\")"); |
| } |
| |
| void TokenTranslator(std::ostream& stream, int e) { |
| switch (e) { |
| case 7: |
| stream << "lucky-seven"; |
| break; |
| default: |
| stream << "???"; |
| } |
| } |
| |
| TEST(TokenInfoTest, ToStringWithBaseAndTranslator) { |
| const absl::string_view base("basement cat"); |
| const TokenInfo::Context context(base, TokenTranslator); |
| TokenInfo token_info(7, base.substr(9, 3)); |
| EXPECT_EQ(token_info.ToString(context), "(#lucky-seven @9-12: \"cat\")"); |
| } |
| |
| TEST(TokenWithContextTest, StreamOutput) { |
| const absl::string_view base("basement cat"); |
| const TokenInfo::Context context(base, TokenTranslator); |
| TokenInfo token_info(7, base.substr(9, 3)); |
| std::ostringstream stream; |
| stream << TokenWithContext{token_info, context}; |
| EXPECT_EQ(stream.str(), "(#lucky-seven @9-12: \"cat\")"); |
| } |
| |
| // RebaseStringView() tests |
| |
| // Test that empty string token rebases correctly. |
| TEST(RebaseStringViewTest, EmptyStringsZeroOffset) { |
| const std::string text = ""; |
| // We want another empty string, but we need to trick too smart compilers |
| // to give us a different memory address. |
| std::string substr = "foo"; |
| substr.resize(0); // Force empty string such as 'text' but memory space |
| ASSERT_NE(text.c_str(), substr.c_str()) << "Mismatch in memory assumption"; |
| |
| EXPECT_FALSE(BoundsEqual(text, substr)); |
| TokenInfo token(0, text); |
| EXPECT_EQ(token.left(text), 0); |
| token.RebaseStringView(substr); |
| EXPECT_EQ(token.left(substr), 0); |
| EXPECT_EQ(token.text(), substr); |
| } |
| |
| // Test that non-empty whole-string copy rebases correctly. |
| TEST(RebaseStringViewTest, IdenticalCopy) { |
| const std::string text = "hello"; |
| const std::string substr = "hello"; // different memory space |
| EXPECT_FALSE(BoundsEqual(text, substr)); |
| TokenInfo token(3, text); |
| EXPECT_EQ(token.left(text), 0); |
| token.RebaseStringView(substr); |
| EXPECT_EQ(token.left(substr), 0); |
| EXPECT_EQ(token.text(), substr); |
| } |
| |
| // Test that substring mismatch between new and old is checked. |
| TEST(RebaseStringViewDeathTest, SubstringMismatch) { |
| const absl::string_view text = "hell0"; |
| const absl::string_view substr = "hello"; |
| TokenInfo token(1, text); |
| EXPECT_EQ(token.left(text), 0); |
| EXPECT_DEATH(token.RebaseStringView(substr), |
| "only valid when the new text referenced matches the old text"); |
| } |
| |
| TEST(RebaseStringViewDeathTest, SubstringMismatch2) { |
| const absl::string_view text = "hello"; |
| const absl::string_view substr = "Hello"; |
| TokenInfo token(1, text); |
| EXPECT_EQ(token.left(text), 0); |
| EXPECT_DEATH(token.RebaseStringView(substr), |
| "only valid when the new text referenced matches the old text"); |
| } |
| |
| // Test that substring in the middle of old string is rebased correctly. |
| TEST(RebaseStringViewTest, NewSubstringNotAtFront) { |
| const absl::string_view text = "hello"; |
| const absl::string_view new_base = "xxxhelloyyy"; |
| TokenInfo token(1, text); |
| token.RebaseStringView(new_base.substr(3, 5)); |
| EXPECT_EQ(token.left(new_base), 3); |
| EXPECT_EQ(token.right(new_base), 8); |
| EXPECT_EQ(token.text(), text); |
| } |
| |
| // Test that substring in the middle of old string is rebased correctly. |
| TEST(RebaseStringViewTest, UsingCharPointer) { |
| const absl::string_view text = "hello"; |
| const absl::string_view new_base = "xxxhelloyyy"; |
| TokenInfo token(1, text); |
| token.RebaseStringView(new_base.begin() + 3); // assume original length |
| EXPECT_EQ(token.left(new_base), 3); |
| EXPECT_EQ(token.right(new_base), 8); |
| EXPECT_EQ(token.text(), text); |
| } |
| |
| // Test integration with substr() function rebases correctly. |
| TEST(RebaseStringViewTest, RelativeToOldBase) { |
| const absl::string_view full_text = "xxxxxxhelloyyyyy"; |
| const absl::string_view substr = full_text.substr(6, 5); |
| EXPECT_EQ(substr, "hello"); |
| TokenInfo token(1, substr); |
| EXPECT_EQ(token.left(full_text), 6); |
| EXPECT_EQ(token.text(), substr); |
| const absl::string_view new_base = "aahellobbb"; |
| token.RebaseStringView(new_base.substr(2, substr.length())); |
| EXPECT_EQ(token.left(new_base), 2); |
| EXPECT_EQ(token.right(new_base), 7); |
| EXPECT_EQ(token.text(), substr); |
| } |
| |
| // Test rebasing into middle of superstring. |
| TEST(RebaseStringViewTest, MiddleOfSuperstring) { |
| const absl::string_view dest_text = "xxxxxxhell0yyyyy"; |
| const absl::string_view src_text = "ccchell0ddd"; |
| const int dest_offset = 6; |
| const absl::string_view src_substr = src_text.substr(3, 5); |
| EXPECT_EQ(src_substr, "hell0"); |
| TokenInfo token(2, src_substr); |
| // src_text[3] lines up with dest_text[6]. |
| token.RebaseStringView(dest_text.substr(dest_offset, src_substr.length())); |
| EXPECT_EQ(token.left(dest_text), dest_offset); |
| EXPECT_EQ(token.text(), src_substr); |
| } |
| |
| // Test rebasing into prefix superstring. |
| TEST(RebaseStringViewTest, PrefixSuperstring) { |
| const absl::string_view dest_text = "xxxhell0yyyyyzzzzzzz"; |
| const absl::string_view src_text = "ccchell0ddd"; |
| const int dest_offset = 3; |
| const absl::string_view src_substr = src_text.substr(3, 5); |
| EXPECT_EQ(src_substr, "hell0"); |
| TokenInfo token(1, src_substr); |
| // src_text[3] lines up with dest_text[3]. |
| token.RebaseStringView(dest_text.substr(dest_offset, src_substr.length())); |
| EXPECT_EQ(token.left(dest_text), dest_offset); |
| EXPECT_EQ(token.text(), src_substr); |
| } |
| |
| // Test that concatenation works on the degenerate case of no-tokens. |
| TEST(TokenInfoConcatenateTest, EmptyTokens) { |
| std::string joined; |
| std::vector<TokenInfo> tokens; |
| TokenInfo::Concatenate(&joined, &tokens); |
| EXPECT_TRUE(joined.empty()); |
| EXPECT_TRUE(tokens.empty()); |
| } |
| |
| // Test that concatenation works on a single token. |
| TEST(TokenInfoConcatenateTest, OneToken) { |
| std::string joined; |
| std::vector<TokenInfo> tokens{ |
| {3, "foo"}, |
| }; |
| TokenInfo::Concatenate(&joined, &tokens); |
| EXPECT_EQ(joined, "foo"); |
| ASSERT_EQ(tokens.size(), 1); |
| EXPECT_EQ(tokens[0].token_enum(), 3); |
| EXPECT_EQ(tokens[0].text(), "foo"); |
| EXPECT_EQ(tokens[0].left(joined), 0); |
| EXPECT_EQ(tokens[0].right(joined), 3); |
| } |
| |
| // Test that concatenation works on multiple tokens. |
| TEST(TokenInfoConcatenateTest, MultipleTokens) { |
| std::string joined; |
| std::vector<TokenInfo> tokens{ |
| {3, "foo"}, |
| {4, " "}, |
| {5, "bar"}, |
| }; |
| TokenInfo::Concatenate(&joined, &tokens); |
| EXPECT_EQ(joined, "foo bar"); |
| EXPECT_EQ(joined.size(), 8); // 3 + 2 + 3 |
| ASSERT_EQ(tokens.size(), 3); |
| |
| EXPECT_EQ(tokens[0].token_enum(), 3); |
| EXPECT_EQ(tokens[0].text(), "foo"); |
| EXPECT_EQ(tokens[0].left(joined), 0); |
| EXPECT_EQ(tokens[0].right(joined), 3); |
| |
| EXPECT_EQ(tokens[1].token_enum(), 4); |
| EXPECT_EQ(tokens[1].text(), " "); |
| EXPECT_EQ(tokens[1].left(joined), 3); |
| EXPECT_EQ(tokens[1].right(joined), 5); |
| |
| EXPECT_EQ(tokens[2].token_enum(), 5); |
| EXPECT_EQ(tokens[2].text(), "bar"); |
| EXPECT_EQ(tokens[2].left(joined), 5); |
| EXPECT_EQ(tokens[2].right(joined), 8); |
| } |
| |
| // Test that concatenation works on multiple tokens, even empty strings. |
| TEST(TokenInfoConcatenateTest, MultipleTokensWithEmptyString) { |
| std::string joined; |
| std::vector<TokenInfo> tokens{ |
| {3, "foo"}, |
| {6, ""}, |
| {5, "barr"}, |
| }; |
| TokenInfo::Concatenate(&joined, &tokens); |
| EXPECT_EQ(joined, "foobarr"); |
| EXPECT_EQ(joined.size(), 7); // 3 + 0 + 4 |
| ASSERT_EQ(tokens.size(), 3); |
| |
| EXPECT_EQ(tokens[0].token_enum(), 3); |
| EXPECT_EQ(tokens[0].text(), "foo"); |
| EXPECT_EQ(tokens[0].left(joined), 0); |
| EXPECT_EQ(tokens[0].right(joined), 3); |
| |
| EXPECT_EQ(tokens[1].token_enum(), 6); |
| EXPECT_EQ(tokens[1].text(), ""); |
| EXPECT_EQ(tokens[1].left(joined), 3); |
| EXPECT_EQ(tokens[1].right(joined), 3); |
| |
| EXPECT_EQ(tokens[2].token_enum(), 5); |
| EXPECT_EQ(tokens[2].text(), "barr"); |
| EXPECT_EQ(tokens[2].left(joined), 3); |
| EXPECT_EQ(tokens[2].right(joined), 7); |
| } |
| |
| } // namespace |
| } // namespace verible |