2018-08-25 06:31:51 +08:00
|
|
|
//===-------------- ItaniumManglingCanonicalizerTest.cpp ------------------===//
|
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is dual licensed under the MIT and the University of Illinois Open
|
|
|
|
// Source Licenses. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "llvm/Support/ItaniumManglingCanonicalizer.h"
|
2018-08-26 18:03:08 +08:00
|
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
|
|
#include "llvm/ADT/StringRef.h"
|
2018-08-25 06:31:51 +08:00
|
|
|
#include "gtest/gtest.h"
|
|
|
|
|
2018-08-25 07:26:05 +08:00
|
|
|
#include <cstdlib>
|
|
|
|
#include <map>
|
2018-08-26 18:03:08 +08:00
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
using namespace llvm;
|
|
|
|
|
|
|
|
namespace {
|
2018-08-25 07:26:05 +08:00
|
|
|
|
2018-08-25 06:31:51 +08:00
|
|
|
using EquivalenceError = llvm::ItaniumManglingCanonicalizer::EquivalenceError;
|
|
|
|
using FragmentKind = llvm::ItaniumManglingCanonicalizer::FragmentKind;
|
|
|
|
|
|
|
|
struct Equivalence {
|
|
|
|
FragmentKind Kind;
|
|
|
|
llvm::StringRef First;
|
|
|
|
llvm::StringRef Second;
|
|
|
|
};
|
|
|
|
|
|
|
|
// A set of manglings that should all be considered equivalent.
|
2018-08-26 18:03:08 +08:00
|
|
|
using EquivalenceClass = std::vector<llvm::StringRef>;
|
2018-08-25 06:31:51 +08:00
|
|
|
|
|
|
|
struct Testcase {
|
|
|
|
// A set of equivalences to register.
|
2018-08-26 18:03:08 +08:00
|
|
|
std::vector<Equivalence> Equivalences;
|
2018-08-25 06:31:51 +08:00
|
|
|
// A set of distinct equivalence classes created by registering the
|
|
|
|
// equivalences.
|
2018-08-26 18:03:08 +08:00
|
|
|
std::vector<EquivalenceClass> Classes;
|
2018-08-25 06:31:51 +08:00
|
|
|
};
|
|
|
|
|
2018-08-26 18:03:08 +08:00
|
|
|
// A function that returns a set of test cases.
|
|
|
|
static std::vector<Testcase> getTestcases() {
|
|
|
|
return {
|
|
|
|
// Three different manglings for std::string (old libstdc++, new libstdc++,
|
|
|
|
// libc++).
|
2018-08-25 06:31:51 +08:00
|
|
|
{
|
2018-08-26 18:03:08 +08:00
|
|
|
{
|
|
|
|
{FragmentKind::Type, "Ss",
|
|
|
|
"NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE"},
|
|
|
|
{FragmentKind::Type, "Ss",
|
|
|
|
"NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{"_Z1fv"},
|
|
|
|
{"_Z1fSs",
|
|
|
|
"_Z1fNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE",
|
|
|
|
"_Z1fNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE"},
|
|
|
|
{"_ZNKSs4sizeEv",
|
|
|
|
"_ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE4sizeEv",
|
|
|
|
"_ZNKSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE4sizeEv"},
|
|
|
|
}
|
2018-08-25 06:31:51 +08:00
|
|
|
},
|
|
|
|
|
2018-08-26 18:03:08 +08:00
|
|
|
// Check that substitutions are properly handled.
|
2018-08-25 06:31:51 +08:00
|
|
|
{
|
2018-08-26 18:03:08 +08:00
|
|
|
{
|
|
|
|
// ::X <-> ::N::X<int>
|
|
|
|
{FragmentKind::Type, "1X", "N1N1XIiEE"},
|
|
|
|
// ::T<T<int, int>, T<int, int>> <-> T<int>
|
|
|
|
{FragmentKind::Type, "1TIS_IiiES0_E", "1TIiE"},
|
|
|
|
// A::B::foo <-> AB::foo
|
|
|
|
{FragmentKind::Name, "N1A1B3fooE", "N2AB3fooE"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{"_Z1f1XPS_RS_", "_Z1fN1N1XIiEEPS1_RS1_"},
|
|
|
|
{"_ZN1A1B3fooE1TIS1_IiiES2_EPS3_RS3_", "_ZN2AB3fooE1TIiEPS1_RS1_"},
|
|
|
|
}
|
2018-08-25 06:31:51 +08:00
|
|
|
},
|
|
|
|
|
2018-08-26 18:03:08 +08:00
|
|
|
// Check that nested equivalences are properly handled.
|
2018-08-25 06:31:51 +08:00
|
|
|
{
|
2018-08-26 18:03:08 +08:00
|
|
|
{
|
|
|
|
// std::__1::char_traits == std::__cxx11::char_traits
|
|
|
|
// (Note that this is unused and should make no difference,
|
|
|
|
// but it should not cause us to fail to match up the cases
|
|
|
|
// below.)
|
|
|
|
{FragmentKind::Name,
|
|
|
|
"NSt3__111char_traitsE",
|
|
|
|
"NSt7__cxx1111char_traitsE"},
|
|
|
|
// std::__1::allocator == std::allocator
|
|
|
|
{FragmentKind::Name,
|
|
|
|
"NSt3__19allocatorE",
|
|
|
|
"Sa"}, // "Sa" is not strictly a <name> but we accept it as one.
|
|
|
|
// std::__1::vector == std::vector
|
|
|
|
{FragmentKind::Name,
|
|
|
|
"St6vector",
|
|
|
|
"NSt3__16vectorE"},
|
|
|
|
// std::__1::basic_string<
|
|
|
|
// char
|
|
|
|
// std::__1::char_traits<char>,
|
|
|
|
// std::__1::allocator<char>> ==
|
|
|
|
// std::__cxx11::basic_string<
|
|
|
|
// char,
|
|
|
|
// std::char_traits<char>,
|
|
|
|
// std::allocator<char>>
|
|
|
|
{FragmentKind::Type,
|
|
|
|
"NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE",
|
|
|
|
"NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE"},
|
|
|
|
// X<A> <-> X<B>
|
|
|
|
{FragmentKind::Type, "1XI1AE", "1XI1BE"},
|
|
|
|
// X <-> Y
|
|
|
|
{FragmentKind::Name, "1X", "1Y"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// f(std::string)
|
|
|
|
{"_Z1fNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE",
|
|
|
|
"_Z1fNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE"},
|
|
|
|
// f(std::vector<int>)
|
|
|
|
{"_Z1fSt6vectorIiSaIiEE", "_Z1fNSt3__16vectorIiNS_9allocatorIiEEEE"},
|
|
|
|
// f(X<A>), f(X<B>), f(Y<A>), f(Y<B>)
|
|
|
|
{"_Z1f1XI1AE", "_Z1f1XI1BE", "_Z1f1YI1AE", "_Z1f1YI1BE"},
|
|
|
|
// f(X<C>), f(Y<C>)
|
|
|
|
{"_Z1f1XI1CE", "_Z1f1YI1CE"},
|
|
|
|
}
|
2018-08-25 06:31:51 +08:00
|
|
|
},
|
|
|
|
|
2018-08-26 18:03:08 +08:00
|
|
|
// Check namespace equivalences.
|
2018-08-25 06:31:51 +08:00
|
|
|
{
|
2018-08-26 18:03:08 +08:00
|
|
|
{
|
|
|
|
// std::__1 == std::__cxx11
|
|
|
|
{FragmentKind::Name, "St3__1", "St7__cxx11"},
|
|
|
|
// std::__1::allocator == std::allocator
|
|
|
|
{FragmentKind::Name, "NSt3__19allocatorE", "Sa"},
|
|
|
|
// std::vector == std::__1::vector
|
|
|
|
{FragmentKind::Name, "St6vector", "NSt3__16vectorE"},
|
|
|
|
// std::__cxx11::char_traits == std::char_traits
|
|
|
|
// (This indirectly means that std::__1::char_traits == std::char_traits,
|
|
|
|
// due to the std::__cxx11 == std::__1 equivalence, which is what we rely
|
|
|
|
// on below.)
|
|
|
|
{FragmentKind::Name, "NSt7__cxx1111char_traitsE", "St11char_traits"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// f(std::foo)
|
|
|
|
{"_Z1fNSt7__cxx113fooE",
|
|
|
|
"_Z1fNSt3__13fooE"},
|
|
|
|
// f(std::string)
|
|
|
|
{"_Z1fNSt7__cxx1111char_traitsIcEE",
|
|
|
|
"_Z1fNSt3__111char_traitsIcEE",
|
|
|
|
"_Z1fSt11char_traitsIcE"},
|
|
|
|
// f(std::string)
|
|
|
|
{"_Z1fNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE",
|
|
|
|
"_Z1fNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE"},
|
|
|
|
// f(std::vector<int>)
|
|
|
|
{"_Z1fSt6vectorIiSaIiEE", "_Z1fNSt3__16vectorIiNS_9allocatorIiEEEE"},
|
|
|
|
}
|
2018-08-25 06:31:51 +08:00
|
|
|
},
|
|
|
|
|
2018-08-26 18:03:08 +08:00
|
|
|
// Check namespace equivalences for namespace 'std'. We support using 'St'
|
|
|
|
// for this, despite it not technically being a <name>.
|
2018-08-25 06:31:51 +08:00
|
|
|
{
|
2018-08-26 18:03:08 +08:00
|
|
|
{
|
|
|
|
// std::__1 == std
|
|
|
|
{FragmentKind::Name, "St3__1", "St"},
|
|
|
|
// std::__1 == std::__cxx11
|
|
|
|
{FragmentKind::Name, "St3__1", "St7__cxx11"},
|
|
|
|
// FIXME: Should a 'std' equivalence also cover the predefined
|
|
|
|
// substitutions?
|
|
|
|
// std::__1::allocator == std::allocator
|
|
|
|
{FragmentKind::Name, "NSt3__19allocatorE", "Sa"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{"_Z1fSt3foo", "_Z1fNSt3__13fooE", "_Z1fNSt7__cxx113fooE"},
|
|
|
|
{"_Z1fNSt3bar3bazE", "_Z1fNSt3__13bar3bazE"},
|
|
|
|
// f(std::string)
|
|
|
|
{"_Z1fNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE",
|
|
|
|
"_Z1fNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE"},
|
|
|
|
// f(std::vector<int>)
|
|
|
|
{"_Z1fSt6vectorIiSaIiEE", "_Z1fNSt3__16vectorIiNS_9allocatorIiEEEE"},
|
|
|
|
}
|
2018-08-25 06:31:51 +08:00
|
|
|
},
|
|
|
|
|
2018-08-26 18:03:08 +08:00
|
|
|
// Check mutually-recursive equivalences.
|
2018-08-25 06:31:51 +08:00
|
|
|
{
|
2018-08-26 18:03:08 +08:00
|
|
|
{
|
|
|
|
{FragmentKind::Type, "1A", "1B"},
|
|
|
|
{FragmentKind::Type, "1A", "1C"},
|
|
|
|
{FragmentKind::Type, "1D", "1B"},
|
|
|
|
{FragmentKind::Type, "1C", "1E"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{"_Z1f1A", "_Z1f1B", "_Z1f1C", "_Z1f1D", "_Z1f1E"},
|
|
|
|
{"_Z1f1F"},
|
|
|
|
}
|
2018-08-25 06:31:51 +08:00
|
|
|
},
|
|
|
|
|
2018-08-26 18:03:08 +08:00
|
|
|
// Check <encoding>s.
|
2018-08-25 06:31:51 +08:00
|
|
|
{
|
2018-08-26 18:03:08 +08:00
|
|
|
{
|
|
|
|
{FragmentKind::Encoding, "1fv", "1gv"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// f(void) -> g(void)
|
|
|
|
{"_Z1fv", "_Z1gv"},
|
|
|
|
// static local 'n' in f(void) -> static local 'n' in g(void)
|
|
|
|
{"_ZZ1fvE1n", "_ZZ1gvE1n"},
|
|
|
|
}
|
2018-08-25 06:31:51 +08:00
|
|
|
},
|
|
|
|
|
2018-08-26 18:03:08 +08:00
|
|
|
// Corner case: the substitution can appear within its own expansion.
|
2018-08-25 06:31:51 +08:00
|
|
|
{
|
2018-08-26 18:03:08 +08:00
|
|
|
{
|
|
|
|
// X <-> Y<X>
|
|
|
|
{FragmentKind::Type, "1X", "1YI1XE"},
|
|
|
|
// A<B> <-> B
|
|
|
|
{FragmentKind::Type, "1AI1BE", "1B"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// f(X) == f(Y<X>) == f(Y<Y<X>>) == f(Y<Y<Y<X>>>)
|
|
|
|
{"_Z1f1X", "_Z1f1YI1XE", "_Z1f1YIS_I1XEE", "_Z1f1YIS_IS_I1XEEE"},
|
|
|
|
// f(B) == f(A<B>) == f(A<A<B>>) == f(A<A<A<B>>>)
|
|
|
|
{"_Z1f1B", "_Z1f1AI1BE", "_Z1f1AIS_I1BEE", "_Z1f1AIS_IS_I1BEEE"},
|
|
|
|
}
|
2018-08-25 06:31:51 +08:00
|
|
|
},
|
|
|
|
|
2018-08-26 18:03:08 +08:00
|
|
|
// Redundant equivalences are accepted (and have no effect).
|
2018-08-25 06:31:51 +08:00
|
|
|
{
|
2018-08-26 18:03:08 +08:00
|
|
|
{
|
|
|
|
{FragmentKind::Name, "3std", "St"},
|
|
|
|
{FragmentKind::Name, "1X", "1Y"},
|
|
|
|
{FragmentKind::Name, "N1X1ZE", "N1Y1ZE"},
|
|
|
|
},
|
|
|
|
{}
|
2018-08-25 06:31:51 +08:00
|
|
|
},
|
2018-09-14 04:00:21 +08:00
|
|
|
|
|
|
|
// Check that ctor and dtor variants are considered distinct.
|
|
|
|
{
|
|
|
|
{},
|
|
|
|
{{"_ZN1XC1Ev"}, {"_ZN1XC2Ev"}, {"_ZN1XD1Ev"}, {"_ZN1XD2Ev"}}
|
|
|
|
},
|
|
|
|
|
|
|
|
// Ensure array types with and without bounds are handled properly.
|
|
|
|
{
|
|
|
|
{
|
|
|
|
{FragmentKind::Type, "A_i", "A1_f"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{"_Z1fRA_i", "_Z1fRA_i", "_Z1fRA1_f"},
|
|
|
|
{"_Z1fRA1_i"}, {"_Z1fRA_f"},
|
|
|
|
}
|
|
|
|
},
|
2018-08-26 18:03:08 +08:00
|
|
|
};
|
|
|
|
}
|
2018-08-25 06:31:51 +08:00
|
|
|
|
2018-08-26 18:03:08 +08:00
|
|
|
// A function to get a set of test cases for forward template references.
|
|
|
|
static std::vector<Testcase> getForwardTemplateReferenceTestcases() {
|
|
|
|
return {
|
|
|
|
// ForwardTemplateReference does not support canonicalization.
|
|
|
|
// FIXME: We should consider ways of fixing this, perhaps by eliminating
|
|
|
|
// the ForwardTemplateReference node with a tree transformation.
|
2018-08-25 06:31:51 +08:00
|
|
|
{
|
2018-08-26 18:03:08 +08:00
|
|
|
{
|
|
|
|
// X::operator T() <with T = A> == Y::operator T() <with T = A>
|
|
|
|
{FragmentKind::Encoding, "N1XcvT_I1AEEv", "N1YcvT_I1AEEv"},
|
|
|
|
// A == B
|
|
|
|
{FragmentKind::Name, "1A", "1B"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// All combinations result in unique equivalence classes.
|
|
|
|
{"_ZN1XcvT_I1AEEv"},
|
|
|
|
{"_ZN1XcvT_I1BEEv"},
|
|
|
|
{"_ZN1YcvT_I1AEEv"},
|
|
|
|
{"_ZN1YcvT_I1BEEv"},
|
|
|
|
// Even giving the same string twice gives a new class.
|
|
|
|
{"_ZN1XcvT_I1AEEv"},
|
|
|
|
}
|
2018-08-25 06:31:51 +08:00
|
|
|
},
|
2018-08-26 18:03:08 +08:00
|
|
|
};
|
|
|
|
}
|
2018-08-25 06:31:51 +08:00
|
|
|
|
2018-08-25 07:26:05 +08:00
|
|
|
template<bool CanonicalizeFirst>
|
2018-08-26 18:03:08 +08:00
|
|
|
static void testTestcases(ArrayRef<Testcase> Testcases) {
|
2018-08-25 06:31:51 +08:00
|
|
|
for (const auto &Testcase : Testcases) {
|
|
|
|
llvm::ItaniumManglingCanonicalizer Canonicalizer;
|
|
|
|
for (const auto &Equiv : Testcase.Equivalences) {
|
|
|
|
auto Result =
|
|
|
|
Canonicalizer.addEquivalence(Equiv.Kind, Equiv.First, Equiv.Second);
|
|
|
|
EXPECT_EQ(Result, EquivalenceError::Success)
|
|
|
|
<< "couldn't add equivalence between " << Equiv.First << " and "
|
|
|
|
<< Equiv.Second;
|
|
|
|
}
|
|
|
|
|
|
|
|
using CanonKey = llvm::ItaniumManglingCanonicalizer::Key;
|
2018-08-25 07:26:05 +08:00
|
|
|
|
|
|
|
std::map<const EquivalenceClass*, CanonKey> Keys;
|
|
|
|
if (CanonicalizeFirst)
|
|
|
|
for (const auto &Class : Testcase.Classes)
|
|
|
|
Keys.insert({&Class, Canonicalizer.canonicalize(*Class.begin())});
|
|
|
|
|
2018-08-25 06:31:51 +08:00
|
|
|
std::map<CanonKey, llvm::StringRef> Found;
|
|
|
|
for (const auto &Class : Testcase.Classes) {
|
2018-08-25 07:26:05 +08:00
|
|
|
CanonKey ClassKey = Keys[&Class];
|
2018-08-25 06:31:51 +08:00
|
|
|
for (llvm::StringRef Str : Class) {
|
2018-08-25 07:26:05 +08:00
|
|
|
// Force a copy to be made when calling lookup to test that it doesn't
|
|
|
|
// retain any part of the provided string.
|
|
|
|
CanonKey ThisKey = CanonicalizeFirst
|
|
|
|
? Canonicalizer.lookup(std::string(Str))
|
|
|
|
: Canonicalizer.canonicalize(Str);
|
2018-08-25 06:31:51 +08:00
|
|
|
EXPECT_NE(ThisKey, CanonKey()) << "couldn't canonicalize " << Str;
|
|
|
|
if (ClassKey) {
|
|
|
|
EXPECT_EQ(ThisKey, ClassKey)
|
|
|
|
<< Str << " not in the same class as " << *Class.begin();
|
|
|
|
} else {
|
|
|
|
ClassKey = ThisKey;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EXPECT_TRUE(Found.insert({ClassKey, *Class.begin()}).second)
|
|
|
|
<< *Class.begin() << " is in the same class as " << Found[ClassKey];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-25 07:26:05 +08:00
|
|
|
TEST(ItaniumManglingCanonicalizerTest, TestCanonicalize) {
|
2018-08-26 18:03:08 +08:00
|
|
|
testTestcases<false>(getTestcases());
|
2018-08-25 07:26:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ItaniumManglingCanonicalizerTest, TestLookup) {
|
2018-08-26 18:03:08 +08:00
|
|
|
testTestcases<true>(getTestcases());
|
2018-08-25 07:26:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ItaniumManglingCanonicalizerTest, TestForwardTemplateReference) {
|
|
|
|
// lookup(...) after canonicalization (intentionally) returns different
|
|
|
|
// values for this testcase.
|
2018-08-26 18:03:08 +08:00
|
|
|
testTestcases<false>(getForwardTemplateReferenceTestcases());
|
2018-08-25 07:26:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-08-25 06:31:51 +08:00
|
|
|
TEST(ItaniumManglingCanonicalizerTest, TestInvalidManglings) {
|
|
|
|
llvm::ItaniumManglingCanonicalizer Canonicalizer;
|
|
|
|
EXPECT_EQ(Canonicalizer.addEquivalence(FragmentKind::Type, "", "1X"),
|
|
|
|
EquivalenceError::InvalidFirstMangling);
|
|
|
|
EXPECT_EQ(Canonicalizer.addEquivalence(FragmentKind::Type, "1X", "1ab"),
|
|
|
|
EquivalenceError::InvalidSecondMangling);
|
|
|
|
EXPECT_EQ(Canonicalizer.canonicalize("_Z3fooE"),
|
|
|
|
llvm::ItaniumManglingCanonicalizer::Key());
|
|
|
|
EXPECT_EQ(Canonicalizer.canonicalize("foo"),
|
|
|
|
llvm::ItaniumManglingCanonicalizer::Key());
|
|
|
|
|
|
|
|
// A reference to a template parameter ('T_' etc) cannot appear in a <name>,
|
|
|
|
// because we don't have template arguments to bind to it. (The arguments in
|
|
|
|
// an 'I ... E' construct in the <name> aren't registered as
|
|
|
|
// backreferenceable arguments in this sense, because they're not part of
|
|
|
|
// the template argument list of an <encoding>.
|
|
|
|
EXPECT_EQ(Canonicalizer.addEquivalence(FragmentKind::Name, "N1XcvT_I1AEE",
|
|
|
|
"1f"),
|
|
|
|
EquivalenceError::InvalidFirstMangling);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(ItaniumManglingCanonicalizerTest, TestBadEquivalenceOrder) {
|
|
|
|
llvm::ItaniumManglingCanonicalizer Canonicalizer;
|
|
|
|
EXPECT_EQ(Canonicalizer.addEquivalence(FragmentKind::Type, "N1P1XE", "N1Q1XE"),
|
|
|
|
EquivalenceError::Success);
|
|
|
|
EXPECT_EQ(Canonicalizer.addEquivalence(FragmentKind::Type, "1P", "1Q"),
|
|
|
|
EquivalenceError::ManglingAlreadyUsed);
|
|
|
|
|
|
|
|
EXPECT_EQ(Canonicalizer.addEquivalence(FragmentKind::Type, "N1C1XE", "N1A1YE"),
|
|
|
|
EquivalenceError::Success);
|
|
|
|
EXPECT_EQ(Canonicalizer.addEquivalence(FragmentKind::Type, "1A", "1B"),
|
|
|
|
EquivalenceError::Success);
|
|
|
|
EXPECT_EQ(Canonicalizer.addEquivalence(FragmentKind::Type, "1C", "1D"),
|
|
|
|
EquivalenceError::Success);
|
|
|
|
EXPECT_EQ(Canonicalizer.addEquivalence(FragmentKind::Type, "1B", "1D"),
|
|
|
|
EquivalenceError::ManglingAlreadyUsed);
|
|
|
|
}
|
2018-08-26 18:03:08 +08:00
|
|
|
|
|
|
|
} // end anonymous namespace
|