1 // 2017 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 package com.ibm.icu.dev.test.format; 4 5 import java.math.BigDecimal; 6 import java.math.RoundingMode; 7 import java.text.ParseException; 8 import java.text.ParsePosition; 9 10 import org.junit.Test; 11 12 import com.ibm.icu.dev.test.TestUtil; 13 import com.ibm.icu.impl.number.DecimalFormatProperties; 14 import com.ibm.icu.impl.number.Padder.PadPosition; 15 import com.ibm.icu.impl.number.Parse; 16 import com.ibm.icu.impl.number.Parse.ParseMode; 17 import com.ibm.icu.impl.number.PatternStringParser; 18 import com.ibm.icu.impl.number.PatternStringUtils; 19 import com.ibm.icu.number.LocalizedNumberFormatter; 20 import com.ibm.icu.number.NumberFormatter; 21 import com.ibm.icu.text.DecimalFormat; 22 import com.ibm.icu.text.DecimalFormat.PropertySetter; 23 import com.ibm.icu.text.DecimalFormatSymbols; 24 import com.ibm.icu.util.CurrencyAmount; 25 import com.ibm.icu.util.ULocale; 26 27 public class NumberFormatDataDrivenTest { 28 29 private static ULocale EN = new ULocale("en"); 30 31 private static Number toNumber(String s) { 32 if (s.equals("NaN")) { 33 return Double.NaN; 34 } else if (s.equals("-Inf")) { 35 return Double.NEGATIVE_INFINITY; 36 } else if (s.equals("Inf")) { 37 return Double.POSITIVE_INFINITY; 38 } 39 return new BigDecimal(s); 40 } 41 42 // Android patch: Android can't access DecimalFormat_ICU58 for testing (b/33448125). 43 // That class lived in a package under test and relied on package access, but 44 // 1.) Android Compatibility Test Suite (CTS) run tests with a different ClassLoader, 45 // preventing package access, and 46 // 2.) By default, the OpenJDK 9 toolchain won't compile non-libcore code that in 47 // libcore packages (see http://b/68224249). 48 /* 49 private DataDrivenNumberFormatTestUtility.CodeUnderTest ICU58 = 50 new DataDrivenNumberFormatTestUtility.CodeUnderTest() { 51 @Override 52 public Character Id() { 53 return 'J'; 54 } 55 56 @Override 57 public String format(DataDrivenNumberFormatTestData tuple) { 58 DecimalFormat_ICU58 fmt = createDecimalFormat(tuple); 59 String actual = fmt.format(toNumber(tuple.format)); 60 String expected = tuple.output; 61 if (!expected.equals(actual)) { 62 return "Expected " + expected + ", got " + actual; 63 } 64 return null; 65 } 66 67 @Override 68 public String toPattern(DataDrivenNumberFormatTestData tuple) { 69 DecimalFormat_ICU58 fmt = createDecimalFormat(tuple); 70 StringBuilder result = new StringBuilder(); 71 if (tuple.toPattern != null) { 72 String expected = tuple.toPattern; 73 String actual = fmt.toPattern(); 74 if (!expected.equals(actual)) { 75 result.append("Expected toPattern=" + expected + ", got " + actual); 76 } 77 } 78 if (tuple.toLocalizedPattern != null) { 79 String expected = tuple.toLocalizedPattern; 80 String actual = fmt.toLocalizedPattern(); 81 if (!expected.equals(actual)) { 82 result.append("Expected toLocalizedPattern=" + expected + ", got " + actual); 83 } 84 } 85 return result.length() == 0 ? null : result.toString(); 86 } 87 88 @Override 89 public String parse(DataDrivenNumberFormatTestData tuple) { 90 DecimalFormat_ICU58 fmt = createDecimalFormat(tuple); 91 ParsePosition ppos = new ParsePosition(0); 92 Number actual = fmt.parse(tuple.parse, ppos); 93 if (ppos.getIndex() == 0) { 94 return "Parse failed; got " + actual + ", but expected " + tuple.output; 95 } 96 if (tuple.output.equals("fail")) { 97 return null; 98 } 99 Number expected = toNumber(tuple.output); 100 // number types cannot be compared, this is the best we can do. 101 if (expected.doubleValue() != actual.doubleValue() 102 && !Double.isNaN(expected.doubleValue()) 103 && !Double.isNaN(expected.doubleValue())) { 104 return "Expected: " + expected + ", got: " + actual; 105 } 106 return null; 107 } 108 109 @Override 110 public String parseCurrency(DataDrivenNumberFormatTestData tuple) { 111 DecimalFormat_ICU58 fmt = createDecimalFormat(tuple); 112 ParsePosition ppos = new ParsePosition(0); 113 CurrencyAmount currAmt = fmt.parseCurrency(tuple.parse, ppos); 114 if (ppos.getIndex() == 0) { 115 return "Parse failed; got " + currAmt + ", but expected " + tuple.output; 116 } 117 if (tuple.output.equals("fail")) { 118 return null; 119 } 120 Number expected = toNumber(tuple.output); 121 Number actual = currAmt.getNumber(); 122 // number types cannot be compared, this is the best we can do. 123 if (expected.doubleValue() != actual.doubleValue() 124 && !Double.isNaN(expected.doubleValue()) 125 && !Double.isNaN(expected.doubleValue())) { 126 return "Expected: " + expected + ", got: " + actual; 127 } 128 129 if (!tuple.outputCurrency.equals(currAmt.getCurrency().toString())) { 130 return "Expected currency: " + tuple.outputCurrency + ", got: " + currAmt.getCurrency(); 131 } 132 return null; 133 } 134 135 /** 136 * @param tuple 137 * @return 138 * 139 private DecimalFormat_ICU58 createDecimalFormat(DataDrivenNumberFormatTestData tuple) { 140 141 DecimalFormat_ICU58 fmt = 142 new DecimalFormat_ICU58( 143 tuple.pattern == null ? "0" : tuple.pattern, 144 new DecimalFormatSymbols(tuple.locale == null ? EN : tuple.locale)); 145 adjustDecimalFormat(tuple, fmt); 146 return fmt; 147 } 148 /** 149 * @param tuple 150 * @param fmt 151 * 152 private void adjustDecimalFormat( 153 DataDrivenNumberFormatTestData tuple, DecimalFormat_ICU58 fmt) { 154 if (tuple.minIntegerDigits != null) { 155 fmt.setMinimumIntegerDigits(tuple.minIntegerDigits); 156 } 157 if (tuple.maxIntegerDigits != null) { 158 fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits); 159 } 160 if (tuple.minFractionDigits != null) { 161 fmt.setMinimumFractionDigits(tuple.minFractionDigits); 162 } 163 if (tuple.maxFractionDigits != null) { 164 fmt.setMaximumFractionDigits(tuple.maxFractionDigits); 165 } 166 if (tuple.currency != null) { 167 fmt.setCurrency(tuple.currency); 168 } 169 if (tuple.minGroupingDigits != null) { 170 // Oops we don't support this. 171 } 172 if (tuple.useSigDigits != null) { 173 fmt.setSignificantDigitsUsed(tuple.useSigDigits != 0); 174 } 175 if (tuple.minSigDigits != null) { 176 fmt.setMinimumSignificantDigits(tuple.minSigDigits); 177 } 178 if (tuple.maxSigDigits != null) { 179 fmt.setMaximumSignificantDigits(tuple.maxSigDigits); 180 } 181 if (tuple.useGrouping != null) { 182 fmt.setGroupingUsed(tuple.useGrouping != 0); 183 } 184 if (tuple.multiplier != null) { 185 fmt.setMultiplier(tuple.multiplier); 186 } 187 if (tuple.roundingIncrement != null) { 188 fmt.setRoundingIncrement(tuple.roundingIncrement.doubleValue()); 189 } 190 if (tuple.formatWidth != null) { 191 fmt.setFormatWidth(tuple.formatWidth); 192 } 193 if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) { 194 fmt.setPadCharacter(tuple.padCharacter.charAt(0)); 195 } 196 if (tuple.useScientific != null) { 197 fmt.setScientificNotation(tuple.useScientific != 0); 198 } 199 if (tuple.grouping != null) { 200 fmt.setGroupingSize(tuple.grouping); 201 } 202 if (tuple.grouping2 != null) { 203 fmt.setSecondaryGroupingSize(tuple.grouping2); 204 } 205 if (tuple.roundingMode != null) { 206 fmt.setRoundingMode(tuple.roundingMode); 207 } 208 if (tuple.currencyUsage != null) { 209 fmt.setCurrencyUsage(tuple.currencyUsage); 210 } 211 if (tuple.minimumExponentDigits != null) { 212 fmt.setMinimumExponentDigits(tuple.minimumExponentDigits.byteValue()); 213 } 214 if (tuple.exponentSignAlwaysShown != null) { 215 fmt.setExponentSignAlwaysShown(tuple.exponentSignAlwaysShown != 0); 216 } 217 if (tuple.decimalSeparatorAlwaysShown != null) { 218 fmt.setDecimalSeparatorAlwaysShown(tuple.decimalSeparatorAlwaysShown != 0); 219 } 220 if (tuple.padPosition != null) { 221 fmt.setPadPosition(tuple.padPosition); 222 } 223 if (tuple.positivePrefix != null) { 224 fmt.setPositivePrefix(tuple.positivePrefix); 225 } 226 if (tuple.positiveSuffix != null) { 227 fmt.setPositiveSuffix(tuple.positiveSuffix); 228 } 229 if (tuple.negativePrefix != null) { 230 fmt.setNegativePrefix(tuple.negativePrefix); 231 } 232 if (tuple.negativeSuffix != null) { 233 fmt.setNegativeSuffix(tuple.negativeSuffix); 234 } 235 if (tuple.localizedPattern != null) { 236 fmt.applyLocalizedPattern(tuple.localizedPattern); 237 } 238 int lenient = tuple.lenient == null ? 1 : tuple.lenient.intValue(); 239 fmt.setParseStrict(lenient == 0); 240 if (tuple.parseIntegerOnly != null) { 241 fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0); 242 } 243 if (tuple.parseCaseSensitive != null) { 244 // Not supported. 245 } 246 if (tuple.decimalPatternMatchRequired != null) { 247 fmt.setDecimalPatternMatchRequired(tuple.decimalPatternMatchRequired != 0); 248 } 249 if (tuple.parseNoExponent != null) { 250 // Oops, not supported for now 251 } 252 } 253 }; 254 */ 255 // Android patch end. 256 257 private DataDrivenNumberFormatTestUtility.CodeUnderTest JDK = 258 new DataDrivenNumberFormatTestUtility.CodeUnderTest() { 259 @Override 260 public Character Id() { 261 return 'K'; 262 } 263 264 @Override 265 public String format(DataDrivenNumberFormatTestData tuple) { 266 java.text.DecimalFormat fmt = createDecimalFormat(tuple); 267 String actual = fmt.format(toNumber(tuple.format)); 268 String expected = tuple.output; 269 if (!expected.equals(actual)) { 270 return "Expected " + expected + ", got " + actual; 271 } 272 return null; 273 } 274 275 @Override 276 public String toPattern(DataDrivenNumberFormatTestData tuple) { 277 java.text.DecimalFormat fmt = createDecimalFormat(tuple); 278 StringBuilder result = new StringBuilder(); 279 if (tuple.toPattern != null) { 280 String expected = tuple.toPattern; 281 String actual = fmt.toPattern(); 282 if (!expected.equals(actual)) { 283 result.append("Expected toPattern=" + expected + ", got " + actual); 284 } 285 } 286 if (tuple.toLocalizedPattern != null) { 287 String expected = tuple.toLocalizedPattern; 288 String actual = fmt.toLocalizedPattern(); 289 if (!expected.equals(actual)) { 290 result.append("Expected toLocalizedPattern=" + expected + ", got " + actual); 291 } 292 } 293 return result.length() == 0 ? null : result.toString(); 294 } 295 296 @Override 297 public String parse(DataDrivenNumberFormatTestData tuple) { 298 java.text.DecimalFormat fmt = createDecimalFormat(tuple); 299 ParsePosition ppos = new ParsePosition(0); 300 Number actual = fmt.parse(tuple.parse, ppos); 301 if (ppos.getIndex() == 0) { 302 return "Parse failed; got " + actual + ", but expected " + tuple.output; 303 } 304 if (tuple.output.equals("fail")) { 305 return null; 306 } 307 Number expected = toNumber(tuple.output); 308 // number types cannot be compared, this is the best we can do. 309 if (expected.doubleValue() != actual.doubleValue() 310 && !Double.isNaN(expected.doubleValue()) 311 && !Double.isNaN(expected.doubleValue())) { 312 return "Expected: " + expected + ", got: " + actual; 313 } 314 return null; 315 } 316 317 /** 318 * @param tuple 319 * @return 320 */ 321 private java.text.DecimalFormat createDecimalFormat(DataDrivenNumberFormatTestData tuple) { 322 java.text.DecimalFormat fmt = 323 new java.text.DecimalFormat( 324 tuple.pattern == null ? "0" : tuple.pattern, 325 new java.text.DecimalFormatSymbols( 326 (tuple.locale == null ? EN : tuple.locale).toLocale())); 327 adjustDecimalFormat(tuple, fmt); 328 return fmt; 329 } 330 331 /** 332 * @param tuple 333 * @param fmt 334 */ 335 private void adjustDecimalFormat( 336 DataDrivenNumberFormatTestData tuple, java.text.DecimalFormat fmt) { 337 if (tuple.minIntegerDigits != null) { 338 fmt.setMinimumIntegerDigits(tuple.minIntegerDigits); 339 } 340 if (tuple.maxIntegerDigits != null) { 341 fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits); 342 } 343 if (tuple.minFractionDigits != null) { 344 fmt.setMinimumFractionDigits(tuple.minFractionDigits); 345 } 346 if (tuple.maxFractionDigits != null) { 347 fmt.setMaximumFractionDigits(tuple.maxFractionDigits); 348 } 349 if (tuple.currency != null) { 350 fmt.setCurrency(java.util.Currency.getInstance(tuple.currency.toString())); 351 } 352 if (tuple.minGroupingDigits != null) { 353 // Oops we don't support this. 354 } 355 if (tuple.useSigDigits != null) { 356 // Oops we don't support this 357 } 358 if (tuple.minSigDigits != null) { 359 // Oops we don't support this 360 } 361 if (tuple.maxSigDigits != null) { 362 // Oops we don't support this 363 } 364 if (tuple.useGrouping != null) { 365 fmt.setGroupingUsed(tuple.useGrouping != 0); 366 } 367 if (tuple.multiplier != null) { 368 fmt.setMultiplier(tuple.multiplier); 369 } 370 if (tuple.roundingIncrement != null) { 371 // Not supported 372 } 373 if (tuple.formatWidth != null) { 374 // Not supported 375 } 376 if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) { 377 // Not supported 378 } 379 if (tuple.useScientific != null) { 380 // Not supported 381 } 382 if (tuple.grouping != null) { 383 fmt.setGroupingSize(tuple.grouping); 384 } 385 if (tuple.grouping2 != null) { 386 // Not supported 387 } 388 if (tuple.roundingMode != null) { 389 // Not supported 390 } 391 if (tuple.currencyUsage != null) { 392 // Not supported 393 } 394 if (tuple.minimumExponentDigits != null) { 395 // Not supported 396 } 397 if (tuple.exponentSignAlwaysShown != null) { 398 // Not supported 399 } 400 if (tuple.decimalSeparatorAlwaysShown != null) { 401 fmt.setDecimalSeparatorAlwaysShown(tuple.decimalSeparatorAlwaysShown != 0); 402 } 403 if (tuple.padPosition != null) { 404 // Not supported 405 } 406 if (tuple.positivePrefix != null) { 407 fmt.setPositivePrefix(tuple.positivePrefix); 408 } 409 if (tuple.positiveSuffix != null) { 410 fmt.setPositiveSuffix(tuple.positiveSuffix); 411 } 412 if (tuple.negativePrefix != null) { 413 fmt.setNegativePrefix(tuple.negativePrefix); 414 } 415 if (tuple.negativeSuffix != null) { 416 fmt.setNegativeSuffix(tuple.negativeSuffix); 417 } 418 if (tuple.localizedPattern != null) { 419 fmt.applyLocalizedPattern(tuple.localizedPattern); 420 } 421 422 // lenient parsing not supported by JDK 423 if (tuple.parseIntegerOnly != null) { 424 fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0); 425 } 426 if (tuple.parseCaseSensitive != null) { 427 // Not supported. 428 } 429 if (tuple.decimalPatternMatchRequired != null) { 430 // Oops, not supported 431 } 432 if (tuple.parseNoExponent != null) { 433 // Oops, not supported for now 434 } 435 } 436 }; 437 438 static void propertiesFromTuple(DataDrivenNumberFormatTestData tuple, DecimalFormatProperties properties) { 439 if (tuple.minIntegerDigits != null) { 440 properties.setMinimumIntegerDigits(tuple.minIntegerDigits); 441 } 442 if (tuple.maxIntegerDigits != null) { 443 properties.setMaximumIntegerDigits(tuple.maxIntegerDigits); 444 } 445 if (tuple.minFractionDigits != null) { 446 properties.setMinimumFractionDigits(tuple.minFractionDigits); 447 } 448 if (tuple.maxFractionDigits != null) { 449 properties.setMaximumFractionDigits(tuple.maxFractionDigits); 450 } 451 if (tuple.currency != null) { 452 properties.setCurrency(tuple.currency); 453 } 454 if (tuple.minGroupingDigits != null) { 455 properties.setMinimumGroupingDigits(tuple.minGroupingDigits); 456 } 457 if (tuple.useSigDigits != null) { 458 // TODO 459 } 460 if (tuple.minSigDigits != null) { 461 properties.setMinimumSignificantDigits(tuple.minSigDigits); 462 } 463 if (tuple.maxSigDigits != null) { 464 properties.setMaximumSignificantDigits(tuple.maxSigDigits); 465 } 466 if (tuple.useGrouping != null && tuple.useGrouping == 0) { 467 properties.setGroupingSize(-1); 468 properties.setSecondaryGroupingSize(-1); 469 } 470 if (tuple.multiplier != null) { 471 properties.setMultiplier(new BigDecimal(tuple.multiplier)); 472 } 473 if (tuple.roundingIncrement != null) { 474 properties.setRoundingIncrement(new BigDecimal(tuple.roundingIncrement.toString())); 475 } 476 if (tuple.formatWidth != null) { 477 properties.setFormatWidth(tuple.formatWidth); 478 } 479 if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) { 480 properties.setPadString(tuple.padCharacter.toString()); 481 } 482 if (tuple.useScientific != null) { 483 properties.setMinimumExponentDigits( 484 tuple.useScientific != 0 ? 1 : -1); 485 } 486 if (tuple.grouping != null) { 487 properties.setGroupingSize(tuple.grouping); 488 } 489 if (tuple.grouping2 != null) { 490 properties.setSecondaryGroupingSize(tuple.grouping2); 491 } 492 if (tuple.roundingMode != null) { 493 properties.setRoundingMode(RoundingMode.valueOf(tuple.roundingMode)); 494 } 495 if (tuple.currencyUsage != null) { 496 properties.setCurrencyUsage(tuple.currencyUsage); 497 } 498 if (tuple.minimumExponentDigits != null) { 499 properties.setMinimumExponentDigits(tuple.minimumExponentDigits.byteValue()); 500 } 501 if (tuple.exponentSignAlwaysShown != null) { 502 properties.setExponentSignAlwaysShown(tuple.exponentSignAlwaysShown != 0); 503 } 504 if (tuple.decimalSeparatorAlwaysShown != null) { 505 properties.setDecimalSeparatorAlwaysShown(tuple.decimalSeparatorAlwaysShown != 0); 506 } 507 if (tuple.padPosition != null) { 508 properties.setPadPosition(PadPosition.fromOld(tuple.padPosition)); 509 } 510 if (tuple.positivePrefix != null) { 511 properties.setPositivePrefix(tuple.positivePrefix); 512 } 513 if (tuple.positiveSuffix != null) { 514 properties.setPositiveSuffix(tuple.positiveSuffix); 515 } 516 if (tuple.negativePrefix != null) { 517 properties.setNegativePrefix(tuple.negativePrefix); 518 } 519 if (tuple.negativeSuffix != null) { 520 properties.setNegativeSuffix(tuple.negativeSuffix); 521 } 522 if (tuple.localizedPattern != null) { 523 DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(tuple.locale); 524 String converted = PatternStringUtils.convertLocalized(tuple.localizedPattern, symbols, false); 525 PatternStringParser.parseToExistingProperties(converted, properties); 526 } 527 if (tuple.lenient != null) { 528 properties.setParseMode(tuple.lenient == 0 ? ParseMode.STRICT : ParseMode.LENIENT); 529 } 530 if (tuple.parseIntegerOnly != null) { 531 properties.setParseIntegerOnly(tuple.parseIntegerOnly != 0); 532 } 533 if (tuple.parseCaseSensitive != null) { 534 properties.setParseCaseSensitive(tuple.parseCaseSensitive != 0); 535 } 536 if (tuple.decimalPatternMatchRequired != null) { 537 properties.setDecimalPatternMatchRequired(tuple.decimalPatternMatchRequired != 0); 538 } 539 if (tuple.parseNoExponent != null) { 540 properties.setParseNoExponent(tuple.parseNoExponent != 0); 541 } 542 } 543 544 /** 545 * Formatting, but no other features. 546 */ 547 private DataDrivenNumberFormatTestUtility.CodeUnderTest ICU60 = 548 new DataDrivenNumberFormatTestUtility.CodeUnderTest() { 549 550 @Override 551 public Character Id() { 552 return 'Q'; 553 } 554 555 /** 556 * Runs a single formatting test. On success, returns null. On failure, returns the error. 557 * This implementation just returns null. Subclasses should override. 558 * 559 * @param tuple contains the parameters of the format test. 560 */ 561 @Override 562 public String format(DataDrivenNumberFormatTestData tuple) { 563 String pattern = (tuple.pattern == null) ? "0" : tuple.pattern; 564 ULocale locale = (tuple.locale == null) ? ULocale.ENGLISH : tuple.locale; 565 DecimalFormatProperties properties = 566 PatternStringParser.parseToProperties( 567 pattern, 568 tuple.currency != null 569 ? PatternStringParser.IGNORE_ROUNDING_ALWAYS 570 : PatternStringParser.IGNORE_ROUNDING_NEVER); 571 propertiesFromTuple(tuple, properties); 572 DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale); 573 LocalizedNumberFormatter fmt = NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(locale); 574 Number number = toNumber(tuple.format); 575 String expected = tuple.output; 576 String actual = fmt.format(number).toString(); 577 if (!expected.equals(actual)) { 578 return "Expected \"" + expected + "\", got \"" + actual + "\""; 579 } 580 return null; 581 } 582 }; 583 584 /** 585 * All features except formatting. 586 */ 587 private DataDrivenNumberFormatTestUtility.CodeUnderTest ICU60_Other = 588 new DataDrivenNumberFormatTestUtility.CodeUnderTest() { 589 590 @Override 591 public Character Id() { 592 return 'S'; 593 } 594 595 /** 596 * Runs a single toPattern test. On success, returns null. On failure, returns the error. This implementation 597 * just returns null. Subclasses should override. 598 * 599 * @param tuple 600 * contains the parameters of the format test. 601 */ 602 @Override 603 public String toPattern(DataDrivenNumberFormatTestData tuple) { 604 String pattern = (tuple.pattern == null) ? "0" : tuple.pattern; 605 final DecimalFormatProperties properties; 606 DecimalFormat df; 607 try { 608 properties = PatternStringParser.parseToProperties( 609 pattern, 610 tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS 611 : PatternStringParser.IGNORE_ROUNDING_NEVER); 612 propertiesFromTuple(tuple, properties); 613 // TODO: Use PatternString.propertiesToString() directly. (How to deal with CurrencyUsage?) 614 df = new DecimalFormat(); 615 df.setProperties(new PropertySetter() { 616 @Override 617 public void set(DecimalFormatProperties props) { 618 props.copyFrom(properties); 619 } 620 }); 621 } catch (IllegalArgumentException e) { 622 e.printStackTrace(); 623 return e.getLocalizedMessage(); 624 } 625 626 if (tuple.toPattern != null) { 627 String expected = tuple.toPattern; 628 String actual = df.toPattern(); 629 if (!expected.equals(actual)) { 630 return "Expected toPattern='" + expected + "'; got '" + actual + "'"; 631 } 632 } 633 if (tuple.toLocalizedPattern != null) { 634 String expected = tuple.toLocalizedPattern; 635 String actual = PatternStringUtils.propertiesToPatternString(properties); 636 if (!expected.equals(actual)) { 637 return "Expected toLocalizedPattern='" + expected + "'; got '" + actual + "'"; 638 } 639 } 640 return null; 641 } 642 643 /** 644 * Runs a single parse test. On success, returns null. On failure, returns the error. This implementation just 645 * returns null. Subclasses should override. 646 * 647 * @param tuple 648 * contains the parameters of the format test. 649 */ 650 @Override 651 public String parse(DataDrivenNumberFormatTestData tuple) { 652 String pattern = (tuple.pattern == null) ? "0" : tuple.pattern; 653 DecimalFormatProperties properties; 654 ParsePosition ppos = new ParsePosition(0); 655 Number actual; 656 try { 657 properties = PatternStringParser.parseToProperties( 658 pattern, 659 tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS 660 : PatternStringParser.IGNORE_ROUNDING_NEVER); 661 propertiesFromTuple(tuple, properties); 662 actual = Parse.parse(tuple.parse, ppos, properties, DecimalFormatSymbols.getInstance(tuple.locale)); 663 } catch (IllegalArgumentException e) { 664 return "parse exception: " + e.getMessage(); 665 } 666 if (actual == null && ppos.getIndex() != 0) { 667 throw new AssertionError("Error: value is null but parse position is not zero"); 668 } 669 if (ppos.getIndex() == 0) { 670 return "Parse failed; got " + actual + ", but expected " + tuple.output; 671 } 672 if (tuple.output.equals("NaN")) { 673 if (!Double.isNaN(actual.doubleValue())) { 674 return "Expected NaN, but got: " + actual; 675 } 676 return null; 677 } else if (tuple.output.equals("Inf")) { 678 if (!Double.isInfinite(actual.doubleValue()) || Double.compare(actual.doubleValue(), 0.0) < 0) { 679 return "Expected Inf, but got: " + actual; 680 } 681 return null; 682 } else if (tuple.output.equals("-Inf")) { 683 if (!Double.isInfinite(actual.doubleValue()) || Double.compare(actual.doubleValue(), 0.0) > 0) { 684 return "Expected -Inf, but got: " + actual; 685 } 686 return null; 687 } else if (tuple.output.equals("fail")) { 688 return null; 689 } else if (new BigDecimal(tuple.output).compareTo(new BigDecimal(actual.toString())) != 0) { 690 return "Expected: " + tuple.output + ", got: " + actual; 691 } else { 692 return null; 693 } 694 } 695 696 /** 697 * Runs a single parse currency test. On success, returns null. On failure, returns the error. This 698 * implementation just returns null. Subclasses should override. 699 * 700 * @param tuple 701 * contains the parameters of the format test. 702 */ 703 @Override 704 public String parseCurrency(DataDrivenNumberFormatTestData tuple) { 705 String pattern = (tuple.pattern == null) ? "0" : tuple.pattern; 706 DecimalFormatProperties properties; 707 ParsePosition ppos = new ParsePosition(0); 708 CurrencyAmount actual; 709 try { 710 properties = PatternStringParser.parseToProperties( 711 pattern, 712 tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS 713 : PatternStringParser.IGNORE_ROUNDING_NEVER); 714 propertiesFromTuple(tuple, properties); 715 actual = Parse 716 .parseCurrency(tuple.parse, ppos, properties, DecimalFormatSymbols.getInstance(tuple.locale)); 717 } catch (ParseException e) { 718 e.printStackTrace(); 719 return "parse exception: " + e.getMessage(); 720 } 721 if (ppos.getIndex() == 0 || actual.getCurrency().getCurrencyCode().equals("XXX")) { 722 return "Parse failed; got " + actual + ", but expected " + tuple.output; 723 } 724 BigDecimal expectedNumber = new BigDecimal(tuple.output); 725 if (expectedNumber.compareTo(new BigDecimal(actual.getNumber().toString())) != 0) { 726 return "Wrong number: Expected: " + expectedNumber + ", got: " + actual; 727 } 728 String expectedCurrency = tuple.outputCurrency; 729 if (!expectedCurrency.equals(actual.getCurrency().toString())) { 730 return "Wrong currency: Expected: " + expectedCurrency + ", got: " + actual; 731 } 732 return null; 733 } 734 735 /** 736 * Runs a single select test. On success, returns null. On failure, returns the error. This implementation just 737 * returns null. Subclasses should override. 738 * 739 * @param tuple 740 * contains the parameters of the format test. 741 */ 742 @Override 743 public String select(DataDrivenNumberFormatTestData tuple) { 744 return null; 745 } 746 }; 747 748 // Android patch: Android can't access DecimalFormat_ICU58 for testing (b/33448125). 749 /* 750 @Test 751 public void TestDataDrivenICU58() { 752 DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures( 753 "numberformattestspecification.txt", ICU58); 754 } 755 */ 756 // Android patch end. 757 758 // Note: This test case is really questionable. Depending on Java version, 759 // something may or may not work. However the test data assumes a specific 760 // Java runtime version. We should probably disable this test case - #13372 761 @Test 762 public void TestDataDrivenJDK() { 763 // Android implements java.text.DecimalFormat with ICU4J (ticket #13322). 764 // Oracle/OpenJDK 9's behavior is not exactly same with Oracle/OpenJDK 8. 765 // Some test cases failed on 8 work well, while some other test cases 766 // fail on 9, but worked on 8. Skip this test case if Java version is not 8. 767 org.junit.Assume.assumeTrue( 768 TestUtil.getJavaVendor() != TestUtil.JavaVendor.Android 769 && TestUtil.getJavaVersion() < 9); 770 771 DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures( 772 "numberformattestspecification.txt", JDK); 773 } 774 775 @Test 776 public void TestDataDrivenICULatest_Format() { 777 DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures( 778 "numberformattestspecification.txt", ICU60); 779 } 780 781 @Test 782 public void TestDataDrivenICULatest_Other() { 783 DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures( 784 "numberformattestspecification.txt", ICU60_Other); 785 } 786 } 787