@Test
public void missingRanges() {

    // First, from empty.
    List<Range> ranges = db.readAsync(tr -> rs.missingRanges(tr).asList()).join();
    assertEquals(1, ranges.size());
    Range r1 = ranges.get(0);
    assertArrayEquals(new byte[]{0x00}, r1.begin);
    assertArrayEquals(new byte[]{(byte)0xff}, r1.end);
    // Now, add some ranges to make the gaps interesting.
    List<Range> toAdd = Arrays.asList(
            new Range(new byte[]{0x10}, new byte[]{0x20}),
            new Range(new byte[]{0x20}, new byte[]{0x20,0x00}),
            new Range(new byte[]{0x30}, new byte[]{0x40}),
            new Range(new byte[]{0x40,0x00}, new byte[]{0x50}),
            new Range(new byte[]{0x60}, new byte[]{0x6a}),
            new Range(new byte[]{0x62}, new byte[]{0x70}),
            new Range(new byte[]{0x71}, new byte[]{0x72}),
            new Range(new byte[]{0x72}, new byte[]{0x7f})
    );
    List<Range> expected = Arrays.asList(
            new Range(new byte[]{0x00}, new byte[]{0x10}),
            new Range(new byte[]{0x20,0x00}, new byte[]{0x30}),
            new Range(new byte[]{0x40}, new byte[]{0x40,0x00}),
            new Range(new byte[]{0x50}, new byte[]{0x60}),
            new Range(new byte[]{0x70}, new byte[]{0x71}),
            new Range(new byte[]{0x7f}, new byte[]{(byte)0xff})
    );
    List<Boolean> added = db.runAsync(tr -> {
        List<CompletableFuture<Boolean>> futures = new ArrayList<>();
        for (Range range : toAdd) {
            futures.add(rs.insertRange(tr, range));
        }
        return AsyncUtil.getAll(futures);
    }).join();
    assertTrue(added.stream().allMatch(b -> b), "Some of the insertions didn't change things");
    ranges = rs.missingRanges(db).join();
    assertEquals(6, ranges.size());
    for (int i = 0; i < ranges.size(); i++) {
        assertArrayEquals(expected.get(i).begin, ranges.get(i).begin);
        assertArrayEquals(expected.get(i).end, ranges.get(i).end);
    }
    ranges = rs.missingRanges(db, new byte[]{0x00}, new byte[]{0x60}).join();
    assertEquals(4, ranges.size());
    for (int i = 0; i < ranges.size(); i++) {
        assertArrayEquals(expected.get(i).begin, ranges.get(i).begin);
        assertArrayEquals(expected.get(i).end, ranges.get(i).end);
    }
    ranges = rs.missingRanges(db, new Range(new byte[]{0x00}, new byte[]{0x60})).join();
    assertEquals(4, ranges.size());
    for (int i = 0; i < ranges.size(); i++) {
        assertArrayEquals(expected.get(i).begin, ranges.get(i).begin);
        assertArrayEquals(expected.get(i).end, ranges.get(i).end);
    }
    ranges = rs.missingRanges(db, new byte[]{0x00}, new byte[]{0x60}, 2).join();
    assertEquals(2, ranges.size());
    for (int i = 0; i < ranges.size(); i++) {
        assertArrayEquals(expected.get(i).begin, ranges.get(i).begin);
        assertArrayEquals(expected.get(i).end, ranges.get(i).end);
    }
    ranges = rs.missingRanges(db, new byte[]{0x00}, new byte[]{0x60}, 1).join();
    assertEquals(1, ranges.size());
    for (int i = 0; i < ranges.size(); i++) {
        assertArrayEquals(expected.get(i).begin, ranges.get(i).begin);
        assertArrayEquals(expected.get(i).end, ranges.get(i).end);
    }
}