diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 2e5622f7b51d..000000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -version: 2 -updates: - - package-ecosystem: "docker" - directory: "/" - schedule: - interval: "weekly" - - - package-ecosystem: "github-actions" - directory: "/.github/workflows/" - schedule: - interval: "daily" - - - package-ecosystem: "maven" - directory: "/" - schedule: - interval: "daily" -... diff --git a/pom.xml b/pom.xml index b7ca85e1407c..f97b55d45853 100644 --- a/pom.xml +++ b/pom.xml @@ -55,6 +55,12 @@ commons-collections4 4.5.0 + + com.github.spotbugs + spotbugs-annotations + 4.9.6 + test + @@ -155,4 +161,71 @@ + + + + dev + + + datastructures + trees + AVLSimple + + + + + + maven-surefire-plugin + + + com/thealgorithms/${modulename}/${packagename}/${classname}Test.java + + + + + + org.pitest + pitest-maven + 1.21.0 + + + org.pitest + pitest-junit5-plugin + 1.2.3 + + + + + com.thealgorithms.${modulename}.${packagename}.${classname}* + + com.thealgorithms.${modulename}.${packagename}.${classname}Test + + DEFAULTS + + 2 + + HTML + XML + + 4000 + + -Xmx2048m + + + true + + + + run-pitest + test + + mutationCoverage + + + + + + + + diff --git a/src/main/java/com/thealgorithms/datastructures/trees/AVLSimple.java b/src/main/java/com/thealgorithms/datastructures/trees/AVLSimple.java index e0309122cc12..57088d403a43 100644 --- a/src/main/java/com/thealgorithms/datastructures/trees/AVLSimple.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/AVLSimple.java @@ -28,7 +28,7 @@ public class AVLSimple { - private class Node { + private static class Node { int data; int height; @@ -82,6 +82,11 @@ private Node insert(Node node, int item) { } public void display() { + if (root == null) { + System.out.println("Tree is empty"); + return; + } + this.display(this.root); System.out.println(this.root.height); } diff --git a/src/main/java/com/thealgorithms/datastructures/trees/LCA.java b/src/main/java/com/thealgorithms/datastructures/trees/LCA.java index 95a289493007..34e97cca576f 100644 --- a/src/main/java/com/thealgorithms/datastructures/trees/LCA.java +++ b/src/main/java/com/thealgorithms/datastructures/trees/LCA.java @@ -7,26 +7,26 @@ public final class LCA { private LCA() { } - private static final Scanner SCANNER = new Scanner(System.in); - public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + // The adjacency list representation of a tree: ArrayList> adj = new ArrayList<>(); // v is the number of vertices and e is the number of edges - int v = SCANNER.nextInt(); + int v = scanner.nextInt(); int e = v - 1; for (int i = 0; i < v; i++) { - adj.add(new ArrayList()); + adj.add(new ArrayList<>()); } // Storing the given tree as an adjacency list int to; int from; for (int i = 0; i < e; i++) { - to = SCANNER.nextInt(); - from = SCANNER.nextInt(); + to = scanner.nextInt(); + from = scanner.nextInt(); adj.get(to).add(from); adj.get(from).add(to); @@ -42,8 +42,8 @@ public static void main(String[] args) { dfs(adj, 0, -1, parent, depth); // Inputting the two vertices whose LCA is to be calculated - int v1 = SCANNER.nextInt(); - int v2 = SCANNER.nextInt(); + int v1 = scanner.nextInt(); + int v2 = scanner.nextInt(); // Outputting the LCA System.out.println(getLCA(v1, v2, depth, parent)); diff --git a/src/test/java/com/thealgorithms/ciphers/AESTest.java b/src/test/java/com/thealgorithms/ciphers/AESTest.java new file mode 100644 index 000000000000..026ddef6d2b3 --- /dev/null +++ b/src/test/java/com/thealgorithms/ciphers/AESTest.java @@ -0,0 +1,167 @@ +package com.thealgorithms.ciphers; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.math.BigInteger; +import org.junit.jupiter.api.Test; + +public class AESTest { + @Test + void testScheduleCore() { + BigInteger input = new BigInteger("1a2b3c4d", 16); + int rconCounter = 1; + + BigInteger expected = new BigInteger("f0ebe3a2", 16); + + BigInteger result = AES.scheduleCore(input, rconCounter); + assertEquals(expected, result, "Should return " + expected); + } + + @Test + void testKeyExpansion() { + BigInteger initialKey = new BigInteger("000102030405060708090a0b0c0d0e0f", 16); + + BigInteger[] roundKeys = AES.keyExpansion(initialKey); + + assertEquals(initialKey, roundKeys[0], "First round key should match initial key"); + + for (int i = 1; i < roundKeys.length; i++) { + assertNotEquals(BigInteger.ZERO, roundKeys[i], "Round key " + i + " should not be zero"); + } + } + + @Test + void testSplitBlockIntoCells() { + StringBuilder binary = new StringBuilder(); + + for (int i = 1; i <= 16; i++) { + String byteStr = String.format("%8s", Integer.toBinaryString(i)).replace(" ", "0"); + binary.append(byteStr); + } + + BigInteger block = new BigInteger(binary.toString(), 2); + + int[] expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + + int[] result = AES.splitBlockIntoCells(block); + + assertArrayEquals(expected, result); + } + + @Test + void testMergeCellsIntoBlocks() { + int[] cells = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + + StringBuilder expectedBinary = new StringBuilder(); + + for (int cell : cells) { + expectedBinary.append(String.format("%8s", Integer.toBinaryString(cell)).replace(" ", "0")); + } + + BigInteger expected = new BigInteger(expectedBinary.toString(), 2); + + BigInteger result = AES.mergeCellsIntoBlock(cells); + + assertEquals(expected, result, "Merged block from cells should match expected BigInteger"); + } + + @Test + void testAddRoundKey() { + BigInteger ciphertext = new BigInteger("a1b2c3d5e5f60718", 16); + BigInteger key = new BigInteger("0f0e0d0c0b0a0909", 16); + + BigInteger expected = new BigInteger("12591166093633785361"); + + BigInteger result = AES.addRoundKey(ciphertext, key); + + assertEquals(expected, result, "Should return XOR of ciphertext and key"); + } + + @Test + void testSubBytes() { + BigInteger input = new BigInteger("000102030405060708090a0b0c0d0e0f", 16); + + BigInteger expected = new BigInteger("132239839819997069106320266673331350390"); + + BigInteger result = AES.subBytes(input); + + assertEquals(expected, result, "Should match correct S-Box substituted block"); + } + + @Test + void testSubBytesDec() { + BigInteger originalBlock = new BigInteger("3243f6a8885a308d313198a2e0370734", 16); + + BigInteger afterSubBytes = AES.subBytes(originalBlock); + BigInteger afterInverse = AES.subBytesDec(afterSubBytes); + + assertEquals(originalBlock, afterInverse, "subBytesDec should reverse subBytes"); + } + + @Test + void testShiftRows() { + BigInteger input = new BigInteger("00010203101112132021222330313233", 16); + + BigInteger expected = new BigInteger("00112233102132032031021330011223", 16); + + BigInteger result = AES.shiftRows(input); + + assertEquals(expected, result, "Should shift rows correctly"); + } + + @Test + void testShiftRowsDec() { + BigInteger originalBlock = new BigInteger("00010203101112132021222330313233", 16); + + BigInteger shifted = AES.shiftRows(originalBlock); + BigInteger unshifted = AES.shiftRowsDec(shifted); + + assertEquals(originalBlock, unshifted, "shiftRowsDec should reverse shiftRows"); + } + + @Test + void testMixColumns() { + BigInteger input = new BigInteger("d4bf5d30e0b452aeb84111f11e2798e5", 16); + + BigInteger expected = new BigInteger("046681e5e0cb199a48f8d37a2806264c", 16); + + BigInteger result = AES.mixColumns(input); + + assertEquals(expected, result, "MixColumns output did not match expected value"); + } + + @Test + void testMixColumnsDec() { + BigInteger input = new BigInteger("000102030405060708090a0b0c0d0e0f", 16); + + BigInteger mixed = AES.mixColumns(input); + BigInteger unmixed = AES.mixColumnsDec(mixed); + + assertEquals(input, unmixed, "mixColumnsDec should reverse mixColumns"); + } + + @Test + void testEncrypt() { + BigInteger plainText = new BigInteger("00112233445566778899aabbccddeeff", 16); + BigInteger key = new BigInteger("000102030405060708090a0b0c0d0e0f", 16); + + BigInteger expectedCipherText = new BigInteger("69c4e0d86a7b0430d8cdb78070b4c55a", 16); + + BigInteger actualCipherText = AES.encrypt(plainText, key); + + assertEquals(expectedCipherText, actualCipherText, "Encrypt output should match known ciphertext"); + } + + @Test + void testDecrypt() { + BigInteger plaintext = new BigInteger("00112233445566778899aabbccddeeff", 16); + BigInteger key = new BigInteger("000102030405060708090a0b0c0d0e0f", 16); + + BigInteger ciphertext = AES.encrypt(plaintext, key); + BigInteger decrypted = AES.decrypt(ciphertext, key); + + assertEquals(plaintext, decrypted, "Decrypt should reverse Encrypt"); + } +} diff --git a/src/test/java/com/thealgorithms/ciphers/CaesarTest.java b/src/test/java/com/thealgorithms/ciphers/CaesarTest.java index 7aa41c4cf423..d71d35dbdfc5 100644 --- a/src/test/java/com/thealgorithms/ciphers/CaesarTest.java +++ b/src/test/java/com/thealgorithms/ciphers/CaesarTest.java @@ -43,4 +43,78 @@ void caesarBruteForce() { assertEquals(27, allPossibleAnswers.length); assertEquals("Encrypt this text", allPossibleAnswers[5]); } + + @Test + void shouldReturnSameAsInputWhenShiftingZeroOr26() { + String message = "message"; + + String encoded1 = caesar.encode(message, 0); + String encoded2 = caesar.encode(message, 26); + + assertEquals(message, encoded1, "Encoded should be same as original"); + assertEquals(message, encoded2, "Encoded should be same as original"); + } + + @Test + void shouldReturnsameAsInputWhenUsingCharactersOutsideLatinAlphabet() { + String message = "!#¤%&/()=?`^½§@£$€{[]}´`¨~'*-.,_:;<>|"; + + String encoded = caesar.encode(message, 10); + + assertEquals(message, encoded); + } + + @Test + void shouldWrapZToAWhenEncodingSingleShift() { + String message = "zZ"; + + String encoded = caesar.encode(message, 1); + String exptected = "aA"; + + assertEquals(exptected, encoded, "zZ should wrap to aA"); + } + + @Test + void shouldWrapAToZWhenDecodingSingleShift() { + String message = "aA"; + + String decoded = caesar.decode(message, 1); + String expected = "zZ"; + + assertEquals(expected, decoded); + } + + @Test + void shouldNotWrapWhenEncodingFromYToZ() { + String message = "yY"; + + String encoded = caesar.encode(message, 1); + String expected = "zZ"; + + assertEquals(expected, encoded); + } + + @Test + void shouldNotWrapWhenDecodingFromBToA() { + String message = "bB"; + + String decoded = caesar.decode(message, 1); + String expected = "aA"; + + assertEquals(expected, decoded); + } + + @Test + void shouldContain27CombinationsFromBruteForce() { + String message = "message"; + + String encoded = caesar.encode(message, 10); + String[] combinations = caesar.bruteforce(encoded); + String expected = "wocckqo"; + + assertEquals(27, combinations.length, "Should contain 27 possible decoded combinations"); + assertEquals(expected, combinations[0], "First combination should contain encoded message"); + assertEquals(message, combinations[10], "10:th entry should contain original message"); + assertEquals(expected, combinations[26], "26:th entry should be the same as the 0:th entry, the encoded message"); + } } diff --git a/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java b/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java index 8deae45099d6..bf1a5c86de40 100644 --- a/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java +++ b/src/test/java/com/thealgorithms/ciphers/ColumnarTranspositionCipherTest.java @@ -1,10 +1,6 @@ package com.thealgorithms.ciphers; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -21,10 +17,10 @@ public void setUp() { @Test public void testEncryption() { String encryptedText = ColumnarTranspositionCipher.encrypt(plaintext, keyword); - assertNotNull(encryptedText, "The encrypted text should not be null."); - assertFalse(encryptedText.isEmpty(), "The encrypted text should not be empty."); + Assertions.assertNotNull(encryptedText, "The encrypted text should not be null."); + Assertions.assertFalse(encryptedText.isEmpty(), "The encrypted text should not be empty."); // Check if the encrypted text is different from the plaintext - assertNotEquals(plaintext, encryptedText, "The encrypted text should be different from the plaintext."); + Assertions.assertNotEquals(plaintext, encryptedText, "The encrypted text should be different from the plaintext."); } @Test @@ -32,8 +28,8 @@ public void testDecryption() { String encryptedText = ColumnarTranspositionCipher.encrypt(plaintext, keyword); String decryptedText = ColumnarTranspositionCipher.decrypt(); - assertEquals(plaintext.replaceAll(" ", ""), decryptedText.replaceAll(" ", ""), "The decrypted text should match the original plaintext, ignoring spaces."); - assertEquals(encryptedText, ColumnarTranspositionCipher.encrypt(plaintext, keyword), "The encrypted text should be the same when encrypted again."); + Assertions.assertEquals(plaintext.replaceAll(" ", ""), decryptedText.replaceAll(" ", ""), "The decrypted text should match the original plaintext, ignoring spaces."); + Assertions.assertEquals(encryptedText, ColumnarTranspositionCipher.encrypt(plaintext, keyword), "The encrypted text should be the same when encrypted again."); } @Test @@ -41,7 +37,118 @@ public void testLongPlainText() { String longText = "This is a significantly longer piece of text to test the encryption and decryption capabilities of the Columnar Transposition Cipher. It should handle long strings gracefully."; String encryptedText = ColumnarTranspositionCipher.encrypt(longText, keyword); String decryptedText = ColumnarTranspositionCipher.decrypt(); - assertEquals(longText.replaceAll(" ", ""), decryptedText.replaceAll(" ", ""), "The decrypted text should match the original long plaintext, ignoring spaces."); - assertEquals(encryptedText, ColumnarTranspositionCipher.encrypt(longText, keyword), "The encrypted text should be the same when encrypted again."); + Assertions.assertEquals(longText.replaceAll(" ", ""), decryptedText.replaceAll(" ", ""), "The decrypted text should match the original long plaintext, ignoring spaces."); + Assertions.assertEquals(encryptedText, ColumnarTranspositionCipher.encrypt(longText, keyword), "The encrypted text should be the same when encrypted again."); + } + + @Test + void shouldNotProduceNullOrEmptyEncryptedText() { + String encrypted = ColumnarTranspositionCipher.encrypt(plaintext, keyword); + + Assertions.assertNotNull(encrypted, "Encrypted text should not be null"); + Assertions.assertFalse(encrypted.isEmpty(), "Encrypted text should not be empty"); + } + + @Test + void shouldChangePlaintextToEncrypted() { + String encrypted = ColumnarTranspositionCipher.encrypt(plaintext, keyword); + + Assertions.assertNotEquals(plaintext, encrypted, "Encrypted text should differ from plaintext"); + } + + @Test + void shouldEncryptDetermenistically() { + String encrypted1 = ColumnarTranspositionCipher.encrypt(plaintext, keyword); + String encrypted2 = ColumnarTranspositionCipher.encrypt(plaintext, keyword); + + Assertions.assertEquals(encrypted1, encrypted2, "Encryptions should be equal"); + } + + @Test + void shouldProduceDifferentEncryptionsWithDifferentKeywoards() { + String keyword2 = keyword + "a"; + + String encrypted1 = ColumnarTranspositionCipher.encrypt(plaintext, keyword); + String encrypted2 = ColumnarTranspositionCipher.encrypt(plaintext, keyword2); + + Assertions.assertNotEquals(encrypted1, encrypted2, "Should produce different encryptions"); + } + + @Test + void shouldMatchWithUnsortedKeyword() { + String myPlaintext = "123456789"; + String myKeyword = "unsorted"; + + String encrypted = ColumnarTranspositionCipher.encrypt(myPlaintext, myKeyword); + String expected = "8≈7≈2≈4≈5≈3≈6≈19"; + + Assertions.assertEquals(expected, encrypted, "Should match"); + } + + @Test + void shouldMatchEncryptionAndDecryptionWithNoSpacesOrPadding() { + String myPlaintext = "NoSpacesOrPadding"; + + ColumnarTranspositionCipher.encrypt(myPlaintext, keyword); + String decrypted = ColumnarTranspositionCipher.decrypt(); + + Assertions.assertEquals(myPlaintext, decrypted, "Decrypted text should match original plaintext"); + } + + @Test + void shouldNotContainPaddingInDecryption() { + String myPlaintext = "text"; + + ColumnarTranspositionCipher.encrypt(myPlaintext, keyword); + String decrypted = ColumnarTranspositionCipher.decrypt(); + + Assertions.assertFalse(decrypted.contains("≈"), "Should not contain padding characters"); + } + + @Test + void shouldEncryptWithKeywordLongerThanPlaintext() { + String myPlaintext = "text"; + String myKeyword = "averylongkeyword"; + + String encryption = ColumnarTranspositionCipher.encrypt(myPlaintext, myKeyword); + + Assertions.assertNotNull(encryption, "Should encrypt where plaintext.length() < keyword.length()"); + } + + @Test + void shouldEncryptWithKeywordWithSameLengthAsPlaintext() { + String myPlaintext = "textaslongaskeyword"; + String myKeyword = "keywordaslongastext"; + + String encryption = ColumnarTranspositionCipher.encrypt(myPlaintext, myKeyword); + + Assertions.assertNotNull(encryption, "Should encrypt where plaintext.length() == keyword.length()"); + } + + @Test + void shouldProduceDifferentEncryptionForSameKeywordButSortedDifferently() { + String unsertedKeyword1 = "EFGHABCD"; + String unsertedKeyword2 = "AEBFCGDH"; + + String encrypted1 = ColumnarTranspositionCipher.encrypt(plaintext, unsertedKeyword1); + String encrypted2 = ColumnarTranspositionCipher.encrypt(plaintext, unsertedKeyword2); + + Assertions.assertNotEquals(encrypted1, encrypted2, "Should differ with different keywords"); + } + + @Test + void shouldEncryptWithCustomAbecedarium() { + String myAbecedarium = "abcdefghijklmnopqrstuvwxyz"; + + String encryption = ColumnarTranspositionCipher.encrypt(plaintext, keyword, myAbecedarium); + + Assertions.assertNotNull(encryption, "Should encrypt with custom abecedarium"); + } + + @Test + void shouldNotEncryptWithInvalidAbecedarium() { + String myAbecedarium = "abcde"; + + Assertions.assertThrows(NullPointerException.class, () -> ColumnarTranspositionCipher.encrypt(plaintext, keyword, myAbecedarium), "Should throw error when keyword contains characters not present in abecedarium"); } } diff --git a/src/test/java/com/thealgorithms/ciphers/PlayfairTest.java b/src/test/java/com/thealgorithms/ciphers/PlayfairTest.java index fa497e4682e8..8bd0480ff5e3 100644 --- a/src/test/java/com/thealgorithms/ciphers/PlayfairTest.java +++ b/src/test/java/com/thealgorithms/ciphers/PlayfairTest.java @@ -2,35 +2,56 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class PlayfairTest { + private PlayfairCipher playfair; + private static final String KEYWORD = "KEYWORD"; + + @BeforeEach + public void setup() { + playfair = new PlayfairCipher(KEYWORD); + } @Test - public void testEncryption() { - PlayfairCipher playfairCipher = new PlayfairCipher("KEYWORD"); + void shouldEncryptAndDecryptDuringSameRowDigraph() { + String plaintext = KEYWORD.substring(0, 2); + + String encrypted = playfair.encrypt(plaintext); + String decrypted = playfair.decrypt(encrypted); + + assertEquals(plaintext, decrypted, "Should not decrypt to the same letters"); + } + + @Test + void shouldPadOddLengthPlaintext() { + String plaintext = "cat"; + String encrypted = playfair.encrypt(plaintext); + + assertEquals(0, encrypted.length() % 2, "Should be even length"); + } + + @Test + public void testEncryption() { String plaintext = "HELLO"; - String encryptedText = playfairCipher.encrypt(plaintext); + String encryptedText = playfair.encrypt(plaintext); assertEquals("GYIZSC", encryptedText); } @Test public void testDecryption() { - PlayfairCipher playfairCipher = new PlayfairCipher("KEYWORD"); - String encryptedText = "UDRIYP"; - String decryptedText = playfairCipher.decrypt(encryptedText); + String decryptedText = playfair.decrypt(encryptedText); assertEquals("NEBFVH", decryptedText); } @Test public void testEncryptionAndDecryption() { - PlayfairCipher playfairCipher = new PlayfairCipher("KEYWORD"); - String plaintext = "PLAYFAIR"; - String encryptedText = playfairCipher.encrypt(plaintext); - String decryptedText = playfairCipher.decrypt(encryptedText); + String encryptedText = playfair.encrypt(plaintext); + String decryptedText = playfair.decrypt(encryptedText); assertEquals(plaintext, decryptedText); } diff --git a/src/test/java/com/thealgorithms/datastructures/trees/AVLSimpleTest.java b/src/test/java/com/thealgorithms/datastructures/trees/AVLSimpleTest.java new file mode 100644 index 000000000000..f8acfdadf8d8 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/AVLSimpleTest.java @@ -0,0 +1,150 @@ +package com.thealgorithms.datastructures.trees; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.thealgorithms.devutils.ConsoleInterceptor; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class AVLSimpleTest { + AVLSimple tree = new AVLSimple(); + ConsoleInterceptor interceptor = new ConsoleInterceptor(); + + /* ======================== + Setup/TearDown + ======================== */ + + /** Starts capturing System.in. */ + @BeforeEach + void setup() { + interceptor.captureOutput(); + } + + /** Cleans up after each test by closing the interceptor. */ + @AfterEach + void tearDown() { + interceptor.close(); + } + + /* ======================== + Helper methods + ======================== */ + + /** + * Returns a string representation of the expected tree after inserting + * the values 10, 20, and 30 in any order. + *

+ * The returned string follows the same format as the tree's display method, + * where each node is represented in the form "left=>value<=right" and "END" + * denotes an empty (null) child. + * @return a string representing the expected tree structure + */ + String getExpectedTree() { + return "10=>20<=30END=>10<=ENDEND=>30<=END2"; + } + + /** + * Returns the actual string representation of the tree as printed + * by the display method. + * + *

This method captures the console output via the interceptor and + * removes all newline characters, returning a single-line string + * suitable for comparison with the expected tree string. + * + * @return the actual tree structure as a single-line string + */ + String getActualTree() { + return interceptor.getAndClearConsoleOutput().replaceAll("[\\r\\n]", ""); + } + + /* ======================== + Tests + ======================== */ + + @Test + @DisplayName("Creates a longer simple AVL tree that matches the expected layout") + void testTreeCreation() { + tree.insert(25); + tree.insert(30); + tree.insert(10); + tree.insert(5); + tree.insert(15); + tree.insert(27); + tree.insert(20); + tree.insert(19); + tree.insert(16); + + tree.display(); + + String expectedTree = "15=>25<=3010=>15<=195=>10<=ENDEND=>5<=END16=>19<=20END=>16<=ENDEND=>20<=END27=>30<=ENDEND=>27<=END4"; + + assertEquals(expectedTree, getActualTree()); + } + + @Test + @DisplayName("A test where an empty tree should return the string \"Tree is empty\".") + void testEmptyTree() { + AVLSimple tree = new AVLSimple(); + + tree.display(); + + assertEquals("Tree is empty", getActualTree()); + } + + @ParameterizedTest + @MethodSource("getTreeNodesValues") + @DisplayName("Test to ensure all rotation paths are covered") + void testAllRotations(int node1, int node2, int node3, String errorMessage) { + assertAll(() -> assertDoesNotThrow(() -> tree.insert(node1), "inserting: " + node1), () -> assertDoesNotThrow(() -> tree.insert(node2), "inserting: " + node2), () -> assertDoesNotThrow(() -> tree.insert(node3), "inserting: " + node3)); + + tree.display(); + + assertEquals(getExpectedTree(), getActualTree(), errorMessage); + } + + public static Stream getTreeNodesValues() { + return Stream.of(Arguments.of(30, 20, 10, "LL rotation failed"), Arguments.of(30, 10, 20, "LR rotation failed"), Arguments.of(10, 20, 30, "RR rotation failed"), Arguments.of(10, 30, 20, "RL rotation failed")); + } + + @ParameterizedTest + @MethodSource("getTreeNodesInputForBFEqualsOneRotations") + @DisplayName("Checks rotation isn't triggered when balance factor equals threshold") + void testRotationsNotTriggeredWhenBFEqualsOne(int boundaryNode, String expectedTree, String errorMessage) { + tree.insert(20); + tree.insert(30); + tree.insert(10); + tree.insert(boundaryNode); + + tree.display(); + + assertEquals(expectedTree, getActualTree(), errorMessage); + } + + public static Stream getTreeNodesInputForBFEqualsOneRotations() { + return Stream.of(Arguments.of(5, "10=>20<=305=>10<=ENDEND=>5<=ENDEND=>30<=END3", "Insertion of 5 should not trigger rotation"), Arguments.of(15, "10=>20<=30END=>10<=15END=>15<=ENDEND=>30<=END3", "Insertion of 15 should not trigger rotation"), + Arguments.of(25, "10=>20<=30END=>10<=END25=>30<=ENDEND=>25<=END3", "Insertion of 25 should not trigger rotation"), Arguments.of(35, "10=>20<=30END=>10<=ENDEND=>30<=35END=>35<=END3", "Insertion of 35 should not trigger rotation")); + } + + @Test + @DisplayName("Ignores duplicate insertions and create a tree with depth one") + void testDuplicateInsertionsIgnored() { + int duplicate = 20; + tree.insert(duplicate); + tree.insert(duplicate); + tree.insert(duplicate); + + tree.display(); + + String actualTree = getActualTree(); + + assertEquals('1', actualTree.charAt(actualTree.length() - 1)); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/GenericTreeTest.java b/src/test/java/com/thealgorithms/datastructures/trees/GenericTreeTest.java new file mode 100644 index 000000000000..44653d43936e --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/GenericTreeTest.java @@ -0,0 +1,174 @@ +package com.thealgorithms.datastructures.trees; + +import com.thealgorithms.devutils.ConsoleInterceptor; +import java.util.InputMismatchException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class GenericTreeTest { + GenericTree tree; + ConsoleInterceptor interceptor = new ConsoleInterceptor(); + + /* ======================== + Setup/TearDown + ======================== */ + + /** + * Sets up the test environment before each test: + *

    + *
  1. Makes the interceptor provide a predefined sequence of values for the tree.
  2. + *
  3. Initializes a new GenericTree instance.
  4. + *
  5. Starts capturing console output via the interceptor.
  6. + *
+ */ + @BeforeEach + void setup() { + String treeValues = "40\n5\n34\n0\n1\n0\n32\n1\n123\n0\n342\n2\n98\n0\n35\n0\n12\n0"; + interceptor.mockInput(treeValues); + + tree = new GenericTree(); + + interceptor.captureOutput(); + } + + /** Cleans up after each test by closing the interceptor. */ + @AfterEach + void tearDown() { + interceptor.close(); + } + + /* ======================== + Helper methods + ======================== */ + + /** + * Returns the string representation of the generic tree that's created in the {@link #setup()} method. + * It looks something like this: + * + *
+     *                    40
+     *                / / | \  \
+     *              /  /  |  \  \
+     *             34 1   32 324 12
+     *                    |  | \
+     *                  123 98 35
+     * 
+ * @return The tree in a string format mimicking what the display() method gives. + */ + String getExpectedTree() { + return "40=>34 1 32 342 12 .34=>.1=>.32=>123 .123=>.342=>98 35 .98=>.35=>.12=>."; + } + + /** + * Returns the actual string representation of the tree as printed + * by the display method. + *

+ * This method captures the console output via the interceptor and + * removes all newline characters, returning a single-line string + * suitable for comparison with the expected tree string. + * @return the actual tree structure as a single-line string + */ + String getConsoleOutput() { + return interceptor.getAndClearConsoleOutput().replaceAll("[\\r\\n]", ""); + } + + /* ======================== + Tests + ======================== */ + + @Test + @DisplayName("Asserts the created tree have the expected structure") + void testValidTree() { + tree.display(); + + Assertions.assertEquals(getExpectedTree(), getConsoleOutput()); + } + + @Test + @DisplayName("Creating GenericTree with invalid input throws InputMismatchException") + void testCreateInValidTree() { + String invalidTreeValues = "a\nb\nc\n"; + interceptor.mockInput(invalidTreeValues); + + Assertions.assertThrows(InputMismatchException.class, GenericTree::new); + } + + @Test + @DisplayName("Gets the correct number of nodes in the tree") + void testGettingCorrectSizeOfTree() { + Assertions.assertEquals(9, tree.size2call()); + } + + @Test + @DisplayName("Gets the highest value among the trees nodes") + void testGettingTheMaximalValueOfTreesNodes() { + Assertions.assertEquals(342, tree.maxcall()); + } + + @Test + @DisplayName("Returns the correct height of the tree") + void testGettingTheHeightOfTree() { + Assertions.assertEquals(2, tree.heightcall()); + } + + @ParameterizedTest + @ValueSource(ints = {40, 34, 1, 32, 342, 123, 98, 35}) + @DisplayName("Returns true when searching for values that is in the tree") + void testFindingAllPresentValuesInTree(int value) { + Assertions.assertTrue(tree.findcall(value), "The expected value " + value + " wasn't in the tree"); + } + + @ParameterizedTest + @ValueSource(ints = {41, 31, 2, 52, 542, 223, 92, 38}) + @DisplayName("Returns false when searching for values that isn't in the tree") + void testFindingAbsentValuesInTree(int value) { + Assertions.assertFalse(tree.findcall(value), "The value " + value + " was unexpectedly in the tree"); + } + + @Test + @DisplayName("Outputs all nodes at each tree level from left to right") + void testFindingAllNodesOfCertainDepthInTree() { + int height = tree.heightcall(); + + for (int i = 0; i <= height; i++) { + tree.depthcaller(i); + } + + Assertions.assertEquals("4034132342121239835", getConsoleOutput()); + } + + @Test + @DisplayName("Prints all node values in pre order") + void testPreOrderPrintsAsExpected() { + tree.preordercall(); + Assertions.assertEquals("40 34 1 32 123 342 98 35 12 .", getConsoleOutput()); + } + + @Test + @DisplayName("Prints all node values in post order") + void testPostOrderPrintsAsExpected() { + tree.postordercall(); + Assertions.assertEquals("34 1 123 32 98 35 342 12 40 .", getConsoleOutput()); + } + + @Test + @DisplayName("Prints all node values in level order") + void testLevelOrderPrintsAsExpected() { + tree.levelorder(); + Assertions.assertEquals("40 34 1 32 342 12 123 98 35 .", getConsoleOutput()); + } + + @Test + @DisplayName("Removes all leaves from the tree") + void testLeavesAreRemoved() { + tree.removeleavescall(); + tree.display(); + + Assertions.assertEquals("40=>32 342 .32=>.342=>.", getConsoleOutput()); + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/trees/LCATest.java b/src/test/java/com/thealgorithms/datastructures/trees/LCATest.java new file mode 100644 index 000000000000..7b42d3c61fb2 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/trees/LCATest.java @@ -0,0 +1,52 @@ +package com.thealgorithms.datastructures.trees; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.thealgorithms.devutils.ConsoleInterceptor; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class LCATest { + /** + * This input creates the following tree: + *

+     *     0
+     *   /   \
+     *  1     2
+     *  |    / \
+     *  5   4   3
+     *  |       |
+     *  6       7
+     *         / \
+     *        9   8
+     * 
+     */
+    private static final String TREE = "10\n0\n1\n0\n2\n1\n5\n5\n6\n2\n4\n2\n3\n3\n7\n7\n9\n7\n8\n";
+
+    /* ========================
+        Tests
+    ======================== */
+
+    @ParameterizedTest
+    @MethodSource("getSimulatedInputAndExpectedParent")
+    @DisplayName("Should return correct common ancestor for any two nodes in the tree")
+    void shouldReturnCorrectLCAThroughMain(String simulatedInput, String expectedParent) {
+        try (ConsoleInterceptor interceptor = new ConsoleInterceptor()) {
+            interceptor.mockInput(simulatedInput);
+            interceptor.captureOutput();
+
+            LCA.main(new String[0]);
+
+            String actualParent = interceptor.getAndClearConsoleOutput().replaceAll("[\\r\\n]", "");
+
+            assertEquals(expectedParent, actualParent, "The two nodes Lowest Common Ancestor wasn't the expected one.");
+        }
+    }
+
+    public static Stream getSimulatedInputAndExpectedParent() {
+        return Stream.of(Arguments.of(TREE + "9\n4\n", "2"), Arguments.of(TREE + "5\n6\n", "5"), Arguments.of(TREE + "5\n4\n", "0"), Arguments.of(TREE + "3\n8\n", "3"), Arguments.of(TREE + "6\n3\n", "0"), Arguments.of(TREE + "3\n3\n", "3"));
+    }
+}
diff --git a/src/test/java/com/thealgorithms/devutils/ConsoleInterceptor.java b/src/test/java/com/thealgorithms/devutils/ConsoleInterceptor.java
new file mode 100644
index 000000000000..ee72c23cdd6a
--- /dev/null
+++ b/src/test/java/com/thealgorithms/devutils/ConsoleInterceptor.java
@@ -0,0 +1,101 @@
+package com.thealgorithms.devutils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.PrintStream;
+
+/**
+ * Utility for mocking System.in and capturing System.out in a single-threaded environment.
+ * Designed for use with try-with-resources inside test methods to automatically restore them after usage.
+ * 

+ * Recommended usage: + *

    + *
  • Inside test methods with try-with-resources for automatic restoration of streams
  • + *
  • + * Or as a global instance in the test class, in which case you should call {@link #close()} + * for good measures in a teardown method + *
  • + *
+ */ +public class ConsoleInterceptor implements AutoCloseable { + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + /** Saved reference to stdout */ + private final PrintStream originalOut = System.out; + /** Saved reference to stdin */ + private final InputStream originalIn = System.in; + private boolean isCapturing; + + /* =========================== + Input + =========================== */ + + /** + * Mock System.in with the provided input. + * Used in test, mainly for simulating user input from the keyboard for scanners. + *

+ * Each line of user input must end with a newline character (\n), + * because {@link java.util.Scanner#nextLine()} and similar methods read input line by line. + *

+ * Example input: + *

+ * "This is input line one\nAnd this is the second line of input\nAnd so on...\n" + */ + public void mockInput(String mockedInput) { + System.setIn(new ByteArrayInputStream(mockedInput.getBytes())); + } + + /* =========================== + Output + =========================== */ + + /** + * Start capturing System.out by replacing stdout with a custom PrintStream. + * All printed data will be stored in outContent - available via {@link #getAndClearConsoleOutput}, for later retrieval. + */ + public void captureOutput() { + if (!isCapturing) { + System.setOut(new PrintStream(outContent)); + isCapturing = true; + } + } + + /** + * Get current captured output and clears the buffer. + * @return the captured output as a string + * @throws IllegalStateException if output hasn't been captured yet + */ + public String getAndClearConsoleOutput() { + if (isCapturing && outContent.size() > 0) { + String output = outContent.toString(); + outContent.reset(); + return output; + } else { + throw new IllegalStateException("Output hasn't been captured yet."); + } + } + + /** Clears the output buffer. */ + public void clearConsoleOutput() { + outContent.reset(); + } + + /* =========================== + Input And Output + =========================== */ + /** + * {@inheritDoc} + *

+ * This override restores the original System.out and System.in streams, + * resets the captured output stored in the internal OutputStream, + * and sets the {@code isCapturing} flag to {@code false} to indicate + * that capturing has stopped and prevent further access to {@code outContent}. + */ + @Override + public void close() { + System.setOut(originalOut); + System.setIn(originalIn); + outContent.reset(); + isCapturing = false; + } +} diff --git a/src/test/java/com/thealgorithms/maths/MathBuilderTest.java b/src/test/java/com/thealgorithms/maths/MathBuilderTest.java index dc381bfca5d3..6e1a0ebe4bb4 100644 --- a/src/test/java/com/thealgorithms/maths/MathBuilderTest.java +++ b/src/test/java/com/thealgorithms/maths/MathBuilderTest.java @@ -1,28 +1,32 @@ package com.thealgorithms.maths; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; - +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class MathBuilderTest { @Test void simpleMath() { double result = new MathBuilder.Builder(100).add(200).multiply(10).print().divideIf(20, (a, b) -> a % 2 == 0).sqrt().print().ceil().build().get(); - assertEquals(13, result); + Assertions.assertEquals(13, result); } @Test void memoryTest() { long result = new MathBuilder.Builder().set(100).sqrt().remember().add(21).recallIf(a -> a < 50, true).mod(2).build().toLong(); - assertEquals(0, result); + Assertions.assertEquals(0, result); } @Test void freeFallDistance() { long distance = new MathBuilder.Builder(9.81).multiply(0.5).multiply(5 * 5).round().build().toLong(); - assertEquals(123, distance); // Expected result: 0.5 * 9.81 * 25 = 122.625 ≈ 123 + Assertions.assertEquals(123, distance); // Expected result: 0.5 * 9.81 * 25 = 122.625 ≈ 123 } @Test @@ -33,20 +37,208 @@ void batchSalaryProcessing() { processedSalaries[i] = new MathBuilder.Builder(salaries[i]).addIf(salaries[i] * 0.1, (sal, bonus) -> sal > 2500).multiply(0.92).round().build().toLong(); } long[] expectedSalaries = {1840, 3036, 4048, 5060}; - assertArrayEquals(expectedSalaries, processedSalaries); + Assertions.assertArrayEquals(expectedSalaries, processedSalaries); } @Test void parenthesis() { // 10 + (20*5) - 40 + (100 / 10) = 80 double result = new MathBuilder.Builder(10).openParenthesis(20).multiply(5).closeParenthesisAndPlus().minus(40).openParenthesis(100).divide(10).closeParenthesisAndPlus().build().get(); - assertEquals(80, result); + Assertions.assertEquals(80, result); } @Test void areaOfCircle() { // Radius is 4 double area = new MathBuilder.Builder().pi().openParenthesis(4).multiply(4).closeParenthesisAndMultiply().build().get(); - assertEquals(Math.PI * 4 * 4, area); + Assertions.assertEquals(Math.PI * 4 * 4, area); + } + + @Test + @DisplayName("Floor Test") + void floorTest() { + // floor(10.5 + (20+2.1)) + double actual = new MathBuilder.Builder(10.5).openParenthesis(20).add(2.1).closeParenthesisAndPlus().floor().build().get(); + double expected = Math.floor(10.5 + 20 + 2.1); + + // 10.5 + floor((20+2.1)) + double actual2 = new MathBuilder.Builder(10.5).openParenthesis(20).add(2.1).floor().closeParenthesisAndPlus().build().get(); + double expected2 = 10.5 + Math.floor(20 + 2.1); + + Assertions.assertEquals(expected, actual); + Assertions.assertEquals(expected2, actual2); + } + + @Test + @DisplayName("Close parenthesis Test") + void closeParenthesisTest() { + // 10.5 - (20+2.1) + double actual = new MathBuilder.Builder(10.5).openParenthesis(20).add(2.1).closeParenthesisAndMinus().build().get(); + double expected = 10.5 - (20 + 2.1); + + // 10.5 / (20+2.1) + double actual2 = new MathBuilder.Builder(10.5).openParenthesis(20).add(2.1).closeParenthesisAndDivide().build().get(); + double expected2 = 10.5 / (20 + 2.1); + + Assertions.assertEquals(expected, actual); + Assertions.assertEquals(expected2, actual2); + } + + @Test + @DisplayName("open parenthesis Test") + void openParenthesisAndABSTest() { + // 10.5 - (20+2.1) + double actual = new MathBuilder.Builder(10.5).openParenthesis(20).minus(2.1).closeParenthesisAndMinus().build().get(); + double expected = 10.5 - (20 - 2.1); + + // 10.5 / (20+2.1) + double actual2 = new MathBuilder.Builder(10.5).openParenthesis(20).add(2.2).abs().closeParenthesisAndPlus().abs().build().get(); + double expected2 = 10.5 + (20 + 2.2); + + Assertions.assertEquals(expected, actual); + Assertions.assertEquals(expected2, actual2); + } + + @Test + @DisplayName("Runtime Errors Tests") + void runtimeErrorTest() { + MathBuilder.Builder actual = new MathBuilder.Builder(10.5); + + Executable randCheck = () -> Assertions.assertThrows(RuntimeException.class, () -> actual.rand(1)); + Executable rangeCheck = () -> Assertions.assertThrows(RuntimeException.class, () -> actual.randomInRange(1, 10)); + Executable piCheck = () -> Assertions.assertThrows(RuntimeException.class, actual::pi); + Executable eCheck = () -> Assertions.assertThrows(RuntimeException.class, actual::e); + Executable setCheck = () -> Assertions.assertThrows(RuntimeException.class, () -> actual.set(1)); + + Assertions.assertAll(randCheck, rangeCheck, piCheck, eCheck, setCheck); + } + + @Test + @DisplayName("Should divide 10 by 2") + void divideByNum() { + double actual = new MathBuilder.Builder(10).divide(2).build().get(); + + double expected = 10.0 / 2.0; + double expected2 = 10.0 / 4.0; + + Assertions.assertEquals(expected, actual); + Assertions.assertNotEquals(expected2, actual); + } + + @ParameterizedTest + @MethodSource("divideDoubleByZeroHelper") + @DisplayName("Test that ensures dividing a double by zero follows IEEE 754") + void divideDoubleByZero(double expected, MathBuilder.Builder actual, String error) { + Executable noThrowOnBuild = () -> Assertions.assertDoesNotThrow(() -> actual.build().get(), "Dividing a double with zero should not throw"); + Executable noThrowOnDivideZero = () -> Assertions.assertDoesNotThrow(() -> actual.divide(0).build().get(), "Dividing infinity with 0 should not throw"); + Executable resultIsInfinite = () -> Assertions.assertTrue(Double.isInfinite(actual.build().get()), "Dividing a double by zero should result in infinity"); + Executable equalsExpected = () -> Assertions.assertEquals(expected, actual.build().get(), error); + + Assertions.assertAll(noThrowOnBuild, noThrowOnDivideZero, resultIsInfinite, equalsExpected); + } + + static List divideDoubleByZeroHelper() { + return List.of(Arguments.of(Double.POSITIVE_INFINITY, new MathBuilder.Builder(10.5).openParenthesis(0).closeParenthesisAndDivide(), "10.5 / 0 should be +Infinity"), + Arguments.of(Double.NEGATIVE_INFINITY, new MathBuilder.Builder(-10.5).openParenthesis(0).closeParenthesisAndDivide(), "-10.5 / 0 should be -Infinity")); + } + + @Test + void randomFunctionsTest() { + double minValue = 0.0; + double maxValue = 2.1; + + double actual = new MathBuilder.Builder().rand(2L).build().get(); + double actual2 = new MathBuilder.Builder().randomInRange(minValue, maxValue).build().get(); + + Executable isBelowMax = () -> Assertions.assertTrue(actual < maxValue, "Random value should be less than maxValue"); + Executable isAboveMin = () -> Assertions.assertTrue(actual2 >= minValue, "RandomInRange value should be >= minValue"); + Executable isWithinRange = () -> Assertions.assertTrue(actual2 <= maxValue, "RandomInRange value should be <= maxValue"); + + Assertions.assertAll(isBelowMax, isAboveMin, isWithinRange); + } + + @ParameterizedTest + @MethodSource("radiansHelper") + void toRadiansTests(double expectedAngle, double actualAngle) { + Assertions.assertEquals(expectedAngle, actualAngle); + } + + private static List radiansHelper() { + return List.of(Arguments.of(Math.toRadians(10), new MathBuilder.Builder(10).toRadians().build().get()), Arguments.of(2 + Math.toRadians(10), new MathBuilder.Builder(2).openParenthesis(10).toRadians().closeParenthesisAndPlus().build().get())); + } + + @Test + void roundCielABSTest() { + double actual = new MathBuilder.Builder(10).openParenthesis(10.5).round().closeParenthesisAndPlus().build().get(); + double expected = 10 + Math.round(10.5); + + double actual2 = new MathBuilder.Builder(10).openParenthesis(10.5).ceil().closeParenthesisAndPlus().build().get(); + double expected2 = 10 + Math.ceil(10.5); + + double actual3 = new MathBuilder.Builder(10).openParenthesis(10.5).abs().closeParenthesisAndPlus().build().get(); + double expected3 = 10 + Math.abs(10.5); + + double actual4 = new MathBuilder.Builder(10).openParenthesis(10.5).closeParenthesisAndPlus().abs().build().get(); + double expected4 = Math.abs(10 + 10.5); + + Executable roundIsNotZero = () -> Assertions.assertNotEquals(0, actual); + Executable ceilIsNotOne = () -> Assertions.assertNotEquals(1, actual2); + Executable roundIsCorrect = () -> Assertions.assertEquals(expected, actual); + Executable ceilIsCorrect = () -> Assertions.assertEquals(expected2, actual2); + Executable absIsCorrect = () -> Assertions.assertEquals(expected3, actual3); + Executable absAfterPlusIsCorrect = () -> Assertions.assertEquals(expected4, actual4); + + Assertions.assertAll(roundIsNotZero, ceilIsNotOne, roundIsCorrect, ceilIsCorrect, absIsCorrect, absAfterPlusIsCorrect); + } + + @Test + void toLongTest() { + MathBuilder posOverflow = new MathBuilder.Builder(10.5).openParenthesis(0).closeParenthesisAndDivide().divide(0).build(); + MathBuilder negOverflow = new MathBuilder.Builder(10.5).openParenthesis(0).closeParenthesisAndDivide().divide(0).multiply(-1).build(); + MathBuilder maxRange = new MathBuilder.Builder(1999999999).multiply(2139999999).multiply(3).build(); + MathBuilder minRange = new MathBuilder.Builder(1999999999).multiply(2139999999).multiply(-3).build(); + + Executable posMaxCheck = () -> Assertions.assertEquals(Long.MAX_VALUE, posOverflow.toLong()); + Executable negMinCheck = () -> Assertions.assertEquals(Long.MIN_VALUE, negOverflow.toLong()); + Executable maxRangeCheck = () -> Assertions.assertEquals(Long.MAX_VALUE, maxRange.toLong()); + Executable minRangeCheck = () -> Assertions.assertEquals(Long.MIN_VALUE, minRange.toLong()); + Executable notZeroCheck = () -> Assertions.assertNotEquals(0, posOverflow.toLong()); + Executable notOneCheck = () -> Assertions.assertNotEquals(1, negOverflow.toLong()); + + Assertions.assertAll(posMaxCheck, negMinCheck, maxRangeCheck, minRangeCheck, notZeroCheck, notOneCheck); + } + + @Test + void maxTest() { + MathBuilder actual = new MathBuilder.Builder(10.5).max(20).build(); + MathBuilder actual2 = new MathBuilder.Builder(13.5).max(10).build(); + MathBuilder actual3 = new MathBuilder.Builder(10.5).openParenthesis(10).max(20).closeParenthesisAndPlus().build(); + MathBuilder actual4 = new MathBuilder.Builder(12.5).openParenthesis(10).closeParenthesisAndPlus().max(20).build(); + + Executable maxCheck1 = () -> Assertions.assertEquals(20, actual.get()); + Executable maxCheck2 = () -> Assertions.assertEquals(13.5, actual2.get()); + Executable maxCheck3 = () -> Assertions.assertEquals(30.5, actual3.get()); + Executable maxCheck4 = () -> Assertions.assertEquals(22.5, actual4.get()); + Executable notEqualsCheck1 = () -> Assertions.assertNotEquals(30, actual4.get()); + Executable notEqualsCheck2 = () -> Assertions.assertNotEquals(5, actual4.get()); + + Assertions.assertAll(maxCheck1, maxCheck2, maxCheck3, maxCheck4, notEqualsCheck1, notEqualsCheck2); + } + + @Test + void minTest() { + MathBuilder actual = new MathBuilder.Builder(10.5).min(20).build(); + MathBuilder actual2 = new MathBuilder.Builder(8.5).min(10).build(); + MathBuilder actual3 = new MathBuilder.Builder(10.5).openParenthesis(10).min(20).closeParenthesisAndPlus().build(); + MathBuilder actual4 = new MathBuilder.Builder(12.5).openParenthesis(10).closeParenthesisAndPlus().min(20).build(); + + Executable minCheck1 = () -> Assertions.assertEquals(10.5, actual.get()); + Executable minCheck2 = () -> Assertions.assertEquals(8.5, actual2.get()); + Executable minCheck3 = () -> Assertions.assertEquals(20.5, actual3.get()); + Executable minCheck4 = () -> Assertions.assertEquals(20, actual4.get()); + Executable notEqualsCheck1 = () -> Assertions.assertNotEquals(5, actual.get()); + Executable notEqualsCheck2 = () -> Assertions.assertNotEquals(-1000, actual3.get()); + + Assertions.assertAll(minCheck1, minCheck2, minCheck3, minCheck4, notEqualsCheck1, notEqualsCheck2); } } diff --git a/src/test/java/com/thealgorithms/maths/RomanNumeralUtilConstructorTest.java b/src/test/java/com/thealgorithms/maths/RomanNumeralUtilConstructorTest.java new file mode 100644 index 000000000000..554795299234 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/RomanNumeralUtilConstructorTest.java @@ -0,0 +1,20 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.lang.reflect.Constructor; +import org.junit.jupiter.api.Test; + +// Covers the private constructor for code coverage tools +public class RomanNumeralUtilConstructorTest { + @SuppressFBWarnings("RFI_SET_ACCESSIBLE") + @Test + void shouldInvokePrivateConstructor() throws Exception { + Constructor constructor = RomanNumeralUtil.class.getDeclaredConstructor(); + constructor.setAccessible(true); + RomanNumeralUtil instance = constructor.newInstance(); + + assertInstanceOf(RomanNumeralUtil.class, instance); + } +} diff --git a/src/test/java/com/thealgorithms/maths/RomanNumeralUtilTest.java b/src/test/java/com/thealgorithms/maths/RomanNumeralUtilTest.java new file mode 100644 index 000000000000..fde79d8d73ac --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/RomanNumeralUtilTest.java @@ -0,0 +1,33 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class RomanNumeralUtilTest { + @ParameterizedTest() + @CsvSource({"2000,MM", "1,I", "2,II", "1003,MIII", "1004,MIV"}) + void minimumMaximumTest(int testInt, String word) { + System.out.println(word + ": " + testInt); + assertEquals(RomanNumeralUtil.generate(testInt), word); + } + + @Test + void calcSomeNumerals() { + assertThrows(IllegalArgumentException.class, () -> RomanNumeralUtil.generate(0)); + assertThrows(IllegalArgumentException.class, () -> RomanNumeralUtil.generate(6000)); + } + + @Test + void shouldNotThrowOnBounds() { + int min = 1; + int max = 5999; + + assertDoesNotThrow(() -> RomanNumeralUtil.generate(min), "Should not throw exception on " + min); + assertDoesNotThrow(() -> RomanNumeralUtil.generate(max), "Should not throw exception on " + max); + } +} diff --git a/src/test/java/com/thealgorithms/maths/SimpsonIntegrationTest.java b/src/test/java/com/thealgorithms/maths/SimpsonIntegrationTest.java new file mode 100644 index 000000000000..28553d06a584 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/SimpsonIntegrationTest.java @@ -0,0 +1,82 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public class SimpsonIntegrationTest { + private final SimpsonIntegration simpson = new SimpsonIntegration(); + private static final double DELTA = 1e-9; + + @Test + void shouldCalculateCorrectFunction() { + assertAll(() -> assertEquals(-0.24893534183931973, simpson.f(3)), () -> assertEquals(0.0, simpson.f(2)), () -> assertEquals(4.0, simpson.f(0)), () -> assertEquals(8.154845485377136, simpson.f(-1))); + } + + @Test + void shouldCalculateCorrectMethod() { + int n = 4; + double a = -1.0; + double b = 1.0; + double h = (b - a) / n; + + double result = simpson.simpsonsMethod(n, h, a); + double expected = 8.51454379418048; + + assertEquals(expected, result, DELTA); + } + + @Test + void shouldIncreaseAccuracy() { + int n1 = 10; + int n2 = 20; + double a = 1.0; + double b = 3.0; + double h1 = (b - a) / n1; + double h2 = (b - a) / n2; + + double result1 = simpson.simpsonsMethod(n1, h1, a); + double result2 = simpson.simpsonsMethod(n2, h2, a); + + assertTrue(Math.abs(result2 - result1) < Math.abs(result1), "Accuracy should improve with more intervals."); + } + + @Test + void shouldNotFailOnOddNButTestResultSanity() { + int n = 3; + double a = -1.0; + double b = 1.0; + double h = (b - a) / n; + + double result = simpson.simpsonsMethod(n, h, a); + + assertTrue(Double.isFinite(result), "Result should be finite even with odd n (though it may be inaccurate)."); + } + + @Test + void shouldReturnZeroWhenAEqualsB() { + int n = 2; + double a = 2.0; + double b = 2.0; + double h = (b - a) / n; + + double expected = 0.0; + double result = simpson.simpsonsMethod(n, h, a); + + assertEquals(expected, result, DELTA, "Integral over zero-width interval should be zero."); + } + + @Test + void shouldHandleMinimalEvenN() { + int n = 2; + double a = 0.0; + double b = 1.0; + double h = (b - a) / n; + + double result = simpson.simpsonsMethod(n, h, a); + + assertTrue(Double.isFinite(result), "Result should be finite with minimal even n=2."); + } +}