testing

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 1 Imported by: 0

README

Fisher-Yates Test Utilities

This package provides reusable test utilities, mocks, and helpers for testing Fisher-Yates implementations.

Overview

The testing package includes:

  • MockRandomSource: Deterministic random source for reproducible tests
  • Test Helpers: Functions for validating permutations and test assertions
  • Test Vectors: Cross-platform verification support

Components

MockRandomSource

A deterministic implementation of the RandomSource interface that cycles through predefined int32 values.

Features
  • Predictable output for reproducible tests
  • Configurable value sequences
  • Reset and update functionality
  • Full RandomSource interface compliance
Usage
import testing "github.com/0verkilll/fisheryates/testing"

// Create mock with specific values
mock := testing.NewMockRandomSource(5, -10, 3, -1, 0)

// Use with Fisher-Yates
fy := fisheryates.NewFisherYates()
perm := fy.Generate(5, mock)

// Reset for reuse
mock.Reset()

// Change values
mock.SetValues(10, 20, 30)
Example: Testing Negative Int32 Handling
func TestNegativeHandling(t *testing.T) {
    mock := testing.NewMockRandomSource(
        -2147483648, // math.MinInt32
        -1,
        0,
        1,
        2147483647, // math.MaxInt32
    )

    fy := fisheryates.NewFisherYates()
    perm := fy.Generate(10, mock)

    testing.AssertValidPermutation(t, perm)
}
Test Helpers
AssertValidPermutation

Verifies that a slice is a valid permutation of [0, n-1].

perm := fy.Generate(10, random)
testing.AssertValidPermutation(t, perm)

Checks:

  • All values in range [0, n-1]
  • No duplicates
  • All expected values present
AssertPermutationsEqual

Verifies determinism by comparing two permutations.

random.Seed([]byte("test"))
perm1 := fy.Generate(10, random)

random.Seed([]byte("test")) // Same seed
perm2 := fy.Generate(10, random)

testing.AssertPermutationsEqual(t, perm1, perm2)
AssertPermutationsDifferent

Verifies that different seeds produce different results.

random1.Seed([]byte("seed1"))
perm1 := fy.Generate(10, random1)

random2.Seed([]byte("seed2"))
perm2 := fy.Generate(10, random2)

testing.AssertPermutationsDifferent(t, perm1, perm2)
AssertBufferCapacityPreserved

Verifies zero-allocation pattern with GenerateInto().

buf := make([]int, 100)
initialCap := cap(buf)

buf = fy.GenerateInto(buf, 50, random)

testing.AssertBufferCapacityPreserved(t, buf, initialCap)
AssertNoAllocations

Verifies that a function performs no heap allocations.

buf := make([]int, 100)
testing.AssertNoAllocations(t, 100, func() {
    buf = fy.GenerateInto(buf, 100, random)
}, 0) // Expect 0 allocations
Test Vectors

Test vectors enable cross-platform verification between Go, Java, TypeScript, and Rust implementations.

GenerateTestVector

Creates a test vector for cross-platform comparison.

vec := testing.GenerateTestVector(
    []byte("test"),
    10,
    []int{5, 3, 1, 4, 2, 8, 6, 9, 0, 7},
)

t.Logf("Test Vector:")
t.Logf("  Seed: %q", vec.Seed)
t.Logf("  Size: %d", vec.Size)
t.Logf("  Output: %v", vec.Output)
Cross-Platform Example

Go:

hasher := sha1.NewSHA1(sha1.NewBigEndian())
random := securerandom.NewSecureRandom(hasher)
random.Seed([]byte("23"))

fy := fisheryates.NewFisherYates()
perm := fy.Generate(10, random)
// Expected: [5 3 1 4 2 8 6 9 0 7]

Java Equivalent:

SecureRandom random = new SecureRandom();
random.setSeed("23".getBytes());
int[] perm = FisherYates.generate(10, random);
// Expected: same output as Go
Utility Functions
CountInversions

Counts inversions in a permutation (useful for randomness analysis).

inversions := testing.CountInversions(perm)
t.Logf("Permutation has %d inversions", inversions)

// Theoretical average for random permutation of size n:
// expected ≈ n*(n-1)/4
IsIdentityPermutation

Checks if a permutation is the identity [0, 1, 2, ..., n-1].

if testing.IsIdentityPermutation(perm) {
    t.Error("Expected shuffled permutation, got identity")
}

Complete Example

package fisheryates

import (
    "testing"

    "github.com/0verkilll/fisheryates"
    testing "github.com/0verkilll/fisheryates/testing"
)

func TestWithUtilities(t *testing.T) {
    // Create mock random source
    mock := testing.NewMockRandomSource(3, 1, 4, 1, 5, 9, 2, 6)

    // Generate permutation
    fy := fisheryates.NewFisherYates()
    perm := fy.Generate(8, mock)

    // Validate using helpers
    testing.AssertValidPermutation(t, perm)

    // Test determinism
    mock.Reset() // Reset to beginning
    perm2 := fy.Generate(8, mock)
    testing.AssertPermutationsEqual(t, perm, perm2)

    // Test buffer reuse
    buf := make([]int, 10)
    initialCap := cap(buf)
    buf = fy.GenerateInto(buf, 8, mock)
    testing.AssertBufferCapacityPreserved(t, buf, initialCap)
}

Benefits

1. Deterministic Testing

MockRandomSource eliminates PRNG non-determinism, making tests reproducible and debuggable.

2. Reduced Boilerplate

Helper functions eliminate repetitive validation code in tests.

3. Better Error Messages

Helpers use t.Helper() to report errors at the correct call site.

4. Cross-Platform Verification

Test vectors enable validation across Go, Java, TypeScript, and Rust.

5. Performance Validation

Allocation helpers verify zero-allocation patterns work correctly.

Package Design

The testing package follows SOLID principles:

  • Single Responsibility: Each helper has one clear purpose
  • Open/Closed: Extensible through composition
  • Liskov Substitution: MockRandomSource satisfies RandomSource interface
  • Interface Segregation: Minimal, focused interfaces
  • Dependency Inversion: Depends on fisheryates interfaces, not implementations

See Also

Documentation

Overview

Package testing provides test utilities, mocks, and helpers for testing Fisher-Yates implementations.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertBufferCapacityPreserved

func AssertBufferCapacityPreserved(t TestReporter, buf []int, expectedCap int)

AssertBufferCapacityPreserved verifies that a buffer's capacity hasn't changed. This is useful for testing the zero-allocation pattern with GenerateInto().

Parameters:

  • t: The test reporter (typically *testing.T)
  • buf: The buffer to check
  • expectedCap: The expected capacity

Example:

buf := make([]int, 100)
initialCap := cap(buf)
buf = fy.GenerateInto(buf, 50, random)
AssertBufferCapacityPreserved(t, buf, initialCap)

func AssertNoAllocations

func AssertNoAllocations(t TestReporter, runs int, fn func(), maxAllocs float64)

AssertNoAllocations verifies that a function performs no heap allocations. This uses testing.AllocsPerRun to measure allocations.

Parameters:

  • t: The test reporter (typically *testing.T)
  • runs: Number of runs to average over
  • fn: The function to test
  • maxAllocs: Maximum allowed allocations per run

Example:

buf := make([]int, 100)
AssertNoAllocations(t, 100, func() {
    buf = fy.GenerateInto(buf, 100, random)
}, 0) // Expect zero allocations

func AssertPermutationsDifferent

func AssertPermutationsDifferent(t TestReporter, perm1, perm2 []int)

AssertPermutationsDifferent verifies that two permutations are different. This is useful for testing that different seeds produce different results.

Parameters:

  • t: The test reporter (typically *testing.T)
  • perm1: First permutation
  • perm2: Second permutation

Example:

perm1 := fy.Generate(10, random1)
perm2 := fy.Generate(10, random2) // Different seed
AssertPermutationsDifferent(t, perm1, perm2)

func AssertPermutationsEqual

func AssertPermutationsEqual(t TestReporter, perm1, perm2 []int)

AssertPermutationsEqual verifies that two permutations are identical.

Parameters:

  • t: The test reporter (typically *testing.T)
  • perm1: First permutation
  • perm2: Second permutation

Example:

perm1 := fy.Generate(10, random)
random.Seed(sameSeed) // Reset to same state
perm2 := fy.Generate(10, random)
AssertPermutationsEqual(t, perm1, perm2) // Verify determinism

func AssertValidPermutation

func AssertValidPermutation(t TestReporter, perm []int)

AssertValidPermutation verifies that a slice is a valid permutation of [0, n-1]. A valid permutation must contain all integers from 0 to n-1 exactly once.

This helper checks:

  • All values are in the range [0, n-1]
  • No duplicate values exist
  • All expected values are present

Parameters:

  • t: The test reporter (typically *testing.T)
  • perm: The permutation slice to validate

Example:

perm := fy.Generate(10, random)
AssertValidPermutation(t, perm) // Fails test if invalid

func CountInversions

func CountInversions(perm []int) int

CountInversions counts the number of inversions in a permutation. An inversion is a pair of indices (i, j) where i < j but perm[i] > perm[j]. This can be useful for analyzing the "randomness" of a permutation.

Parameters:

  • perm: The permutation to analyze

Returns:

  • The number of inversions

Example:

inversions := CountInversions(perm)
t.Logf("Permutation has %d inversions", inversions)

func IsIdentityPermutation

func IsIdentityPermutation(perm []int) bool

IsIdentityPermutation checks if a permutation is the identity permutation [0, 1, 2, ..., n-1].

Parameters:

  • perm: The permutation to check

Returns:

  • true if perm is the identity permutation, false otherwise

Example:

if IsIdentityPermutation(perm) {
    t.Error("Expected shuffled permutation, got identity")
}

Types

type MockRandomSource

type MockRandomSource struct {
	// contains filtered or unexported fields
}

MockRandomSource provides a deterministic random source for testing. It cycles through a predefined set of int32 values, making tests reproducible.

This mock implements the f5prng.RandomSource interface and is useful for:

  • Testing specific permutation sequences
  • Verifying negative int32 handling
  • Creating deterministic test scenarios

Example usage:

mock := NewMockRandomSource(5, -10, 3, -1, 0)
fy := fisheryates.NewFisherYates()
perm := fy.Generate(5, mock)
// perm will be generated using values [5, -10, 3, -1, 0] in sequence

func NewMockRandomSource

func NewMockRandomSource(values ...int32) *MockRandomSource

NewMockRandomSource creates a new MockRandomSource with the specified values. The mock will cycle through these values repeatedly when NextInt() is called.

Parameters:

  • values: The int32 values to return in sequence (cycles if exhausted)

Returns:

  • A MockRandomSource ready for use in tests

Example:

// Create mock that returns specific values
mock := NewMockRandomSource(-2147483648, 0, 2147483647)

// Use in permutation generation
perm := fy.Generate(10, mock)

func (*MockRandomSource) Clear

func (m *MockRandomSource) Clear()

Clear is a no-op for MockRandomSource since it holds no sensitive state. This method exists to satisfy the f5prng.RandomSource interface.

func (*MockRandomSource) NextBytes

func (m *MockRandomSource) NextBytes(n int) []byte

NextBytes returns a byte slice of the specified length. All bytes are zero for simplicity in this mock implementation.

Parameters:

  • n: Number of bytes to generate

Returns:

  • A byte slice of length n filled with zeros

func (*MockRandomSource) NextInt

func (m *MockRandomSource) NextInt() int32

NextInt returns the next int32 value from the predefined sequence. The sequence cycles back to the beginning when exhausted.

Returns:

  • The next int32 value in the sequence

func (*MockRandomSource) Reset

func (m *MockRandomSource) Reset()

Reset resets the mock's internal index to the beginning of the value sequence. This is useful when you want to repeat the same sequence in multiple tests.

Example:

mock := NewMockRandomSource(1, 2, 3)
perm1 := fy.Generate(5, mock) // Uses 1, 2, 3, 1, 2
mock.Reset()
perm2 := fy.Generate(5, mock) // Uses 1, 2, 3, 1, 2 (same sequence)

func (*MockRandomSource) Seed

func (m *MockRandomSource) Seed(_ []byte) error

Seed is a no-op for MockRandomSource since it provides deterministic values. This method exists to satisfy the RandomSource interface.

Parameters:

  • seed: Ignored (not used in deterministic mock)

Returns:

  • Always nil (mock cannot fail to seed)

func (*MockRandomSource) SetValues

func (m *MockRandomSource) SetValues(values ...int32)

SetValues replaces the current value sequence with a new one and resets the index. This allows reusing the same mock instance with different test data.

Parameters:

  • values: New int32 values to use in the sequence

Example:

mock := NewMockRandomSource(1, 2, 3)
perm1 := fy.Generate(3, mock)

mock.SetValues(10, 20, 30) // Change values for next test
perm2 := fy.Generate(3, mock)

type TestReporter

type TestReporter interface {
	Helper()
	Errorf(format string, args ...interface{})
	Fatalf(format string, args ...interface{})
	Error(args ...interface{})
}

TestReporter is a minimal interface for test reporting used by assertion helpers. This interface is automatically satisfied by *testing.T and *testing.B.

type TestVector

type TestVector struct {
	Seed   []byte // The seed bytes used for the PRNG
	Output []int  // The expected permutation output
	Size   int    // The permutation size
}

TestVector represents a cross-platform test vector. Test vectors are used to verify that implementations in different programming languages (Go, Java, TypeScript, Rust) produce identical results.

func GenerateTestVector

func GenerateTestVector(seed []byte, size int, perm []int) TestVector

GenerateTestVector creates a test vector for cross-platform verification. A test vector includes the input parameters and expected output for verifying implementations across different languages.

Parameters:

  • seed: The seed bytes
  • size: The permutation size
  • perm: The expected permutation output

Returns:

  • A TestVector struct

Example:

vec := GenerateTestVector([]byte("test"), 10, expectedPerm)
t.Logf("Test Vector: seed=%q, size=%d, output=%v", vec.Seed, vec.Size, vec.Output)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL