From 518f52ea6ec5f000ac8d8f219d6584cc28f18ba8 Mon Sep 17 00:00:00 2001 From: Octavia Togami Date: Wed, 3 Jun 2020 14:51:57 -0700 Subject: [PATCH] Test more of the math/collections, fix VectorPList Fixes #1291 (cherry picked from commit e0346ff53b8629a368cdbf62cbd347caecfae5fd) --- .../util/collection/VectorPositionList.java | 47 ++++--- .../worldedit/math/BlockVector3Test.java | 17 +++ .../worldedit/util/VariedVectorsProvider.java | 133 ------------------ .../util/collection/BlockMapTest.java | 75 +++++----- .../util/collection/PositionListTest.java | 79 +++++++++++ .../util/test/VariedVectorGenerator.java | 81 +++++++++++ .../worldedit/util/test/VariedVectors.java | 87 ++++++++++++ 7 files changed, 330 insertions(+), 189 deletions(-) create mode 100644 worldedit-core/src/test/java/com/sk89q/worldedit/math/BlockVector3Test.java delete mode 100644 worldedit-core/src/test/java/com/sk89q/worldedit/util/VariedVectorsProvider.java create mode 100644 worldedit-core/src/test/java/com/sk89q/worldedit/util/collection/PositionListTest.java create mode 100644 worldedit-core/src/test/java/com/sk89q/worldedit/util/test/VariedVectorGenerator.java create mode 100644 worldedit-core/src/test/java/com/sk89q/worldedit/util/test/VariedVectors.java diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/VectorPositionList.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/VectorPositionList.java index 6c8d27ddf..1617edf1b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/VectorPositionList.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/collection/VectorPositionList.java @@ -30,49 +30,54 @@ import java.util.Iterator; class VectorPositionList implements PositionList { - private final IntList delegate = new IntArrayList(); + private final IntList delegateX = new IntArrayList(); + private final IntList delegateY = new IntArrayList(); + private final IntList delegateZ = new IntArrayList(); @Override public BlockVector3 get(int index) { - int ri = index * 3; return BlockVector3.at( - delegate.getInt(ri), - delegate.getInt(ri + 1), - delegate.getInt(ri + 2)); + delegateX.getInt(index), + delegateY.getInt(index), + delegateZ.getInt(index)); } @Override public void add(BlockVector3 vector) { - delegate.add(vector.getX()); - delegate.add(vector.getY()); - delegate.add(vector.getZ()); + delegateX.add(vector.getX()); + delegateY.add(vector.getY()); + delegateZ.add(vector.getZ()); } @Override public int size() { - return delegate.size(); + return delegateX.size(); } @Override public void clear() { - delegate.clear(); + delegateX.clear(); + delegateY.clear(); + delegateZ.clear(); } @Override public Iterator iterator() { return new AbstractIterator() { - private final IntIterator iterator = delegate.iterator(); + private final IntIterator iteratorX = delegateX.iterator(); + private final IntIterator iteratorY = delegateY.iterator(); + private final IntIterator iteratorZ = delegateZ.iterator(); @Override protected BlockVector3 computeNext() { - if (!iterator.hasNext()) { + if (!iteratorX.hasNext()) { return endOfData(); } return BlockVector3.at( - iterator.nextInt(), - iterator.nextInt(), - iterator.nextInt()); + iteratorX.nextInt(), + iteratorY.nextInt(), + iteratorZ.nextInt()); } }; } @@ -81,17 +86,19 @@ class VectorPositionList implements PositionList { public Iterator reverseIterator() { return new AbstractIterator() { - private final IntListIterator iterator = delegate.listIterator(delegate.size()); + private final IntListIterator iteratorX = delegateX.listIterator(delegateX.size()); + private final IntListIterator iteratorY = delegateY.listIterator(delegateY.size()); + private final IntListIterator iteratorZ = delegateZ.listIterator(delegateZ.size()); @Override protected BlockVector3 computeNext() { - if (!iterator.hasPrevious()) { + if (!iteratorX.hasPrevious()) { return endOfData(); } return BlockVector3.at( - iterator.previousInt(), - iterator.previousInt(), - iterator.previousInt()); + iteratorX.previousInt(), + iteratorY.previousInt(), + iteratorZ.previousInt()); } }; } diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/math/BlockVector3Test.java b/worldedit-core/src/test/java/com/sk89q/worldedit/math/BlockVector3Test.java new file mode 100644 index 000000000..7b4b541df --- /dev/null +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/math/BlockVector3Test.java @@ -0,0 +1,17 @@ +package com.sk89q.worldedit.math; + +import com.sk89q.worldedit.util.test.VariedVectors; +import org.junit.jupiter.api.DisplayName; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@DisplayName("A 3D block vector") +public class BlockVector3Test { + + @VariedVectors.Test(capToVanilla = true, divisionsXZ = 50, divisionsY = 50) + @DisplayName("survives a round-trip through long-packing") + void longPackingRoundTrip(BlockVector3 vec) { + assertEquals(vec, BlockVector3.fromLongPackedForm(vec.toLongPackedForm())); + } + +} diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/util/VariedVectorsProvider.java b/worldedit-core/src/test/java/com/sk89q/worldedit/util/VariedVectorsProvider.java deleted file mode 100644 index 10a835392..000000000 --- a/worldedit-core/src/test/java/com/sk89q/worldedit/util/VariedVectorsProvider.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * WorldEdit, a Minecraft world manipulation toolkit - * Copyright (C) sk89q - * Copyright (C) WorldEdit team and contributors - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.sk89q.worldedit.util; - -import com.google.common.collect.AbstractIterator; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Streams; -import com.sk89q.worldedit.math.BlockVector3; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.junit.jupiter.params.support.AnnotationConsumer; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Iterator; -import java.util.Set; -import java.util.stream.Stream; - -/** - * Argument provider for various vectors. - */ -public final class VariedVectorsProvider implements ArgumentsProvider, AnnotationConsumer { - - @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) - @Retention(RetentionPolicy.RUNTIME) - @Documented - @ArgumentsSource(VariedVectorsProvider.class) - @ParameterizedTest(name = ParameterizedTest.ARGUMENTS_PLACEHOLDER) - public @interface Test { - - /** - * If {@code true}, provide a non-matching vector from - * the existing vectors set as well. This will nearly - * square the number of tests executed, since it will - * test every non-matching vector. - */ - boolean provideNonMatching() default false; - - } - - private static final int WORLD_XZ_MINMAX = 30_000_000; - private static final long WORLD_Y_MAX = Integer.MAX_VALUE; - private static final long WORLD_Y_MIN = Integer.MIN_VALUE; - - // For better coverage assurance, increase these values for a local Gradle run. - // Don't do it for IntelliJ, it'll probably run out of memory. - private static final int DIVISIONS_XZ = Integer.getInteger("variedvecs.divisions.xz", 5); - private static final int DIVISIONS_Y = Integer.getInteger("variedvecs.divisions.y", 5); - - private static final int XZ_STEP = (WORLD_XZ_MINMAX * 2) / DIVISIONS_XZ; - private static final long Y_STEP = (WORLD_Y_MAX * 2) / DIVISIONS_Y; - - private static final Set ALWAYS_INCLUDE = - ImmutableSet.of(BlockVector3.ZERO, BlockVector3.ONE, - BlockVector3.at(-WORLD_XZ_MINMAX, 0, -WORLD_XZ_MINMAX), - BlockVector3.at(WORLD_XZ_MINMAX, 0, WORLD_XZ_MINMAX), - BlockVector3.at(-WORLD_XZ_MINMAX, WORLD_Y_MAX, -WORLD_XZ_MINMAX), - BlockVector3.at(WORLD_XZ_MINMAX, WORLD_Y_MAX, WORLD_XZ_MINMAX)); - - private boolean provideNonMatching; - - @Override - public void accept(Test test) { - provideNonMatching = test.provideNonMatching(); - } - - @Override - public Stream provideArguments(ExtensionContext context) { - if (provideNonMatching) { - return makeVectorsStream() - .flatMap(vec -> makeVectorsStream().filter(v -> !v.equals(vec)) - .map(v -> Arguments.of(vec, v))); - } - return makeVectorsStream().map(Arguments::of); - } - - public static Stream makeVectorsStream() { - return Stream.concat( - ALWAYS_INCLUDE.stream(), - Streams.stream(generateVectors()).filter(v -> !ALWAYS_INCLUDE.contains(v)) - ); - } - - private static Iterator generateVectors() { - return new AbstractIterator() { - - private int x = -WORLD_XZ_MINMAX + 1; - private int z = -WORLD_XZ_MINMAX + 1; - private long y = WORLD_Y_MAX; - - @Override - protected BlockVector3 computeNext() { - if (x > WORLD_XZ_MINMAX) { - return endOfData(); - } - BlockVector3 newVector = BlockVector3.at(x, (int) y, z); - y += Y_STEP; - if (y > WORLD_Y_MAX) { - y = 0; - z += XZ_STEP; - if (z > WORLD_XZ_MINMAX) { - z = -WORLD_XZ_MINMAX; - x += XZ_STEP; - } - } - return newVector; - } - }; - } -} diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/util/collection/BlockMapTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/util/collection/BlockMapTest.java index d3faa9de1..98b7e741d 100644 --- a/worldedit-core/src/test/java/com/sk89q/worldedit/util/collection/BlockMapTest.java +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/util/collection/BlockMapTest.java @@ -27,7 +27,8 @@ import com.sk89q.worldedit.extension.platform.PlatformManager; import com.sk89q.worldedit.extension.platform.Preference; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.registry.Registry; -import com.sk89q.worldedit.util.VariedVectorsProvider; +import com.sk89q.worldedit.util.test.VariedVectorGenerator; +import com.sk89q.worldedit.util.test.VariedVectors; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; @@ -265,49 +266,49 @@ class BlockMapTest { // being able to parameterize classes. It's not part of JUnit // yet though: https://github.com/junit-team/junit5/issues/878 - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("has a size of one") void hasSizeOne(BlockVector3 vec) { map.put(vec, air); assertEquals(1, map.size()); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("is equal to another map with the same entry") void isEqualToSimilarMap(BlockVector3 vec) { map.put(vec, air); assertEquals(ImmutableMap.of(vec, air), map); } - @VariedVectorsProvider.Test(provideNonMatching = true) + @VariedVectors.Test(provideNonMatching = true) @DisplayName("is not equal to another map with a different key") void isNotEqualToDifferentKeyMap(BlockVector3 vec, BlockVector3 nonMatch) { map.put(vec, air); assertNotEquals(ImmutableMap.of(nonMatch, air), map); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("is not equal to another map with a different value") void isNotEqualToDifferentValueMap(BlockVector3 vec) { map.put(vec, air); assertNotEquals(ImmutableMap.of(vec, oakWood), map); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("is not equal to an empty map") void isNotEqualToEmptyMap(BlockVector3 vec) { map.put(vec, air); assertNotEquals(ImmutableMap.of(), map); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("has the same hashCode as another map with the same entry") void isHashCodeEqualToSimilarMap(BlockVector3 vec) { map.put(vec, air); assertEquals(ImmutableMap.of(vec, air).hashCode(), map.hashCode()); } - @VariedVectorsProvider.Test(provideNonMatching = true) + @VariedVectors.Test(provideNonMatching = true) @DisplayName("has a different hashCode from another map with a different key") void isHashCodeNotEqualToDifferentKeyMap(BlockVector3 vec, BlockVector3 nonMatch) { assumeFalse(vec.hashCode() == nonMatch.hashCode(), @@ -316,35 +317,35 @@ class BlockMapTest { assertNotEquals(ImmutableMap.of(nonMatch, air).hashCode(), map.hashCode()); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("has a different hashCode from another map with a different value") void isHashCodeNotEqualToDifferentValueMap(BlockVector3 vec) { map.put(vec, air); assertNotEquals(ImmutableMap.of(vec, oakWood).hashCode(), map.hashCode()); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("has a different hashCode from an empty map") void isHashCodeNotEqualToEmptyMap(BlockVector3 vec) { map.put(vec, air); assertNotEquals(ImmutableMap.of().hashCode(), map.hashCode()); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("returns value from get") void returnsValueFromGet(BlockVector3 vec) { map.put(vec, air); assertEquals(air, map.get(vec)); } - @VariedVectorsProvider.Test(provideNonMatching = true) + @VariedVectors.Test(provideNonMatching = true) @DisplayName("returns `null` from get with different key") void returnsValueFromGet(BlockVector3 vec, BlockVector3 nonMatch) { map.put(vec, air); assertNotEquals(air, map.get(nonMatch)); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("contains the key") void containsTheKey(BlockVector3 vec) { map.put(vec, air); @@ -353,7 +354,7 @@ class BlockMapTest { assertTrue(map.containsKey(vec)); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("contains the value") void containsTheValue(BlockVector3 vec) { map.put(vec, air); @@ -362,7 +363,7 @@ class BlockMapTest { assertTrue(map.containsValue(air)); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("contains the entry") void containsTheEntry(BlockVector3 vec) { map.put(vec, air); @@ -370,21 +371,21 @@ class BlockMapTest { assertEquals(new AbstractMap.SimpleImmutableEntry<>(vec, air), map.entrySet().iterator().next()); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("returns the provided value from getOrDefault") void returnsProvidedFromGetOrDefault(BlockVector3 vec) { map.put(vec, air); assertEquals(air, map.getOrDefault(vec, oakWood)); } - @VariedVectorsProvider.Test(provideNonMatching = true) + @VariedVectors.Test(provideNonMatching = true) @DisplayName("returns the default value from getOrDefault with a different key") void returnsDefaultFromGetOrDefaultWrongKey(BlockVector3 vec, BlockVector3 nonMatch) { map.put(vec, air); assertEquals(oakWood, map.getOrDefault(nonMatch, oakWood)); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("calls the forEach action once") void neverCallsForEachAction(BlockVector3 vec) { map.put(vec, air); @@ -393,7 +394,7 @@ class BlockMapTest { verifyNoMoreInteractions(biConsumer); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("replaces value using replaceAll") void neverCallsReplaceAllFunction(BlockVector3 vec) { map.put(vec, air); @@ -401,7 +402,7 @@ class BlockMapTest { assertEquals(oakWood, map.get(vec)); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("does not insert on `putIfAbsent`") void noInsertOnPutIfAbsent(BlockVector3 vec) { map.put(vec, air); @@ -410,7 +411,7 @@ class BlockMapTest { assertEquals(air, map.get(vec)); } - @VariedVectorsProvider.Test(provideNonMatching = true) + @VariedVectors.Test(provideNonMatching = true) @DisplayName("inserts on `putIfAbsent` to a different key") void insertOnPutIfAbsentDifferentKey(BlockVector3 vec, BlockVector3 nonMatch) { map.put(vec, air); @@ -420,7 +421,7 @@ class BlockMapTest { assertEquals(oakWood, map.get(nonMatch)); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("remove(key) returns the old value") void removeKeyReturnsOldValue(BlockVector3 vec) { map.put(vec, air); @@ -428,7 +429,7 @@ class BlockMapTest { assertEquals(0, map.size()); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("keySet().remove(key) removes the entry from the map") void keySetRemovePassesThrough(BlockVector3 vec) { map.put(vec, air); @@ -436,7 +437,7 @@ class BlockMapTest { assertEquals(0, map.size()); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("entrySet().iterator().remove() removes the entry from the map") void entrySetIteratorRemovePassesThrough(BlockVector3 vec) { map.put(vec, air); @@ -448,7 +449,7 @@ class BlockMapTest { assertEquals(0, map.size()); } - @VariedVectorsProvider.Test(provideNonMatching = true) + @VariedVectors.Test(provideNonMatching = true) @DisplayName("remove(nonMatch) returns null") void removeNonMatchingKeyReturnsNull(BlockVector3 vec, BlockVector3 nonMatch) { map.put(vec, air); @@ -456,7 +457,7 @@ class BlockMapTest { assertEquals(1, map.size()); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("remove(key, value) returns true") void removeKeyValueReturnsTrue(BlockVector3 vec) { map.put(vec, air); @@ -464,7 +465,7 @@ class BlockMapTest { assertEquals(0, map.size()); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("remove(key, value) returns false for wrong value") void removeKeyValueReturnsFalseWrongValue(BlockVector3 vec) { map.put(vec, air); @@ -472,7 +473,7 @@ class BlockMapTest { assertEquals(1, map.size()); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("replaces value at key") void replacesValueAtKey(BlockVector3 vec) { map.put(vec, air); @@ -486,7 +487,7 @@ class BlockMapTest { assertEquals(air, map.get(vec)); } - @VariedVectorsProvider.Test(provideNonMatching = true) + @VariedVectors.Test(provideNonMatching = true) @DisplayName("does not replace value at different key") void doesNotReplaceAtDifferentKey(BlockVector3 vec, BlockVector3 nonMatch) { map.put(vec, air); @@ -500,7 +501,7 @@ class BlockMapTest { assertEquals(air, map.get(vec)); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("does not insert on computeIfAbsent") void doesNotInsertComputeIfAbsent(BlockVector3 vec) { map.put(vec, air); @@ -512,7 +513,7 @@ class BlockMapTest { assertEquals(air, map.get(vec)); } - @VariedVectorsProvider.Test(provideNonMatching = true) + @VariedVectors.Test(provideNonMatching = true) @DisplayName("inserts on computeIfAbsent with different key") void insertsOnComputeIfAbsentDifferentKey(BlockVector3 vec, BlockVector3 nonMatch) { map.put(vec, air); @@ -525,7 +526,7 @@ class BlockMapTest { assertEquals(oakWood, map.get(nonMatch)); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("replaces on compute") void replaceOnCompute(BlockVector3 vec) { map.put(vec, air); @@ -540,7 +541,7 @@ class BlockMapTest { assertEquals(0, map.size()); } - @VariedVectorsProvider.Test(provideNonMatching = true) + @VariedVectors.Test(provideNonMatching = true) @DisplayName("inserts on compute with different key") void insertOnComputeDifferentKey(BlockVector3 vec, BlockVector3 nonMatch) { map.put(vec, air); @@ -557,7 +558,7 @@ class BlockMapTest { assertEquals(air, map.get(vec)); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("replaces on computeIfPresent") void replacesOnComputeIfPresent(BlockVector3 vec) { map.put(vec, air); @@ -572,7 +573,7 @@ class BlockMapTest { assertEquals(0, map.size()); } - @VariedVectorsProvider.Test + @VariedVectors.Test @DisplayName("inserts on merge, with call to merge function") void insertsOnMerge(BlockVector3 vec) { map.put(vec, air); @@ -590,7 +591,9 @@ class BlockMapTest { @Test @DisplayName("contains all inserted vectors") void containsAllInsertedVectors() { - Set allVectors = VariedVectorsProvider.makeVectorsStream().collect(Collectors.toSet()); + Set allVectors = new VariedVectorGenerator() + .makeVectorsStream() + .collect(Collectors.toSet()); for (BlockVector3 vec : allVectors) { map.put(vec, air); } diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/util/collection/PositionListTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/util/collection/PositionListTest.java new file mode 100644 index 000000000..a1867aea7 --- /dev/null +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/util/collection/PositionListTest.java @@ -0,0 +1,79 @@ +package com.sk89q.worldedit.util.collection; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.test.VariedVectors; +import org.junit.jupiter.api.DisplayName; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +abstract class PositionListTest { + + static class Long extends PositionListTest { + protected Long() { + super(new LongPositionList()); + } + } + + static class Vector extends PositionListTest { + protected Vector() { + super(new VectorPositionList()); + } + } + + private final PositionList positionList; + + protected PositionListTest(PositionList positionList) { + this.positionList = positionList; + } + + @VariedVectors.Test(capToVanilla = true) + @DisplayName("calling add(vec) increases size by 1") + void addIncreasesSizeByOne(BlockVector3 vec) { + positionList.add(vec); + assertEquals(1, positionList.size()); + } + + @VariedVectors.Test(capToVanilla = true) + @DisplayName("calling get(0) after add(vec) returns vec") + void canGetVectorAfterAdd(BlockVector3 vec) { + positionList.add(vec); + assertEquals(vec, positionList.get(0)); + } + + @VariedVectors.Test(capToVanilla = true) + @DisplayName("calling iterator().hasNext() after add(vec) returns true") + void hasNextAfterAdd(BlockVector3 vec) { + positionList.add(vec); + assertTrue(positionList.iterator().hasNext()); + } + + @VariedVectors.Test(capToVanilla = true) + @DisplayName("calling iterator().next() after add(vec) returns vec") + void nextAfterAdd(BlockVector3 vec) { + positionList.add(vec); + assertEquals(vec, positionList.iterator().next()); + } + + @VariedVectors.Test(capToVanilla = true) + @DisplayName("calling reverseIterator().hasNext() after add(vec) returns true") + void reverseHasNextAfterAdd(BlockVector3 vec) { + positionList.add(vec); + assertTrue(positionList.reverseIterator().hasNext()); + } + + @VariedVectors.Test(capToVanilla = true) + @DisplayName("calling reverseIterator().next() after add(vec) returns vec") + void reverseNextAfterAdd(BlockVector3 vec) { + positionList.add(vec); + assertEquals(vec, positionList.reverseIterator().next()); + } + + @VariedVectors.Test(capToVanilla = true) + @DisplayName("calling clear() after add(vec) makes the size() zero") + void clearAfterAdd(BlockVector3 vec) { + positionList.add(vec); + positionList.clear(); + assertEquals(0, positionList.size()); + } +} diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/util/test/VariedVectorGenerator.java b/worldedit-core/src/test/java/com/sk89q/worldedit/util/test/VariedVectorGenerator.java new file mode 100644 index 000000000..13c9f5dcc --- /dev/null +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/util/test/VariedVectorGenerator.java @@ -0,0 +1,81 @@ +package com.sk89q.worldedit.util.test; + +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Streams; +import com.sk89q.worldedit.math.BlockVector3; + +import java.util.Iterator; +import java.util.Set; +import java.util.stream.Stream; + +public class VariedVectorGenerator { + + // For better coverage assurance, increase these values for a local Gradle run. + // Don't do it for IntelliJ, it'll probably run out of memory. + private static final int DEFAULT_DIVISIONS_XZ = + Integer.getInteger("variedvecs.divisions.xz", 5); + private static final int DEFAULT_DIVISIONS_Y = + Integer.getInteger("variedvecs.divisions.y", 5); + + public final int divisionsXZ; + public final int divisionsY; + public final int maxXZ; + public final int maxY; + public final int xzStep; + public final int yStep; + public final Set alwaysInclude; + + public VariedVectorGenerator() { + this(false, -1, -1); + } + + public VariedVectorGenerator(boolean vanilla, int divisionsXZ, int divisionsY) { + this.divisionsXZ = divisionsXZ == -1 ? DEFAULT_DIVISIONS_XZ : divisionsXZ; + this.divisionsY = divisionsY == -1 ? DEFAULT_DIVISIONS_Y : divisionsY; + maxXZ = 30_000_000; + maxY = vanilla ? 255 : Integer.MAX_VALUE; + xzStep = (maxXZ * 2) / this.divisionsXZ; + yStep = (maxY * 2) / this.divisionsY; + alwaysInclude = + ImmutableSet.of(BlockVector3.ZERO, BlockVector3.ONE, + BlockVector3.at(-maxXZ, 0, -maxXZ), + BlockVector3.at(maxXZ, 0, maxXZ), + BlockVector3.at(-maxXZ, maxY, -maxXZ), + BlockVector3.at(maxXZ, maxY, maxXZ)); + } + + public Stream makeVectorsStream() { + return Stream.concat( + alwaysInclude.stream(), + Streams.stream(generateVectors()).filter(v -> !alwaysInclude.contains(v)) + ); + } + + private Iterator generateVectors() { + return new AbstractIterator() { + + private int x = -maxXZ + 1; + private int z = -maxXZ + 1; + private long y = maxY; + + @Override + protected BlockVector3 computeNext() { + if (x > maxXZ) { + return endOfData(); + } + BlockVector3 newVector = BlockVector3.at(x, (int) y, z); + y += yStep; + if (y > maxY) { + y = 0; + z += xzStep; + if (z > maxXZ) { + z = -maxXZ; + x += xzStep; + } + } + return newVector; + } + }; + } +} diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/util/test/VariedVectors.java b/worldedit-core/src/test/java/com/sk89q/worldedit/util/test/VariedVectors.java new file mode 100644 index 000000000..daadda021 --- /dev/null +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/util/test/VariedVectors.java @@ -0,0 +1,87 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.util.test; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.support.AnnotationConsumer; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.stream.Stream; + +/** + * Argument provider for various vectors. + */ +public final class VariedVectors implements ArgumentsProvider, AnnotationConsumer { + + @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @ArgumentsSource(VariedVectors.class) + @ParameterizedTest(name = ParameterizedTest.ARGUMENTS_PLACEHOLDER) + public @interface Test { + + /** + * If {@code true}, provide a non-matching vector from + * the existing vectors set as well. This will nearly + * square the number of tests executed, since it will + * test every non-matching vector. + */ + boolean provideNonMatching() default false; + + /** + * If {@code true}, only provide vectors inside the range of Vanilla MC. + * This caps the Y value to 255. + */ + boolean capToVanilla() default false; + + int divisionsXZ() default -1; + + int divisionsY() default -1; + + } + + private boolean provideNonMatching; + private VariedVectorGenerator generator; + + @Override + public void accept(Test test) { + provideNonMatching = test.provideNonMatching(); + generator = new VariedVectorGenerator(test.capToVanilla(), test.divisionsXZ(), test.divisionsY()); + } + + @Override + public Stream provideArguments(ExtensionContext context) { + if (provideNonMatching) { + return generator.makeVectorsStream() + .flatMap(vec -> generator.makeVectorsStream().filter(v -> !v.equals(vec)) + .map(v -> Arguments.of(vec, v))); + } + return generator.makeVectorsStream().map(Arguments::of); + } + +}