Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 1 | // © 2016 and later: Unicode, Inc. and others. |
| 2 | // License & terms of use: http://www.unicode.org/copyright.html |
| 3 | /******************************************************************** |
| 4 | * COPYRIGHT: |
| 5 | * Copyright (c) 2015, International Business Machines Corporation and |
| 6 | * others. All Rights Reserved. |
| 7 | ********************************************************************/ |
| 8 | |
| 9 | #include "datadrivennumberformattestsuite.h" |
| 10 | |
| 11 | #if !UCONFIG_NO_FORMATTING |
| 12 | |
| 13 | #include "charstr.h" |
| 14 | #include "ucbuf.h" |
| 15 | #include "unicode/localpointer.h" |
| 16 | #include "ustrfmt.h" |
| 17 | |
| 18 | static UBool isCROrLF(UChar c) { return c == 0xa || c == 0xd; } |
| 19 | static UBool isSpace(UChar c) { return c == 9 || c == 0x20 || c == 0x3000; } |
| 20 | |
| 21 | void DataDrivenNumberFormatTestSuite::run(const char *fileName, UBool runAllTests) { |
| 22 | fFileLineNumber = 0; |
| 23 | fFormatTestNumber = 0; |
| 24 | UErrorCode status = U_ZERO_ERROR; |
| 25 | for (int32_t i = 0; i < UPRV_LENGTHOF(fPreviousFormatters); ++i) { |
| 26 | delete fPreviousFormatters[i]; |
| 27 | fPreviousFormatters[i] = newFormatter(status); |
| 28 | } |
| 29 | if (!assertSuccess("Can't create previous formatters", status)) { |
| 30 | return; |
| 31 | } |
| 32 | CharString path(getSourceTestData(status), status); |
| 33 | path.appendPathPart(fileName, status); |
| 34 | const char *codePage = "UTF-8"; |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 35 | LocalUCHARBUFPointer f(ucbuf_open(path.data(), &codePage, true, false, &status)); |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 36 | if (!assertSuccess("Can't open data file", status)) { |
| 37 | return; |
| 38 | } |
| 39 | UnicodeString columnValues[kNumberFormatTestTupleFieldCount]; |
| 40 | ENumberFormatTestTupleField columnTypes[kNumberFormatTestTupleFieldCount]; |
| 41 | int32_t columnCount = 0; |
| 42 | int32_t state = 0; |
| 43 | while(U_SUCCESS(status)) { |
| 44 | // Read a new line if necessary. |
| 45 | if(fFileLine.isEmpty()) { |
| 46 | if(!readLine(f.getAlias(), status)) { break; } |
| 47 | if (fFileLine.isEmpty() && state == 2) { |
| 48 | state = 0; |
| 49 | } |
| 50 | continue; |
| 51 | } |
| 52 | if (fFileLine.startsWith("//")) { |
| 53 | fFileLine.remove(); |
| 54 | continue; |
| 55 | } |
| 56 | // Initial setup of test. |
| 57 | if (state == 0) { |
| 58 | if (fFileLine.startsWith(UNICODE_STRING("test ", 5))) { |
| 59 | fFileTestName = fFileLine; |
| 60 | fTuple.clear(); |
| 61 | } else if(fFileLine.startsWith(UNICODE_STRING("set ", 4))) { |
| 62 | setTupleField(status); |
| 63 | } else if(fFileLine.startsWith(UNICODE_STRING("begin", 5))) { |
| 64 | state = 1; |
| 65 | } else { |
| 66 | showError("Unrecognized verb."); |
| 67 | return; |
| 68 | } |
| 69 | // column specification |
| 70 | } else if (state == 1) { |
| 71 | columnCount = splitBy(columnValues, UPRV_LENGTHOF(columnValues), 0x9); |
| 72 | for (int32_t i = 0; i < columnCount; ++i) { |
| 73 | columnTypes[i] = NumberFormatTestTuple::getFieldByName( |
| 74 | columnValues[i]); |
| 75 | if (columnTypes[i] == kNumberFormatTestTupleFieldCount) { |
| 76 | showError("Unrecognized field name."); |
| 77 | return; |
| 78 | } |
| 79 | } |
| 80 | state = 2; |
| 81 | // run the tests |
| 82 | } else { |
| 83 | int32_t columnsInThisRow = splitBy(columnValues, columnCount, 0x9); |
| 84 | for (int32_t i = 0; i < columnsInThisRow; ++i) { |
| 85 | fTuple.setField( |
| 86 | columnTypes[i], columnValues[i].unescape(), status); |
| 87 | } |
| 88 | for (int32_t i = columnsInThisRow; i < columnCount; ++i) { |
| 89 | fTuple.clearField(columnTypes[i], status); |
| 90 | } |
| 91 | if (U_FAILURE(status)) { |
| 92 | showError("Invalid column values"); |
| 93 | return; |
| 94 | } |
| 95 | if (runAllTests || !breaksC()) { |
| 96 | UnicodeString errorMessage; |
| 97 | UBool shouldFail = (NFTT_GET_FIELD(fTuple, output, "") == "fail") |
| 98 | ? !breaksC() |
| 99 | : breaksC(); |
| 100 | UBool actualSuccess = isPass(fTuple, errorMessage, status); |
| 101 | if (shouldFail && actualSuccess) { |
| 102 | showFailure("Expected failure, but passed: " + errorMessage); |
| 103 | break; |
| 104 | } else if (!shouldFail && !actualSuccess) { |
| 105 | showFailure(errorMessage); |
| 106 | break; |
| 107 | } |
| 108 | status = U_ZERO_ERROR; |
| 109 | } |
| 110 | } |
| 111 | fFileLine.remove(); |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | DataDrivenNumberFormatTestSuite::~DataDrivenNumberFormatTestSuite() { |
| 116 | for (int32_t i = 0; i < UPRV_LENGTHOF(fPreviousFormatters); ++i) { |
| 117 | delete fPreviousFormatters[i]; |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | UBool DataDrivenNumberFormatTestSuite::breaksC() { |
| 122 | return (NFTT_GET_FIELD(fTuple, breaks, "").toUpper().indexOf((UChar)0x43) != -1); |
| 123 | } |
| 124 | |
| 125 | void DataDrivenNumberFormatTestSuite::setTupleField(UErrorCode &status) { |
| 126 | if (U_FAILURE(status)) { |
| 127 | return; |
| 128 | } |
| 129 | UnicodeString parts[3]; |
| 130 | int32_t partCount = splitBy(parts, UPRV_LENGTHOF(parts), 0x20); |
| 131 | if (partCount < 3) { |
| 132 | showError("Set expects 2 parameters"); |
| 133 | status = U_PARSE_ERROR; |
| 134 | return; |
| 135 | } |
| 136 | if (!fTuple.setField( |
| 137 | NumberFormatTestTuple::getFieldByName(parts[1]), |
| 138 | parts[2].unescape(), |
| 139 | status)) { |
| 140 | showError("Invalid field value"); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | |
| 145 | int32_t |
| 146 | DataDrivenNumberFormatTestSuite::splitBy( |
| 147 | UnicodeString *columnValues, |
| 148 | int32_t columnValuesCount, |
| 149 | UChar delimiter) { |
| 150 | int32_t colIdx = 0; |
| 151 | int32_t colStart = 0; |
| 152 | int32_t len = fFileLine.length(); |
| 153 | for (int32_t idx = 0; colIdx < columnValuesCount - 1 && idx < len; ++idx) { |
| 154 | UChar ch = fFileLine.charAt(idx); |
| 155 | if (ch == delimiter) { |
| 156 | columnValues[colIdx++] = |
| 157 | fFileLine.tempSubString(colStart, idx - colStart); |
| 158 | colStart = idx + 1; |
| 159 | } |
| 160 | } |
| 161 | columnValues[colIdx++] = |
| 162 | fFileLine.tempSubString(colStart, len - colStart); |
| 163 | return colIdx; |
| 164 | } |
| 165 | |
| 166 | void DataDrivenNumberFormatTestSuite::showLineInfo() { |
| 167 | UnicodeString indent(" "); |
| 168 | infoln(indent + fFileTestName); |
| 169 | infoln(indent + fFileLine); |
| 170 | } |
| 171 | |
| 172 | void DataDrivenNumberFormatTestSuite::showError(const char *message) { |
| 173 | errln("line %d: %s", (int) fFileLineNumber, message); |
| 174 | showLineInfo(); |
| 175 | } |
| 176 | |
| 177 | void DataDrivenNumberFormatTestSuite::showFailure(const UnicodeString &message) { |
| 178 | UChar lineStr[20]; |
| 179 | uprv_itou( |
| 180 | lineStr, UPRV_LENGTHOF(lineStr), (uint32_t) fFileLineNumber, 10, 1); |
| 181 | UnicodeString fullMessage("line "); |
| 182 | dataerrln(fullMessage.append(lineStr).append(": ") |
| 183 | .append(prettify(message))); |
| 184 | showLineInfo(); |
| 185 | } |
| 186 | |
| 187 | UBool DataDrivenNumberFormatTestSuite::readLine( |
| 188 | UCHARBUF *f, UErrorCode &status) { |
| 189 | int32_t lineLength; |
| 190 | const UChar *line = ucbuf_readline(f, &lineLength, &status); |
| 191 | if(line == NULL || U_FAILURE(status)) { |
| 192 | if (U_FAILURE(status)) { |
| 193 | errln("Error reading line from file."); |
| 194 | } |
| 195 | fFileLine.remove(); |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 196 | return false; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 197 | } |
| 198 | ++fFileLineNumber; |
| 199 | // Strip trailing CR/LF, comments, and spaces. |
| 200 | while(lineLength > 0 && isCROrLF(line[lineLength - 1])) { --lineLength; } |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 201 | fFileLine.setTo(false, line, lineLength); |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 202 | while(lineLength > 0 && isSpace(line[lineLength - 1])) { --lineLength; } |
| 203 | if (lineLength == 0) { |
| 204 | fFileLine.remove(); |
| 205 | } |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 206 | return true; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 207 | } |
| 208 | |
| 209 | UBool DataDrivenNumberFormatTestSuite::isPass( |
| 210 | const NumberFormatTestTuple &tuple, |
| 211 | UnicodeString &appendErrorMessage, |
| 212 | UErrorCode &status) { |
| 213 | if (U_FAILURE(status)) { |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 214 | return false; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 215 | } |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 216 | UBool result = false; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 217 | if (tuple.formatFlag && tuple.outputFlag) { |
| 218 | ++fFormatTestNumber; |
| 219 | result = isFormatPass( |
| 220 | tuple, |
| 221 | fPreviousFormatters[ |
| 222 | fFormatTestNumber % UPRV_LENGTHOF(fPreviousFormatters)], |
| 223 | appendErrorMessage, |
| 224 | status); |
| 225 | } |
| 226 | else if (tuple.toPatternFlag || tuple.toLocalizedPatternFlag) { |
| 227 | result = isToPatternPass(tuple, appendErrorMessage, status); |
| 228 | } else if (tuple.parseFlag && tuple.outputFlag && tuple.outputCurrencyFlag) { |
| 229 | result = isParseCurrencyPass(tuple, appendErrorMessage, status); |
| 230 | |
| 231 | } else if (tuple.parseFlag && tuple.outputFlag) { |
| 232 | result = isParsePass(tuple, appendErrorMessage, status); |
| 233 | } else if (tuple.pluralFlag) { |
| 234 | result = isSelectPass(tuple, appendErrorMessage, status); |
| 235 | } else { |
| 236 | appendErrorMessage.append("Unrecognized test type."); |
| 237 | status = U_ILLEGAL_ARGUMENT_ERROR; |
| 238 | } |
| 239 | if (!result) { |
| 240 | if (appendErrorMessage.length() > 0) { |
| 241 | appendErrorMessage.append(": "); |
| 242 | } |
| 243 | if (U_FAILURE(status)) { |
| 244 | appendErrorMessage.append(u_errorName(status)); |
| 245 | appendErrorMessage.append(": "); |
| 246 | } |
| 247 | tuple.toString(appendErrorMessage); |
| 248 | } |
| 249 | return result; |
| 250 | } |
| 251 | |
| 252 | UBool DataDrivenNumberFormatTestSuite::isFormatPass( |
| 253 | const NumberFormatTestTuple & /* tuple */, |
| 254 | UnicodeString & /*appendErrorMessage*/, |
| 255 | UErrorCode &status) { |
| 256 | if (U_FAILURE(status)) { |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 257 | return false; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 258 | } |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 259 | return true; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 260 | } |
| 261 | |
| 262 | UBool DataDrivenNumberFormatTestSuite::isFormatPass( |
| 263 | const NumberFormatTestTuple &tuple, |
| 264 | UObject * /* somePreviousFormatter */, |
| 265 | UnicodeString &appendErrorMessage, |
| 266 | UErrorCode &status) { |
| 267 | return isFormatPass(tuple, appendErrorMessage, status); |
| 268 | } |
| 269 | |
| 270 | UObject *DataDrivenNumberFormatTestSuite::newFormatter( |
| 271 | UErrorCode & /*status*/) { |
| 272 | return NULL; |
| 273 | } |
| 274 | |
| 275 | UBool DataDrivenNumberFormatTestSuite::isToPatternPass( |
| 276 | const NumberFormatTestTuple & /* tuple */, |
| 277 | UnicodeString & /*appendErrorMessage*/, |
| 278 | UErrorCode &status) { |
| 279 | if (U_FAILURE(status)) { |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 280 | return false; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 281 | } |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 282 | return true; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 283 | } |
| 284 | |
| 285 | UBool DataDrivenNumberFormatTestSuite::isParsePass( |
| 286 | const NumberFormatTestTuple & /* tuple */, |
| 287 | UnicodeString & /*appendErrorMessage*/, |
| 288 | UErrorCode &status) { |
| 289 | if (U_FAILURE(status)) { |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 290 | return false; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 291 | } |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 292 | return true; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 293 | } |
| 294 | |
| 295 | UBool DataDrivenNumberFormatTestSuite::isParseCurrencyPass( |
| 296 | const NumberFormatTestTuple & /* tuple */, |
| 297 | UnicodeString & /*appendErrorMessage*/, |
| 298 | UErrorCode &status) { |
| 299 | if (U_FAILURE(status)) { |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 300 | return false; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 301 | } |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 302 | return true; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 303 | } |
| 304 | |
| 305 | UBool DataDrivenNumberFormatTestSuite::isSelectPass( |
| 306 | const NumberFormatTestTuple & /* tuple */, |
| 307 | UnicodeString & /*appendErrorMessage*/, |
| 308 | UErrorCode &status) { |
| 309 | if (U_FAILURE(status)) { |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 310 | return false; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 311 | } |
Frank Tang | 1f164ee | 2022-11-08 12:31:27 -0800 | [diff] [blame^] | 312 | return true; |
Frank Tang | 3e05d9d | 2021-11-08 14:04:04 -0800 | [diff] [blame] | 313 | } |
| 314 | #endif /* !UCONFIG_NO_FORMATTING */ |