Back to home page

OSCL-LXR

 
 

    


0001 /*
0002  * Licensed to the Apache Software Foundation (ASF) under one or more
0003  * contributor license agreements.  See the NOTICE file distributed with
0004  * this work for additional information regarding copyright ownership.
0005  * The ASF licenses this file to You under the Apache License, Version 2.0
0006  * (the "License"); you may not use this file except in compliance with
0007  * the License.  You may obtain a copy of the License at
0008  *
0009  *    http://www.apache.org/licenses/LICENSE-2.0
0010  *
0011  * Unless required by applicable law or agreed to in writing, software
0012  * distributed under the License is distributed on an "AS IS" BASIS,
0013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014  * See the License for the specific language governing permissions and
0015  * limitations under the License.
0016  */
0017 
0018 package org.apache.spark.util.kvstore;
0019 
0020 import java.util.Arrays;
0021 import java.util.ArrayList;
0022 import java.util.Collections;
0023 import java.util.Comparator;
0024 import java.util.Iterator;
0025 import java.util.List;
0026 import java.util.Random;
0027 
0028 import com.google.common.collect.Iterables;
0029 import com.google.common.collect.Iterators;
0030 import com.google.common.collect.Lists;
0031 import org.junit.AfterClass;
0032 import org.junit.Before;
0033 import org.junit.BeforeClass;
0034 import org.junit.Test;
0035 import org.slf4j.Logger;
0036 import org.slf4j.LoggerFactory;
0037 import static org.junit.Assert.*;
0038 
0039 public abstract class DBIteratorSuite {
0040 
0041   private static final Logger LOG = LoggerFactory.getLogger(DBIteratorSuite.class);
0042 
0043   private static final int MIN_ENTRIES = 42;
0044   private static final int MAX_ENTRIES = 1024;
0045   private static final Random RND = new Random();
0046 
0047   private static List<CustomType1> allEntries;
0048   private static List<CustomType1> clashingEntries;
0049   private static KVStore db;
0050 
0051   private interface BaseComparator extends Comparator<CustomType1> {
0052     /**
0053      * Returns a comparator that falls back to natural order if this comparator's ordering
0054      * returns equality for two elements. Used to mimic how the index sorts things internally.
0055      */
0056     default BaseComparator fallback() {
0057       return (t1, t2) -> {
0058         int result = BaseComparator.this.compare(t1, t2);
0059         if (result != 0) {
0060           return result;
0061         }
0062 
0063         return t1.key.compareTo(t2.key);
0064       };
0065     }
0066 
0067     /** Reverses the order of this comparator. */
0068     default BaseComparator reverse() {
0069       return (t1, t2) -> -BaseComparator.this.compare(t1, t2);
0070     }
0071   }
0072 
0073   private static final BaseComparator NATURAL_ORDER = (t1, t2) -> t1.key.compareTo(t2.key);
0074   private static final BaseComparator REF_INDEX_ORDER = (t1, t2) -> t1.id.compareTo(t2.id);
0075   private static final BaseComparator COPY_INDEX_ORDER = (t1, t2) -> t1.name.compareTo(t2.name);
0076   private static final BaseComparator NUMERIC_INDEX_ORDER = (t1, t2) -> {
0077     return Integer.valueOf(t1.num).compareTo(t2.num);
0078   };
0079   private static final BaseComparator CHILD_INDEX_ORDER = (t1, t2) -> t1.child.compareTo(t2.child);
0080 
0081   /**
0082    * Implementations should override this method; it is called only once, before all tests are
0083    * run. Any state can be safely stored in static variables and cleaned up in a @AfterClass
0084    * handler.
0085    */
0086   protected abstract KVStore createStore() throws Exception;
0087 
0088   @BeforeClass
0089   public static void setupClass() {
0090     long seed = RND.nextLong();
0091     LOG.info("Random seed: {}", seed);
0092     RND.setSeed(seed);
0093   }
0094 
0095   @AfterClass
0096   public static void cleanupData() throws Exception {
0097     allEntries = null;
0098     db = null;
0099   }
0100 
0101   @Before
0102   public void setup() throws Exception {
0103     if (db != null) {
0104       return;
0105     }
0106 
0107     db = createStore();
0108 
0109     int count = RND.nextInt(MAX_ENTRIES) + MIN_ENTRIES;
0110 
0111     allEntries = new ArrayList<>(count);
0112     for (int i = 0; i < count; i++) {
0113       CustomType1 t = new CustomType1();
0114       t.key = "key" + i;
0115       t.id = "id" + i;
0116       t.name = "name" + RND.nextInt(MAX_ENTRIES);
0117       // Force one item to have an integer value of zero to test the fix for SPARK-23103.
0118       t.num = (i != 0) ? (int) RND.nextLong() : 0;
0119       t.child = "child" + (i % MIN_ENTRIES);
0120       allEntries.add(t);
0121     }
0122 
0123     // Shuffle the entries to avoid the insertion order matching the natural ordering. Just in case.
0124     Collections.shuffle(allEntries, RND);
0125     for (CustomType1 e : allEntries) {
0126       db.write(e);
0127     }
0128 
0129     // Pick the first generated value, and forcefully create a few entries that will clash
0130     // with the indexed values (id and name), to make sure the index behaves correctly when
0131     // multiple entities are indexed by the same value.
0132     //
0133     // This also serves as a test for the test code itself, to make sure it's sorting indices
0134     // the same way the store is expected to.
0135     CustomType1 first = allEntries.get(0);
0136     clashingEntries = new ArrayList<>();
0137 
0138     int clashCount = RND.nextInt(MIN_ENTRIES) + 1;
0139     for (int i = 0; i < clashCount; i++) {
0140       CustomType1 t = new CustomType1();
0141       t.key = "n-key" + (count + i);
0142       t.id = first.id;
0143       t.name = first.name;
0144       t.num = first.num;
0145       t.child = first.child;
0146       allEntries.add(t);
0147       clashingEntries.add(t);
0148       db.write(t);
0149     }
0150 
0151     // Create another entry that could cause problems: take the first entry, and make its indexed
0152     // name be an extension of the existing ones, to make sure the implementation sorts these
0153     // correctly even considering the separator character (shorter strings first).
0154     CustomType1 t = new CustomType1();
0155     t.key = "extended-key-0";
0156     t.id = first.id;
0157     t.name = first.name + "a";
0158     t.num = first.num;
0159     t.child = first.child;
0160     allEntries.add(t);
0161     db.write(t);
0162   }
0163 
0164   @Test
0165   public void naturalIndex() throws Exception {
0166     testIteration(NATURAL_ORDER, view(), null, null);
0167   }
0168 
0169   @Test
0170   public void refIndex() throws Exception {
0171     testIteration(REF_INDEX_ORDER, view().index("id"), null, null);
0172   }
0173 
0174   @Test
0175   public void copyIndex() throws Exception {
0176     testIteration(COPY_INDEX_ORDER, view().index("name"), null, null);
0177   }
0178 
0179   @Test
0180   public void numericIndex() throws Exception {
0181     testIteration(NUMERIC_INDEX_ORDER, view().index("int"), null, null);
0182   }
0183 
0184   @Test
0185   public void childIndex() throws Exception {
0186     CustomType1 any = pickLimit();
0187     testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id), null, null);
0188   }
0189 
0190   @Test
0191   public void naturalIndexDescending() throws Exception {
0192     testIteration(NATURAL_ORDER, view().reverse(), null, null);
0193   }
0194 
0195   @Test
0196   public void refIndexDescending() throws Exception {
0197     testIteration(REF_INDEX_ORDER, view().index("id").reverse(), null, null);
0198   }
0199 
0200   @Test
0201   public void copyIndexDescending() throws Exception {
0202     testIteration(COPY_INDEX_ORDER, view().index("name").reverse(), null, null);
0203   }
0204 
0205   @Test
0206   public void numericIndexDescending() throws Exception {
0207     testIteration(NUMERIC_INDEX_ORDER, view().index("int").reverse(), null, null);
0208   }
0209 
0210   @Test
0211   public void childIndexDescending() throws Exception {
0212     CustomType1 any = pickLimit();
0213     testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id).reverse(), null, null);
0214   }
0215 
0216   @Test
0217   public void naturalIndexWithStart() throws Exception {
0218     CustomType1 first = pickLimit();
0219     testIteration(NATURAL_ORDER, view().first(first.key), first, null);
0220   }
0221 
0222   @Test
0223   public void refIndexWithStart() throws Exception {
0224     CustomType1 first = pickLimit();
0225     testIteration(REF_INDEX_ORDER, view().index("id").first(first.id), first, null);
0226   }
0227 
0228   @Test
0229   public void copyIndexWithStart() throws Exception {
0230     CustomType1 first = pickLimit();
0231     testIteration(COPY_INDEX_ORDER, view().index("name").first(first.name), first, null);
0232   }
0233 
0234   @Test
0235   public void numericIndexWithStart() throws Exception {
0236     CustomType1 first = pickLimit();
0237     testIteration(NUMERIC_INDEX_ORDER, view().index("int").first(first.num), first, null);
0238   }
0239 
0240   @Test
0241   public void childIndexWithStart() throws Exception {
0242     CustomType1 any = pickLimit();
0243     testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id).first(any.child), null,
0244       null);
0245   }
0246 
0247   @Test
0248   public void naturalIndexDescendingWithStart() throws Exception {
0249     CustomType1 first = pickLimit();
0250     testIteration(NATURAL_ORDER, view().reverse().first(first.key), first, null);
0251   }
0252 
0253   @Test
0254   public void refIndexDescendingWithStart() throws Exception {
0255     CustomType1 first = pickLimit();
0256     testIteration(REF_INDEX_ORDER, view().reverse().index("id").first(first.id), first, null);
0257   }
0258 
0259   @Test
0260   public void copyIndexDescendingWithStart() throws Exception {
0261     CustomType1 first = pickLimit();
0262     testIteration(COPY_INDEX_ORDER, view().reverse().index("name").first(first.name), first, null);
0263   }
0264 
0265   @Test
0266   public void numericIndexDescendingWithStart() throws Exception {
0267     CustomType1 first = pickLimit();
0268     testIteration(NUMERIC_INDEX_ORDER, view().reverse().index("int").first(first.num), first, null);
0269   }
0270 
0271   @Test
0272   public void childIndexDescendingWithStart() throws Exception {
0273     CustomType1 any = pickLimit();
0274     testIteration(CHILD_INDEX_ORDER,
0275       view().index("child").parent(any.id).first(any.child).reverse(), null, null);
0276   }
0277 
0278   @Test
0279   public void naturalIndexWithSkip() throws Exception {
0280     testIteration(NATURAL_ORDER, view().skip(pickCount()), null, null);
0281   }
0282 
0283   @Test
0284   public void refIndexWithSkip() throws Exception {
0285     testIteration(REF_INDEX_ORDER, view().index("id").skip(pickCount()), null, null);
0286   }
0287 
0288   @Test
0289   public void copyIndexWithSkip() throws Exception {
0290     testIteration(COPY_INDEX_ORDER, view().index("name").skip(pickCount()), null, null);
0291   }
0292 
0293   @Test
0294   public void childIndexWithSkip() throws Exception {
0295     CustomType1 any = pickLimit();
0296     testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id).skip(pickCount()),
0297       null, null);
0298   }
0299 
0300   @Test
0301   public void naturalIndexWithMax() throws Exception {
0302     testIteration(NATURAL_ORDER, view().max(pickCount()), null, null);
0303   }
0304 
0305   @Test
0306   public void copyIndexWithMax() throws Exception {
0307     testIteration(COPY_INDEX_ORDER, view().index("name").max(pickCount()), null, null);
0308   }
0309 
0310   @Test
0311   public void childIndexWithMax() throws Exception {
0312     CustomType1 any = pickLimit();
0313     testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id).max(pickCount()), null,
0314       null);
0315   }
0316 
0317   @Test
0318   public void naturalIndexWithLast() throws Exception {
0319     CustomType1 last = pickLimit();
0320     testIteration(NATURAL_ORDER, view().last(last.key), null, last);
0321   }
0322 
0323   @Test
0324   public void refIndexWithLast() throws Exception {
0325     CustomType1 last = pickLimit();
0326     testIteration(REF_INDEX_ORDER, view().index("id").last(last.id), null, last);
0327   }
0328 
0329   @Test
0330   public void copyIndexWithLast() throws Exception {
0331     CustomType1 last = pickLimit();
0332     testIteration(COPY_INDEX_ORDER, view().index("name").last(last.name), null, last);
0333   }
0334 
0335   @Test
0336   public void numericIndexWithLast() throws Exception {
0337     CustomType1 last = pickLimit();
0338     testIteration(NUMERIC_INDEX_ORDER, view().index("int").last(last.num), null, last);
0339   }
0340 
0341   @Test
0342   public void childIndexWithLast() throws Exception {
0343     CustomType1 any = pickLimit();
0344     testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id).last(any.child), null,
0345       null);
0346   }
0347 
0348   @Test
0349   public void naturalIndexDescendingWithLast() throws Exception {
0350     CustomType1 last = pickLimit();
0351     testIteration(NATURAL_ORDER, view().reverse().last(last.key), null, last);
0352   }
0353 
0354   @Test
0355   public void refIndexDescendingWithLast() throws Exception {
0356     CustomType1 last = pickLimit();
0357     testIteration(REF_INDEX_ORDER, view().reverse().index("id").last(last.id), null, last);
0358   }
0359 
0360   @Test
0361   public void copyIndexDescendingWithLast() throws Exception {
0362     CustomType1 last = pickLimit();
0363     testIteration(COPY_INDEX_ORDER, view().reverse().index("name").last(last.name),
0364       null, last);
0365   }
0366 
0367   @Test
0368   public void numericIndexDescendingWithLast() throws Exception {
0369     CustomType1 last = pickLimit();
0370     testIteration(NUMERIC_INDEX_ORDER, view().reverse().index("int").last(last.num),
0371       null, last);
0372    }
0373 
0374   @Test
0375   public void childIndexDescendingWithLast() throws Exception {
0376     CustomType1 any = pickLimit();
0377     testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id).last(any.child).reverse(),
0378       null, null);
0379   }
0380 
0381   @Test
0382   public void testRefWithIntNaturalKey() throws Exception {
0383     LevelDBSuite.IntKeyType i = new LevelDBSuite.IntKeyType();
0384     i.key = 1;
0385     i.id = "1";
0386     i.values = Arrays.asList("1");
0387 
0388     db.write(i);
0389 
0390     try(KVStoreIterator<?> it = db.view(i.getClass()).closeableIterator()) {
0391       Object read = it.next();
0392       assertEquals(i, read);
0393     }
0394   }
0395 
0396   private CustomType1 pickLimit() {
0397     // Picks an element that has clashes with other elements in the given index.
0398     return clashingEntries.get(RND.nextInt(clashingEntries.size()));
0399   }
0400 
0401   private int pickCount() {
0402     int count = RND.nextInt(allEntries.size() / 2);
0403     return Math.max(count, 1);
0404   }
0405 
0406   /**
0407    * Compares the two values and falls back to comparing the natural key of CustomType1
0408    * if they're the same, to mimic the behavior of the indexing code.
0409    */
0410   private <T extends Comparable<T>> int compareWithFallback(
0411       T v1,
0412       T v2,
0413       CustomType1 ct1,
0414       CustomType1 ct2) {
0415     int result = v1.compareTo(v2);
0416     if (result != 0) {
0417       return result;
0418     }
0419 
0420     return ct1.key.compareTo(ct2.key);
0421   }
0422 
0423   private void testIteration(
0424       final BaseComparator order,
0425       final KVStoreView<CustomType1> params,
0426       final CustomType1 first,
0427       final CustomType1 last) throws Exception {
0428     List<CustomType1> indexOrder = sortBy(order.fallback());
0429     if (!params.ascending) {
0430       indexOrder = Lists.reverse(indexOrder);
0431     }
0432 
0433     Iterable<CustomType1> expected = indexOrder;
0434     BaseComparator expectedOrder = params.ascending ? order : order.reverse();
0435 
0436     if (params.parent != null) {
0437       expected = Iterables.filter(expected, v -> params.parent.equals(v.id));
0438     }
0439 
0440     if (first != null) {
0441       expected = Iterables.filter(expected, v -> expectedOrder.compare(first, v) <= 0);
0442     }
0443 
0444     if (last != null) {
0445       expected = Iterables.filter(expected, v -> expectedOrder.compare(v, last) <= 0);
0446     }
0447 
0448     if (params.skip > 0) {
0449       expected = Iterables.skip(expected, (int) params.skip);
0450     }
0451 
0452     if (params.max != Long.MAX_VALUE) {
0453       expected = Iterables.limit(expected, (int) params.max);
0454     }
0455 
0456     List<CustomType1> actual = collect(params);
0457     compareLists(expected, actual);
0458   }
0459 
0460   /** Could use assertEquals(), but that creates hard to read errors for large lists. */
0461   private void compareLists(Iterable<?> expected, List<?> actual) {
0462     Iterator<?> expectedIt = expected.iterator();
0463     Iterator<?> actualIt = actual.iterator();
0464 
0465     int count = 0;
0466     while (expectedIt.hasNext()) {
0467       if (!actualIt.hasNext()) {
0468         break;
0469       }
0470       count++;
0471       assertEquals(expectedIt.next(), actualIt.next());
0472     }
0473 
0474     String message;
0475     Object[] remaining;
0476     int expectedCount = count;
0477     int actualCount = count;
0478 
0479     if (expectedIt.hasNext()) {
0480       remaining = Iterators.toArray(expectedIt, Object.class);
0481       expectedCount += remaining.length;
0482       message = "missing";
0483     } else {
0484       remaining = Iterators.toArray(actualIt, Object.class);
0485       actualCount += remaining.length;
0486       message = "stray";
0487     }
0488 
0489     assertEquals(String.format("Found %s elements: %s", message, Arrays.asList(remaining)),
0490       expectedCount, actualCount);
0491   }
0492 
0493   private KVStoreView<CustomType1> view() throws Exception {
0494     return db.view(CustomType1.class);
0495   }
0496 
0497   private List<CustomType1> collect(KVStoreView<CustomType1> view) throws Exception {
0498     return Arrays.asList(Iterables.toArray(view, CustomType1.class));
0499   }
0500 
0501   private List<CustomType1> sortBy(Comparator<CustomType1> comp) {
0502     List<CustomType1> copy = new ArrayList<>(allEntries);
0503     Collections.sort(copy, comp);
0504     return copy;
0505   }
0506 
0507 }