From 73e0f2fa363a82a4060d5abd14287791f40c6ba4 Mon Sep 17 00:00:00 2001 From: 66-m <53166536+66-m@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:07:32 +0100 Subject: [PATCH] Add unit tests for sorting algorithms and configure CI pipeline --- .github/workflows/ci.yml | 29 ++++ pom.xml | 18 +++ .../SortingAlgorithmsTest.java | 151 ++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 src/test/java/io/github/compilerstuck/SortingAlgorithms/SortingAlgorithmsTest.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ea1012a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 25 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 25 + - name: Cache Maven packages + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Build and run tests + run: | + mvn --batch-mode clean verify diff --git a/pom.xml b/pom.xml index 57c381e..96ddca4 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,17 @@ + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + **/*Test.java + + + @@ -76,5 +87,12 @@ forms_rt 7.0.3 + + + org.junit.jupiter + junit-jupiter + 5.10.0 + test + diff --git a/src/test/java/io/github/compilerstuck/SortingAlgorithms/SortingAlgorithmsTest.java b/src/test/java/io/github/compilerstuck/SortingAlgorithms/SortingAlgorithmsTest.java new file mode 100644 index 0000000..4bce7c4 --- /dev/null +++ b/src/test/java/io/github/compilerstuck/SortingAlgorithms/SortingAlgorithmsTest.java @@ -0,0 +1,151 @@ +package io.github.compilerstuck.SortingAlgorithms; + +import io.github.compilerstuck.Control.ArrayController; +import io.github.compilerstuck.Control.MainController; +import org.junit.jupiter.api.BeforeAll; +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; + +import processing.core.PApplet; + +import java.util.List; +import java.util.Random; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class SortingAlgorithmsTest { + + /** + * Minimal stub implementation of the Processing runtime used by the visualizer. + * Most of the sorting code only ever calls {@code delay(int)}, so we provide a + * no-op implementation to avoid starting a real graphics context during tests. + */ + static class DummyProcessing extends PApplet { + @Override + public void delay(int ms) { + // do nothing, keep tests fast + } + } + + @BeforeAll + static void setupMainController() { + // set static processing field so that algorithms don't hit NPE if they + // accidentally call it even though we disable delays in the tests. + MainController.processing = new DummyProcessing(); + } + + /** + * Create a random permutation of 0..size-1. Using a fixed seed ensures + * deterministic behaviour across runs. + */ + private static int[] randomPermutation(int size, long seed) { + int[] arr = new int[size]; + for (int i = 0; i < size; i++) { + arr[i] = i; + } + Random rnd = new Random(seed); + for (int i = size - 1; i > 0; i--) { + int j = rnd.nextInt(i + 1); + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + return arr; + } + + /** + * Provide every algorithm except for the deliberately pathological BogoSort. + * Each combination will be executed with two different array sizes to increase + * confidence that the implementation works for both small and moderately + * sized inputs. + */ + static Stream algorithmAndSizes() { + List> algs = List.of( + BubbleSort.class, + SelectionSort.class, + InsertionSort.class, + MergeSort.class, + QuickSortMiddlePivot.class, + QuickSortDualPivot.class, + HeapSort.class, + ShellSort.class, + TimSort.class, + CountingSort.class, + RadixLSDSortBase10.class, + BucketSort.class, + AmericanFlagSort.class, + CycleSort.class, + CombSort.class, + GnomeSort.class, + OddEvenSort.class, + ShakerSort.class, + DoubleSelectionSort.class, + PigeonholeSort.class, + GravitySort.class + ); + + return algs.stream() + .flatMap(alg -> Stream.of(Arguments.of(alg, 10), Arguments.of(alg, 50))); + } + + @ParameterizedTest(name = "{0} sorts {1} elements") + @MethodSource("algorithmAndSizes") + void algorithmShouldSort(Class algoClass, int size) throws Exception { + // we'll test three common scenarios: random data, already-sorted, and reverse-sorted + int[][] datasets = new int[4][]; + datasets[0] = randomPermutation(size, 12345L + size); + datasets[1] = new int[size]; + datasets[2] = new int[size]; + datasets[3] = new int[size]; + for (int i = 0; i < size; i++) { + datasets[1][i] = i; // already sorted + datasets[2][i] = size - i - 1; // reverse order + datasets[3][i] = i % 5; // many duplicates + } + + for (int idx = 0; idx < datasets.length; idx++) { + int[] values = datasets[idx]; + ArrayController controller = new ArrayController(size); + for (int i = 0; i < size; i++) { + controller.set(i, values[i]); + } + SortingAlgorithm algorithm = algoClass.getConstructor(ArrayController.class) + .newInstance(controller); + algorithm.setDelay(false); + algorithm.sort(); + String type; + switch (idx) { + case 0 -> type = "random"; + case 1 -> type = "sorted"; + case 2 -> type = "reverse"; + case 3 -> type = "duplicates"; + default -> type = "unknown"; + } + assertTrue(controller.isSorted(), algoClass.getSimpleName() + " failed to sort " + type + " dataset"); + } + } + + @Test + @DisplayName("BogoSort eventually produces a sorted array (small size)") + @org.junit.jupiter.api.Timeout(value = 5) + void bogoSortSmallArray() { + int size = 4; // small enough that random swaps finish in a reasonable time + ArrayController controller = new ArrayController(size); + int[] values = randomPermutation(size, 42L); + for (int i = 0; i < size; i++) { + controller.set(i, values[i]); + } + + BogoSort algo = new BogoSort(controller); + algo.setDelay(false); + // run for a capped number of iterations to avoid infinite loops in case + // something is wrong; the implementation keeps trying until sorted, so we + // simply execute the method on the small array. + algo.sort(); + assertTrue(controller.isSorted(), "BogoSort did not sort the small array"); + } +}