xref: /aosp_15_r20/external/icu/icu4c/source/test/intltest/messageformat2test_utils.h (revision 0e209d3975ff4a8c132096b14b0e9364a753506e)
1 // © 2024 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 
4 #ifndef _TESTMESSAGEFORMAT2_UTILS
5 #define _TESTMESSAGEFORMAT2_UTILS
6 
7 #include "unicode/utypes.h"
8 
9 #if !UCONFIG_NO_FORMATTING
10 
11 #if !UCONFIG_NO_MF2
12 
13 #include "unicode/locid.h"
14 #include "unicode/messageformat2_formattable.h"
15 #include "unicode/messageformat2.h"
16 #include "intltest.h"
17 #include "messageformat2_macros.h"
18 #include "messageformat2_serializer.h"
19 
20 U_NAMESPACE_BEGIN namespace message2 {
21 
22 class TestCase : public UMemory {
23     private:
24     /* const */ UnicodeString testName;
25     /* const */ UnicodeString pattern;
26     /* const */ Locale locale;
27     /* const */ std::map<UnicodeString, Formattable> arguments;
28     /* const */ UErrorCode expectedError;
29     /* const */ bool expectedNoSyntaxError;
30     /* const */ bool hasExpectedOutput;
31     /* const */ UnicodeString expected;
32     /* const */ bool hasLineNumberAndOffset;
33     /* const */ uint32_t lineNumber;
34     /* const */ uint32_t offset;
35     /* const */ bool ignoreError;
36 
37     // Function registry is not owned by the TestCase object
38     const MFFunctionRegistry* functionRegistry = nullptr;
39 
40     public:
getPattern()41     const UnicodeString& getPattern() const { return pattern; }
getLocale()42     const Locale& getLocale() const { return locale; }
getArguments()43     std::map<UnicodeString, Formattable> getArguments() const { return std::move(arguments); }
getTestName()44     const UnicodeString& getTestName() const { return testName; }
expectSuccess()45     bool expectSuccess() const {
46         return (!ignoreError && U_SUCCESS(expectedError));
47     }
expectFailure()48     bool expectFailure() const {
49         return (!ignoreError && U_FAILURE(expectedError));
50     }
expectNoSyntaxError()51     bool expectNoSyntaxError() const {
52         return expectedNoSyntaxError;
53     }
expectedErrorCode()54     UErrorCode expectedErrorCode() const {
55         U_ASSERT(!expectSuccess());
56         return expectedError;
57     }
lineNumberAndOffsetMatch(uint32_t actualLine,uint32_t actualOffset)58     bool lineNumberAndOffsetMatch(uint32_t actualLine, uint32_t actualOffset) const {
59         return (!hasLineNumberAndOffset ||
60                 ((actualLine == lineNumber) && actualOffset == offset));
61     }
outputMatches(const UnicodeString & result)62     bool outputMatches(const UnicodeString& result) const {
63         return (!hasExpectedOutput || (expected == result));
64     }
expectedOutput()65     const UnicodeString& expectedOutput() const {
66         U_ASSERT(hasExpectedOutput);
67         return expected;
68     }
getLineNumber()69     uint32_t getLineNumber() const {
70         U_ASSERT(hasLineNumberAndOffset);
71         return lineNumber;
72     }
getOffset()73     uint32_t getOffset() const {
74         U_ASSERT(hasLineNumberAndOffset);
75         return offset;
76     }
hasCustomRegistry()77     bool hasCustomRegistry() const { return functionRegistry != nullptr; }
getCustomRegistry()78     const MFFunctionRegistry* getCustomRegistry() const {
79         U_ASSERT(hasCustomRegistry());
80         return functionRegistry;
81     }
82     TestCase(const TestCase&);
83     TestCase& operator=(TestCase&& other) noexcept = default;
84     virtual ~TestCase();
85 
86     class Builder : public UObject {
87         friend class TestCase;
88 
89         public:
setName(UnicodeString name)90         Builder& setName(UnicodeString name) { testName = name; return *this; }
setPattern(UnicodeString pat)91         Builder& setPattern(UnicodeString pat) { pattern = pat; return *this; }
setArgument(const UnicodeString & k,const UnicodeString & val)92         Builder& setArgument(const UnicodeString& k, const UnicodeString& val) {
93             arguments[k] = Formattable(val);
94             return *this;
95         }
setArgument(const UnicodeString & k,const Formattable * val,int32_t count)96         Builder& setArgument(const UnicodeString& k, const Formattable* val, int32_t count) {
97             U_ASSERT(val != nullptr);
98             arguments[k] = Formattable(val, count);
99             return *this;
100         }
setArgument(const UnicodeString & k,double val)101         Builder& setArgument(const UnicodeString& k, double val) {
102             arguments[k] = Formattable(val);
103             return *this;
104         }
setArgument(const UnicodeString & k,int64_t val)105         Builder& setArgument(const UnicodeString& k, int64_t val) {
106             arguments[k] = Formattable(val);
107             return *this;
108         }
setDateArgument(const UnicodeString & k,UDate date)109         Builder& setDateArgument(const UnicodeString& k, UDate date) {
110             arguments[k] = Formattable::forDate(date);
111             return *this;
112         }
setDecimalArgument(const UnicodeString & k,std::string_view decimal,UErrorCode & errorCode)113         Builder& setDecimalArgument(const UnicodeString& k, std::string_view decimal, UErrorCode& errorCode) {
114             THIS_ON_ERROR(errorCode);
115             arguments[k] = Formattable::forDecimal(decimal, errorCode);
116             return *this;
117         }
setArgument(const UnicodeString & k,const FormattableObject * val)118         Builder& setArgument(const UnicodeString& k, const FormattableObject* val) {
119             U_ASSERT(val != nullptr);
120             arguments[k] = Formattable(val);
121             return *this;
122         }
clearArguments()123         Builder& clearArguments() {
124             arguments.clear();
125             return *this;
126         }
setExpected(UnicodeString e)127         Builder& setExpected(UnicodeString e) {
128             hasExpectedOutput = true;
129             expected = e;
130             return *this;
131         }
clearExpected()132         Builder& clearExpected() {
133             hasExpectedOutput = false;
134             return *this;
135         }
setExpectedError(UErrorCode errorCode)136         Builder& setExpectedError(UErrorCode errorCode) {
137             expectedError = U_SUCCESS(errorCode) ? U_ZERO_ERROR : errorCode;
138             return *this;
139         }
setNoSyntaxError()140         Builder& setNoSyntaxError() {
141             expectNoSyntaxError = true;
142             return *this;
143         }
setExpectSuccess()144         Builder& setExpectSuccess() {
145             return setExpectedError(U_ZERO_ERROR);
146         }
setLocale(Locale && loc)147         Builder& setLocale(Locale&& loc) {
148             locale = loc;
149             return *this;
150         }
setExpectedLineNumberAndOffset(uint32_t line,uint32_t o)151         Builder& setExpectedLineNumberAndOffset(uint32_t line, uint32_t o) {
152             hasLineNumberAndOffset = true;
153             lineNumber = line;
154             offset = o;
155             return *this;
156         }
setIgnoreError()157         Builder& setIgnoreError() {
158             ignoreError = true;
159             return *this;
160         }
clearIgnoreError()161         Builder& clearIgnoreError() {
162             ignoreError = false;
163             return *this;
164         }
setFunctionRegistry(const MFFunctionRegistry * reg)165         Builder& setFunctionRegistry(const MFFunctionRegistry* reg) {
166             U_ASSERT(reg != nullptr);
167             functionRegistry = reg;
168             return *this;
169         }
build()170         TestCase build() const {
171             return TestCase(*this);
172         }
173         virtual ~Builder();
174 
175         private:
176         UnicodeString testName;
177         UnicodeString pattern;
178         Locale locale;
179         std::map<UnicodeString, Formattable> arguments;
180         bool hasExpectedOutput;
181         UnicodeString expected;
182         UErrorCode expectedError;
183         bool expectNoSyntaxError;
184         bool hasLineNumberAndOffset;
185         uint32_t lineNumber;
186         uint32_t offset;
187         bool ignoreError;
188         const MFFunctionRegistry* functionRegistry  = nullptr; // Not owned
189 
190         public:
Builder()191         Builder() : pattern(""), locale(Locale::getDefault()), hasExpectedOutput(false), expected(""), expectedError(U_ZERO_ERROR), expectNoSyntaxError(false), hasLineNumberAndOffset(false), ignoreError(false) {}
192     };
193 
194     private:
TestCase(const Builder & builder)195     TestCase(const Builder& builder) :
196         testName(builder.testName),
197         pattern(builder.pattern),
198         locale(builder.locale),
199         arguments(builder.arguments),
200         expectedError(builder.expectedError),
201         expectedNoSyntaxError(builder.expectNoSyntaxError),
202         hasExpectedOutput(builder.hasExpectedOutput),
203         expected(builder.expected),
204         hasLineNumberAndOffset(builder.hasLineNumberAndOffset),
205         lineNumber(builder.hasLineNumberAndOffset ? builder.lineNumber : 0),
206         offset(builder.hasLineNumberAndOffset ? builder.offset : 0),
207         ignoreError(builder.ignoreError),
208         functionRegistry(builder.functionRegistry) {
209         // If an error is not expected, then the expected
210         // output should be present
211         U_ASSERT(expectFailure() || expectNoSyntaxError() || hasExpectedOutput);
212     }
213 }; // class TestCase
214 
215 class TestUtils {
216     public:
217 
218     // Runs a single test case
runTestCase(IntlTest & tmsg,const TestCase & testCase,IcuTestErrorCode & errorCode)219     static void runTestCase(IntlTest& tmsg,
220                             const TestCase& testCase,
221                             IcuTestErrorCode& errorCode) {
222         CHECK_ERROR(errorCode);
223 
224         UParseError parseError;
225 	MessageFormatter::Builder mfBuilder(errorCode);
226         mfBuilder.setPattern(testCase.getPattern(), parseError, errorCode).setLocale(testCase.getLocale());
227 
228         if (testCase.hasCustomRegistry()) {
229             mfBuilder.setFunctionRegistry(*testCase.getCustomRegistry());
230         }
231 	MessageFormatter mf = mfBuilder.build(errorCode);
232         UnicodeString result;
233 
234         if (U_SUCCESS(errorCode)) {
235             result = mf.formatToString(MessageArguments(testCase.getArguments(), errorCode), errorCode);
236         }
237 
238         if (testCase.expectSuccess() || (testCase.expectedErrorCode() != U_MF_SYNTAX_ERROR
239                                          // For now, don't round-trip messages with these errors,
240                                          // since duplicate options are dropped
241                                          && testCase.expectedErrorCode() != U_MF_DUPLICATE_OPTION_NAME_ERROR)) {
242             const UnicodeString& in = mf.getNormalizedPattern();
243             UnicodeString out;
244             if (!roundTrip(in, mf.getDataModel(), out)) {
245                 failRoundTrip(tmsg, testCase, in, out);
246             }
247         }
248 
249         if (testCase.expectNoSyntaxError()) {
250             if (errorCode == U_MF_SYNTAX_ERROR) {
251                 failSyntaxError(tmsg, testCase);
252             }
253             errorCode.reset();
254             return;
255         }
256         if (testCase.expectSuccess() && U_FAILURE(errorCode)) {
257             failExpectedSuccess(tmsg, testCase, errorCode);
258             return;
259         }
260         if (testCase.expectFailure() && errorCode != testCase.expectedErrorCode()) {
261             failExpectedFailure(tmsg, testCase, errorCode);
262             return;
263         }
264         if (!testCase.lineNumberAndOffsetMatch(parseError.line, parseError.offset)) {
265             failWrongOffset(tmsg, testCase, parseError.line, parseError.offset);
266         }
267         if (!testCase.outputMatches(result)) {
268             failWrongOutput(tmsg, testCase, result);
269             return;
270         }
271         errorCode.reset();
272     }
273 
roundTrip(const UnicodeString & normalizedInput,const MFDataModel & dataModel,UnicodeString & result)274     static bool roundTrip(const UnicodeString& normalizedInput, const MFDataModel& dataModel, UnicodeString& result) {
275         Serializer(dataModel, result).serialize();
276         return (normalizedInput == result);
277     }
278 
failSyntaxError(IntlTest & tmsg,const TestCase & testCase)279     static void failSyntaxError(IntlTest& tmsg, const TestCase& testCase) {
280         tmsg.dataerrln(testCase.getTestName());
281         tmsg.logln(testCase.getTestName() + " failed test with pattern: " + testCase.getPattern() + " and error code U_MF_SYNTAX_WARNING; expected no syntax error");
282     }
283 
failExpectedSuccess(IntlTest & tmsg,const TestCase & testCase,IcuTestErrorCode & errorCode)284     static void failExpectedSuccess(IntlTest& tmsg, const TestCase& testCase, IcuTestErrorCode& errorCode) {
285         tmsg.dataerrln(testCase.getTestName());
286         tmsg.logln(testCase.getTestName() + " failed test with pattern: " + testCase.getPattern() + " and error code " + ((int32_t) errorCode));
287         errorCode.reset();
288     }
failExpectedFailure(IntlTest & tmsg,const TestCase & testCase,IcuTestErrorCode & errorCode)289     static void failExpectedFailure(IntlTest& tmsg, const TestCase& testCase, IcuTestErrorCode& errorCode) {
290         tmsg.dataerrln(testCase.getTestName());
291         tmsg.logln(testCase.getTestName() + " failed test with wrong error code; pattern: " + testCase.getPattern() + " and error code " + ((int32_t) errorCode) + "(expected error code: " + ((int32_t) testCase.expectedErrorCode()) + " )");
292         errorCode.reset();
293     }
failWrongOutput(IntlTest & tmsg,const TestCase & testCase,const UnicodeString & result)294     static void failWrongOutput(IntlTest& tmsg, const TestCase& testCase, const UnicodeString& result) {
295         tmsg.dataerrln(testCase.getTestName());
296         tmsg.logln(testCase.getTestName() + " failed test with wrong output; pattern: " + testCase.getPattern() + " and expected output = " + testCase.expectedOutput() + " and actual output = " + result);
297     }
298 
failRoundTrip(IntlTest & tmsg,const TestCase & testCase,const UnicodeString & in,const UnicodeString & output)299     static void failRoundTrip(IntlTest& tmsg, const TestCase& testCase, const UnicodeString& in, const UnicodeString& output) {
300         tmsg.dataerrln(testCase.getTestName());
301         tmsg.logln(testCase.getTestName() + " failed test with wrong output; normalized input = " + in + " serialized data model = " + output);
302     }
303 
failWrongOffset(IntlTest & tmsg,const TestCase & testCase,uint32_t actualLine,uint32_t actualOffset)304     static void failWrongOffset(IntlTest& tmsg, const TestCase& testCase, uint32_t actualLine, uint32_t actualOffset) {
305         tmsg.dataerrln("Test failed with wrong line or character offset in parse error; expected (line %d, offset %d), got (line %d, offset %d)", testCase.getLineNumber(), testCase.getOffset(),
306                   actualLine, actualOffset);
307         tmsg.logln(UnicodeString(testCase.getTestName()) + " pattern = " + testCase.getPattern() + " - failed by returning the wrong line number or offset in the parse error");
308     }
309 }; // class TestUtils
310 
311 } // namespace message2
312 U_NAMESPACE_END
313 
314 #endif /* #if !UCONFIG_NO_MF2 */
315 
316 #endif /* #if !UCONFIG_NO_FORMATTING */
317 
318 #endif
319