Home | History | Annotate | Download | only in testing
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package libcore.tzdata.testing;
     17 
     18 import java.io.ByteArrayOutputStream;
     19 import java.nio.ByteBuffer;
     20 import java.nio.charset.StandardCharsets;
     21 import java.util.ArrayList;
     22 import java.util.HashMap;
     23 import java.util.List;
     24 import java.util.Map;
     25 
     26 /**
     27  * Helps with creating valid and invalid test data.
     28  */
     29 public class ZoneInfoTestHelper {
     30 
     31     private ZoneInfoTestHelper() {}
     32 
     33     /**
     34      * Constructs valid and invalid zic data for tests.
     35      */
     36     public static class ZicDataBuilder {
     37 
     38         private int magic = 0x545a6966; // Default, valid magic.
     39         private Integer transitionCountOverride; // Used to override the correct transition count.
     40         private int[] transitionTimes; // Time of each transition, one per transition.
     41         private byte[] transitionTypes; // Type of each transition, one per transition.
     42         private Integer typesCountOverride; // Used to override the correct type count.
     43         private int[] isDsts; // Whether a type uses DST, one per type.
     44         private int[] offsetsSeconds; // The UTC offset, one per type.
     45 
     46         public ZicDataBuilder() {}
     47 
     48         public ZicDataBuilder setMagic(int magic) {
     49             this.magic = magic;
     50             return this;
     51         }
     52 
     53         public ZicDataBuilder setTypeCountOverride(int typesCountOverride) {
     54             this.typesCountOverride = typesCountOverride;
     55             return this;
     56         }
     57 
     58         public ZicDataBuilder setTransitionCountOverride(int transitionCountOverride) {
     59             this.transitionCountOverride = transitionCountOverride;
     60             return this;
     61         }
     62 
     63         /**
     64          * See {@link #setTransitions(int[][])} and {@link #setTypes(int[][])}.
     65          */
     66         public ZicDataBuilder setTransitionsAndTypes(
     67                 int[][] transitionPairs, int[][] typePairs) {
     68             setTransitions(transitionPairs);
     69             setTypes(typePairs);
     70             return this;
     71         }
     72         /**
     73          * Sets transition information using an array of pairs of ints. e.g.
     74          *
     75          * new int[][] {
     76          *   { transitionTimeSeconds1, typeIndex1 },
     77          *   { transitionTimeSeconds2, typeIndex1 },
     78          * }
     79          */
     80         public ZicDataBuilder setTransitions(int[][] transitionPairs) {
     81             int[] transitions = new int[transitionPairs.length];
     82             byte[] types = new byte[transitionPairs.length];
     83             for (int i = 0; i < transitionPairs.length; i++) {
     84                 transitions[i] = transitionPairs[i][0];
     85                 types[i] = (byte) transitionPairs[i][1];
     86             }
     87             this.transitionTimes = transitions;
     88             this.transitionTypes = types;
     89             return this;
     90         }
     91 
     92         /**
     93          * Sets transition information using an array of pairs of ints. e.g.
     94          *
     95          * new int[][] {
     96          *   { typeIsDst1, offsetSeconds1 },
     97          *   { typeIsDst2, offsetSeconds2 },
     98          * }
     99          */
    100         public ZicDataBuilder setTypes(int[][] typePairs) {
    101             int[] isDsts = new int[typePairs.length];
    102             int[] offsetSeconds = new int[typePairs.length];
    103             for (int i = 0; i < typePairs.length; i++) {
    104                 offsetSeconds[i] = typePairs[i][0];
    105                 isDsts[i] = typePairs[i][1];
    106             }
    107             this.isDsts = isDsts;
    108             this.offsetsSeconds = offsetSeconds;
    109             return this;
    110         }
    111 
    112         /** Initializes to a minimum viable ZoneInfo data. */
    113         public ZicDataBuilder initializeToValid() {
    114             setTransitions(new int[0][0]);
    115             setTypes(new int[][] {
    116                     { 3600, 0}
    117             });
    118             return this;
    119         }
    120 
    121         public byte[] build() {
    122             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    123 
    124             // Magic number.
    125             writeInt(baos, magic);
    126 
    127             // Some useless stuff in the header.
    128             for (int i = 0; i < 28; ++i) {
    129                 baos.write(i);
    130             }
    131 
    132             // Transition time count
    133             int transitionsCount = transitionCountOverride != null
    134                     ? transitionCountOverride : transitionTimes.length;
    135             writeInt(baos, transitionsCount);
    136 
    137             // Transition type count.
    138             int typesCount = typesCountOverride != null
    139                     ? typesCountOverride : offsetsSeconds.length;
    140             writeInt(baos, typesCount);
    141             // Useless stuff.
    142             writeInt(baos, 0xdeadbeef);
    143 
    144             // Transition time array, as ints.
    145             writeIntArray(baos, transitionTimes);
    146 
    147             // Transition type array.
    148             writeByteArray(baos, transitionTypes);
    149 
    150             // Offset / DST
    151             for (int i = 0; i < offsetsSeconds.length; i++) {
    152                 writeInt(baos, offsetsSeconds[i]);
    153                 writeByte(baos, isDsts[i]);
    154                 // Useless stuff.
    155                 writeByte(baos, i);
    156             }
    157             return baos.toByteArray();
    158         }
    159     }
    160 
    161     /**
    162      * Constructs valid and invalid tzdata files for tests. See also ZoneCompactor class in
    163      * system/timezone/zone_compactor which is the real thing.
    164      */
    165     public static class TzDataBuilder {
    166 
    167         private String headerMagic;
    168         // A list is used in preference to a Map to allow simulation of badly ordered / duplicate
    169         // IDs.
    170         private List<ZicDatum> zicData = new ArrayList<>();
    171         private String zoneTab;
    172         private Integer indexOffsetOverride;
    173         private Integer dataOffsetOverride;
    174         private Integer zoneTabOffsetOverride;
    175 
    176         public TzDataBuilder() {}
    177 
    178         /** Sets the header. A valid header is in the form "tzdata2016g". */
    179         public TzDataBuilder setHeaderMagic(String headerMagic) {
    180             this.headerMagic = headerMagic;
    181             return this;
    182         }
    183 
    184         public TzDataBuilder setIndexOffsetOverride(int indexOffset) {
    185             this.indexOffsetOverride = indexOffset;
    186             return this;
    187         }
    188 
    189         public TzDataBuilder setDataOffsetOverride(int dataOffset) {
    190             this.dataOffsetOverride = dataOffset;
    191             return this;
    192         }
    193 
    194         public TzDataBuilder setZoneTabOffsetOverride(int zoneTabOffset) {
    195             this.zoneTabOffsetOverride = zoneTabOffset;
    196             return this;
    197         }
    198 
    199         /**
    200          * Adds data for a new zone. These must be added in ID string order to generate
    201          * a valid file.
    202          */
    203         public TzDataBuilder addZicData(String id, byte[] data) {
    204             zicData.add(new ZicDatum(id, data));
    205             return this;
    206         }
    207 
    208         public TzDataBuilder setZoneTab(String zoneTab) {
    209             this.zoneTab = zoneTab;
    210             return this;
    211         }
    212 
    213         public TzDataBuilder initializeToValid() {
    214             setHeaderMagic("tzdata9999a");
    215             addZicData("Europe/Elbonia", new ZicDataBuilder().initializeToValid().build());
    216             setZoneTab("ZoneTab data");
    217             return this;
    218         }
    219 
    220         public TzDataBuilder clearZicData() {
    221             zicData.clear();
    222             return this;
    223         }
    224 
    225         public byte[] build() {
    226             ByteArrayOutputStream baos = new ByteArrayOutputStream();
    227 
    228             byte[] headerMagicBytes = headerMagic.getBytes(StandardCharsets.US_ASCII);
    229             baos.write(headerMagicBytes, 0, headerMagicBytes.length);
    230             baos.write(0);
    231 
    232             // Write out the offsets for later manipulation.
    233             int indexOffsetOffset = baos.size();
    234             writeInt(baos, 0);
    235             int dataOffsetOffset = baos.size();
    236             writeInt(baos, 0);
    237             int zoneTabOffsetOffset = baos.size();
    238             writeInt(baos, 0);
    239 
    240             // Construct the data section in advance, so we know the offsets.
    241             ByteArrayOutputStream dataBytes = new ByteArrayOutputStream();
    242             Map<String, Integer> offsets = new HashMap<>();
    243             for (ZicDatum datum : zicData) {
    244                 int offset = dataBytes.size();
    245                 offsets.put(datum.id, offset);
    246                 writeByteArray(dataBytes, datum.data);
    247             }
    248 
    249             int indexOffset = baos.size();
    250 
    251             // Write the index section.
    252             for (ZicDatum zicDatum : zicData) {
    253                 // Write the ID.
    254                 String id = zicDatum.id;
    255                 byte[] idBytes = id.getBytes(StandardCharsets.US_ASCII);
    256                 byte[] paddedIdBytes = new byte[40];
    257                 System.arraycopy(idBytes, 0, paddedIdBytes, 0, idBytes.length);
    258                 writeByteArray(baos, paddedIdBytes);
    259                 // Write offset of zic data in the data section.
    260                 Integer offset = offsets.get(id);
    261                 writeInt(baos, offset);
    262                 // Write the length of the zic data.
    263                 writeInt(baos, zicDatum.data.length);
    264                 // Write a filler value (not used)
    265                 writeInt(baos, 0);
    266             }
    267 
    268             // Write the data section.
    269             int dataOffset = baos.size();
    270             writeByteArray(baos, dataBytes.toByteArray());
    271 
    272             // Write the zoneTab section.
    273             int zoneTabOffset = baos.size();
    274             byte[] zoneTabBytes = zoneTab.getBytes(StandardCharsets.US_ASCII);
    275             writeByteArray(baos, zoneTabBytes);
    276 
    277             byte[] bytes = baos.toByteArray();
    278             setInt(bytes, indexOffsetOffset,
    279                     indexOffsetOverride != null ? indexOffsetOverride : indexOffset);
    280             setInt(bytes, dataOffsetOffset,
    281                     dataOffsetOverride != null ? dataOffsetOverride : dataOffset);
    282             setInt(bytes, zoneTabOffsetOffset,
    283                     zoneTabOffsetOverride != null ? zoneTabOffsetOverride : zoneTabOffset);
    284             return bytes;
    285         }
    286 
    287         private static class ZicDatum {
    288             public final String id;
    289             public final byte[] data;
    290 
    291             ZicDatum(String id, byte[] data) {
    292                 this.id = id;
    293                 this.data = data;
    294             }
    295         }
    296     }
    297 
    298     static void writeByteArray(ByteArrayOutputStream baos, byte[] array) {
    299         baos.write(array, 0, array.length);
    300     }
    301 
    302     static void writeByte(ByteArrayOutputStream baos, int value) {
    303         baos.write(value);
    304     }
    305 
    306     static void writeIntArray(ByteArrayOutputStream baos, int[] array) {
    307         for (int value : array) {
    308             writeInt(baos, value);
    309         }
    310     }
    311 
    312     static void writeInt(ByteArrayOutputStream os, int value) {
    313         byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
    314         writeByteArray(os, bytes);
    315     }
    316 
    317     static void setInt(byte[] bytes, int offset, int value) {
    318         bytes[offset] = (byte) (value >>> 24);
    319         bytes[offset + 1] = (byte) (value >>> 16);
    320         bytes[offset + 2] = (byte) (value >>> 8);
    321         bytes[offset + 3] = (byte) value;
    322     }
    323 }
    324