dcapwell commented on code in PR #50: URL: https://github.com/apache/cassandra-accord/pull/50#discussion_r1261782070
########## accord-core/src/test/java/accord/utils/SimpleBitSetTest.java: ########## @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package accord.utils; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; +import java.util.Random; +import java.util.function.BiConsumer; +import java.util.function.IntConsumer; + +import com.google.common.collect.Lists; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SimpleBitSetTest +{ + private static final int NOT_FOUND = Integer.MAX_VALUE; + + private static class Check + { + final SimpleBitSet test; + final BitSet canon; + final int size; + + private Check(SimpleBitSet test, BitSet canon, int size) + { + this.test = test; + this.canon = canon; + this.size = size; + } + + void check(Random random) + { + assertEquals(canon.cardinality(), test.setBitCount()); + assertEquals(canon.nextSetBit(0), test.firstSetBit()); + assertEquals(normaliseNotFound(canon.nextSetBit(0)), test.firstSetBit(NOT_FOUND)); + assertEquals(canon.previousSetBit(size), test.lastSetBit()); + assertEquals(normaliseNotFound(canon.previousSetBit(size)), test.lastSetBit(NOT_FOUND)); + + forIndices(random, i -> assertEquals(canon.nextSetBit(i), test.nextSetBit(i))); + forIndices(random, i -> assertEquals(normaliseBefore(canon.nextSetBit(0), i), test.firstSetBitBefore(i))); + forIndices(random, i -> assertEquals(normaliseNotFoundBefore(canon.nextSetBit(0), i), test.firstSetBitBefore(i, NOT_FOUND))); + forRanges(random, (i, j) -> assertEquals(normaliseBefore(canon.nextSetBit(i), j), test.nextSetBitBefore(i, j))); + forRanges(random, (i, j) -> assertEquals(normaliseNotFoundBefore(canon.nextSetBit(i), j), test.nextSetBitBefore(i, j, NOT_FOUND))); + + forIndices(random, i -> assertEquals(i == 0 ? -1 : canon.previousSetBit(i - 1), test.prevSetBit(i))); + forIndices(random, i -> assertEquals(normaliseNotBefore(size == 0 ? -1 : canon.previousSetBit(size - 1), i), test.lastSetBitNotBefore(i))); + forIndices(random, i -> assertEquals(normaliseNotFoundNotBefore(size == 0 ? -1 : canon.previousSetBit(size - 1), i), test.lastSetBitNotBefore(i, NOT_FOUND))); + forRanges(random, (i, j) -> assertEquals(normaliseNotBefore(j == 0 ? -1 : canon.previousSetBit(j - 1), i), test.prevSetBitNotBefore(j, i))); + forRanges(random, (i, j) -> assertEquals(normaliseNotFoundNotBefore(j == 0 ? -1 : canon.previousSetBit(j - 1), i), test.prevSetBitNotBefore(j, i, NOT_FOUND))); + + List<Integer> canonCollect = new ArrayList<>(), testCollect = new ArrayList<>(); + canon.stream().forEachOrdered(canonCollect::add); + test.forEach(testCollect, List::add); + assertEquals(canonCollect, testCollect); + + canonCollect = Lists.reverse(canonCollect); + testCollect.clear(); + test.reverseForEach(testCollect, List::add); + assertEquals(canonCollect, testCollect); + } + + void forIndices(Random random, IntConsumer consumer) + { + for (int c = 0 ; c < 100 ; ++c) + { + int i = random.nextInt(size + 1); + consumer.accept(i); + } + } + + void forRanges(Random random, BiConsumer<Integer, Integer> consumer) + { + for (int c = 0 ; c < 100 ; ++c) + { + int i = random.nextInt(size + 1); + int j = random.nextInt(size + 1); + if (i > j) { int t = i; i = j; j = t; } + consumer.accept(i, j); + } + } + + static Check generate(Random random, int maxSize, int modCount, int runLength, float runChance, float clearChance) + { + int size = random.nextInt(maxSize); + runLength = Math.min(size, runLength); + BitSet canon = new BitSet(size); + SimpleBitSet test = new SimpleBitSet(size); + if (size > 0) + { + while (modCount-- > 0) + { + boolean set = random.nextFloat() >= clearChance; + boolean run = runLength > 1 && random.nextFloat() < runChance; + if (run) + { + int i = random.nextInt(size); + int j = random.nextInt(size); + if (j < i) { int t = i; i = j; j = t; } + j = Math.min(i + 1 + random.nextInt(runLength - 1), j); + + if (set) + { + canon.set(i, j); + test.setRange(i, j); + } + else + { + canon.clear(i, j); + while (i < j) + test.unset(i++); + } + } + else + { + int i = random.nextInt(size); + if (set) + { + assertEquals(!canon.get(i), test.set(i)); + canon.set(i); + } + else + { + assertEquals(canon.get(i), test.unset(i)); + canon.clear(i); + } + } + assertEquals(canon.cardinality(), test.setBitCount()); + } + } + return new Check(test, canon, size); + } + } + + @Test + public void testRandomBitSets() + { + Random random = new Random(); + long seed = random.nextLong(); + System.err.println("Seed: " + seed); + random.setSeed(seed); + testRandomBitSets(random, 100000); Review Comment: > and already supports trivially running a single example at any point by passing the relevant sub seed, which is the work of maybe 20s to establish the first time you debug And when it runs in CI you don't know the seed it ran with... so you can do the code change to run it, but you don't know the seed... logging is very problematic in CI as we *hope* that the logs got captured in JUnit and we *link* to the correct test (in this case there is only one test), and that they were not truncated... This leads to working some times, and failing other times... its a common bug with this pattern. > I prefer this pattern, I am not a fan of orchestration layers, and I will not likely migrate to using qt in future This pattern is something I normally have to fix when I see failures in CI as we loose all knowledge... had to fix this in Simulator, BurnTest, Maelstorm's SimpleRandomTest, and other places in C*.... It is fine not to reuse, but when a hand roll one the setup it really must think about what happens when failures are detected in CI, and I keep finding this is lacking (which makes it very hard to retry locally)... we can keep hand rolling, but every one I have seen has done it incorrectly and I tend to fix later... so I prefer reusing when we can so we don't think about these things again -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]

