Home | History | Annotate | Download | only in format
      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