
Code Permutation Testing
- 9 installs
- 4 repo stars
- Updated April 11, 2026
- 89jobrien/steve
code-permutation-testing is a Claude Code skill that systematically tests code variations, edge cases, boundary conditions, and alternative implementations using input permutation, code-path analysis, and mutation testin
About
code-permutation-testing is a Claude Code skill for systematic testing of code variations, edge cases, and boundary conditions. A developer uses it to generate exhaustive test matrices, analyze code-path coverage, run mutation testing, and compare alternative implementations. It bundles Python helper scripts for generating boundaries and analyzing paths, plus reference docs for mutation testing. Note: the skill frontmatter marks it DEPRECATED.
- Generates boundary, edge-case and combinatorial test matrices from function signatures
- Runs mutation testing via cargo-mutants (Rust) and mutmut (Python) to verify suite effectiveness
- Marked DEPRECATED in its own frontmatter (deprecated_in 2026-01-20)
Code Permutation Testing by the numbers
- 9 all-time installs (skills.sh)
- Ranked #1,560 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
code-permutation-testing capabilities & compatibility
- Capabilities
- mutation testing · boundary testing · test generation · coverage analysis
- Use cases
- testing
- Pricing
- Free
What code-permutation-testing says it does
Systematic testing of code variations, edge cases, boundary conditions, and alternative implementations.
Verify test suite effectiveness by introducing controlled mutations
npx skills add https://github.com/89jobrien/steve --skill code-permutation-testingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 9 |
|---|---|
| repo stars | ★ 4 |
| Last updated | April 11, 2026 |
| Repository | 89jobrien/steve ↗ |
What it does
Use it to build comprehensive test coverage for critical code through input permutation, code-path analysis, and mutation testing.
Who is it for?
Testing functions with complex input domains and verifying test-suite effectiveness with mutation testing.
Skip if: Quick single-case unit tests or projects not written in Rust or Python.
When should I use this skill?
When ensuring code robustness through comprehensive test coverage or exploring edge cases and boundary conditions.
What you get
A comprehensive test matrix covering boundaries and mutations that proves suite effectiveness.
- boundary test cases
- code-path coverage analysis
- mutation-testing report
By the numbers
- 4 core testing modes
- 5-step workflow
Files
Code Permutation Testing
Overview
This skill enables systematic testing of code variations, edge cases, and alternative implementations to ensure robustness and comprehensive test coverage. It provides methodologies for input permutation, code path analysis, mutation testing, and implementation exploration.
When to Use
Invoke this skill when:
- Testing functions with complex input domains requiring boundary testing
- Exploring edge cases and corner conditions systematically
- Analyzing code path coverage and branch conditions
- Running mutation testing to verify test suite effectiveness
- Comparing alternative implementations for correctness
- Generating comprehensive test suites for critical code
- Validating error handling and recovery paths
Core Testing Modes
1. Input Permutation Testing
Generate comprehensive test cases covering:
- Boundary values: Min, max, just inside, just outside boundaries
- Edge cases: Empty inputs, null values, extreme sizes
- Type variations: Different numeric types, string encodings, data structures
- Combinatorial testing: Pairwise and n-wise testing of input combinations
Load references/boundary_patterns.md for common boundary testing patterns and strategies.
Use scripts/generate_boundaries.py to automatically generate boundary test cases from function signatures.
2. Code Path Analysis
Analyze all possible execution paths through code:
- Branch coverage: Test all conditional branches
- Loop coverage: Zero, one, many, maximum iterations
- Exception paths: Force error conditions systematically
- State transitions: Test all valid and invalid state changes
Use scripts/analyze_paths.py to identify uncovered code paths and generate test suggestions.
3. Mutation Testing
Verify test suite effectiveness by introducing controlled mutations:
For Rust projects:
# Install cargo-mutants
cargo install cargo-mutants
# Run mutation testing
cargo mutants --no-shuffle --test-timeout 30
# Generate detailed report
cargo mutants --json > mutations.jsonFor Python projects:
# Install mutmut
pip install mutmut
# Run mutation testing
mutmut run --paths-to-mutate src/
# Show results
mutmut resultsLoad references/mutation_testing.md for detailed mutation testing guidance and interpretation.
4. Implementation Alternatives
Test different algorithmic approaches:
- Iterative vs Recursive: Compare implementations for stack safety
- Mutable vs Immutable: Test functional and imperative variations
- Synchronous vs Asynchronous: Verify concurrency behavior
- Naive vs Optimized: Validate optimizations maintain correctness
Workflow
Step 1: Analyze Target Code
First, understand the code structure:
- Identify function signatures and parameter types
- Map control flow and decision points
- Note error handling and edge cases
- Document assumptions and invariants
Step 2: Generate Test Matrix
Create comprehensive test coverage:
# Example for a function: divide(a: i32, b: i32) -> Result<i32, Error>
# Boundary tests
test_cases = [
# Normal cases
(10, 2, Ok(5)),
(10, 3, Ok(3)),
# Boundary values
(i32::MAX, 1, Ok(i32::MAX)),
(i32::MIN, 1, Ok(i32::MIN)),
(i32::MIN, -1, Err(Overflow)), # Special overflow case
# Edge cases
(0, 5, Ok(0)),
(5, 0, Err(DivisionByZero)),
(0, 0, Err(DivisionByZero)),
# Sign variations
(-10, 2, Ok(-5)),
(10, -2, Ok(-5)),
(-10, -2, Ok(5)),
]Step 3: Execute Permutation Tests
Run the generated test matrix:
- Execute each test case
- Verify expected outcomes
- Check for unexpected behaviors
- Monitor performance characteristics
Step 4: Analyze Coverage
Evaluate test completeness:
- Generate coverage reports
- Identify uncovered paths
- Run mutation testing
- Add tests for gaps
Step 5: Document Results
Create comprehensive test documentation:
- List all tested scenarios
- Document discovered edge cases
- Note performance characteristics
- Highlight critical paths
Language-Specific Guidance
Rust
#[cfg(test)]
mod permutation_tests {
use super::*;
use proptest::prelude::*;
// Property-based testing for automatic permutation
proptest! {
#[test]
fn test_function_properties(
a in any::<i32>(),
b in any::<i32>().prop_filter("non-zero", |x| *x != 0)
) {
let result = divide(a, b);
prop_assert!(result.is_ok());
prop_assert_eq!(result.unwrap(), a / b);
}
}
// Boundary value testing
#[test]
fn test_boundaries() {
let boundaries = vec![
i32::MIN, i32::MIN + 1, -1, 0, 1, i32::MAX - 1, i32::MAX
];
for a in &boundaries {
for b in &boundaries {
if *b != 0 {
let result = divide(*a, *b);
// Verify result or expected error
}
}
}
}
}Python
import hypothesis.strategies as st
from hypothesis import given, assume
import pytest
# Property-based testing
@given(st.integers(), st.integers())
def test_division_properties(a, b):
assume(b != 0) # Precondition
result = divide(a, b)
assert result == a // b
# Parametrized boundary testing
@pytest.mark.parametrize("a,b,expected", [
(10, 2, 5),
(sys.maxsize, 1, sys.maxsize),
(-sys.maxsize-1, 1, -sys.maxsize-1),
(0, 5, 0),
# Add more boundary cases
])
def test_boundaries(a, b, expected):
assert divide(a, b) == expectedResources
Scripts
scripts/generate_boundaries.py- Analyzes function signatures and generates boundary test casesscripts/analyze_paths.py- Identifies code paths and suggests tests for uncovered branches
References
references/boundary_patterns.md- Common patterns for boundary value analysis and edge case identificationreferences/mutation_testing.md- Comprehensive guide to mutation testing tools and result interpretation
Best Practices
1. Start with boundaries: Test limits before normal cases 2. Consider combinations: Use pairwise testing for multiple parameters 3. Test error paths: Ensure error handling is robust 4. Use property-based testing: Let tools generate test cases automatically 5. Verify with mutations: Ensure tests actually catch bugs 6. Document discoveries: Record edge cases for future reference 7. Automate generation: Use scripts to create test matrices 8. Monitor performance: Track execution time across permutations
# Example Asset File
This placeholder represents where asset files would be stored.
Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed.
Asset files are NOT intended to be loaded into context, but rather used within
the output Claude produces.
Example asset files from other skills:
- Brand guidelines: logo.png, slides_template.pptx
- Frontend builder: hello-world/ directory with HTML/React boilerplate
- Typography: custom-font.ttf, font-family.woff2
- Data: sample_data.csv, test_dataset.json
## Common Asset Types
- Templates: .pptx, .docx, boilerplate directories
- Images: .png, .jpg, .svg, .gif
- Fonts: .ttf, .otf, .woff, .woff2
- Boilerplate code: Project directories, starter files
- Icons: .ico, .svg
- Data files: .csv, .json, .xml, .yaml
Note: This is a text placeholder. Actual assets can be any file type.
Reference Documentation for Code Permutation Testing
This is a placeholder for detailed reference documentation. Replace with actual reference content or delete if not needed.
Example real reference docs from other skills:
- product-management/references/communication.md - Comprehensive guide for status updates
- product-management/references/context_building.md - Deep-dive on gathering context
- bigquery/references/ - API references and query examples
When Reference Docs Are Useful
Reference docs are ideal for:
- Comprehensive API documentation
- Detailed workflow guides
- Complex multi-step processes
- Information too lengthy for main SKILL.md
- Content that's only needed for specific use cases
Structure Suggestions
API Reference Example
- Overview
- Authentication
- Endpoints with examples
- Error codes
- Rate limits
Workflow Guide Example
- Prerequisites
- Step-by-step instructions
- Common patterns
- Troubleshooting
- Best practices
Boundary Testing Patterns
Overview
Boundary value analysis (BVA) is a testing technique that focuses on values at the edges of input domains. Most errors occur at boundaries rather than in the middle of valid ranges.
Core Boundary Categories
1. Numeric Boundaries
Integer Types
For any integer type with range [MIN, MAX]:
Test Values:
- MIN - 1 (underflow)
- MIN (minimum valid)
- MIN + 1 (just inside minimum)
- -1 (if signed)
- 0 (zero boundary)
- 1 (smallest positive)
- MAX - 1 (just inside maximum)
- MAX (maximum valid)
- MAX + 1 (overflow)Language-specific limits:
// Rust
i8: [-128, 127]
i16: [-32768, 32767]
i32: [-2147483648, 2147483647]
i64: [-9223372036854775808, 9223372036854775807]
u8: [0, 255]
u16: [0, 65535]
u32: [0, 4294967295]
u64: [0, 18446744073709551615]# Python
import sys
sys.maxsize # 9223372036854775807 on 64-bit
-sys.maxsize - 1 # -9223372036854775808Floating Point
Special values to test:
0.0and-0.0- Smallest positive:
f32::MIN_POSITIVE,f64::MIN_POSITIVE - Largest finite:
f32::MAX,f64::MAX - Infinity:
f32::INFINITY,f32::NEG_INFINITY - Not-a-Number:
f32::NAN - Subnormal numbers (very small)
- Epsilon boundaries for precision
2. Collection Boundaries
Arrays/Vectors/Lists
Test Sizes:
- Empty collection (size 0)
- Single element (size 1)
- Two elements (size 2) - tests pair logic
- Typical size (3-10 elements)
- Large size (1000+ elements)
- Maximum allowed size
- Size that triggers reallocationStrings
Test Cases:
- Empty string ""
- Single character "a"
- Single space " "
- Only whitespace " \t\n"
- Maximum length string
- Unicode boundaries (ASCII vs UTF-8)
- Special characters (\0, \n, \r, \t)
- Mixed scripts (Latin + Cyrillic + Emoji)3. Date/Time Boundaries
Critical Dates:
- Epoch: 1970-01-01 00:00:00 UTC
- Unix timestamp limits: 2038-01-19 (32-bit)
- Leap years: Feb 28/29
- DST transitions
- Time zone boundaries (+14:00 to -12:00)
- Month boundaries (28, 29, 30, 31 days)
- Year boundaries (Dec 31 -> Jan 1)4. Loop Boundaries
Iteration Counts:
- 0 iterations (never enters loop)
- 1 iteration (single pass)
- 2 iterations (tests loop continuation)
- N-1 iterations (off-by-one check)
- N iterations (exact expected)
- N+1 iterations (off-by-one check)
- Maximum iterations before timeoutBoundary Testing Strategies
Strategy 1: Two-Value Boundary Testing
Test exactly on the boundary and just outside:
- For upper bound N: test N and N+1
- For lower bound M: test M and M-1
Strategy 2: Three-Value Boundary Testing
Test on, just inside, and just outside:
- For upper bound N: test N-1, N, N+1
- For lower bound M: test M-1, M, M+1
Strategy 3: Domain Matrix Testing
For functions with multiple parameters, create a matrix:
def test_rectangle_area(width, height):
boundaries_width = [0, 1, 100, 1000]
boundaries_height = [0, 1, 100, 1000]
for w in boundaries_width:
for h in boundaries_height:
result = calculate_area(w, h)
# Verify resultStrategy 4: Equivalence Partitioning + Boundaries
Divide input into equivalence classes, then test boundaries:
Age Groups Example:
- Invalid: age < 0 → test -1, -100
- Child: 0 ≤ age < 13 → test 0, 1, 12, 13
- Teen: 13 ≤ age < 20 → test 13, 14, 19, 20
- Adult: 20 ≤ age < 65 → test 20, 21, 64, 65
- Senior: age ≥ 65 → test 65, 66, 100, 150Common Boundary Bugs
Off-by-One Errors
// Wrong: excludes last element
for i in 0..array.len() - 1 { // Should be 0..array.len()
process(array[i]);
}
// Wrong: includes invalid index
for i in 0..=array.len() { // Should be 0..array.len()
process(array[i]); // Panic on last iteration
}Integer Overflow
// Dangerous
let result = a + b; // Can overflow
// Safe
let result = a.checked_add(b).ok_or(Error::Overflow)?;Floating Point Comparison
// Wrong
if float_a == float_b { // Exact comparison fails
// Right
if (float_a - float_b).abs() < EPSILON {Empty Collection Handling
# Wrong
def average(numbers):
return sum(numbers) / len(numbers) # Division by zero
# Right
def average(numbers):
if not numbers:
return None
return sum(numbers) / len(numbers)Boundary Test Checklist
For Any Function
- [ ] Identify all input parameters
- [ ] Determine valid ranges for each parameter
- [ ] List boundary values for each range
- [ ] Test combinations of boundaries (if multiple params)
- [ ] Verify error handling at invalid boundaries
- [ ] Check for overflow/underflow conditions
- [ ] Test special values (null, empty, zero)
For Numeric Functions
- [ ] Test with 0, 1, -1
- [ ] Test MIN and MAX values for type
- [ ] Test just inside and outside valid range
- [ ] Test overflow scenarios
- [ ] Test precision boundaries (for floats)
- [ ] Test NaN, Infinity (for floats)
For String Functions
- [ ] Empty string
- [ ] Single character
- [ ] Maximum length
- [ ] Special characters (\0, \n, etc.)
- [ ] Unicode edge cases
- [ ] Whitespace-only strings
For Collection Functions
- [ ] Empty collection
- [ ] Single element
- [ ] Two elements (pair operations)
- [ ] Maximum size
- [ ] Duplicate elements
- [ ] Sorted vs unsorted
For Time/Date Functions
- [ ] Epoch boundaries
- [ ] Leap years
- [ ] DST transitions
- [ ] Time zone boundaries
- [ ] Month/year transitions
- [ ] Invalid dates (Feb 30, etc.)
Automated Boundary Generation
Python Example
def generate_integer_boundaries(min_val, max_val):
"""Generate boundary test values for integer range."""
boundaries = set()
# Add boundary values
boundaries.add(min_val - 1) # Underflow
boundaries.add(min_val) # Minimum
boundaries.add(min_val + 1) # Just inside min
# Add zero if in range
if min_val <= 0 <= max_val:
boundaries.add(-1)
boundaries.add(0)
boundaries.add(1)
boundaries.add(max_val - 1) # Just inside max
boundaries.add(max_val) # Maximum
boundaries.add(max_val + 1) # Overflow
return sorted(boundaries)
# Usage
test_values = generate_integer_boundaries(-100, 100)Rust Example
fn generate_boundaries<T>(min: T, max: T) -> Vec<T>
where
T: Copy + PartialOrd + std::ops::Add<Output = T> + std::ops::Sub<Output = T> + From<i8>,
{
let mut boundaries = Vec::new();
let one = T::from(1);
// Boundary values
boundaries.push(min);
boundaries.push(max);
// Just inside boundaries (if possible)
if min < max {
boundaries.push(min + one);
boundaries.push(max - one);
}
boundaries
}Best Practices
1. Document boundary assumptions: Make valid ranges explicit 2. Test boundaries first: They're most likely to fail 3. Combine boundaries: Test multiple parameters at boundaries simultaneously 4. Use property-based testing: Tools like QuickCheck/Hypothesis can generate boundaries 5. Consider domain knowledge: Business rules may create additional boundaries 6. Test boundary interactions: How do boundaries in one field affect another? 7. Automate generation: Use scripts to generate boundary test cases 8. Review historical bugs: Past boundary bugs indicate testing gaps
Mutation Testing Guide
What is Mutation Testing?
Mutation testing is a technique to evaluate the quality of your test suite by introducing small changes (mutations) to your code and checking if tests catch these changes. If a mutation survives (tests still pass), it indicates a gap in test coverage.
Core Concepts
Mutants
A mutant is a version of your code with one small, deliberate change. Common mutations include:
- Arithmetic:
+→-,*→/ - Relational:
<→<=,==→!= - Logical:
&&→||,!→ (removed) - Constant:
0→1,true→false - Statement: Remove statements or function calls
- Return: Change or remove return values
Mutation Score
Mutation Score = (Killed Mutants / Total Mutants) × 100%- Killed: Test suite detected the mutation (tests failed)
- Survived: Tests passed despite mutation (coverage gap)
- Timeout: Mutation caused infinite loop
- Unviable: Mutation created invalid code
Target: 80%+ mutation score for critical code
Tool-Specific Guides
Rust: cargo-mutants
Installation
cargo install cargo-mutantsBasic Usage
# Run mutation testing on entire project
cargo mutants
# Test specific package
cargo mutants -p my_package
# Test specific file
cargo mutants --file src/lib.rs
# Generate detailed JSON report
cargo mutants --json > mutants.json
# Run with timeout per test
cargo mutants --test-timeout 30
# Run in parallel (faster)
cargo mutants --jobs 4Configuration
Create .cargo/mutants.toml:
# Exclude files from mutation
exclude_dirs = ["tests", "benches"]
exclude_globs = ["**/generated.rs"]
# Customize timeout
timeout_multiplier = 1.5
# Error on surviving mutants (CI)
error_on_survived = trueInterpreting Results
SURVIVED src/calculator.rs:45: replace + with - in add()
→ Your tests don't verify the addition operation properly
KILLED src/validator.rs:23: replace >= with > in check_age()
→ Good! Tests caught this boundary change
TIMEOUT src/loop.rs:67: replace < with <= in while loop
→ Mutation likely caused infinite loopPython: mutmut
Installation
pip install mutmutBasic Usage
# Initialize mutmut
mutmut run --paths-to-mutate src/ --tests-dir tests/
# Show results summary
mutmut results
# Show surviving mutants
mutmut show
# Show specific mutant
mutmut show 47
# Apply mutant to see the change
mutmut apply 47
# Generate HTML report
mutmut htmlConfiguration
Create .mutmut.yml:
paths_to_mutate:
- src/
tests_dir: tests/
runner: python -m pytest
dict_synonyms:
- dict
- OrderedDict
exclude_patterns:
- test_*
- */migrations/*Common Python Mutations
# Original
def calculate_discount(price, rate):
if rate > 0.5:
rate = 0.5
return price * (1 - rate)
# Mutations:
# 1. rate > 0.5 → rate >= 0.5
# 2. rate > 0.5 → rate < 0.5
# 3. rate = 0.5 → rate = 0.4
# 4. return price * (1 - rate) → return price * (1 + rate)JavaScript/TypeScript: Stryker
Installation
npm install --save-dev @stryker-mutator/core
npx stryker init # Interactive setupConfiguration
stryker.config.js:
module.exports = {
mutate: ['src/**/*.js', '!src/**/*.test.js'],
testRunner: 'jest',
coverageAnalysis: 'perTest',
thresholds: { high: 80, low: 60, break: 50 },
mutator: {
excludedMutations: ['StringLiteral'],
},
};Running Stryker
# Run mutation testing
npx stryker run
# Generate HTML report
npx stryker run --reporters html
# Run specific mutators
npx stryker run --mutate 'src/utils.js'Go: go-mutesting
Installation
go install github.com/zimmski/go-mutesting/cmd/go-mutesting@latestUsage
# Run on package
go-mutesting ./...
# With specific test timeout
go-mutesting --timeout 10s ./...
# Generate report
go-mutesting --report mutations.json ./...Mutation Patterns and Strategies
High-Value Mutations
Focus on mutations that reveal important test gaps:
1. Boundary Mutations: < → <=, > → >= 2. Off-by-One: i → i+1, len-1 → len 3. Null/Empty Checks: Remove null checks 4. Error Handling: Change error returns 5. Business Logic: Invert conditions in domain rules
Mutation Testing Workflow
graph TD
A[Run Mutation Testing] --> B{Mutants Survived?}
B -->|Yes| C[Analyze Survivors]
B -->|No| D[Good Coverage!]
C --> E[Write Missing Tests]
E --> F[Re-run Mutations]
F --> BAnalyzing Surviving Mutants
For each surviving mutant:
1. Is it testable? Some mutations might not affect observable behavior 2. Is it important? Focus on business logic over implementation details 3. What test is missing? Write a test that would kill this mutant 4. Is the code necessary? Surviving mutants might indicate dead code
Common Mutation Categories
Arithmetic Operator Mutations
// Original
let result = a + b;
// Mutations
let result = a - b; // AOR (Arithmetic Operator Replacement)
let result = a * b; // AOR
let result = a / b; // AOR
let result = a % b; // AORConditional Boundary Mutations
// Original
if x > 10 {
// Mutations
if x >= 10 { // CBM (Conditional Boundary Mutation)
if x < 10 { // ROR (Relational Operator Replacement)
if x == 10 { // ROR
if true { // COR (Conditional Operator Replacement)
if false { // CORLogical Operator Mutations
// Original
if a && b {
// Mutations
if a || b { // LOR (Logical Operator Replacement)
if !a && b { // LCR (Logical Connector Replacement)
if a { // Statement DeletionReturn Value Mutations
// Original
return Some(value);
// Mutations
return None; // RVM (Return Value Mutation)
return Some(value + 1); // RVM
// (return deleted) // Statement DeletionBest Practices
1. Start Small
Begin with critical modules:
- Core business logic
- Security-sensitive code
- Frequently modified code
- Code with history of bugs
2. Set Realistic Goals
- 100% mutation coverage is rarely achievable or necessary
- Aim for 80%+ on critical code
- 60%+ on general application code
- Lower thresholds for UI/presentation code
3. Handle Equivalent Mutants
Some mutations don't change behavior:
// Original
let x = a * 0;
// Equivalent mutant (same result)
let x = a & 0;Mark these as ignored in your tool configuration.
4. Optimize Performance
Mutation testing is slow. Speed it up:
- Run incrementally (only changed files)
- Use parallel execution
- Set reasonable timeouts
- Cache unchanged results
- Run in CI on pull requests only
5. Integrate with CI/CD
# GitHub Actions example
- name: Run Mutation Tests
run: |
cargo mutants --error-on-survived --json > mutations.json
continue-on-error: true
- name: Comment PR
uses: actions/github-script@v6
with:
script: |
const mutations = require('./mutations.json');
// Post summary to PRInterpreting Results
Good Mutations to Kill
// This mutation should be killed
fn calculate_tax(amount: f64) -> f64 {
amount * 0.15 // → amount * 0.14
}
// Test should verify exact tax calculationAcceptable Survivors
// This mutation might survive acceptably
fn log_error(msg: &str) {
eprintln!("Error: {}", msg); // → println!
}
// If logging isn't tested, that might be OKCritical Survivors
// This MUST be killed
fn check_authorization(user: &User) -> bool {
user.is_admin // → !user.is_admin
}
// Security logic must be thoroughly testedMutation Testing Anti-Patterns
1. Testing Implementation, Not Behavior
// Bad: Tests internal details
#[test]
fn test_uses_hashmap() {
let cache = Cache::new();
assert!(cache.internal_map.is_empty()); // Too specific
}
// Good: Tests behavior
#[test]
fn test_cache_starts_empty() {
let cache = Cache::new();
assert_eq!(cache.get("key"), None);
}2. Mutation Score Gaming
Don't write useless tests just to kill mutants:
// Bad: Pointless test to kill mutant
#[test]
fn test_constant() {
assert_eq!(BUFFER_SIZE, 1024); // Tests a constant
}3. Ignoring Timeout Mutants
Timeouts often indicate missing infinite loop tests:
// If this mutation causes timeout, add a test
while i < len { // → while i <= len
// ... process
i += 1;
}Quick Reference
Mutation Testing Checklist
- [ ] Install mutation testing tool for your language
- [ ] Configure exclusions (tests, generated code)
- [ ] Run on critical modules first
- [ ] Analyze surviving mutants
- [ ] Write tests to kill important survivors
- [ ] Set mutation score thresholds
- [ ] Integrate into CI pipeline
- [ ] Document equivalent mutants
- [ ] Review results regularly
Recommended Thresholds by Code Type
| Code Type | Target Score | Minimum |
|---|---|---|
| Security | 90%+ | 85% |
| Business Logic | 85%+ | 75% |
| Data Processing | 80%+ | 70% |
| Controllers | 70%+ | 60% |
| UI/Views | 60%+ | 40% |
| Utilities | 75%+ | 65% |
#!/usr/bin/env python3
"""Example helper script for code-permutation-testing
This is a placeholder script that can be executed directly.
Replace with actual implementation or delete if not needed.
Example real scripts from other skills:
- pdf/scripts/fill_fillable_fields.py - Fills PDF form fields
- pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images
"""
def main():
print("This is an example script for code-permutation-testing")
# TODO: Add actual script logic here
# This could be data processing, file conversion, API calls, etc.
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Generate boundary test cases from function signatures.
This script analyzes function parameters and generates comprehensive boundary
test cases including edge cases, boundary values, and common error conditions.
Usage:
python generate_boundaries.py --lang rust --func "divide(a: i32, b: i32) -> Result<i32>"
python generate_boundaries.py --lang python --func "calculate_age(birth_year: int, current_year: int) -> int"
python generate_boundaries.py --interactive
"""
import argparse
import json
import re
import sys
from dataclasses import dataclass
from enum import Enum
from typing import Any
class ParamType(Enum):
"""Supported parameter types."""
INT8 = "i8"
INT16 = "i16"
INT32 = "i32"
INT64 = "i64"
UINT8 = "u8"
UINT16 = "u16"
UINT32 = "u32"
UINT64 = "u64"
FLOAT32 = "f32"
FLOAT64 = "f64"
BOOL = "bool"
STRING = "string"
ARRAY = "array"
GENERIC_INT = "int"
GENERIC_FLOAT = "float"
GENERIC_STR = "str"
UNKNOWN = "unknown"
@dataclass
class Parameter:
"""Function parameter representation."""
name: str
param_type: ParamType
optional: bool = False
default_value: Any | None = None
@dataclass
class TestCase:
"""Represents a single test case."""
inputs: dict[str, Any]
category: str
description: str
expected_behavior: str = "verify"
class BoundaryGenerator:
"""Generate boundary test cases for different types."""
# Type limits
TYPE_LIMITS = {
ParamType.INT8: (-128, 127),
ParamType.INT16: (-32768, 32767),
ParamType.INT32: (-2147483648, 2147483647),
ParamType.INT64: (-9223372036854775808, 9223372036854775807),
ParamType.UINT8: (0, 255),
ParamType.UINT16: (0, 65535),
ParamType.UINT32: (0, 4294967295),
ParamType.UINT64: (0, 18446744073709551615),
ParamType.GENERIC_INT: (-9223372036854775808, 9223372036854775807),
}
def __init__(self):
self.test_cases: list[TestCase] = []
def generate_integer_boundaries(self, param: Parameter) -> list[Any]:
"""Generate boundary values for integer types."""
if param.param_type not in self.TYPE_LIMITS:
return [0, 1, -1, 10, -10, 100, -100]
min_val, max_val = self.TYPE_LIMITS[param.param_type]
boundaries = []
# Basic boundaries
boundaries.extend([min_val, min_val + 1, max_val - 1, max_val])
# Zero boundaries if in range
if min_val <= 0 <= max_val:
boundaries.extend([-1, 0, 1])
# Common values
if min_val <= -100 <= max_val:
boundaries.append(-100)
if min_val <= 100 <= max_val:
boundaries.append(100)
# Powers of 2 (important for binary operations)
for power in [8, 16, 32, 64, 128, 256, 1024]:
if min_val <= power <= max_val:
boundaries.append(power)
if min_val <= -power <= max_val:
boundaries.append(-power)
return sorted(set(boundaries))
def generate_float_boundaries(self, param: Parameter) -> list[Any]:
"""Generate boundary values for floating-point types."""
boundaries = [
0.0, -0.0, 1.0, -1.0,
0.1, -0.1, 0.5, -0.5,
1.1, -1.1, 10.0, -10.0,
100.0, -100.0,
1e-10, -1e-10, # Very small
1e10, -1e10, # Very large
float('inf'), float('-inf'), # Infinity
float('nan'), # NaN
]
if param.param_type == ParamType.FLOAT32:
boundaries.extend([
3.4028235e38, # f32::MAX
-3.4028235e38, # f32::MIN
1.1754944e-38, # f32::MIN_POSITIVE
])
else: # f64
boundaries.extend([
1.7976931348623157e308, # f64::MAX
-1.7976931348623157e308, # f64::MIN
2.2250738585072014e-308, # f64::MIN_POSITIVE
])
return boundaries
def generate_bool_boundaries(self, param: Parameter) -> list[Any]:
"""Generate boundary values for boolean types."""
return [True, False]
def generate_string_boundaries(self, param: Parameter) -> list[Any]:
"""Generate boundary values for string types."""
return [
"", # Empty
" ", # Single space
"a", # Single char
"abc", # Short
" ", # Whitespace only
"\t\n\r", # Special whitespace
"Hello World", # Normal
"123", # Numeric string
"!@#$%^&*()", # Special characters
"null", # Literal null
"undefined", # Literal undefined
"true", # Literal true
"false", # Literal false
"0", # Zero string
"\\n\\t\\r", # Escaped characters
"🚀🎉😀", # Emoji/Unicode
"A" * 1000, # Long string
"日本語", # Non-ASCII
"<script>alert()</script>", # Potential XSS
"'; DROP TABLE;", # SQL injection attempt
]
def generate_array_boundaries(self, param: Parameter) -> list[Any]:
"""Generate boundary values for array/list types."""
return [
[], # Empty
[1], # Single element
[1, 2], # Two elements
[1, 2, 3], # Few elements
[0] * 100, # Many same elements
list(range(100)), # Many different elements
[1, None, 3], # With None/null
[-1, 0, 1], # Mixed signs
[1, "2", 3], # Mixed types (if allowed)
]
def generate_test_cases(self, params: list[Parameter]) -> list[TestCase]:
"""Generate comprehensive test cases for given parameters."""
test_cases = []
# Single parameter boundary testing
for param in params:
boundaries = self._get_boundaries_for_type(param)
for value in boundaries:
inputs = {p.name: None for p in params}
inputs[param.name] = value
# Set normal values for other params
for other_param in params:
if other_param.name != param.name:
inputs[other_param.name] = self._get_normal_value(other_param)
category = self._categorize_value(value, param)
description = f"Boundary test for {param.name}={value}"
test_cases.append(TestCase(
inputs=inputs,
category=category,
description=description
))
# Combinatorial boundary testing (pairwise)
if len(params) >= 2:
for i, param1 in enumerate(params):
for param2 in params[i+1:]:
boundaries1 = self._get_boundaries_for_type(param1)[:3] # Limit for combinations
boundaries2 = self._get_boundaries_for_type(param2)[:3]
for val1 in boundaries1:
for val2 in boundaries2:
inputs = {p.name: self._get_normal_value(p) for p in params}
inputs[param1.name] = val1
inputs[param2.name] = val2
test_cases.append(TestCase(
inputs=inputs,
category="combinatorial",
description=f"Combination: {param1.name}={val1}, {param2.name}={val2}"
))
return test_cases
def _get_boundaries_for_type(self, param: Parameter) -> list[Any]:
"""Get boundary values based on parameter type."""
if param.param_type in [ParamType.INT8, ParamType.INT16, ParamType.INT32,
ParamType.INT64, ParamType.UINT8, ParamType.UINT16,
ParamType.UINT32, ParamType.UINT64, ParamType.GENERIC_INT]:
return self.generate_integer_boundaries(param)
if param.param_type in [ParamType.FLOAT32, ParamType.FLOAT64, ParamType.GENERIC_FLOAT]:
return self.generate_float_boundaries(param)
if param.param_type == ParamType.BOOL:
return self.generate_bool_boundaries(param)
if param.param_type in [ParamType.STRING, ParamType.GENERIC_STR]:
return self.generate_string_boundaries(param)
if param.param_type == ParamType.ARRAY:
return self.generate_array_boundaries(param)
return [None, "default", 0]
def _get_normal_value(self, param: Parameter) -> Any:
"""Get a normal (non-boundary) value for a parameter."""
if param.default_value is not None:
return param.default_value
type_defaults = {
ParamType.INT8: 5,
ParamType.INT16: 5,
ParamType.INT32: 5,
ParamType.INT64: 5,
ParamType.UINT8: 5,
ParamType.UINT16: 5,
ParamType.UINT32: 5,
ParamType.UINT64: 5,
ParamType.GENERIC_INT: 5,
ParamType.FLOAT32: 5.0,
ParamType.FLOAT64: 5.0,
ParamType.GENERIC_FLOAT: 5.0,
ParamType.BOOL: True,
ParamType.STRING: "test",
ParamType.GENERIC_STR: "test",
ParamType.ARRAY: [1, 2, 3],
}
return type_defaults.get(param.param_type, "default")
def _categorize_value(self, value: Any, param: Parameter) -> str:
"""Categorize a test value."""
if value is None:
return "null"
if param.param_type in [ParamType.STRING, ParamType.GENERIC_STR]:
if value == "":
return "empty"
if len(value) > 100:
return "large"
if not value.strip():
return "whitespace"
return "boundary"
if param.param_type == ParamType.ARRAY:
if len(value) == 0:
return "empty"
if len(value) == 1:
return "single"
if len(value) > 50:
return "large"
return "boundary"
if isinstance(value, (int, float)):
if value == 0:
return "zero"
if abs(value) == 1:
return "unit"
if param.param_type in self.TYPE_LIMITS:
min_val, max_val = self.TYPE_LIMITS[param.param_type]
if value == min_val or value == max_val:
return "limit"
return "boundary"
return "boundary"
class FunctionParser:
"""Parse function signatures from different languages."""
@staticmethod
def parse_rust(signature: str) -> tuple[str, list[Parameter]]:
"""Parse Rust function signature."""
# Example: divide(a: i32, b: i32) -> Result<i32>
match = re.match(r'(\w+)\s*\((.*?)\)', signature)
if not match:
raise ValueError(f"Invalid Rust signature: {signature}")
func_name = match.group(1)
params_str = match.group(2)
params = []
if params_str:
for param_str in params_str.split(','):
param_match = re.match(r'\s*(\w+)\s*:\s*(\S+)', param_str.strip())
if param_match:
name = param_match.group(1)
type_str = param_match.group(2)
# Map Rust types to ParamType
type_map = {
'i8': ParamType.INT8, 'i16': ParamType.INT16,
'i32': ParamType.INT32, 'i64': ParamType.INT64,
'u8': ParamType.UINT8, 'u16': ParamType.UINT16,
'u32': ParamType.UINT32, 'u64': ParamType.UINT64,
'f32': ParamType.FLOAT32, 'f64': ParamType.FLOAT64,
'bool': ParamType.BOOL,
'&str': ParamType.STRING, 'String': ParamType.STRING,
'Vec': ParamType.ARRAY, '&[': ParamType.ARRAY,
}
param_type = ParamType.UNKNOWN
for rust_type, param_enum in type_map.items():
if rust_type in type_str:
param_type = param_enum
break
params.append(Parameter(name, param_type))
return func_name, params
@staticmethod
def parse_python(signature: str) -> tuple[str, list[Parameter]]:
"""Parse Python function signature."""
# Example: calculate_age(birth_year: int, current_year: int = 2024) -> int
match = re.match(r'(\w+)\s*\((.*?)\)', signature)
if not match:
raise ValueError(f"Invalid Python signature: {signature}")
func_name = match.group(1)
params_str = match.group(2)
params = []
if params_str:
for param_str in params_str.split(','):
# Handle type hints and defaults
param_match = re.match(r'\s*(\w+)\s*(?::\s*(\w+))?\s*(?:=\s*(.+))?', param_str.strip())
if param_match:
name = param_match.group(1)
type_str = param_match.group(2) or 'any'
default = param_match.group(3)
# Map Python types to ParamType
type_map = {
'int': ParamType.GENERIC_INT,
'float': ParamType.GENERIC_FLOAT,
'bool': ParamType.BOOL,
'str': ParamType.GENERIC_STR,
'list': ParamType.ARRAY,
'List': ParamType.ARRAY,
}
param_type = type_map.get(type_str, ParamType.UNKNOWN)
params.append(Parameter(name, param_type, default_value=default))
return func_name, params
def format_test_output(func_name: str, test_cases: list[TestCase], lang: str) -> str:
"""Format test cases for output."""
output = []
if lang == "rust":
output.append(f"// Boundary tests for {func_name}")
output.append("#[cfg(test)]")
output.append("mod boundary_tests {")
output.append(" use super::*;\n")
for i, test in enumerate(test_cases):
output.append(" #[test]")
output.append(f" fn test_{func_name}_boundary_{i}() {{")
output.append(f" // Category: {test.category}")
output.append(f" // {test.description}")
args = ", ".join(str(v) for v in test.inputs.values())
output.append(f" let result = {func_name}({args});")
output.append(" // TODO: Assert expected behavior")
output.append(" }\n")
output.append("}")
elif lang == "python":
output.append(f"# Boundary tests for {func_name}")
output.append("import pytest\n")
# Create parametrize data
param_names = list(test_cases[0].inputs.keys()) if test_cases else []
param_str = ",".join(param_names)
output.append(f"@pytest.mark.parametrize(\"{param_str}\", [")
for test in test_cases:
values = tuple(test.inputs.values())
output.append(f" {values}, # {test.category}: {test.description}")
output.append("])")
output.append(f"def test_{func_name}_boundaries({param_str}):")
output.append(f" result = {func_name}({param_str})")
output.append(" # TODO: Add assertions based on expected behavior")
else: # JSON format
json_output = {
"function": func_name,
"test_cases": [
{
"inputs": test.inputs,
"category": test.category,
"description": test.description,
"expected_behavior": test.expected_behavior
}
for test in test_cases
]
}
output.append(json.dumps(json_output, indent=2))
return "\n".join(output)
def interactive_mode():
"""Run in interactive mode."""
print("Boundary Test Case Generator - Interactive Mode")
print("=" * 50)
lang = input("Language (rust/python): ").lower()
if lang not in ["rust", "python"]:
print("Unsupported language. Using generic mode.")
lang = "generic"
signature = input("Function signature: ")
try:
if lang == "rust":
func_name, params = FunctionParser.parse_rust(signature)
elif lang == "python":
func_name, params = FunctionParser.parse_python(signature)
else:
print("Please specify parameters manually.")
return
print(f"\nParsed function: {func_name}")
print("Parameters:")
for param in params:
print(f" - {param.name}: {param.param_type.value}")
generator = BoundaryGenerator()
test_cases = generator.generate_test_cases(params)
print(f"\nGenerated {len(test_cases)} test cases")
output_format = input("\nOutput format (code/json) [code]: ").lower() or "code"
if output_format == "json":
print("\n" + format_test_output(func_name, test_cases, "json"))
else:
print("\n" + format_test_output(func_name, test_cases, lang))
except Exception as e:
print(f"Error: {e}")
def main():
parser = argparse.ArgumentParser(description="Generate boundary test cases from function signatures")
parser.add_argument("--lang", choices=["rust", "python"], help="Programming language")
parser.add_argument("--func", help="Function signature")
parser.add_argument("--output", choices=["code", "json"], default="code", help="Output format")
parser.add_argument("--interactive", action="store_true", help="Run in interactive mode")
parser.add_argument("--limit", type=int, help="Limit number of test cases")
args = parser.parse_args()
if args.interactive:
interactive_mode()
return
if not args.func or not args.lang:
print("Error: --lang and --func are required (or use --interactive)")
sys.exit(1)
try:
if args.lang == "rust":
func_name, params = FunctionParser.parse_rust(args.func)
else: # python
func_name, params = FunctionParser.parse_python(args.func)
generator = BoundaryGenerator()
test_cases = generator.generate_test_cases(params)
if args.limit:
test_cases = test_cases[:args.limit]
output_lang = "json" if args.output == "json" else args.lang
print(format_test_output(func_name, test_cases, output_lang))
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Related skills
FAQ
Which languages does it support for mutation testing?
Rust via cargo-mutants and Python via mutmut, per the SKILL.md examples.
What testing modes does it cover?
Input permutation, code-path analysis, mutation testing, and implementation alternatives.