// 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));
 }
  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");
  }
  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);
  }