blob: f35cdf045d067952828adb9fa0beb32a2e554bdc [file] [log] [blame]
// 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