Thanks to visit codestin.com
Credit goes to tamnd.github.io

6.23 Testing Sort Correctness

Verify both the ordering and permutation properties of sorting implementations using randomized, edge-case, and adversarial test strategies.

6.23 Testing Sort Correctness

Sorting implementations are easy to get almost right and hard to get completely correct. Subtle bugs often appear only on edge cases, duplicate-heavy inputs, or adversarial patterns. A disciplined testing strategy verifies both the ordering and permutation properties under a wide range of inputs.

Problem

You have implemented a sorting algorithm. You want to verify that it is correct, stable when required, and robust across inputs.

Core Properties to Test

Every sorting algorithm must satisfy two fundamental properties:

Order condition

for all i:
  A[i] <= A[i+1]

Permutation condition

The output must contain exactly the same elements as the input.

These two properties are independent. Passing one does not imply the other.

Minimal Test Oracle

A simple correctness checker:

is_sorted(A):
  for i = 0 to n - 2:
    if A[i] > A[i+1]:
      return false
  return true

Permutation check using frequency maps:

same_multiset(A, B):
  countA = frequency_map(A)
  countB = frequency_map(B)
  return countA == countB

Combined test:

check_sort(input, output):
  assert is_sorted(output)
  assert same_multiset(input, output)

Stability Testing

If stability is required, test that equal keys preserve input order.

Decorate each element with its original index:

input = [(value, index)]

After sorting by value, verify:

for all equal values:
  indices are in increasing order

This detects whether equal elements have been reordered.

Edge Cases

Test boundary inputs explicitly:

empty array
single element
two elements (sorted and unsorted)
all elements equal
already sorted
reverse sorted
alternating high and low values

These cases often expose off-by-one errors and incorrect comparisons.

Adversarial Inputs

Some algorithms degrade on specific patterns.

Examples:

sorted input (quick sort worst case)
reverse sorted input
many duplicates (partition issues)
nearly sorted (adaptive behavior)

Generate inputs that stress the algorithm’s weak points.

Random Testing

Randomized testing covers a wide input space.

for many trials:
  A = random array
  B = sort(A)
  check_sort(A, B)

Use different distributions:

uniform random
small integer range (many duplicates)
large integer range
skewed distributions

Random testing often reveals bugs missed by fixed test cases.

Differential Testing

Compare against a trusted implementation.

reference = built_in_sort(A)
test = your_sort(A)
assert reference == test

This is powerful because the reference implementation handles many edge cases correctly.

Property-Based Testing

Instead of fixed inputs, define properties:

sorted output is nondecreasing
output length equals input length
output contains same elements

Generate random inputs and verify properties automatically.

Frameworks such as QuickCheck-style systems automate this approach.

Large Input Testing

Test performance and correctness on large arrays:

n = 10^5 or higher
random and structured inputs

This reveals:

  • performance issues
  • stack overflow (recursive algorithms)
  • memory allocation problems

Floating-Point Testing

If sorting floating-point values, test special cases:

NaN values
+0 and -0
infinity
very large and very small values

Define a consistent comparison policy. Many languages treat NaN as unordered.

Comparator Testing

If using custom comparators, verify:

transitivity
symmetry
consistency across calls

Incorrect comparators can break otherwise correct sorting algorithms.

Regression Tests

When a bug is found, add the failing input to a regression suite:

test_cases.append(failing_input)

This ensures the bug does not reappear after future changes.

Performance Testing

Measure runtime for different input sizes and patterns:

vary n
vary distribution
record time

Check that performance matches expected complexity:

O(n log n) for general sorts
O(n) for counting or radix under constraints

Unexpected slowdowns often indicate hidden inefficiencies.

Common Bugs

  • Off-by-one errors in loops
  • Incorrect pivot handling in quick sort
  • Unstable behavior when stability is required
  • Losing elements during swaps or merges
  • Incorrect handling of duplicates
  • Incorrect boundary conditions in partitioning

Takeaway

Sorting correctness depends on verifying order, permutation, and optionally stability. Combine edge cases, random testing, and differential testing to build confidence in the implementation.