1bb677cacSAndrew Litteken //===- unittests/Support/SuffixTreeTest.cpp - suffix tree tests -----------===//
2bb677cacSAndrew Litteken //
3bb677cacSAndrew Litteken // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4bb677cacSAndrew Litteken // See https://llvm.org/LICENSE.txt for license information.
5bb677cacSAndrew Litteken // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6bb677cacSAndrew Litteken //
7bb677cacSAndrew Litteken //===----------------------------------------------------------------------===//
8bb677cacSAndrew Litteken
9bb677cacSAndrew Litteken #include "llvm/Support/SuffixTree.h"
10bb677cacSAndrew Litteken #include "gtest/gtest.h"
11bb677cacSAndrew Litteken #include <vector>
12bb677cacSAndrew Litteken
13bb677cacSAndrew Litteken using namespace llvm;
14bb677cacSAndrew Litteken
15bb677cacSAndrew Litteken namespace {
16bb677cacSAndrew Litteken
17bb677cacSAndrew Litteken // Each example vector has a unique element at the end to represent the end of
18bb677cacSAndrew Litteken // the string
19bb677cacSAndrew Litteken
20bb677cacSAndrew Litteken // Tests that The SuffixTree finds a simple repetition of the substring {1, 2}
21bb677cacSAndrew Litteken // {1, 2} twice in the provided string.
TEST(SuffixTreeTest,TestSingleRepetition)22bb677cacSAndrew Litteken TEST(SuffixTreeTest, TestSingleRepetition) {
23bb677cacSAndrew Litteken std::vector<unsigned> SimpleRepetitionData = {1, 2, 1, 2, 3};
24bb677cacSAndrew Litteken SuffixTree ST(SimpleRepetitionData);
25bb677cacSAndrew Litteken std::vector<SuffixTree::RepeatedSubstring> SubStrings;
26bb677cacSAndrew Litteken for (auto It = ST.begin(); It != ST.end(); It++)
27bb677cacSAndrew Litteken SubStrings.push_back(*It);
28bb677cacSAndrew Litteken ASSERT_EQ(SubStrings.size(), 1u);
29bb677cacSAndrew Litteken EXPECT_EQ(SubStrings[0].Length, 2u);
30bb677cacSAndrew Litteken EXPECT_EQ(SubStrings[0].StartIndices.size(), 2u);
31bb677cacSAndrew Litteken for (unsigned StartIdx : SubStrings[0].StartIndices) {
32bb677cacSAndrew Litteken EXPECT_EQ(SimpleRepetitionData[StartIdx], 1u);
33bb677cacSAndrew Litteken EXPECT_EQ(SimpleRepetitionData[StartIdx + 1], 2u);
34bb677cacSAndrew Litteken }
35bb677cacSAndrew Litteken }
36bb677cacSAndrew Litteken
37bb677cacSAndrew Litteken // Tests that the SuffixTree is able to find the substrings {1, 2, 3} at
38bb677cacSAndrew Litteken // at indices 0 and 3 as well as the substrings {2, 3} at indices 1 and 4.
39bb677cacSAndrew Litteken // This test also serves as a flag for improvements to the suffix tree.
40bb677cacSAndrew Litteken //
41bb677cacSAndrew Litteken // FIXME: Right now, the longest repeated substring from a specific index is
42bb677cacSAndrew Litteken // returned, this could be improved to return the longest repeated substring, as
43bb677cacSAndrew Litteken // well as those that are smaller.
TEST(SuffixTreeTest,TestLongerRepetition)44bb677cacSAndrew Litteken TEST(SuffixTreeTest, TestLongerRepetition) {
45bb677cacSAndrew Litteken std::vector<unsigned> RepeatedRepetitionData = {1, 2, 3, 1, 2, 3, 4};
46bb677cacSAndrew Litteken SuffixTree ST(RepeatedRepetitionData);
47bb677cacSAndrew Litteken std::vector<SuffixTree::RepeatedSubstring> SubStrings;
48bb677cacSAndrew Litteken for (auto It = ST.begin(); It != ST.end(); It++)
49bb677cacSAndrew Litteken SubStrings.push_back(*It);
50bb677cacSAndrew Litteken EXPECT_EQ(SubStrings.size(), 2u);
51bb677cacSAndrew Litteken unsigned Len;
52bb677cacSAndrew Litteken for (SuffixTree::RepeatedSubstring &RS : SubStrings) {
53bb677cacSAndrew Litteken Len = RS.Length;
54bb677cacSAndrew Litteken bool IsExpectedLen = (Len == 3u || Len == 2u);
55bb677cacSAndrew Litteken bool IsExpectedIndex;
56bb677cacSAndrew Litteken ASSERT_TRUE(IsExpectedLen);
57bb677cacSAndrew Litteken
58bb677cacSAndrew Litteken if (Len == 3u) {
59bb677cacSAndrew Litteken for (unsigned StartIdx : RS.StartIndices) {
60bb677cacSAndrew Litteken IsExpectedIndex = (StartIdx == 0u || StartIdx == 3u);
61bb677cacSAndrew Litteken EXPECT_TRUE(IsExpectedIndex);
62bb677cacSAndrew Litteken EXPECT_EQ(RepeatedRepetitionData[StartIdx], 1u);
63bb677cacSAndrew Litteken EXPECT_EQ(RepeatedRepetitionData[StartIdx + 1], 2u);
64bb677cacSAndrew Litteken EXPECT_EQ(RepeatedRepetitionData[StartIdx + 2], 3u);
65bb677cacSAndrew Litteken }
66bb677cacSAndrew Litteken } else {
67bb677cacSAndrew Litteken for (unsigned StartIdx : RS.StartIndices) {
68bb677cacSAndrew Litteken IsExpectedIndex = (StartIdx == 1u || StartIdx == 4u);
69bb677cacSAndrew Litteken EXPECT_TRUE(IsExpectedIndex);
70bb677cacSAndrew Litteken EXPECT_EQ(RepeatedRepetitionData[StartIdx], 2u);
71bb677cacSAndrew Litteken EXPECT_EQ(RepeatedRepetitionData[StartIdx + 1], 3u);
72bb677cacSAndrew Litteken }
73bb677cacSAndrew Litteken }
74bb677cacSAndrew Litteken }
75bb677cacSAndrew Litteken }
76bb677cacSAndrew Litteken
77bb677cacSAndrew Litteken // Tests that the SuffixTree is able to find substring {1, 1, 1, 1, 1} at
78bb677cacSAndrew Litteken // indices 0 and 1.
79bb677cacSAndrew Litteken //
80bb677cacSAndrew Litteken // FIXME: Add support for detecting {1, 1} and {1, 1, 1}
81*d9a00ed3SXuan Zhang // See Test TestSingleCharacterRepeatWithLeafDescendants for the fix
TEST(SuffixTreeTest,TestSingleCharacterRepeat)82bb677cacSAndrew Litteken TEST(SuffixTreeTest, TestSingleCharacterRepeat) {
83bb677cacSAndrew Litteken std::vector<unsigned> RepeatedRepetitionData = {1, 1, 1, 1, 1, 1, 2};
84bb677cacSAndrew Litteken std::vector<unsigned>::iterator RRDIt, RRDIt2;
85bb677cacSAndrew Litteken SuffixTree ST(RepeatedRepetitionData);
86bb677cacSAndrew Litteken std::vector<SuffixTree::RepeatedSubstring> SubStrings;
87bb677cacSAndrew Litteken for (auto It = ST.begin(); It != ST.end(); It++)
88bb677cacSAndrew Litteken SubStrings.push_back(*It);
89bb677cacSAndrew Litteken EXPECT_EQ(SubStrings.size(), 1u);
90bb677cacSAndrew Litteken for (SuffixTree::RepeatedSubstring &RS : SubStrings) {
91bb677cacSAndrew Litteken EXPECT_EQ(RS.StartIndices.size(),
92bb677cacSAndrew Litteken RepeatedRepetitionData.size() - RS.Length);
93bb677cacSAndrew Litteken for (unsigned StartIdx : SubStrings[0].StartIndices) {
94bb677cacSAndrew Litteken RRDIt = RRDIt2 = RepeatedRepetitionData.begin();
95bb677cacSAndrew Litteken std::advance(RRDIt, StartIdx);
96bb677cacSAndrew Litteken std::advance(RRDIt2, StartIdx + SubStrings[0].Length);
97bb677cacSAndrew Litteken ASSERT_TRUE(
98bb677cacSAndrew Litteken all_of(make_range<std::vector<unsigned>::iterator>(RRDIt, RRDIt2),
99bb677cacSAndrew Litteken [](unsigned Elt) { return Elt == 1; }));
100bb677cacSAndrew Litteken }
101bb677cacSAndrew Litteken }
102bb677cacSAndrew Litteken }
103bb677cacSAndrew Litteken
104*d9a00ed3SXuan Zhang // Tests that the SuffixTree is able to find the following substrings:
105*d9a00ed3SXuan Zhang // {1, 1} at indices 0, 1, 2, 3, and 4;
106*d9a00ed3SXuan Zhang // {1, 1, 1} at indices 0, 1, 2, and 3;
107*d9a00ed3SXuan Zhang // {1, 1, 1, 1} at indices 0, 1, and 2; and
108*d9a00ed3SXuan Zhang // {1, 1, 1, 1, 1} at indices 0 and 1.
109*d9a00ed3SXuan Zhang //
110*d9a00ed3SXuan Zhang // This is the fix for TestSingleCharacterRepeat.
TEST(SuffixTreeTest,TestSingleCharacterRepeatWithLeafDescendants)111*d9a00ed3SXuan Zhang TEST(SuffixTreeTest, TestSingleCharacterRepeatWithLeafDescendants) {
112*d9a00ed3SXuan Zhang std::vector<unsigned> RepeatedRepetitionData = {1, 1, 1, 1, 1, 1, 2};
113*d9a00ed3SXuan Zhang std::vector<unsigned>::iterator RRDIt, RRDIt2;
114*d9a00ed3SXuan Zhang SuffixTree ST(RepeatedRepetitionData, /*OutlinerLeafDescendants=*/true);
115*d9a00ed3SXuan Zhang std::vector<SuffixTree::RepeatedSubstring> SubStrings;
116*d9a00ed3SXuan Zhang for (auto It = ST.begin(); It != ST.end(); It++)
117*d9a00ed3SXuan Zhang SubStrings.push_back(*It);
118*d9a00ed3SXuan Zhang EXPECT_EQ(SubStrings.size(), 4u);
119*d9a00ed3SXuan Zhang for (SuffixTree::RepeatedSubstring &RS : SubStrings) {
120*d9a00ed3SXuan Zhang EXPECT_EQ(RS.StartIndices.size(),
121*d9a00ed3SXuan Zhang RepeatedRepetitionData.size() - RS.Length);
122*d9a00ed3SXuan Zhang for (unsigned StartIdx : SubStrings[0].StartIndices) {
123*d9a00ed3SXuan Zhang RRDIt = RRDIt2 = RepeatedRepetitionData.begin();
124*d9a00ed3SXuan Zhang std::advance(RRDIt, StartIdx);
125*d9a00ed3SXuan Zhang std::advance(RRDIt2, StartIdx + SubStrings[0].Length);
126*d9a00ed3SXuan Zhang ASSERT_TRUE(
127*d9a00ed3SXuan Zhang all_of(make_range<std::vector<unsigned>::iterator>(RRDIt, RRDIt2),
128*d9a00ed3SXuan Zhang [](unsigned Elt) { return Elt == 1; }));
129*d9a00ed3SXuan Zhang }
130*d9a00ed3SXuan Zhang }
131*d9a00ed3SXuan Zhang }
132*d9a00ed3SXuan Zhang
133bb677cacSAndrew Litteken // The suffix tree cannot currently find repeated substrings in strings of the
134bb677cacSAndrew Litteken // form {1, 2, 3, 1, 2, 3}, because the two {1, 2, 3}s are adjacent ("tandem
135bb677cacSAndrew Litteken // repeats")
136bb677cacSAndrew Litteken //
137bb677cacSAndrew Litteken // FIXME: Teach the SuffixTree to recognize these cases.
TEST(SuffixTreeTest,TestTandemRepeat)138bb677cacSAndrew Litteken TEST(SuffixTreeTest, TestTandemRepeat) {
139bb677cacSAndrew Litteken std::vector<unsigned> RepeatedRepetitionData = {1, 2, 3, 1, 2, 3};
140bb677cacSAndrew Litteken SuffixTree ST(RepeatedRepetitionData);
141bb677cacSAndrew Litteken std::vector<SuffixTree::RepeatedSubstring> SubStrings;
142bb677cacSAndrew Litteken for (auto It = ST.begin(); It != ST.end(); It++)
143bb677cacSAndrew Litteken SubStrings.push_back(*It);
144bb677cacSAndrew Litteken EXPECT_EQ(SubStrings.size(), 0u);
145bb677cacSAndrew Litteken }
146bb677cacSAndrew Litteken
147bb677cacSAndrew Litteken // Tests that the SuffixTree does not erroneously include values that are not
148bb677cacSAndrew Litteken // in repeated substrings. That is, only finds {1, 1} at indices 0 and 3 and
149bb677cacSAndrew Litteken // does not include 2 and 3.
TEST(SuffixTreeTest,TestExclusion)150bb677cacSAndrew Litteken TEST(SuffixTreeTest, TestExclusion) {
151bb677cacSAndrew Litteken std::vector<unsigned> RepeatedRepetitionData = {1, 1, 2, 1, 1, 3};
152bb677cacSAndrew Litteken std::vector<unsigned>::iterator RRDIt, RRDIt2;
153bb677cacSAndrew Litteken SuffixTree ST(RepeatedRepetitionData);
154bb677cacSAndrew Litteken std::vector<SuffixTree::RepeatedSubstring> SubStrings;
155bb677cacSAndrew Litteken for (auto It = ST.begin(); It != ST.end(); It++)
156bb677cacSAndrew Litteken SubStrings.push_back(*It);
157bb677cacSAndrew Litteken EXPECT_EQ(SubStrings.size(), 1u);
158bb677cacSAndrew Litteken bool IsExpectedIndex;
159bb677cacSAndrew Litteken for (SuffixTree::RepeatedSubstring &RS : SubStrings) {
160bb677cacSAndrew Litteken for (unsigned StartIdx : RS.StartIndices) {
161bb677cacSAndrew Litteken IsExpectedIndex = (StartIdx == 0u || StartIdx == 3u);
162bb677cacSAndrew Litteken EXPECT_TRUE(IsExpectedIndex);
163bb677cacSAndrew Litteken RRDIt = RRDIt2 = RepeatedRepetitionData.begin();
164bb677cacSAndrew Litteken std::advance(RRDIt, StartIdx);
165bb677cacSAndrew Litteken std::advance(RRDIt2, StartIdx + RS.Length);
166bb677cacSAndrew Litteken EXPECT_TRUE(
167bb677cacSAndrew Litteken all_of(make_range<std::vector<unsigned>::iterator>(RRDIt, RRDIt2),
168bb677cacSAndrew Litteken [](unsigned Elt) { return Elt == 1; }));
169bb677cacSAndrew Litteken }
170bb677cacSAndrew Litteken }
171bb677cacSAndrew Litteken }
172bb677cacSAndrew Litteken
173*d9a00ed3SXuan Zhang // Tests that the SuffixTree is able to find three substrings
174*d9a00ed3SXuan Zhang // {1, 2, 3} at indices 6 and 10;
175*d9a00ed3SXuan Zhang // {2, 3} at indices 7 and 11; and
176*d9a00ed3SXuan Zhang // {1, 2} at indicies 0 and 3.
177*d9a00ed3SXuan Zhang //
178*d9a00ed3SXuan Zhang // FIXME: {1, 2} has indices 6 and 10 missing as it is a substring of {1, 2, 3}
179*d9a00ed3SXuan Zhang // See Test TestSubstringRepeatsWithLeafDescendants for the fix
TEST(SuffixTreeTest,TestSubstringRepeats)180*d9a00ed3SXuan Zhang TEST(SuffixTreeTest, TestSubstringRepeats) {
181*d9a00ed3SXuan Zhang std::vector<unsigned> RepeatedRepetitionData = {1, 2, 100, 1, 2, 101, 1,
182*d9a00ed3SXuan Zhang 2, 3, 103, 1, 2, 3, 104};
183*d9a00ed3SXuan Zhang SuffixTree ST(RepeatedRepetitionData);
184*d9a00ed3SXuan Zhang std::vector<SuffixTree::RepeatedSubstring> SubStrings;
185*d9a00ed3SXuan Zhang for (auto It = ST.begin(); It != ST.end(); It++)
186*d9a00ed3SXuan Zhang SubStrings.push_back(*It);
187*d9a00ed3SXuan Zhang EXPECT_EQ(SubStrings.size(), 3u);
188*d9a00ed3SXuan Zhang unsigned Len;
189*d9a00ed3SXuan Zhang for (SuffixTree::RepeatedSubstring &RS : SubStrings) {
190*d9a00ed3SXuan Zhang Len = RS.Length;
191*d9a00ed3SXuan Zhang bool IsExpectedLen = (Len == 3u || Len == 2u);
192*d9a00ed3SXuan Zhang ASSERT_TRUE(IsExpectedLen);
193*d9a00ed3SXuan Zhang bool IsExpectedIndex;
194*d9a00ed3SXuan Zhang
195*d9a00ed3SXuan Zhang if (Len == 3u) { // {1, 2, 3}
196*d9a00ed3SXuan Zhang EXPECT_EQ(RS.StartIndices.size(), 2u);
197*d9a00ed3SXuan Zhang for (unsigned StartIdx : RS.StartIndices) {
198*d9a00ed3SXuan Zhang IsExpectedIndex = (StartIdx == 6u || StartIdx == 10u);
199*d9a00ed3SXuan Zhang EXPECT_TRUE(IsExpectedIndex);
200*d9a00ed3SXuan Zhang EXPECT_EQ(RepeatedRepetitionData[StartIdx], 1u);
201*d9a00ed3SXuan Zhang EXPECT_EQ(RepeatedRepetitionData[StartIdx + 1], 2u);
202*d9a00ed3SXuan Zhang EXPECT_EQ(RepeatedRepetitionData[StartIdx + 2], 3u);
203*d9a00ed3SXuan Zhang }
204*d9a00ed3SXuan Zhang } else {
205*d9a00ed3SXuan Zhang if (RepeatedRepetitionData[RS.StartIndices[0]] == 1u) { // {1, 2}
206*d9a00ed3SXuan Zhang EXPECT_EQ(RS.StartIndices.size(), 2u);
207*d9a00ed3SXuan Zhang for (unsigned StartIdx : RS.StartIndices) {
208*d9a00ed3SXuan Zhang IsExpectedIndex = (StartIdx == 0u || StartIdx == 3u);
209*d9a00ed3SXuan Zhang EXPECT_TRUE(IsExpectedIndex);
210*d9a00ed3SXuan Zhang EXPECT_EQ(RepeatedRepetitionData[StartIdx + 1], 2u);
211*d9a00ed3SXuan Zhang }
212*d9a00ed3SXuan Zhang } else { // {2, 3}
213*d9a00ed3SXuan Zhang EXPECT_EQ(RS.StartIndices.size(), 2u);
214*d9a00ed3SXuan Zhang for (unsigned StartIdx : RS.StartIndices) {
215*d9a00ed3SXuan Zhang IsExpectedIndex = (StartIdx == 7u || StartIdx == 11u);
216*d9a00ed3SXuan Zhang EXPECT_TRUE(IsExpectedIndex);
217*d9a00ed3SXuan Zhang EXPECT_EQ(RepeatedRepetitionData[StartIdx], 2u);
218*d9a00ed3SXuan Zhang EXPECT_EQ(RepeatedRepetitionData[StartIdx + 1], 3u);
219*d9a00ed3SXuan Zhang }
220*d9a00ed3SXuan Zhang }
221*d9a00ed3SXuan Zhang }
222*d9a00ed3SXuan Zhang }
223*d9a00ed3SXuan Zhang }
224*d9a00ed3SXuan Zhang
225*d9a00ed3SXuan Zhang // Tests that the SuffixTree is able to find three substrings
226*d9a00ed3SXuan Zhang // {1, 2, 3} at indices 6 and 10;
227*d9a00ed3SXuan Zhang // {2, 3} at indices 7 and 11; and
228*d9a00ed3SXuan Zhang // {1, 2} at indicies 0, 3, 6, and 10.
229*d9a00ed3SXuan Zhang //
230*d9a00ed3SXuan Zhang // This is the fix for TestSubstringRepeats
TEST(SuffixTreeTest,TestSubstringRepeatsWithLeafDescendants)231*d9a00ed3SXuan Zhang TEST(SuffixTreeTest, TestSubstringRepeatsWithLeafDescendants) {
232*d9a00ed3SXuan Zhang std::vector<unsigned> RepeatedRepetitionData = {1, 2, 100, 1, 2, 101, 1,
233*d9a00ed3SXuan Zhang 2, 3, 103, 1, 2, 3, 104};
234*d9a00ed3SXuan Zhang SuffixTree ST(RepeatedRepetitionData, /*OutlinerLeafDescendants=*/true);
235*d9a00ed3SXuan Zhang std::vector<SuffixTree::RepeatedSubstring> SubStrings;
236*d9a00ed3SXuan Zhang for (auto It = ST.begin(); It != ST.end(); It++)
237*d9a00ed3SXuan Zhang SubStrings.push_back(*It);
238*d9a00ed3SXuan Zhang EXPECT_EQ(SubStrings.size(), 3u);
239*d9a00ed3SXuan Zhang unsigned Len;
240*d9a00ed3SXuan Zhang for (SuffixTree::RepeatedSubstring &RS : SubStrings) {
241*d9a00ed3SXuan Zhang Len = RS.Length;
242*d9a00ed3SXuan Zhang bool IsExpectedLen = (Len == 3u || Len == 2u);
243*d9a00ed3SXuan Zhang ASSERT_TRUE(IsExpectedLen);
244*d9a00ed3SXuan Zhang bool IsExpectedIndex;
245*d9a00ed3SXuan Zhang
246*d9a00ed3SXuan Zhang if (Len == 3u) { // {1, 2, 3}
247*d9a00ed3SXuan Zhang EXPECT_EQ(RS.StartIndices.size(), 2u);
248*d9a00ed3SXuan Zhang for (unsigned StartIdx : RS.StartIndices) {
249*d9a00ed3SXuan Zhang IsExpectedIndex = (StartIdx == 6u || StartIdx == 10u);
250*d9a00ed3SXuan Zhang EXPECT_TRUE(IsExpectedIndex);
251*d9a00ed3SXuan Zhang EXPECT_EQ(RepeatedRepetitionData[StartIdx], 1u);
252*d9a00ed3SXuan Zhang EXPECT_EQ(RepeatedRepetitionData[StartIdx + 1], 2u);
253*d9a00ed3SXuan Zhang EXPECT_EQ(RepeatedRepetitionData[StartIdx + 2], 3u);
254*d9a00ed3SXuan Zhang }
255*d9a00ed3SXuan Zhang } else {
256*d9a00ed3SXuan Zhang if (RepeatedRepetitionData[RS.StartIndices[0]] == 1u) { // {1, 2}
257*d9a00ed3SXuan Zhang EXPECT_EQ(RS.StartIndices.size(), 4u);
258*d9a00ed3SXuan Zhang for (unsigned StartIdx : RS.StartIndices) {
259*d9a00ed3SXuan Zhang IsExpectedIndex = (StartIdx == 0u || StartIdx == 3u ||
260*d9a00ed3SXuan Zhang StartIdx == 6u || StartIdx == 10u);
261*d9a00ed3SXuan Zhang EXPECT_TRUE(IsExpectedIndex);
262*d9a00ed3SXuan Zhang EXPECT_EQ(RepeatedRepetitionData[StartIdx + 1], 2u);
263*d9a00ed3SXuan Zhang }
264*d9a00ed3SXuan Zhang } else { // {2, 3}
265*d9a00ed3SXuan Zhang EXPECT_EQ(RS.StartIndices.size(), 2u);
266*d9a00ed3SXuan Zhang for (unsigned StartIdx : RS.StartIndices) {
267*d9a00ed3SXuan Zhang IsExpectedIndex = (StartIdx == 7u || StartIdx == 11u);
268*d9a00ed3SXuan Zhang EXPECT_TRUE(IsExpectedIndex);
269*d9a00ed3SXuan Zhang EXPECT_EQ(RepeatedRepetitionData[StartIdx], 2u);
270*d9a00ed3SXuan Zhang EXPECT_EQ(RepeatedRepetitionData[StartIdx + 1], 3u);
271*d9a00ed3SXuan Zhang }
272*d9a00ed3SXuan Zhang }
273*d9a00ed3SXuan Zhang }
274*d9a00ed3SXuan Zhang }
275*d9a00ed3SXuan Zhang }
276*d9a00ed3SXuan Zhang
277bb677cacSAndrew Litteken } // namespace
278