1 // © 2017 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3
4 #include "unicode/utypes.h"
5
6 #if !UCONFIG_NO_FORMATTING
7
8 #include "unicode/utf16.h"
9 #include "putilimp.h"
10 #include "intltest.h"
11 #include "formatted_string_builder.h"
12 #include "formattedval_impl.h"
13 #include "unicode/unum.h"
14
15
16 class FormattedStringBuilderTest : public IntlTest {
17 public:
18 void testInsertAppendUnicodeString();
19 void testSplice();
20 void testInsertAppendCodePoint();
21 void testCopy();
22 void testFields();
23 void testUnlimitedCapacity();
24 void testCodePoints();
25
26 void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0);
27
28 private:
29 void assertEqualsImpl(const UnicodeString &a, const FormattedStringBuilder &b);
30 };
31
32 static const char16_t *EXAMPLE_STRINGS[] = {
33 u"",
34 u"xyz",
35 u"The quick brown fox jumps over the lazy dog",
36 u"",
37 u"mixed and ASCII",
38 u"with combining characters like ",
39 u"A very very very very very very very very very very long string to force heap"};
40
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)41 void FormattedStringBuilderTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
42 if (exec) {
43 logln("TestSuite FormattedStringBuilderTest: ");
44 }
45 TESTCASE_AUTO_BEGIN;
46 TESTCASE_AUTO(testInsertAppendUnicodeString);
47 TESTCASE_AUTO(testSplice);
48 TESTCASE_AUTO(testInsertAppendCodePoint);
49 TESTCASE_AUTO(testCopy);
50 TESTCASE_AUTO(testFields);
51 TESTCASE_AUTO(testUnlimitedCapacity);
52 TESTCASE_AUTO(testCodePoints);
53 TESTCASE_AUTO_END;
54 }
55
testInsertAppendUnicodeString()56 void FormattedStringBuilderTest::testInsertAppendUnicodeString() {
57 UErrorCode status = U_ZERO_ERROR;
58 UnicodeString sb1;
59 FormattedStringBuilder sb2;
60 for (const char16_t* strPtr : EXAMPLE_STRINGS) {
61 UnicodeString str(strPtr);
62
63 FormattedStringBuilder sb3;
64 sb1.append(str);
65 sb2.append(str, kUndefinedField, status);
66 assertSuccess("Appending to sb2", status);
67 sb3.append(str, kUndefinedField, status);
68 assertSuccess("Appending to sb3", status);
69 assertEqualsImpl(sb1, sb2);
70 assertEqualsImpl(str, sb3);
71
72 UnicodeString sb4;
73 FormattedStringBuilder sb5;
74 sb4.append(u"");
75 sb4.append(str);
76 sb4.append(u"xx");
77 sb5.append(u"xx", kUndefinedField, status);
78 assertSuccess("Appending to sb5", status);
79 sb5.insert(2, str, kUndefinedField, status);
80 assertSuccess("Inserting into sb5", status);
81 assertEqualsImpl(sb4, sb5);
82
83 int start = uprv_min(1, str.length());
84 int end = uprv_min(10, str.length());
85 sb4.insert(3, str, start, end - start); // UnicodeString uses length instead of end index
86 sb5.insert(3, str, start, end, kUndefinedField, status);
87 assertSuccess("Inserting into sb5 again", status);
88 assertEqualsImpl(sb4, sb5);
89
90 UnicodeString sb4cp(sb4);
91 FormattedStringBuilder sb5cp(sb5);
92 sb4.append(sb4cp);
93 sb5.append(sb5cp, status);
94 assertSuccess("Appending again to sb5", status);
95 assertEqualsImpl(sb4, sb5);
96 }
97 }
98
testSplice()99 void FormattedStringBuilderTest::testSplice() {
100 static const struct TestCase {
101 const char16_t* input;
102 const int32_t startThis;
103 const int32_t endThis;
104 } cases[] = {
105 { u"", 0, 0 },
106 { u"abc", 0, 0 },
107 { u"abc", 1, 1 },
108 { u"abc", 1, 2 },
109 { u"abc", 0, 2 },
110 { u"abc", 0, 3 },
111 { u"lorem ipsum dolor sit amet", 8, 8 },
112 { u"lorem ipsum dolor sit amet", 8, 11 }, // 3 chars, equal to replacement "xyz"
113 { u"lorem ipsum dolor sit amet", 8, 18 } }; // 10 chars, larger than several replacements
114
115 UErrorCode status = U_ZERO_ERROR;
116 UnicodeString sb1;
117 FormattedStringBuilder sb2;
118 for (auto cas : cases) {
119 for (const char16_t* replacementPtr : EXAMPLE_STRINGS) {
120 UnicodeString replacement(replacementPtr);
121
122 // Test replacement with full string
123 sb1.remove();
124 sb1.append(cas.input);
125 sb1.replace(cas.startThis, cas.endThis - cas.startThis, replacement);
126 sb2.clear();
127 sb2.append(cas.input, kUndefinedField, status);
128 sb2.splice(cas.startThis, cas.endThis, replacement, 0, replacement.length(), kUndefinedField, status);
129 assertSuccess("Splicing into sb2 first time", status);
130 assertEqualsImpl(sb1, sb2);
131
132 // Test replacement with partial string
133 if (replacement.length() <= 2) {
134 continue;
135 }
136 sb1.remove();
137 sb1.append(cas.input);
138 sb1.replace(cas.startThis, cas.endThis - cas.startThis, UnicodeString(replacement, 1, 2));
139 sb2.clear();
140 sb2.append(cas.input, kUndefinedField, status);
141 sb2.splice(cas.startThis, cas.endThis, replacement, 1, 3, kUndefinedField, status);
142 assertSuccess("Splicing into sb2 second time", status);
143 assertEqualsImpl(sb1, sb2);
144 }
145 }
146 }
147
testInsertAppendCodePoint()148 void FormattedStringBuilderTest::testInsertAppendCodePoint() {
149 static const UChar32 cases[] = {
150 0, 1, 60, 127, 128, 0x7fff, 0x8000, 0xffff, 0x10000, 0x1f000, 0x10ffff};
151 UErrorCode status = U_ZERO_ERROR;
152 UnicodeString sb1;
153 FormattedStringBuilder sb2;
154 for (UChar32 cas : cases) {
155 FormattedStringBuilder sb3;
156 sb1.append(cas);
157 sb2.appendCodePoint(cas, kUndefinedField, status);
158 assertSuccess("Appending to sb2", status);
159 sb3.appendCodePoint(cas, kUndefinedField, status);
160 assertSuccess("Appending to sb3", status);
161 assertEqualsImpl(sb1, sb2);
162 assertEquals("Length of sb3", U16_LENGTH(cas), sb3.length());
163 assertEquals("Code point count of sb3", 1, sb3.codePointCount());
164 assertEquals(
165 "First code unit in sb3",
166 !U_IS_SUPPLEMENTARY(cas) ? (char16_t) cas : U16_LEAD(cas),
167 sb3.charAt(0));
168
169 UnicodeString sb4;
170 FormattedStringBuilder sb5;
171 sb4.append(u"xx");
172 sb4.insert(2, cas);
173 sb5.append(u"xx", kUndefinedField, status);
174 assertSuccess("Appending to sb5", status);
175 sb5.insertCodePoint(2, cas, kUndefinedField, status);
176 assertSuccess("Inserting into sb5", status);
177 assertEqualsImpl(sb4, sb5);
178
179 UnicodeString sb6;
180 FormattedStringBuilder sb7;
181 sb6.append(cas);
182 if (U_IS_SUPPLEMENTARY(cas)) {
183 sb7.appendChar16(U16_TRAIL(cas), kUndefinedField, status);
184 sb7.insertChar16(0, U16_LEAD(cas), kUndefinedField, status);
185 } else {
186 sb7.insertChar16(0, cas, kUndefinedField, status);
187 }
188 assertSuccess("Insert/append into sb7", status);
189 assertEqualsImpl(sb6, sb7);
190 }
191 }
192
testCopy()193 void FormattedStringBuilderTest::testCopy() {
194 UErrorCode status = U_ZERO_ERROR;
195 for (UnicodeString str : EXAMPLE_STRINGS) {
196 FormattedStringBuilder sb1;
197 sb1.append(str, kUndefinedField, status);
198 assertSuccess("Appending to sb1 first time", status);
199 FormattedStringBuilder sb2(sb1);
200 assertTrue("Content should equal itself", sb1.contentEquals(sb2));
201
202 sb1.append("12345", kUndefinedField, status);
203 assertSuccess("Appending to sb1 second time", status);
204 assertFalse("Content should no longer equal itself", sb1.contentEquals(sb2));
205 }
206 }
207
testFields()208 void FormattedStringBuilderTest::testFields() {
209 typedef FormattedStringBuilder::Field Field;
210 UErrorCode status = U_ZERO_ERROR;
211 // Note: This is a C++11 for loop that calls the UnicodeString constructor on each iteration.
212 for (UnicodeString str : EXAMPLE_STRINGS) {
213 FormattedValueStringBuilderImpl sbi(kUndefinedField);
214 FormattedStringBuilder& sb = sbi.getStringRef();
215 sb.append(str, kUndefinedField, status);
216 assertSuccess("Appending to sb", status);
217 sb.append(str, {UFIELD_CATEGORY_NUMBER, UNUM_CURRENCY_FIELD}, status);
218 assertSuccess("Appending to sb", status);
219 assertEquals("Reference string copied twice", str.length() * 2, sb.length());
220 for (int32_t i = 0; i < str.length(); i++) {
221 assertEquals("Null field first",
222 kUndefinedField.bits, sb.fieldAt(i).bits);
223 assertEquals("Currency field second",
224 Field(UFIELD_CATEGORY_NUMBER, UNUM_CURRENCY_FIELD).bits,
225 sb.fieldAt(i + str.length()).bits);
226 }
227
228 // Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
229 // Let NumberFormatTest also take care of FieldPositionIterator material.
230 FieldPosition fp(UNUM_CURRENCY_FIELD);
231 sbi.nextFieldPosition(fp, status);
232 assertSuccess("Populating the FieldPosition", status);
233 assertEquals("Currency start position", str.length(), fp.getBeginIndex());
234 assertEquals("Currency end position", str.length() * 2, fp.getEndIndex());
235
236 if (str.length() > 0) {
237 sb.insertCodePoint(2, 100, {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD}, status);
238 assertSuccess("Inserting code point into sb", status);
239 assertEquals("New length", str.length() * 2 + 1, sb.length());
240 assertEquals("Integer field",
241 Field(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD).bits,
242 sb.fieldAt(2).bits);
243 }
244
245 FormattedStringBuilder old(sb);
246 sb.append(old, status);
247 assertSuccess("Appending to myself", status);
248 int32_t numNull = 0;
249 int32_t numCurr = 0;
250 int32_t numInt = 0;
251 for (int32_t i = 0; i < sb.length(); i++) {
252 auto field = sb.fieldAt(i);
253 assertEquals("Field should equal location in old",
254 old.fieldAt(i % old.length()).bits, field.bits);
255 if (field == kUndefinedField) {
256 numNull++;
257 } else if (field == Field(UFIELD_CATEGORY_NUMBER, UNUM_CURRENCY_FIELD)) {
258 numCurr++;
259 } else if (field == Field(UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD)) {
260 numInt++;
261 } else {
262 errln("Encountered unknown field");
263 }
264 }
265 assertEquals("Number of null fields", str.length() * 2, numNull);
266 assertEquals("Number of currency fields", numNull, numCurr);
267 assertEquals("Number of integer fields", str.length() > 0 ? 2 : 0, numInt);
268 }
269 }
270
testUnlimitedCapacity()271 void FormattedStringBuilderTest::testUnlimitedCapacity() {
272 UErrorCode status = U_ZERO_ERROR;
273 FormattedStringBuilder builder;
274 // The builder should never fail upon repeated appends.
275 for (int i = 0; i < 1000; i++) {
276 UnicodeString message("Iteration #");
277 message += Int64ToUnicodeString(i);
278 assertEquals(message, builder.length(), i);
279 builder.appendCodePoint(u'x', kUndefinedField, status);
280 assertSuccess(message, status);
281 assertEquals(message, builder.length(), i + 1);
282 }
283 }
284
testCodePoints()285 void FormattedStringBuilderTest::testCodePoints() {
286 UErrorCode status = U_ZERO_ERROR;
287 FormattedStringBuilder nsb;
288 assertEquals("First is -1 on empty string", -1, nsb.getFirstCodePoint());
289 assertEquals("Last is -1 on empty string", -1, nsb.getLastCodePoint());
290 assertEquals("Length is 0 on empty string", 0, nsb.codePointCount());
291
292 nsb.append(u"q", kUndefinedField, status);
293 assertSuccess("Spot 1", status);
294 assertEquals("First is q", u'q', nsb.getFirstCodePoint());
295 assertEquals("Last is q", u'q', nsb.getLastCodePoint());
296 assertEquals("0th is q", u'q', nsb.codePointAt(0));
297 assertEquals("Before 1st is q", u'q', nsb.codePointBefore(1));
298 assertEquals("Code point count is 1", 1, nsb.codePointCount());
299
300 // is two char16s
301 nsb.append(u"", kUndefinedField, status);
302 assertSuccess("Spot 2" ,status);
303 assertEquals("First is still q", u'q', nsb.getFirstCodePoint());
304 assertEquals("Last is space ship", 128640, nsb.getLastCodePoint());
305 assertEquals("1st is space ship", 128640, nsb.codePointAt(1));
306 assertEquals("Before 1st is q", u'q', nsb.codePointBefore(1));
307 assertEquals("Before 3rd is space ship", 128640, nsb.codePointBefore(3));
308 assertEquals("Code point count is 2", 2, nsb.codePointCount());
309 }
310
assertEqualsImpl(const UnicodeString & a,const FormattedStringBuilder & b)311 void FormattedStringBuilderTest::assertEqualsImpl(const UnicodeString &a, const FormattedStringBuilder &b) {
312 // TODO: Why won't this compile without the IntlTest:: qualifier?
313 IntlTest::assertEquals("Lengths should be the same", a.length(), b.length());
314 IntlTest::assertEquals("Code point counts should be the same", a.countChar32(), b.codePointCount());
315
316 if (a.length() != b.length()) {
317 return;
318 }
319
320 for (int32_t i = 0; i < a.length(); i++) {
321 IntlTest::assertEquals(
322 UnicodeString(u"Char at position ") + Int64ToUnicodeString(i) +
323 UnicodeString(u" in \"") + a + UnicodeString("\" versus \"") +
324 b.toUnicodeString() + UnicodeString("\""), a.charAt(i), b.charAt(i));
325 }
326 }
327
328
createFormattedStringBuilderTest()329 extern IntlTest *createFormattedStringBuilderTest() {
330 return new FormattedStringBuilderTest();
331 }
332
333 #endif /* #if !UCONFIG_NO_FORMATTING */
334