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