From 24e1a1bbbab324b3feddcdd4c66c40cfac1d349a Mon Sep 17 00:00:00 2001 From: Andrew Wong <42793301+md5sha256@users.noreply.github.com> Date: Thu, 29 Jul 2021 02:18:38 +0200 Subject: [PATCH 1/5] Add unit tests for CopyUtils --- .../dough/collections/DummyData.java | 49 +++++++ .../dough/collections/TestCopyUtils.java | 124 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java create mode 100644 dough-data/src/test/java/io/github/bakedlibs/dough/collections/TestCopyUtils.java diff --git a/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java new file mode 100644 index 00000000..26e2b3d1 --- /dev/null +++ b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java @@ -0,0 +1,49 @@ +package io.github.bakedlibs.dough.collections; + +import javax.annotation.Nonnull; + +public class DummyData implements Cloneable { + + private final int value; + + public DummyData(int value) { + this.value = value; + } + + private DummyData(@Nonnull DummyData other) { + this.value = other.value; + } + + @Override + public @Nonnull DummyData clone() { + try { + super.clone(); + } catch (CloneNotSupportedException ex) { + ex.printStackTrace(); + } + return new DummyData(this); + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + DummyData dummyData = (DummyData) o; + + return value == dummyData.value; + } + + @Override + public int hashCode() { + return value; + } + + @Override + public String toString() { + return "DummyData{" + "value=" + value + '}'; + } + +} diff --git a/dough-data/src/test/java/io/github/bakedlibs/dough/collections/TestCopyUtils.java b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/TestCopyUtils.java new file mode 100644 index 00000000..e2611cf3 --- /dev/null +++ b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/TestCopyUtils.java @@ -0,0 +1,124 @@ +package io.github.bakedlibs.dough.collections; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +class TestCopyUtils { + + private static boolean haveSameElements(@Nonnull Collection c1, @Nonnull Collection c2) { + if (c1.size() != c2.size()) { + return false; + } + return c1.containsAll(c2); + } + + private static boolean haveSameElements(@Nonnull Map m1, @Nonnull Map m2) { + if (m1.size() != m2.size()) { + return false; + } + for (Map.Entry entry : m1.entrySet()) { + if (!Objects.equals(m2.get(entry.getKey()), entry.getValue())) { + return false; + } + } + return true; + } + + private static boolean haveClonedElements(@Nonnull Collection c1, @Nonnull Collection c2) { + if (c1.size() != c2.size()) { + return false; + } + for (T t1 : c1) { + for (T t2 : c2) { + // If they are the same, it did not clone and thus we fail. + if (t1 == t2) { + return false; + } else if (t1.equals(t2)) { + // If they are equal, the element has been cloned, thus, we break and check the next element. + break; + } + } + } + return true; + } + + private static boolean haveClonedElements(@Nonnull Map m1, @Nonnull Map m2) { + if (m1.size() != m2.size()) { + return false; + } + for (Map.Entry entry : m1.entrySet()) { + V otherValue = m2.get(entry.getKey()); + // If they are the same, it did not clone and thus we fail. + if (entry.getValue() == otherValue) { + return false; + } else if (entry.getValue().equals(otherValue)) { + // If they are equal, the element has been cloned, thus, we break and check the next element. + break; + } + } + return true; + } + + + @Test + @DisplayName("Test if collections are deeply cloned properly") + void testCloningCollections() { + Collection dummyCollection = + Arrays.asList(new DummyData(1), new DummyData(2), new DummyData(3)); + Collection clonedCollection = + CopyUtils.deepCopy(dummyCollection, DummyData::clone, ArrayList::new); + Assertions.assertTrue(haveSameElements(dummyCollection, clonedCollection)); + Assertions.assertTrue(haveClonedElements(dummyCollection, clonedCollection)); + } + + @Test + @DisplayName("Test if maps are deeply cloned properly") + void testCloningMaps() { + Map dummyMap = new HashMap<>(); + dummyMap.put(1, new DummyData(1)); + dummyMap.put(2, new DummyData(2)); + dummyMap.put(3, new DummyData(3)); + Map clonedMap = CopyUtils.deepCopy(dummyMap, DummyData::clone, HashMap::new); + Assertions.assertTrue(haveSameElements(dummyMap, clonedMap)); + Assertions.assertTrue(haveClonedElements(dummyMap, clonedMap)); + // We first perform a shallow copy from clonedMap + Map clonedMapDeepCopy = new HashMap<>(clonedMap); + // We then mutate the shallow copy and turn it into a deep copy. + CopyUtils.deepCopy(clonedMapDeepCopy, DummyData::clone); + Assertions.assertTrue(haveSameElements(clonedMap, clonedMapDeepCopy)); + Assertions.assertTrue(haveClonedElements(clonedMap, clonedMapDeepCopy)); + } + + @Test + @DisplayName("Test if arrays are deeply cloned properly") + void testCloningArrays() { + DummyData[] dummyArray = new DummyData[]{new DummyData(1), new DummyData(2), new DummyData(3)}; + DummyData[] clonedArray = CopyUtils.deepCopy(dummyArray, DummyData::clone, DummyData[]::new); + Assertions.assertTrue(haveSameElements(Arrays.asList(dummyArray), Arrays.asList(clonedArray))); + Assertions.assertTrue(haveClonedElements(Arrays.asList(dummyArray), Arrays.asList(clonedArray))); + DummyData[] deepClonedArray = new DummyData[clonedArray.length]; + CopyUtils.deepCopy(clonedArray, DummyData::clone, deepClonedArray); + Assertions.assertTrue(haveSameElements(Arrays.asList(dummyArray), Arrays.asList(deepClonedArray))); + Assertions.assertTrue(haveClonedElements(Arrays.asList(dummyArray), Arrays.asList(deepClonedArray))); + // Cannot clone if the length of the sink < length of source + DummyData[] invalidArray = new DummyData[clonedArray.length - 1]; + Assertions.assertThrows(IllegalArgumentException.class, () -> CopyUtils.deepCopy(dummyArray, DummyData::clone, invalidArray)); + try { + DummyData[] validArray = new DummyData[clonedArray.length + 1]; + CopyUtils.deepCopy(dummyArray, DummyData::clone, validArray); + } catch (Exception ex) { + // Unexpected failure + Assertions.fail(ex); + } + } + +} From dc88fb335b334ed97fd6a3c35b75c8e9d4bf0426 Mon Sep 17 00:00:00 2001 From: Andrew Wong <42793301+md5sha256@users.noreply.github.com> Date: Thu, 29 Jul 2021 02:21:32 +0200 Subject: [PATCH 2/5] Make DummyData package-private --- .../java/io/github/bakedlibs/dough/collections/DummyData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java index 26e2b3d1..6918f7a3 100644 --- a/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java +++ b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java @@ -2,7 +2,7 @@ import javax.annotation.Nonnull; -public class DummyData implements Cloneable { +class DummyData implements Cloneable { private final int value; From ba66f6679a980ce89282f7e166ea9db17928ced4 Mon Sep 17 00:00:00 2001 From: Andrew Wong <42793301+md5sha256@users.noreply.github.com> Date: Thu, 29 Jul 2021 02:18:38 +0200 Subject: [PATCH 3/5] Add unit tests for CopyUtils --- .../dough/collections/DummyData.java | 49 +++++++ .../dough/collections/TestCopyUtils.java | 124 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java create mode 100644 dough-data/src/test/java/io/github/bakedlibs/dough/collections/TestCopyUtils.java diff --git a/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java new file mode 100644 index 00000000..26e2b3d1 --- /dev/null +++ b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java @@ -0,0 +1,49 @@ +package io.github.bakedlibs.dough.collections; + +import javax.annotation.Nonnull; + +public class DummyData implements Cloneable { + + private final int value; + + public DummyData(int value) { + this.value = value; + } + + private DummyData(@Nonnull DummyData other) { + this.value = other.value; + } + + @Override + public @Nonnull DummyData clone() { + try { + super.clone(); + } catch (CloneNotSupportedException ex) { + ex.printStackTrace(); + } + return new DummyData(this); + } + + @Override + public boolean equals(final Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + DummyData dummyData = (DummyData) o; + + return value == dummyData.value; + } + + @Override + public int hashCode() { + return value; + } + + @Override + public String toString() { + return "DummyData{" + "value=" + value + '}'; + } + +} diff --git a/dough-data/src/test/java/io/github/bakedlibs/dough/collections/TestCopyUtils.java b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/TestCopyUtils.java new file mode 100644 index 00000000..e2611cf3 --- /dev/null +++ b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/TestCopyUtils.java @@ -0,0 +1,124 @@ +package io.github.bakedlibs.dough.collections; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +class TestCopyUtils { + + private static boolean haveSameElements(@Nonnull Collection c1, @Nonnull Collection c2) { + if (c1.size() != c2.size()) { + return false; + } + return c1.containsAll(c2); + } + + private static boolean haveSameElements(@Nonnull Map m1, @Nonnull Map m2) { + if (m1.size() != m2.size()) { + return false; + } + for (Map.Entry entry : m1.entrySet()) { + if (!Objects.equals(m2.get(entry.getKey()), entry.getValue())) { + return false; + } + } + return true; + } + + private static boolean haveClonedElements(@Nonnull Collection c1, @Nonnull Collection c2) { + if (c1.size() != c2.size()) { + return false; + } + for (T t1 : c1) { + for (T t2 : c2) { + // If they are the same, it did not clone and thus we fail. + if (t1 == t2) { + return false; + } else if (t1.equals(t2)) { + // If they are equal, the element has been cloned, thus, we break and check the next element. + break; + } + } + } + return true; + } + + private static boolean haveClonedElements(@Nonnull Map m1, @Nonnull Map m2) { + if (m1.size() != m2.size()) { + return false; + } + for (Map.Entry entry : m1.entrySet()) { + V otherValue = m2.get(entry.getKey()); + // If they are the same, it did not clone and thus we fail. + if (entry.getValue() == otherValue) { + return false; + } else if (entry.getValue().equals(otherValue)) { + // If they are equal, the element has been cloned, thus, we break and check the next element. + break; + } + } + return true; + } + + + @Test + @DisplayName("Test if collections are deeply cloned properly") + void testCloningCollections() { + Collection dummyCollection = + Arrays.asList(new DummyData(1), new DummyData(2), new DummyData(3)); + Collection clonedCollection = + CopyUtils.deepCopy(dummyCollection, DummyData::clone, ArrayList::new); + Assertions.assertTrue(haveSameElements(dummyCollection, clonedCollection)); + Assertions.assertTrue(haveClonedElements(dummyCollection, clonedCollection)); + } + + @Test + @DisplayName("Test if maps are deeply cloned properly") + void testCloningMaps() { + Map dummyMap = new HashMap<>(); + dummyMap.put(1, new DummyData(1)); + dummyMap.put(2, new DummyData(2)); + dummyMap.put(3, new DummyData(3)); + Map clonedMap = CopyUtils.deepCopy(dummyMap, DummyData::clone, HashMap::new); + Assertions.assertTrue(haveSameElements(dummyMap, clonedMap)); + Assertions.assertTrue(haveClonedElements(dummyMap, clonedMap)); + // We first perform a shallow copy from clonedMap + Map clonedMapDeepCopy = new HashMap<>(clonedMap); + // We then mutate the shallow copy and turn it into a deep copy. + CopyUtils.deepCopy(clonedMapDeepCopy, DummyData::clone); + Assertions.assertTrue(haveSameElements(clonedMap, clonedMapDeepCopy)); + Assertions.assertTrue(haveClonedElements(clonedMap, clonedMapDeepCopy)); + } + + @Test + @DisplayName("Test if arrays are deeply cloned properly") + void testCloningArrays() { + DummyData[] dummyArray = new DummyData[]{new DummyData(1), new DummyData(2), new DummyData(3)}; + DummyData[] clonedArray = CopyUtils.deepCopy(dummyArray, DummyData::clone, DummyData[]::new); + Assertions.assertTrue(haveSameElements(Arrays.asList(dummyArray), Arrays.asList(clonedArray))); + Assertions.assertTrue(haveClonedElements(Arrays.asList(dummyArray), Arrays.asList(clonedArray))); + DummyData[] deepClonedArray = new DummyData[clonedArray.length]; + CopyUtils.deepCopy(clonedArray, DummyData::clone, deepClonedArray); + Assertions.assertTrue(haveSameElements(Arrays.asList(dummyArray), Arrays.asList(deepClonedArray))); + Assertions.assertTrue(haveClonedElements(Arrays.asList(dummyArray), Arrays.asList(deepClonedArray))); + // Cannot clone if the length of the sink < length of source + DummyData[] invalidArray = new DummyData[clonedArray.length - 1]; + Assertions.assertThrows(IllegalArgumentException.class, () -> CopyUtils.deepCopy(dummyArray, DummyData::clone, invalidArray)); + try { + DummyData[] validArray = new DummyData[clonedArray.length + 1]; + CopyUtils.deepCopy(dummyArray, DummyData::clone, validArray); + } catch (Exception ex) { + // Unexpected failure + Assertions.fail(ex); + } + } + +} From 2aea412a6df37f49d93ce5c11be01c13d4ad5575 Mon Sep 17 00:00:00 2001 From: Andrew Wong <42793301+md5sha256@users.noreply.github.com> Date: Thu, 29 Jul 2021 02:21:32 +0200 Subject: [PATCH 4/5] Make DummyData package-private --- .../java/io/github/bakedlibs/dough/collections/DummyData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java index 26e2b3d1..6918f7a3 100644 --- a/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java +++ b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java @@ -2,7 +2,7 @@ import javax.annotation.Nonnull; -public class DummyData implements Cloneable { +class DummyData implements Cloneable { private final int value; From 3d042aa498927c9de5c7224449f4766a4f609c7b Mon Sep 17 00:00:00 2001 From: md5sha256 <42793301+md5sha256@users.noreply.github.com> Date: Fri, 6 Aug 2021 22:56:27 +0200 Subject: [PATCH 5/5] Address PR changes --- .../dough/collections/DummyData.java | 2 +- .../dough/collections/TestCopyUtils.java | 63 +++++++------------ 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java index 6918f7a3..0b205faf 100644 --- a/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java +++ b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/DummyData.java @@ -6,7 +6,7 @@ class DummyData implements Cloneable { private final int value; - public DummyData(int value) { + DummyData(int value) { this.value = value; } diff --git a/dough-data/src/test/java/io/github/bakedlibs/dough/collections/TestCopyUtils.java b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/TestCopyUtils.java index e2611cf3..1e99d094 100644 --- a/dough-data/src/test/java/io/github/bakedlibs/dough/collections/TestCopyUtils.java +++ b/dough-data/src/test/java/io/github/bakedlibs/dough/collections/TestCopyUtils.java @@ -14,58 +14,43 @@ class TestCopyUtils { - private static boolean haveSameElements(@Nonnull Collection c1, @Nonnull Collection c2) { - if (c1.size() != c2.size()) { - return false; - } - return c1.containsAll(c2); + private static void assertHaveEqualElements(@Nonnull Collection c1, @Nonnull Collection c2) { + Assertions.assertEquals(c1.size(), c2.size()); + Assertions.assertTrue(c1.containsAll(c2)); } - private static boolean haveSameElements(@Nonnull Map m1, @Nonnull Map m2) { - if (m1.size() != m2.size()) { - return false; - } + private static void assertHaveEqualElements(@Nonnull Map m1, @Nonnull Map m2) { + Assertions.assertEquals(m1.size(), m2.size()); for (Map.Entry entry : m1.entrySet()) { - if (!Objects.equals(m2.get(entry.getKey()), entry.getValue())) { - return false; - } + Assertions.assertEquals(m2.get(entry.getKey()), entry.getValue()); } - return true; } - private static boolean haveClonedElements(@Nonnull Collection c1, @Nonnull Collection c2) { - if (c1.size() != c2.size()) { - return false; - } + private static void assertHaveClonedElements(@Nonnull Collection c1, @Nonnull Collection c2) { + Assertions.assertEquals(c1.size(), c2.size()); for (T t1 : c1) { for (T t2 : c2) { // If they are the same, it did not clone and thus we fail. - if (t1 == t2) { - return false; - } else if (t1.equals(t2)) { + Assertions.assertNotSame(t1, t2); + if (t1.equals(t2)) { // If they are equal, the element has been cloned, thus, we break and check the next element. break; } } } - return true; } - private static boolean haveClonedElements(@Nonnull Map m1, @Nonnull Map m2) { - if (m1.size() != m2.size()) { - return false; - } + private static void assertHaveClonedElements(@Nonnull Map m1, @Nonnull Map m2) { + Assertions.assertEquals(m1.size(), m2.size()); for (Map.Entry entry : m1.entrySet()) { V otherValue = m2.get(entry.getKey()); // If they are the same, it did not clone and thus we fail. - if (entry.getValue() == otherValue) { - return false; - } else if (entry.getValue().equals(otherValue)) { + Assertions.assertNotSame(entry.getValue(), otherValue); + if (entry.getValue().equals(otherValue)) { // If they are equal, the element has been cloned, thus, we break and check the next element. break; } } - return true; } @@ -76,8 +61,8 @@ void testCloningCollections() { Arrays.asList(new DummyData(1), new DummyData(2), new DummyData(3)); Collection clonedCollection = CopyUtils.deepCopy(dummyCollection, DummyData::clone, ArrayList::new); - Assertions.assertTrue(haveSameElements(dummyCollection, clonedCollection)); - Assertions.assertTrue(haveClonedElements(dummyCollection, clonedCollection)); + assertHaveEqualElements(dummyCollection, clonedCollection); + assertHaveClonedElements(dummyCollection, clonedCollection); } @Test @@ -88,14 +73,14 @@ void testCloningMaps() { dummyMap.put(2, new DummyData(2)); dummyMap.put(3, new DummyData(3)); Map clonedMap = CopyUtils.deepCopy(dummyMap, DummyData::clone, HashMap::new); - Assertions.assertTrue(haveSameElements(dummyMap, clonedMap)); - Assertions.assertTrue(haveClonedElements(dummyMap, clonedMap)); + assertHaveEqualElements(dummyMap, clonedMap); + assertHaveClonedElements(dummyMap, clonedMap); // We first perform a shallow copy from clonedMap Map clonedMapDeepCopy = new HashMap<>(clonedMap); // We then mutate the shallow copy and turn it into a deep copy. CopyUtils.deepCopy(clonedMapDeepCopy, DummyData::clone); - Assertions.assertTrue(haveSameElements(clonedMap, clonedMapDeepCopy)); - Assertions.assertTrue(haveClonedElements(clonedMap, clonedMapDeepCopy)); + assertHaveEqualElements(clonedMap, clonedMapDeepCopy); + assertHaveClonedElements(clonedMap, clonedMapDeepCopy); } @Test @@ -103,12 +88,12 @@ void testCloningMaps() { void testCloningArrays() { DummyData[] dummyArray = new DummyData[]{new DummyData(1), new DummyData(2), new DummyData(3)}; DummyData[] clonedArray = CopyUtils.deepCopy(dummyArray, DummyData::clone, DummyData[]::new); - Assertions.assertTrue(haveSameElements(Arrays.asList(dummyArray), Arrays.asList(clonedArray))); - Assertions.assertTrue(haveClonedElements(Arrays.asList(dummyArray), Arrays.asList(clonedArray))); + assertHaveEqualElements(Arrays.asList(dummyArray), Arrays.asList(clonedArray)); + assertHaveClonedElements(Arrays.asList(dummyArray), Arrays.asList(clonedArray)); DummyData[] deepClonedArray = new DummyData[clonedArray.length]; CopyUtils.deepCopy(clonedArray, DummyData::clone, deepClonedArray); - Assertions.assertTrue(haveSameElements(Arrays.asList(dummyArray), Arrays.asList(deepClonedArray))); - Assertions.assertTrue(haveClonedElements(Arrays.asList(dummyArray), Arrays.asList(deepClonedArray))); + assertHaveEqualElements(Arrays.asList(dummyArray), Arrays.asList(deepClonedArray)); + assertHaveClonedElements(Arrays.asList(dummyArray), Arrays.asList(deepClonedArray)); // Cannot clone if the length of the sink < length of source DummyData[] invalidArray = new DummyData[clonedArray.length - 1]; Assertions.assertThrows(IllegalArgumentException.class, () -> CopyUtils.deepCopy(dummyArray, DummyData::clone, invalidArray));