package queue; import base.Asserts; import base.ExtendedRandom; import base.TestCounter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; /** * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) */ public class ArrayQueueTester { private static final int OPERATIONS = 50_000; /* package-private */ static final Object[] ELEMENTS = new Object[]{ "Hello", "world", 1, 2, 3, List.of("a"), List.of("a"), List.of("b"), Map.of() }; private final Class model; private final Queues.QueueChecker tester; protected ArrayQueueTester(final Class model, final Queues.QueueChecker tester, final Queues.Splitter splitter) { this.model = model; this.tester = new Queues.QueueChecker<>() { @Override public M wrap(final ArrayDeque reference) { return tester.wrap(reference); } @Override public void check(final M queue, final ExtendedRandom random) { tester.check(queue, random); } @Override public void add(final M queue, final Object element, final ExtendedRandom random) { tester.add(queue, element, random); } @Override public void remove(final M queue, final ExtendedRandom random) { tester.remove(queue, random); } @Override public List linearTest(final M queue, final ExtendedRandom random) { if (random.nextInt(50) == 0) { queue.clear(); return List.of(); } else { return splitter.split(this, queue, random); } } }; } protected void test(final TestCounter counter) { test(counter, "ArrayQueue", ReflectionTest.Mode.values()); } protected void test(final TestCounter counter, final String className, final ReflectionTest.Mode... modes) { for (final ReflectionTest.Mode mode : modes) { final String scope = "Running %s for %s in %s mode%n" .formatted(model.getEnclosingClass().getSimpleName(), className, mode); counter.scope(scope, () -> new Variant(counter).test(className, mode)); } } protected void checkImplementation(final Class implementation) { // Do nothing by default } private static List toList(final Queues.QueueModel queue) { final List list = Stream.generate(queue::dequeue).limit(queue.size()).toList(); list.forEach(queue::enqueue); return list; } protected static ArrayDeque collect(final Stream elements) { return elements.collect(Collectors.toCollection(ArrayDeque::new)); } private class Variant extends ReflectionTest { private final TestCounter counter; public Variant(final TestCounter counter) { this.counter = counter; } protected void testEmpty(final M queue) { counter.scope("testEmpty", () -> assertSize(0, queue)); } protected void testSingleton(final M queue) { counter.scope("testSingleton", () -> { assertSize(0, queue); final String value = "value"; queue.enqueue(value); assertSize(1, queue); Asserts.assertEquals("element()", value, queue.element()); Asserts.assertEquals("dequeue()", value, queue.dequeue()); assertSize(0, queue); }); } protected void testClear(final M queue) { counter.scope("testClear", () -> { assertSize(0, queue); final String value = "value"; queue.enqueue(value); queue.enqueue(value); queue.clear(); assertSize(0, queue); final String value1 = "value1"; queue.enqueue(value1); Asserts.assertEquals("deque()", value1, queue.dequeue()); }); } private int checkAndSize(final M queue) { final int size = queue.size(); if (!queue.isEmpty() && random().nextBoolean()) { tester.check(queue, random()); } return size; } protected Object randomElement() { return ELEMENTS[random().nextInt(ELEMENTS.length)]; } protected void assertSize(final int size, final M queue) { counter.test(() -> { Asserts.assertEquals("size()", size, queue.size()); Asserts.assertTrue("Expected isEmpty() " + (size == 0) + ", found " + queue.isEmpty(), (size == 0) == queue.isEmpty()); }); } @Override protected void checkResult(final String call, final Object expected, final Object actual) { if (expected instanceof Queues.QueueModel model) { super.checkResult(call, toList(model), toList((Queues.QueueModel) actual)); } else { super.checkResult(call, expected, actual); } } protected Supplier factory(final String name, final Mode mode) { final ProxyFactory factory = new ProxyFactory<>(model, mode, "queue." + name); checkImplementation(factory.implementation); return () -> checking(counter, model, tester.wrap(new ArrayDeque<>()), factory.create()); } private void test(final String className, final Mode mode) { final Supplier factory = factory(className, mode); testEmpty(factory.get()); testSingleton(factory.get()); testClear(factory.get()); for (int i = 0; i <= 10; i += 2) { testRandom(factory.get(), (double) i / 10); } } private void testRandom(final M initial, final double addFreq) { counter.scope("testRandom, add frequency = " + addFreq, () -> { final List queues = new ArrayList<>(); queues.add(initial); int ops = 0; for (int i = 0; i < OPERATIONS / TestCounter.DENOMINATOR / TestCounter.DENOMINATOR; i++) { final M queue = queues.get(random().nextInt(queues.size())); final int size = counter.testV(() -> { if (queue.isEmpty() || random().nextDouble() < addFreq) { tester.add(queue, randomElement(), random()); } else { tester.remove(queue, random()); } return checkAndSize(queue); }); if (ops++ >= size && random().nextInt(4) == 0) { ops -= size; counter.test(() -> { queues.addAll(tester.linearTest(queue, random())); checkAndSize(queue); }); } } for (final M queue : queues) { counter.test(() -> { tester.linearTest(queue, random()); checkAndSize(queue); for (int i = queue.size(); i > 0; i--) { tester.remove(queue, random()); checkAndSize(queue); } }); } }); } private ExtendedRandom random() { return counter.random(); } } }