public static void main(String[] args)
      throws ExecutionException, InterruptedException, InvocationTargetException,
          IllegalAccessException {
    for (String arg : args) {
      if (arg.startsWith("fan=")) System.setProperty("cassandra.btree.fanfactor", arg.substring(4));
      else if (arg.startsWith("min=")) minTreeSize = Integer.parseInt(arg.substring(4));
      else if (arg.startsWith("max=")) maxTreeSize = Integer.parseInt(arg.substring(4));
      else if (arg.startsWith("count=")) perThreadTrees = Integer.parseInt(arg.substring(6));
      else exit();
    }

    List<Method> methods = new ArrayList<>();
    for (Method m : LongBTreeTest.class.getDeclaredMethods()) {
      if (m.getParameters().length > 0) continue;
      for (Annotation annotation : m.getAnnotations())
        if (annotation.annotationType() == Test.class) methods.add(m);
    }

    LongBTreeTest test = new LongBTreeTest();
    Collections.sort(methods, (a, b) -> a.getName().compareTo(b.getName()));
    log(Lists.transform(methods, (m) -> m.getName()).toString());
    for (Method m : methods) {
      log(m.getName());
      m.invoke(test);
    }
    log("success");
  }
 // select a random subset of the keys, with an optional random population of keys inbetween those
 // that are present
 // return a value with the search position
 private static List<Integer> randomKeys(
     Iterable<Integer> canonical, boolean mixInNotPresentItems) {
   ThreadLocalRandom rnd = ThreadLocalRandom.current();
   boolean useFake = mixInNotPresentItems && rnd.nextBoolean();
   final float fakeRatio = rnd.nextFloat();
   List<Integer> results = new ArrayList<>();
   Long fakeLb = null, fakeUb = null;
   for (Integer v : canonical) {
     if (!useFake
         || fakeLb == null
         || (fakeUb == null ? v - 1 : fakeUb) <= fakeLb + 1
         || rnd.nextFloat() < fakeRatio) {
       // if we cannot safely construct a fake value, or our randomizer says not to, we emit the
       // next real value
       results.add(v);
       fakeLb = v.longValue();
       fakeUb = null;
     } else {
       // otherwise we emit a fake value in the range immediately proceeding the last real value,
       // and not
       // exceeding the real value that would have proceeded (ignoring any other suppressed real
       // values since)
       if (fakeUb == null) fakeUb = v.longValue() - 1;
       long mid = (fakeLb + fakeUb) / 2;
       assert mid < fakeUb;
       results.add((int) mid);
       fakeLb = mid;
     }
   }
   final float useChance = rnd.nextFloat();
   return Lists.newArrayList(filter(results, (x) -> rnd.nextFloat() < useChance));
 }
  private static RandomTree randomTreeByBuilder(int minSize, int maxSize) {
    assert minSize > 3;
    ThreadLocalRandom random = ThreadLocalRandom.current();
    BTree.Builder<Integer> builder = BTree.builder(naturalOrder());

    int targetSize = random.nextInt(minSize, maxSize);
    int maxModificationSize = (int) Math.sqrt(targetSize);

    TreeSet<Integer> canonical = new TreeSet<>();

    int curSize = 0;
    TreeSet<Integer> ordered = new TreeSet<>();
    List<Integer> shuffled = new ArrayList<>();
    while (curSize < targetSize) {
      int nextSize = maxModificationSize <= 1 ? 1 : random.nextInt(1, maxModificationSize);

      // leave a random selection of previous values
      (random.nextBoolean() ? ordered.headSet(random.nextInt()) : ordered.tailSet(random.nextInt()))
          .clear();
      shuffled =
          new ArrayList<>(
              shuffled.subList(0, shuffled.size() < 2 ? 0 : random.nextInt(shuffled.size() / 2)));

      for (int i = 0; i < nextSize; i++) {
        Integer next = random.nextInt();
        ordered.add(next);
        shuffled.add(next);
        canonical.add(next);
      }

      switch (random.nextInt(5)) {
        case 0:
          builder.addAll(ordered);
          break;
        case 1:
          builder.addAll(BTreeSet.of(ordered));
          break;
        case 2:
          for (Integer i : ordered) builder.add(i);
        case 3:
          builder.addAll(shuffled);
          break;
        case 4:
          for (Integer i : shuffled) builder.add(i);
      }

      curSize += nextSize;
      maxModificationSize = Math.min(maxModificationSize, targetSize - curSize);
    }

    BTreeSet<Integer> btree = BTreeSet.<Integer>wrap(builder.build(), naturalOrder());
    Assert.assertEquals(canonical.size(), btree.size());
    return new RandomTree(canonical, btree);
  }
    RandomSelection select(boolean narrow, boolean mixInNotPresentItems, boolean permitReversal) {
      ThreadLocalRandom random = ThreadLocalRandom.current();
      NavigableSet<Integer> canonicalSet = this.canonical;
      BTreeSet<Integer> testAsSet = this.test;
      List<Integer> canonicalList = new ArrayList<>(canonicalSet);
      BTreeSet<Integer> testAsList = this.test;

      Assert.assertEquals(canonicalSet.size(), testAsSet.size());
      Assert.assertEquals(canonicalList.size(), testAsList.size());

      // sometimes select keys first, so we cover full range
      List<Integer> allKeys = randomKeys(canonical, mixInNotPresentItems);
      List<Integer> keys = allKeys;

      int narrowCount = random.nextInt(3);
      while (narrow && canonicalList.size() > 10 && keys.size() > 10 && narrowCount-- > 0) {
        boolean useLb = random.nextBoolean();
        boolean useUb = random.nextBoolean();
        if (!(useLb | useUb)) continue;

        // select a range smaller than the total span when we have more narrowing iterations left
        int indexRange = keys.size() / (narrowCount + 1);

        boolean lbInclusive = true;
        Integer lbKey = canonicalList.get(0);
        int lbKeyIndex = 0, lbIndex = 0;
        boolean ubInclusive = true;
        Integer ubKey = canonicalList.get(canonicalList.size() - 1);
        int ubKeyIndex = keys.size(), ubIndex = canonicalList.size();

        if (useLb) {
          lbKeyIndex = random.nextInt(0, indexRange - 1);
          Integer candidate = keys.get(lbKeyIndex);
          if (useLb = (candidate > lbKey && candidate <= ubKey)) {
            lbInclusive = random.nextBoolean();
            lbKey = keys.get(lbKeyIndex);
            lbIndex = Collections.binarySearch(canonicalList, lbKey);
            if (lbIndex >= 0 && !lbInclusive) lbIndex++;
            else if (lbIndex < 0) lbIndex = -1 - lbIndex;
          }
        }
        if (useUb) {
          ubKeyIndex =
              random.nextInt(Math.max(lbKeyIndex, keys.size() - indexRange), keys.size() - 1);
          Integer candidate = keys.get(ubKeyIndex);
          if (useUb = (candidate < ubKey && candidate >= lbKey)) {
            ubInclusive = random.nextBoolean();
            ubKey = keys.get(ubKeyIndex);
            ubIndex = Collections.binarySearch(canonicalList, ubKey);
            if (ubIndex >= 0 && ubInclusive) {
              ubIndex++;
            } else if (ubIndex < 0) ubIndex = -1 - ubIndex;
          }
        }
        if (ubIndex < lbIndex) {
          ubIndex = lbIndex;
          ubKey = lbKey;
          ubInclusive = false;
        }

        canonicalSet =
            !useLb
                ? canonicalSet.headSet(ubKey, ubInclusive)
                : !useUb
                    ? canonicalSet.tailSet(lbKey, lbInclusive)
                    : canonicalSet.subSet(lbKey, lbInclusive, ubKey, ubInclusive);
        testAsSet =
            !useLb
                ? testAsSet.headSet(ubKey, ubInclusive)
                : !useUb
                    ? testAsSet.tailSet(lbKey, lbInclusive)
                    : testAsSet.subSet(lbKey, lbInclusive, ubKey, ubInclusive);

        keys = keys.subList(lbKeyIndex, ubKeyIndex);
        canonicalList = canonicalList.subList(lbIndex, ubIndex);
        testAsList = testAsList.subList(lbIndex, ubIndex);

        Assert.assertEquals(canonicalSet.size(), testAsSet.size());
        Assert.assertEquals(canonicalList.size(), testAsList.size());
      }

      // possibly restore full set of keys, to test case where we are provided existing keys that
      // are out of bounds
      if (keys != allKeys && random.nextBoolean()) keys = allKeys;

      Comparator<Integer> comparator = naturalOrder();
      if (permitReversal && random.nextBoolean()) {
        if (allKeys != keys) keys = new ArrayList<>(keys);
        if (canonicalSet != canonical) canonicalList = new ArrayList<>(canonicalList);
        Collections.reverse(keys);
        Collections.reverse(canonicalList);
        testAsList = testAsList.descendingSet();

        canonicalSet = canonicalSet.descendingSet();
        testAsSet = testAsSet.descendingSet();
        comparator = reverseOrder();
      }

      Assert.assertEquals(canonicalSet.size(), testAsSet.size());
      Assert.assertEquals(canonicalList.size(), testAsList.size());
      if (!canonicalSet.isEmpty()) {
        Assert.assertEquals(canonicalSet.first(), canonicalList.get(0));
        Assert.assertEquals(canonicalSet.last(), canonicalList.get(canonicalList.size() - 1));
        Assert.assertEquals(canonicalSet.first(), testAsSet.first());
        Assert.assertEquals(canonicalSet.last(), testAsSet.last());
        Assert.assertEquals(canonicalSet.first(), testAsList.get(0));
        Assert.assertEquals(canonicalSet.last(), testAsList.get(testAsList.size() - 1));
      }

      return new RandomSelection(
          keys, canonicalSet, testAsSet, canonicalList, testAsList, comparator);
    }