Testing and Quality Assurance
The libmagic-rs project maintains high quality standards through comprehensive testing, strict linting, and continuous integration. This chapter covers the testing strategy, current test coverage, and quality assurance practices.
Testing Philosophy
Comprehensive Coverage
The project aims for comprehensive test coverage across all components:
- Unit Tests: Test individual functions and methods in isolation
- Integration Tests: Test component interactions and workflows
- Property Tests: Use property-based testing for edge cases
- Compatibility Tests: Validate against GNU
file
command results - Performance Tests: Benchmark critical path performance
Quality Gates
All code must pass these quality gates:
- Zero Warnings:
cargo clippy -- -D warnings
must pass - All Tests Pass: Complete test suite must pass
- Code Coverage: Target >85% coverage for new code
- Documentation: All public APIs must be documented
- Memory Safety: No unsafe code except in vetted dependencies
Current Test Coverage
Test Statistics
Total Tests: 98 passing unit tests
$ cargo test
running 98 tests
test result: ok. 98 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Test Distribution
AST Structure Tests (29 tests)
OffsetSpec Tests:
test_offset_spec_absolute
- Basic absolute offset creationtest_offset_spec_indirect
- Complex indirect offset structurestest_offset_spec_relative
- Relative offset handlingtest_offset_spec_from_end
- End-relative offset calculationstest_offset_spec_serialization
- JSON serialization round-tripstest_all_offset_spec_variants
- Comprehensive variant testingtest_endianness_variants
- Endianness handling in all contexts
Value Tests:
test_value_uint
- Unsigned integer values including extremestest_value_int
- Signed integer values including boundariestest_value_bytes
- Byte sequence handling and comparisontest_value_string
- String values including Unicodetest_value_comparison
- Cross-type comparison behaviortest_value_serialization
- Complete serialization testingtest_value_serialization_edge_cases
- Boundary and extreme values
TypeKind Tests:
test_type_kind_byte
- Single byte type handlingtest_type_kind_short
- 16-bit integer types with endiannesstest_type_kind_long
- 32-bit integer types with endiannesstest_type_kind_string
- String types with length limitstest_type_kind_serialization
- All type serialization
Operator Tests:
test_operator_variants
- All operator typestest_operator_serialization
- Operator serialization
MagicRule Tests:
test_magic_rule_creation
- Basic rule constructiontest_magic_rule_with_children
- Hierarchical rule structurestest_magic_rule_serialization
- Complete rule serialization
Parser Component Tests (50 tests)
Number Parsing Tests:
test_parse_decimal_number
- Basic decimal parsingtest_parse_hex_number
- Hexadecimal parsing with 0x prefixtest_parse_number_positive
- Positive number handlingtest_parse_number_negative
- Negative number handlingtest_parse_number_edge_cases
- Boundary values and error conditionstest_parse_number_with_remaining_input
- Partial parsing behavior
Offset Parsing Tests:
test_parse_offset_absolute_positive
- Positive absolute offsetstest_parse_offset_absolute_negative
- Negative absolute offsetstest_parse_offset_with_whitespace
- Whitespace tolerancetest_parse_offset_with_remaining_input
- Partial parsingtest_parse_offset_edge_cases
- Error conditions and boundariestest_parse_offset_common_magic_file_values
- Real-world patternstest_parse_offset_boundary_values
- Extreme values
Operator Parsing Tests:
test_parse_operator_equality
- Equality operators (= and ==)test_parse_operator_inequality
- Inequality operators (!= and <>)test_parse_operator_bitwise_and
- Bitwise AND operator (&)test_parse_operator_with_remaining_input
- Partial parsingtest_parse_operator_precedence
- Operator precedence handlingtest_parse_operator_invalid_input
- Error condition handlingtest_parse_operator_edge_cases
- Boundary conditionstest_parse_operator_common_magic_file_patterns
- Real patterns
Value Parsing Tests:
test_parse_quoted_string_simple
- Basic string parsingtest_parse_quoted_string_with_escapes
- Escape sequence handlingtest_parse_quoted_string_with_whitespace
- Whitespace handlingtest_parse_quoted_string_invalid
- Error conditionstest_parse_hex_bytes_with_backslash_x
- \x prefix hex bytestest_parse_hex_bytes_without_prefix
- Raw hex byte sequencestest_parse_hex_bytes_mixed_case
- Case insensitive hextest_parse_numeric_value_positive
- Positive numeric valuestest_parse_numeric_value_negative
- Negative numeric valuestest_parse_value_string_literals
- String literal parsingtest_parse_value_numeric_literals
- Numeric literal parsingtest_parse_value_hex_byte_sequences
- Hex byte parsingtest_parse_value_type_precedence
- Type detection precedencetest_parse_value_edge_cases
- Boundary conditionstest_parse_value_invalid_input
- Error handling
Test Categories
Unit Tests
Located alongside source code using #[cfg(test)]
:
#![allow(unused)] fn main() { #[cfg(test)] mod tests { use super::*; #[test] fn test_basic_functionality() { let result = parse_number("123"); assert_eq!(result, Ok(("", 123))); } #[test] fn test_error_conditions() { let result = parse_number("invalid"); assert!(result.is_err()); } #[test] fn test_edge_cases() { // Test boundary values assert_eq!(parse_number("0"), Ok(("", 0))); assert_eq!(parse_number("-0"), Ok(("", 0))); // Test extreme values let max_val = i64::MAX.to_string(); assert_eq!(parse_number(&max_val), Ok(("", i64::MAX))); } } }
Integration Tests (Planned)
Will be located in tests/
directory:
#![allow(unused)] fn main() { // tests/parser_integration.rs use libmagic_rs::parser::*; #[test] fn test_complete_rule_parsing() { let magic_line = "0 string \\x7fELF ELF executable"; let rule = parse_magic_rule(magic_line).unwrap(); assert_eq!(rule.offset, OffsetSpec::Absolute(0)); assert_eq!(rule.message, "ELF executable"); } #[test] fn test_hierarchical_rules() { let magic_content = r#" 0 string \x7fELF ELF >4 byte 1 32-bit >4 byte 2 64-bit "#; let rules = parse_magic_file_content(magic_content).unwrap(); assert_eq!(rules.len(), 1); assert_eq!(rules[0].children.len(), 2); } }
Property Tests (Planned)
Using proptest
for fuzz-like testing:
#![allow(unused)] fn main() { use proptest::prelude::*; proptest! { #[test] fn test_number_parsing_roundtrip(n in any::<i64>()) { let s = n.to_string(); let (remaining, parsed) = parse_number(&s).unwrap(); assert_eq!(remaining, ""); assert_eq!(parsed, n); } #[test] fn test_offset_parsing_never_panics(s in ".*") { // Should never panic, even on invalid input let _ = parse_offset(&s); } } }
Compatibility Tests (Planned)
Validate against GNU file
command:
#![allow(unused)] fn main() { #[test] fn test_elf_detection_compatibility() { let gnu_result = run_gnu_file("test_files/elf64_sample"); let our_result = evaluate_file("test_files/elf64_sample"); assert_eq!(extract_file_type(&gnu_result), our_result.description); } }
Test Utilities and Helpers
Common Test Patterns
Whitespace Testing Helper:
#![allow(unused)] fn main() { fn test_with_whitespace_variants<T, F>(input: &str, expected: &T, parser: F) where T: Clone + PartialEq + std::fmt::Debug, F: Fn(&str) -> IResult<&str, T>, { let variants = vec![ format!(" {}", input), // Leading space format!(" {}", input), // Leading spaces format!("\t{}", input), // Leading tab format!("{} ", input), // Trailing space format!("{} ", input), // Trailing spaces format!("{}\t", input), // Trailing tab format!(" {} ", input), // Both sides ]; for variant in variants { assert_eq!( parser(&variant), Ok(("", expected.clone())), "Failed with whitespace: '{}'", variant ); } } }
Error Testing Patterns:
#![allow(unused)] fn main() { #[test] fn test_parser_error_conditions() { let error_cases = vec![ ("", "empty input"), ("abc", "invalid characters"), ("0xGG", "invalid hex digits"), ("--123", "double negative"), ]; for (input, description) in error_cases { assert!( parse_number(input).is_err(), "Should fail on {}: '{}'", description, input ); } } }
Test Data Management
Test Fixtures:
#![allow(unused)] fn main() { // Common test data const ELF_MAGIC: &[u8] = &[0x7f, 0x45, 0x4c, 0x46]; const ZIP_MAGIC: &[u8] = &[0x50, 0x4b, 0x03, 0x04]; const PDF_MAGIC: &str = "%PDF-"; fn create_test_rule() -> MagicRule { MagicRule { offset: OffsetSpec::Absolute(0), typ: TypeKind::Byte, op: Operator::Equal, value: Value::Uint(0x7f), message: "Test rule".to_string(), children: vec![], level: 0, } } }
Running Tests
Basic Test Execution
# Run all tests
cargo test
# Run specific test module
cargo test parser::grammar::tests
# Run specific test
cargo test test_parse_number_positive
# Run tests with output
cargo test -- --nocapture
# Run ignored tests (if any)
cargo test -- --ignored
Enhanced Test Running
# Use nextest for faster execution
cargo nextest run
# Run tests with coverage
cargo llvm-cov --html
# Run tests in release mode
cargo test --release
# Test documentation examples
cargo test --doc
Continuous Testing
# Auto-run tests on file changes
cargo watch -x test
# Auto-run specific tests
cargo watch -x "test parser"
# Run checks and tests together
cargo watch -x check -x test
Code Coverage
Coverage Tools
# Install coverage tool
cargo install cargo-llvm-cov
# Generate HTML coverage report
cargo llvm-cov --html
# Generate lcov format for CI
cargo llvm-cov --lcov --output-path coverage.lcov
# Show coverage summary
cargo llvm-cov --summary-only
Coverage Targets
- Overall Coverage: Target >85% for the project
- New Code: Require >90% coverage for new features
- Critical Paths: Require 100% coverage for parser and evaluator
- Public APIs: Require 100% coverage for all public functions
Coverage Exclusions
Some code is excluded from coverage requirements:
#![allow(unused)] fn main() { // Debug/development code #[cfg(debug_assertions)] fn debug_helper() { /* ... */ } // Error handling that's hard to trigger #[cfg_attr(coverage, coverage(off))] fn handle_system_error() { /* ... */ } }
Quality Assurance
Automated Checks
All code must pass these automated checks:
# Formatting check
cargo fmt -- --check
# Linting with strict rules
cargo clippy -- -D warnings
# Documentation generation
cargo doc --document-private-items
# Security audit
cargo audit
# Dependency check
cargo tree --duplicates
Manual Review Checklist
For code reviews:
- Functionality: Does the code work as intended?
- Tests: Are there comprehensive tests covering the changes?
- Documentation: Are public APIs documented with examples?
- Error Handling: Are errors handled gracefully?
- Performance: Are there any performance implications?
- Memory Safety: Is all buffer access bounds-checked?
- Compatibility: Does this maintain API compatibility?
Performance Testing
# Run benchmarks
cargo bench
# Profile with flamegraph
cargo install flamegraph
cargo flamegraph --bench parser_bench
# Memory usage analysis
valgrind --tool=massif target/release/rmagic large_file.bin
Future Testing Plans
Integration Testing
- Complete Workflow Tests: End-to-end magic file parsing and evaluation
- File Format Tests: Comprehensive testing against known file formats
- Error Recovery Tests: Graceful handling of malformed inputs
Compatibility Testing
- GNU file Compatibility: Validate results against original implementation
- Magic File Compatibility: Test with real-world magic databases
- Performance Parity: Ensure comparable performance to libmagic
Fuzzing Integration
- Parser Fuzzing: Use cargo-fuzz for parser robustness
- Evaluator Fuzzing: Test evaluation engine with malformed files
- Continuous Fuzzing: Integrate with OSS-Fuzz for ongoing testing
The comprehensive testing strategy ensures libmagic-rs maintains high quality, reliability, and compatibility while enabling confident refactoring and feature development.