Home | History | Annotate | Download | only in test
      1 //  2016 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html#License
      3 /*
      4  **********************************************************************
      5  * Copyright (c) 2006-2016, International Business Machines
      6  * Corporation and others.  All Rights Reserved.
      7  **********************************************************************
      8  * Created on 2006-4-21
      9  */
     10 package com.ibm.icu.dev.test;
     11 
     12 import java.util.Arrays;
     13 import java.util.HashMap;
     14 import java.util.Iterator;
     15 import java.util.Map;
     16 import java.util.MissingResourceException;
     17 import java.util.NoSuchElementException;
     18 
     19 import com.ibm.icu.impl.ICUResourceBundle;
     20 import com.ibm.icu.util.UResourceBundle;
     21 import com.ibm.icu.util.UResourceBundleIterator;
     22 import com.ibm.icu.util.UResourceTypeMismatchException;
     23 
     24 /**
     25  * Represents a collection of test data described in a UResourceBoundle file.
     26  *
     27  * The root of the UResourceBoundle file is a table resource, and it has one
     28  * Info and one TestData sub-resources. The Info describes the data module
     29  * itself. The TestData, which is a table resource, has a collection of test
     30  * data.
     31  *
     32  * The test data is a named table resource which has Info, Settings, Headers,
     33  * and Cases sub-resources.
     34  *
     35  * <pre>
     36  * DataModule:table(nofallback){
     37  *   Info:table {}
     38  *   TestData:table {
     39  *     entry_name:table{
     40  *       Info:table{}
     41  *       Settings:array{}
     42  *       Headers:array{}
     43  *       Cases:array{}
     44  *     }
     45  *   }
     46  * }
     47  * </pre>
     48  *
     49  * The test data is expected to be fed to test code by following sequence
     50  *
     51  *   for each setting in Setting{
     52  *       prepare the setting
     53  *     for each test data in Cases{
     54  *       perform the test
     55  *     }
     56  *   }
     57  *
     58  * For detail of the specification, please refer to the code. The code is
     59  * initially ported from "icu4c/source/tools/ctestfw/unicode/tstdtmod.h"
     60  * and should be maintained parallelly.
     61  *
     62  * @author Raymond Yang
     63  */
     64 class ResourceModule implements TestDataModule {
     65     private static final String INFO = "Info";
     66 //    private static final String DESCRIPTION = "Description";
     67 //    private static final String LONG_DESCRIPTION = "LongDescription";
     68     private static final String TEST_DATA = "TestData";
     69     private static final String SETTINGS = "Settings";
     70     private static final String HEADER = "Headers";
     71     private static final String DATA = "Cases";
     72 
     73 
     74     UResourceBundle res;
     75     UResourceBundle info;
     76     UResourceBundle defaultHeader;
     77     UResourceBundle testData;
     78 
     79     ResourceModule(String baseName, String localeName) throws DataModuleFormatError{
     80 
     81         res = (UResourceBundle) UResourceBundle.getBundleInstance(baseName, localeName,
     82                 getClass().getClassLoader());
     83         info = getFromTable(res, INFO, UResourceBundle.TABLE);
     84         testData = getFromTable(res, TEST_DATA, UResourceBundle.TABLE);
     85 
     86         try {
     87             // unfortunately, actually, data can be either ARRAY or STRING
     88             defaultHeader = getFromTable(info, HEADER, new int[]{UResourceBundle.ARRAY, UResourceBundle.STRING});
     89         } catch (MissingResourceException e){
     90             defaultHeader = null;
     91         }
     92     }
     93 
     94     public String getName() {
     95         return res.getKey();
     96     }
     97 
     98     public DataMap getInfo() {
     99         return new UTableResource(info);
    100     }
    101 
    102     public TestData getTestData(String testName) throws DataModuleFormatError {
    103         return new UResourceTestData(defaultHeader, testData.get(testName));
    104     }
    105 
    106     public Iterator getTestDataIterator() {
    107         return new IteratorAdapter(testData){
    108             protected Object prepareNext(UResourceBundle nextRes) throws DataModuleFormatError {
    109                 return new UResourceTestData(defaultHeader, nextRes);
    110             }
    111         };
    112     }
    113 
    114     /**
    115      * To make UResourceBundleIterator works like Iterator
    116      * and return various data-driven test object for next() call
    117      *
    118      * @author Raymond Yang
    119      */
    120     private abstract static class IteratorAdapter implements Iterator{
    121         private UResourceBundle res;
    122         private UResourceBundleIterator itr;
    123         private Object preparedNextElement = null;
    124         // fix a strange behavior for UResourceBundleIterator for
    125         // UResourceBundle.STRING. It support hasNext(), but does
    126         // not support next() now.
    127         //
    128         // Use the iterated resource itself as the result from next() call
    129         private boolean isStrRes = false;
    130         private boolean isStrResPrepared = false; // for STRING resouce, we only prepare once
    131 
    132         IteratorAdapter(UResourceBundle theRes) {
    133             assert_not (theRes == null);
    134             res = theRes;
    135             itr = ((ICUResourceBundle)res).getIterator();
    136             isStrRes = res.getType() == UResourceBundle.STRING;
    137         }
    138 
    139         public void remove() {
    140             // do nothing
    141         }
    142 
    143         private boolean hasNextForStrRes(){
    144             assert_is (isStrRes);
    145             assert_not (!isStrResPrepared && preparedNextElement != null);
    146             if (isStrResPrepared && preparedNextElement != null) return true;
    147             if (isStrResPrepared && preparedNextElement == null) return false; // only prepare once
    148             assert_is (!isStrResPrepared && preparedNextElement == null);
    149 
    150             try {
    151                 preparedNextElement = prepareNext(res);
    152                 assert_not (preparedNextElement == null, "prepareNext() should not return null");
    153                 isStrResPrepared = true; // toggle the tag
    154                 return true;
    155             } catch (DataModuleFormatError e) {
    156                 throw new RuntimeException(e.getMessage(),e);
    157             }
    158         }
    159         public boolean hasNext() {
    160             if (isStrRes) return hasNextForStrRes();
    161 
    162             if (preparedNextElement != null) return true;
    163             UResourceBundle t = null;
    164             if (itr.hasNext()) {
    165                 // Notice, other RuntimeException may be throwed
    166                 t = itr.next();
    167             } else {
    168                 return false;
    169             }
    170 
    171             try {
    172                 preparedNextElement = prepareNext(t);
    173                 assert_not (preparedNextElement == null, "prepareNext() should not return null");
    174                 return true;
    175             } catch (DataModuleFormatError e) {
    176                 // Sadly, we throw RuntimeException also
    177                 throw new RuntimeException(e.getMessage(),e);
    178             }
    179         }
    180 
    181         public Object next(){
    182             if (hasNext()) {
    183                 Object t = preparedNextElement;
    184                 preparedNextElement = null;
    185                 return t;
    186             } else {
    187                 throw new NoSuchElementException();
    188             }
    189         }
    190         /**
    191          * To prepare data-driven test object for next() call, should not return null
    192          */
    193         abstract protected Object prepareNext(UResourceBundle nextRes) throws DataModuleFormatError;
    194     }
    195 
    196 
    197     /**
    198      * Avoid use Java 1.4 language new assert keyword
    199      */
    200     static void assert_is(boolean eq, String msg){
    201         if (!eq) throw new Error("test code itself has error: " + msg);
    202     }
    203     static void assert_is(boolean eq){
    204         if (!eq) throw new Error("test code itself has error.");
    205     }
    206     static void assert_not(boolean eq, String msg){
    207         assert_is(!eq, msg);
    208     }
    209     static void assert_not(boolean eq){
    210         assert_is(!eq);
    211     }
    212 
    213     /**
    214      * Internal helper function to get resource with following add-on
    215      *
    216      * 1. Assert the returned resource is never null.
    217      * 2. Check the type of resource.
    218      *
    219      * The UResourceTypeMismatchException for various get() method is a
    220      * RuntimeException which can be silently bypassed. This behavior is a
    221      * trouble. One purpose of the class is to enforce format checking for
    222      * resource file. We don't want to the exceptions are silently bypassed
    223      * and spreaded to our customer's code.
    224      *
    225      * Notice, the MissingResourceException for get() method is also a
    226      * RuntimeException. The caller functions should avoid sepread the execption
    227      * silently also. The behavior is modified because some resource are
    228      * optional and can be missed.
    229      */
    230     static UResourceBundle getFromTable(UResourceBundle res, String key, int expResType) throws DataModuleFormatError{
    231         return getFromTable(res, key, new int[]{expResType});
    232     }
    233 
    234     static UResourceBundle getFromTable(UResourceBundle res, String key, int[] expResTypes) throws DataModuleFormatError{
    235         assert_is (res != null && key != null && res.getType() == UResourceBundle.TABLE);
    236         UResourceBundle t = res.get(key);
    237 
    238         assert_not (t ==null);
    239         int type = t.getType();
    240         Arrays.sort(expResTypes);
    241         if (Arrays.binarySearch(expResTypes, type) >= 0) {
    242             return t;
    243         } else {
    244             throw new DataModuleFormatError(new UResourceTypeMismatchException("Actual type " + t.getType()
    245                     + " != expected types " + Arrays.toString(expResTypes) + "."));
    246         }
    247     }
    248 
    249     /**
    250      * Unfortunately, UResourceBundle is unable to treat one string as string array.
    251      * This function return a String[] from UResourceBundle, regardless it is an array or a string
    252      */
    253     static String[] getStringArrayHelper(UResourceBundle res, String key) throws DataModuleFormatError{
    254         UResourceBundle t = getFromTable(res, key, new int[]{UResourceBundle.ARRAY, UResourceBundle.STRING});
    255         return getStringArrayHelper(t);
    256     }
    257 
    258     static String[] getStringArrayHelper(UResourceBundle res) throws DataModuleFormatError{
    259         try{
    260             int type = res.getType();
    261             switch (type) {
    262             case UResourceBundle.ARRAY:
    263                 return res.getStringArray();
    264             case UResourceBundle.STRING:
    265                 return new String[]{res.getString()};
    266             default:
    267                 throw new UResourceTypeMismatchException("Only accept ARRAY and STRING types.");
    268             }
    269         } catch (UResourceTypeMismatchException e){
    270             throw new DataModuleFormatError(e);
    271         }
    272     }
    273 
    274     public static void main(String[] args){
    275         try {
    276             TestDataModule m = new ResourceModule("com/ibm/icu/dev/data/testdata/","DataDrivenCollationTest");
    277         System.out.println("hello: " + m.getName());
    278         m.getInfo();
    279         m.getTestDataIterator();
    280         } catch (DataModuleFormatError e) {
    281             // TODO Auto-generated catch block
    282             System.out.println("???");
    283             e.printStackTrace();
    284         }
    285     }
    286 
    287     private static class UResourceTestData implements TestData{
    288         private UResourceBundle res;
    289         private UResourceBundle info;
    290         private UResourceBundle settings;
    291         private UResourceBundle header;
    292         private UResourceBundle data;
    293 
    294         UResourceTestData(UResourceBundle defaultHeader, UResourceBundle theRes) throws DataModuleFormatError{
    295 
    296             assert_is (theRes != null && theRes.getType() == UResourceBundle.TABLE);
    297             res = theRes;
    298             // unfortunately, actually, data can be either ARRAY or STRING
    299             data = getFromTable(res, DATA, new int[]{UResourceBundle.ARRAY, UResourceBundle.STRING});
    300 
    301 
    302 
    303             try {
    304                 // unfortunately, actually, data can be either ARRAY or STRING
    305                 header = getFromTable(res, HEADER, new int[]{UResourceBundle.ARRAY, UResourceBundle.STRING});
    306             } catch (MissingResourceException e){
    307                 if (defaultHeader == null) {
    308                     throw new DataModuleFormatError("Unable to find a header for test data '" + res.getKey() + "' and no default header exist.");
    309                 } else {
    310                     header = defaultHeader;
    311                 }
    312             }
    313          try{
    314                 settings = getFromTable(res, SETTINGS, UResourceBundle.ARRAY);
    315                 info = getFromTable(res, INFO, UResourceBundle.TABLE);
    316             } catch (MissingResourceException e){
    317                 // do nothing, left them null;
    318                 settings = data;
    319             }
    320         }
    321 
    322         public String getName() {
    323             return res.getKey();
    324         }
    325 
    326         public DataMap getInfo() {
    327             return info == null ? null : new UTableResource(info);
    328         }
    329 
    330         public Iterator getSettingsIterator() {
    331             assert_is (settings.getType() == UResourceBundle.ARRAY);
    332             return new IteratorAdapter(settings){
    333                 protected Object prepareNext(UResourceBundle nextRes) throws DataModuleFormatError {
    334                     return new UTableResource(nextRes);
    335                 }
    336             };
    337         }
    338 
    339         public Iterator getDataIterator() {
    340             // unfortunately,
    341             assert_is (data.getType() == UResourceBundle.ARRAY
    342                  || data.getType() == UResourceBundle.STRING);
    343             return new IteratorAdapter(data){
    344                 protected Object prepareNext(UResourceBundle nextRes) throws DataModuleFormatError {
    345                     return new UArrayResource(header, nextRes);
    346                 }
    347             };
    348         }
    349     }
    350 
    351     private static class UTableResource implements DataMap{
    352         private UResourceBundle res;
    353 
    354         UTableResource(UResourceBundle theRes){
    355             res = theRes;
    356         }
    357         public String getString(String key) {
    358             String t;
    359             try{
    360                 t = res.getString(key);
    361             } catch (MissingResourceException e){
    362                 t = null;
    363             }
    364             return t;
    365         }
    366          public Object getObject(String key) {
    367 
    368             return res.get(key);
    369         }
    370     }
    371 
    372     private static class UArrayResource implements DataMap{
    373         private Map theMap;
    374         UArrayResource(UResourceBundle theHeader, UResourceBundle theData) throws DataModuleFormatError{
    375             assert_is (theHeader != null && theData != null);
    376             String[] header;
    377 
    378             header = getStringArrayHelper(theHeader);
    379             if (theData.getSize() != header.length)
    380                 throw new DataModuleFormatError("The count of Header and Data is mismatch.");
    381             theMap = new HashMap();
    382             for (int i = 0; i < header.length; i++) {
    383                 if(theData.getType()==UResourceBundle.ARRAY){
    384                     theMap.put(header[i], theData.get(i));
    385                 }else if(theData.getType()==UResourceBundle.STRING){
    386                     theMap.put(header[i], theData.getString());
    387                 }else{
    388                     throw new DataModuleFormatError("Did not get the expected data!");
    389                 }
    390             }
    391 
    392         }
    393 
    394         public String getString(String key) {
    395             Object o = theMap.get(key);
    396             UResourceBundle rb;
    397             if(o instanceof UResourceBundle) {
    398                 // unpack ResourceBundle strings
    399                 rb = (UResourceBundle)o;
    400                 return rb.getString();
    401             }
    402             return (String)o;
    403         }
    404         public Object getObject(String key) {
    405             return theMap.get(key);
    406         }
    407     }
    408 }
    409