1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
|
// © 2024 and later: Unicode, Inc. and others.
// License & terms of use: https://www.unicode.org/copyright.html
#include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
#include "unicode/gregocal.h"
#include "messageformat2test.h"
using namespace icu::message2;
void
TestMessageFormat2::runIndexedTest(int32_t index, UBool exec,
const char* &name, char* /*par*/) {
TESTCASE_AUTO_BEGIN;
TESTCASE_AUTO(testAPICustomFunctions);
TESTCASE_AUTO(testCustomFunctions);
TESTCASE_AUTO(testAPI);
TESTCASE_AUTO(testAPISimple);
TESTCASE_AUTO(testDataModelAPI);
TESTCASE_AUTO(testFormatterAPI);
TESTCASE_AUTO(testHighLoneSurrogate);
TESTCASE_AUTO(testLowLoneSurrogate);
TESTCASE_AUTO(testLoneSurrogateInQuotedLiteral);
TESTCASE_AUTO(dataDrivenTests);
TESTCASE_AUTO_END;
}
// Needs more tests
void TestMessageFormat2::testDataModelAPI() {
IcuTestErrorCode errorCode1(*this, "testAPI");
UErrorCode errorCode = (UErrorCode) errorCode1;
using Pattern = data_model::Pattern;
Pattern::Builder builder(errorCode);
builder.add("a", errorCode);
builder.add("b", errorCode);
builder.add("c", errorCode);
Pattern p = builder.build(errorCode);
int32_t i = 0;
for (auto iter = p.begin(); iter != p.end(); ++iter) {
std::variant<UnicodeString, Expression, Markup> part = *iter;
UnicodeString val = *std::get_if<UnicodeString>(&part);
if (i == 0) {
assertEquals("testDataModelAPI", val, "a");
} else if (i == 1) {
assertEquals("testDataModelAPI", val, "b");
} else if (i == 2) {
assertEquals("testDataModelAPI", val, "c");
}
i++;
}
assertEquals("testDataModelAPI", i, 3);
}
// Needs more tests
void TestMessageFormat2::testFormatterAPI() {
IcuTestErrorCode errorCode(*this, "testFormatterAPI");
UnicodeString result;
UParseError parseError;
// Check that constructing the formatter fails
// if there's a syntax error
UnicodeString pattern = "{{}";
MessageFormatter::Builder mfBuilder(errorCode);
mfBuilder.setErrorHandlingBehavior(MessageFormatter::U_MF_BEST_EFFORT); // This shouldn't matter, since there's a syntax error
mfBuilder.setPattern(pattern, parseError, errorCode);
MessageFormatter mf = mfBuilder.build(errorCode);
errorCode.expectErrorAndReset(U_MF_SYNTAX_ERROR,
"testFormatterAPI: expected syntax error, best-effort error handling");
// Parsing is done when setPattern() is called,
// so setErrorHandlingBehavior(MessageFormatter::U_MF_STRICT) or setSuppressErrors must be called
// _before_ setPattern() to get the right behavior,
// and if either method is called after setting a pattern,
// setPattern() has to be called again.
// Should get the same behavior with strict errors
mfBuilder.setErrorHandlingBehavior(MessageFormatter::U_MF_STRICT);
// Force re-parsing, as above comment
mfBuilder.setPattern(pattern, parseError, errorCode);
mf = mfBuilder.build(errorCode);
errorCode.expectErrorAndReset(U_MF_SYNTAX_ERROR,
"testFormatterAPI: expected syntax error, strict error handling");
// Try the same thing for a pattern with a resolution error
pattern = "{{{$x}}}";
// Check that a pattern with a resolution error gives fallback output
mfBuilder.setErrorHandlingBehavior(MessageFormatter::U_MF_BEST_EFFORT);
mfBuilder.setPattern(pattern, parseError, errorCode);
mf = mfBuilder.build(errorCode);
errorCode.errIfFailureAndReset("testFormatterAPI: expected success from builder, best-effort error handling");
result = mf.formatToString(MessageArguments(), errorCode);
errorCode.errIfFailureAndReset("testFormatterAPI: expected success from formatter, best-effort error handling");
assertEquals("testFormatterAPI: fallback for message with unresolved variable",
result, "{$x}");
// Check that we do get an error with strict errors
mfBuilder.setErrorHandlingBehavior(MessageFormatter::U_MF_STRICT);
mf = mfBuilder.build(errorCode);
errorCode.errIfFailureAndReset("testFormatterAPI: builder should succeed with resolution error");
result = mf.formatToString(MessageArguments(), errorCode);
errorCode.expectErrorAndReset(U_MF_UNRESOLVED_VARIABLE_ERROR,
"testFormatterAPI: formatting should fail with resolution error and strict error handling");
// Finally, check a valid pattern
pattern = "hello";
mfBuilder.setPattern(pattern, parseError, errorCode);
mfBuilder.setErrorHandlingBehavior(MessageFormatter::U_MF_BEST_EFFORT);
mf = mfBuilder.build(errorCode);
errorCode.errIfFailureAndReset("testFormatterAPI: expected success from builder with valid pattern, best-effort error handling");
result = mf.formatToString(MessageArguments(), errorCode);
errorCode.errIfFailureAndReset("testFormatterAPI: expected success from formatter with valid pattern, best-effort error handling");
assertEquals("testFormatterAPI: wrong output with valid pattern, best-effort error handling",
result, "hello");
// Check that behavior is the same with strict errors
mfBuilder.setErrorHandlingBehavior(MessageFormatter::U_MF_STRICT);
mf = mfBuilder.build(errorCode);
errorCode.errIfFailureAndReset("testFormatterAPI: expected success from builder with valid pattern, strict error handling");
result = mf.formatToString(MessageArguments(), errorCode);
errorCode.errIfFailureAndReset("testFormatterAPI: expected success from formatter with valid pattern, strict error handling");
assertEquals("testFormatterAPI: wrong output with valid pattern, strict error handling",
result, "hello");
}
// Example for design doc -- version without null and error checks
void TestMessageFormat2::testAPISimple() {
IcuTestErrorCode errorCode1(*this, "testAPI");
UErrorCode errorCode = (UErrorCode) errorCode1;
UParseError parseError;
Locale locale = "en_US";
// Since this is the example used in the
// design doc, it elides null checks and error checks.
// To be used in the test suite, it should include those checks
// Null checks and error checks elided
MessageFormatter::Builder builder(errorCode);
MessageFormatter mf = builder.setPattern(u"Hello, {$userName}!", parseError, errorCode)
.build(errorCode);
std::map<UnicodeString, message2::Formattable> argsBuilder;
argsBuilder["userName"] = message2::Formattable("John");
MessageArguments args(argsBuilder, errorCode);
UnicodeString result;
result = mf.formatToString(args, errorCode);
assertEquals("testAPI", result, "Hello, John!");
mf = builder.setPattern("Today is {$today :date style=full}.", parseError, errorCode)
.setLocale(locale)
.build(errorCode);
GregorianCalendar cal(errorCode);
// Sunday, October 28, 2136 8:39:12 AM PST
cal.set(2136, Calendar::OCTOBER, 28, 8, 39, 12);
argsBuilder.clear();
DateInfo dateInfo = { cal.getTime(errorCode),
"Pacific Standard Time" };
argsBuilder["today"] = message2::Formattable(std::move(dateInfo));
args = MessageArguments(argsBuilder, errorCode);
result = mf.formatToString(args, errorCode);
assertEquals("testAPI", "Today is Sunday, October 28, 2136.", result);
argsBuilder.clear();
argsBuilder["photoCount"] = message2::Formattable(static_cast<int64_t>(12));
argsBuilder["userGender"] = message2::Formattable("feminine");
argsBuilder["userName"] = message2::Formattable("Maria");
args = MessageArguments(argsBuilder, errorCode);
mf = builder.setPattern(".input {$photoCount :number} .input {$userGender :string}\n\
.match $photoCount $userGender\n \
1 masculine {{{$userName} added a new photo to his album.}}\n \
1 feminine {{{$userName} added a new photo to her album.}}\n \
1 * {{{$userName} added a new photo to their album.}}\n \
* masculine {{{$userName} added {$photoCount} photos to his album.}}\n \
* feminine {{{$userName} added {$photoCount} photos to her album.}}\n \
* * {{{$userName} added {$photoCount} photos to their album.}}", parseError, errorCode)
.setLocale(locale)
.build(errorCode);
result = mf.formatToString(args, errorCode);
assertEquals("testAPI", "Maria added 12 photos to her album.", result);
}
// Design doc example, with more details
void TestMessageFormat2::testAPI() {
IcuTestErrorCode errorCode(*this, "testAPI");
TestCase::Builder testBuilder;
// Pattern: "Hello, {$userName}!"
TestCase test(testBuilder.setName("testAPI")
.setPattern("Hello, {$userName}!")
.setArgument("userName", "John")
.setExpected("Hello, John!")
.setLocale("en_US")
.build());
TestUtils::runTestCase(*this, test, errorCode);
// Pattern: "{Today is {$today ..."
LocalPointer<Calendar> cal(Calendar::createInstance(errorCode));
// Sunday, October 28, 2136 8:39:12 AM PST
cal->set(2136, Calendar::OCTOBER, 28, 8, 39, 12);
UDate date = cal->getTime(errorCode);
test = testBuilder.setName("testAPI")
.setPattern("Today is {$today :date style=full}.")
.setDateArgument("today", date)
.setExpected("Today is Sunday, October 28, 2136.")
.setLocale("en_US")
.build();
TestUtils::runTestCase(*this, test, errorCode);
// Pattern matching - plural
UnicodeString pattern = ".input {$photoCount :number} .input {$userGender :string}\n\
.match $photoCount $userGender\n\
1 masculine {{{$userName} added a new photo to his album.}}\n \
1 feminine {{{$userName} added a new photo to her album.}}\n \
1 * {{{$userName} added a new photo to their album.}}\n \
* masculine {{{$userName} added {$photoCount} photos to his album.}}\n \
* feminine {{{$userName} added {$photoCount} photos to her album.}}\n \
* * {{{$userName} added {$photoCount} photos to their album.}}";
int64_t photoCount = 12;
test = testBuilder.setName("testAPI")
.setPattern(pattern)
.setArgument("photoCount", photoCount)
.setArgument("userGender", "feminine")
.setArgument("userName", "Maria")
.setExpected("Maria added 12 photos to her album.")
.setLocale("en_US")
.build();
TestUtils::runTestCase(*this, test, errorCode);
// Built-in functions
pattern = ".input {$photoCount :number} .input {$userGender :string}\n\
.match $photoCount $userGender\n \
1 masculine {{{$userName} added a new photo to his album.}}\n \
1 feminine {{{$userName} added a new photo to her album.}}\n \
1 * {{{$userName} added a new photo to their album.}}\n \
* masculine {{{$userName} added {$photoCount} photos to his album.}}\n \
* feminine {{{$userName} added {$photoCount} photos to her album.}}\n \
* * {{{$userName} added {$photoCount} photos to their album.}}";
photoCount = 1;
test = testBuilder.setName("testAPI")
.setPattern(pattern)
.setArgument("photoCount", photoCount)
.setArgument("userGender", "feminine")
.setArgument("userName", "Maria")
.setExpected("Maria added a new photo to her album.")
.setLocale("en_US")
.build();
TestUtils::runTestCase(*this, test, errorCode);
}
// Custom functions example from the ICU4C API design doc
// Note: error/null checks are omitted
void TestMessageFormat2::testAPICustomFunctions() {
IcuTestErrorCode errorCode1(*this, "testAPICustomFunctions");
UErrorCode errorCode = (UErrorCode) errorCode1;
UParseError parseError;
Locale locale = "en_US";
// Set up custom function registry
MFFunctionRegistry::Builder builder(errorCode);
MFFunctionRegistry functionRegistry =
builder.adoptFormatter(data_model::FunctionName("person"), new PersonNameFormatterFactory(), errorCode)
.build();
Person* person = new Person(UnicodeString("Mr."), UnicodeString("John"), UnicodeString("Doe"));
std::map<UnicodeString, message2::Formattable> argsBuilder;
argsBuilder["name"] = message2::Formattable(person);
MessageArguments arguments(argsBuilder, errorCode);
MessageFormatter::Builder mfBuilder(errorCode);
UnicodeString result;
// This fails, because we did not provide a function registry:
MessageFormatter mf = mfBuilder.setErrorHandlingBehavior(MessageFormatter::U_MF_STRICT)
.setPattern("Hello {$name :person formality=informal}",
parseError, errorCode)
.setLocale(locale)
.build(errorCode);
result = mf.formatToString(arguments, errorCode);
assertEquals("testAPICustomFunctions", U_MF_UNKNOWN_FUNCTION_ERROR, errorCode);
errorCode = U_ZERO_ERROR;
mfBuilder.setFunctionRegistry(functionRegistry).setLocale(locale);
mf = mfBuilder.setPattern("Hello {$name :person formality=informal}", parseError, errorCode)
.build(errorCode);
result = mf.formatToString(arguments, errorCode);
assertEquals("testAPICustomFunctions", "Hello John", result);
mf = mfBuilder.setPattern("Hello {$name :person formality=formal}", parseError, errorCode)
.build(errorCode);
result = mf.formatToString(arguments, errorCode);
assertEquals("testAPICustomFunctions", "Hello Mr. Doe", result);
mf = mfBuilder.setPattern("Hello {$name :person formality=formal length=long}", parseError, errorCode)
.build(errorCode);
result = mf.formatToString(arguments, errorCode);
assertEquals("testAPICustomFunctions", "Hello Mr. John Doe", result);
// By type
MFFunctionRegistry::Builder builderByType(errorCode);
FunctionName personFormatterName("person");
MFFunctionRegistry functionRegistryByType =
builderByType.adoptFormatter(personFormatterName,
new PersonNameFormatterFactory(),
errorCode)
.setDefaultFormatterNameByType("person",
personFormatterName,
errorCode)
.build();
mfBuilder.setFunctionRegistry(functionRegistryByType);
mf = mfBuilder.setPattern("Hello {$name}", parseError, errorCode)
.setLocale(locale)
.build(errorCode);
result = mf.formatToString(arguments, errorCode);
assertEquals("testAPICustomFunctions", U_ZERO_ERROR, errorCode);
// Expect "Hello John" because in the custom function we registered,
// "informal" is the default formality and "length" is the default length
assertEquals("testAPICustomFunctions", "Hello John", result);
delete person;
}
// ICU-22890 lone surrogate cause infinity loop
void TestMessageFormat2::testHighLoneSurrogate() {
IcuTestErrorCode errorCode(*this, "testHighLoneSurrogate");
UParseError pe = { 0, 0, {0}, {0} };
// Lone surrogate with only high surrogate
UnicodeString loneSurrogate({0xda02, 0});
icu::message2::MessageFormatter msgfmt1 =
icu::message2::MessageFormatter::Builder(errorCode)
.setPattern(loneSurrogate, pe, errorCode)
.build(errorCode);
UnicodeString result = msgfmt1.formatToString({}, errorCode);
assertEquals("testHighLoneSurrogate", loneSurrogate, result);
errorCode.errIfFailureAndReset("testHighLoneSurrogate");
}
// ICU-22890 lone surrogate cause infinity loop
void TestMessageFormat2::testLowLoneSurrogate() {
IcuTestErrorCode errorCode(*this, "testLowLoneSurrogate");
UParseError pe = { 0, 0, {0}, {0} };
// Lone surrogate with only low surrogate
UnicodeString loneSurrogate({0xdc02, 0});
icu::message2::MessageFormatter msgfmt2 =
icu::message2::MessageFormatter::Builder(errorCode)
.setPattern(loneSurrogate, pe, errorCode)
.build(errorCode);
UnicodeString result = msgfmt2.formatToString({}, errorCode);
assertEquals("testLowLoneSurrogate", loneSurrogate, result);
errorCode.errIfFailureAndReset("testLowLoneSurrogate");
}
void TestMessageFormat2::testLoneSurrogateInQuotedLiteral() {
IcuTestErrorCode errorCode(*this, "testLoneSurrogateInQuotedLiteral");
UParseError pe = { 0, 0, {0}, {0} };
// |\udc02|
UnicodeString literal("{|");
literal += 0xdc02;
literal += "|}";
UnicodeString expectedResult({0xdc02, 0});
icu::message2::MessageFormatter msgfmt2 =
icu::message2::MessageFormatter::Builder(errorCode)
.setPattern(literal, pe, errorCode)
.build(errorCode);
UnicodeString result = msgfmt2.formatToString({}, errorCode);
assertEquals("testLoneSurrogateInQuotedLiteral", expectedResult, result);
errorCode.errIfFailureAndReset("testLoneSurrogateInQuotedLiteral");
}
void TestMessageFormat2::dataDrivenTests() {
IcuTestErrorCode errorCode(*this, "jsonTests");
jsonTestsFromFiles(errorCode);
}
TestCase::~TestCase() {}
TestCase::Builder::~Builder() {}
#endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
|