Comprehensive test plan with acceptance criteria for the Virtual Memory Manager simulator.
- Unit Tests: Individual module testing
- Integration Tests: Multi-component interaction
- Functional Tests: End-to-end scenarios
- Performance Tests: Efficiency and scalability
- Regression Tests: Ensure fixes don't break existing functionality
Test Cases:
| ID | Test | Expected Result |
|---|---|---|
| UT-1.1 | Allocate single frame | Returns frame number 0-N |
| UT-1.2 | Allocate all frames | All succeed until exhausted |
| UT-1.3 | Allocate when full | Returns -1 |
| UT-1.4 | Free allocated frame | Success, frame returns to pool |
| UT-1.5 | Free already-free frame | Warning logged, no crash |
| UT-1.6 | Allocate after free | Previously freed frame can be reused |
| UT-1.7 | Frame metadata | PID, VPN correctly set |
| UT-1.8 | Aging operation | Age counters shift correctly |
Acceptance Criteria:
- Zero memory leaks (verified with valgrind)
- All allocations O(1) time
- Bitmap correctly reflects allocation state
Test Cases:
| ID | Test | Expected Result |
|---|---|---|
| UT-2.1 | Create single-level PT | Allocates correct number of PTEs |
| UT-2.2 | Create two-level PT | L1 allocated, L2 allocated on-demand |
| UT-2.3 | Lookup unmapped page | Returns invalid PTE |
| UT-2.4 | Map virtual to physical | PTE marked valid, correct frame |
| UT-2.5 | Lookup mapped page | Returns valid PTE with correct frame |
| UT-2.6 | Unmap page | PTE marked invalid |
| UT-2.7 | Out-of-bounds address | Returns NULL safely |
| UT-2.8 | Two-level L2 allocation | L2 table allocated only when accessed |
Acceptance Criteria:
- Single-level: O(1) lookup
- Two-level: O(1) lookup with lazy L2 allocation
- No memory leaks
Test Cases:
| ID | Test | Expected Result |
|---|---|---|
| UT-3.1 | Lookup in empty TLB | Miss |
| UT-3.2 | Insert and lookup | Hit with correct PFN |
| UT-3.3 | Fill TLB and insert | Evicts according to policy |
| UT-3.4 | Multi-process tagging | PID correctly distinguishes entries |
| UT-3.5 | Invalidate single entry | Entry removed |
| UT-3.6 | Invalidate all for PID | All PID entries removed |
| UT-3.7 | FIFO replacement | Evicts oldest entry |
| UT-3.8 | LRU replacement | Evicts least recently used |
Acceptance Criteria:
- Hit/miss correctly detected
- Replacement policy correctly implemented
- No thrashing with reasonable TLB size
Test Cases:
| ID | Test | Expected Result |
|---|---|---|
| UT-4.1 | Allocate swap slot | Returns slot number |
| UT-4.2 | Allocate all slots | All succeed until exhausted |
| UT-4.3 | Allocate when full | Returns -1 |
| UT-4.4 | Free swap slot | Slot returned to pool |
| UT-4.5 | Swap out | Returns simulated latency |
| UT-4.6 | Swap in | Returns simulated latency |
| UT-4.7 | Statistics | Swap-in/out counts incremented |
Acceptance Criteria:
- Swap I/O simulates realistic latency (5ms)
- No slot leaks
Test Cases:
| ID | Algorithm | Test | Expected Result |
|---|---|---|---|
| UT-5.1 | FIFO | Select victim | Oldest allocated frame |
| UT-5.2 | FIFO | Insert order | Maintains insertion order |
| UT-5.3 | LRU | Select victim | Frame with minimum access time |
| UT-5.4 | LRU | Update on access | Access time updated |
| UT-5.5 | Clock | Select victim | First frame with ref_bit=0 |
| UT-5.6 | Clock | Second chance | Clears ref_bit on pass |
| UT-5.7 | Approx-LRU | Aging | Age counters shift correctly |
| UT-5.8 | OPT | Select victim | Frame used furthest in future |
Acceptance Criteria:
- Each algorithm selects appropriate victim
- FIFO/LRU/Clock: O(N) worst case
- Approx-LRU: Closely approximates LRU
Test Cases:
| ID | Test | Expected Result |
|---|---|---|
| UT-6.1 | Load valid trace | All entries parsed |
| UT-6.2 | Load malformed trace | Graceful error, skip bad lines |
| UT-6.3 | Generate sequential | Addresses sequential |
| UT-6.4 | Generate random | Addresses random, deterministic with seed |
| UT-6.5 | Generate working_set | 90% within working set |
| UT-6.6 | Save and reload | Identical trace |
Acceptance Criteria:
- Supports hex and decimal addresses
- Deterministic generation with seed
Scenario: TLB miss should trigger page table walk
Steps:
- Access unmapped page (TLB miss)
- Page fault occurs
- Map page in page table
- Insert into TLB
- Re-access (should be TLB hit)
Expected:
- First access: TLB miss, page fault
- Second access: TLB hit
- TLB hit rate: 50%
Scenario: Page fault allocates frame
Steps:
- Access unmapped page
- Page fault handler invoked
- Frame allocated
- Page mapped to frame
Expected:
- Page fault count: 1
- Frame allocated
- Valid PTE created
Scenario: No free frames triggers eviction and swap
Setup: RAM size < working set
Steps:
- Fill all frames
- Access new page
- Victim selected
- Dirty victim swapped out
- New page loaded
Expected:
- Replacement count: >0
- Swap-out count: >0 (if victims dirty)
- No frame leaks
Scenario: Processes have isolated address spaces
Steps:
- Process 0 maps page at 0x1000
- Process 1 maps page at 0x1000
- Both access 0x1000
Expected:
- Different physical frames
- No cross-contamination
- TLB correctly tagged by PID
Trace: sequential.trace (1000 accesses, sequential pages)
Configuration:
- RAM: 16 MB (4096 frames)
- Page size: 4KB
- Algorithm: Any
Expected Metrics:
- Page faults: ~100-200 (working set fits in memory)
- Page fault rate: <20%
- TLB hit rate: >85% (high locality)
Trace: random.trace (5000 accesses, random pages)
Configuration:
- RAM: 16 MB
- Page size: 4KB
- Algorithm: LRU
Expected Metrics:
- Page faults: >500
- Page fault rate: >10%
- TLB hit rate: <70% (low locality)
Trace: working_set.trace (10000 accesses, 90% within working set)
Configuration:
- RAM: 32 MB
- Page size: 4KB
- Algorithm: LRU
Expected Metrics:
- Page faults: 200-500
- Page fault rate: 2-5%
- TLB hit rate: >90%
Trace: thrashing.trace (15000 accesses, working set > RAM)
Configuration:
- RAM: 8 MB (small)
- Page size: 4KB
- Algorithm: FIFO
Expected Metrics:
- Page faults: >5000
- Page fault rate: >30%
- Swap I/O: Very high
- Performance: Poor AMT
Trace: Same trace for all algorithms
Algorithms: FIFO, LRU, Clock, OPT
Expected Order (page faults, best to worst):
- OPT (best)
- LRU
- Clock
- FIFO (worst, subject to Belady's anomaly)
Acceptance:
- OPT has fewest page faults
- LRU ≈ Clock (within 10%)
- FIFO may be worse than LRU
Trace: locality.trace
Configurations:
- TLB size: 8, 16, 32, 64, 128, 256
Expected:
- TLB hit rate increases with size
- Diminishing returns after 128 entries
- AMT decreases with larger TLB
Trace: Same trace
Configurations:
- Page size: 4KB, 8KB, 16KB
Expected:
- Larger pages: Fewer page faults (more data per fault)
- Larger pages: Potential internal fragmentation
- Trade-off visible in metrics
Configuration:
- RAM: 1 GB (262,144 frames)
- Trace: 100,000 accesses
Acceptance:
- Completes within 10 seconds
- Memory usage: <2 GB
- No performance degradation
Configuration:
- RAM: 64 MB
- Trace: 1,000,000 accesses
Acceptance:
- Completes within 30 seconds
- Linear time complexity O(n)
Measure: Victim selection time
Expected:
- FIFO: O(1)
- Clock: O(1) amortized
- Approx-LRU: O(1) amortized
- LRU: O(num_frames) (acceptable for <10K frames)
- OPT: O(num_frames × trace_remaining) (slow, for comparison only)
Configuration:
- RAM: 64 MB
- Processes: 16
Acceptance:
- Single-level PT: ~16 MB per process (expected)
- Two-level PT: <1 MB per process (sparse)
- Choose PT type based on address space size
Tool: Valgrind
Command:
valgrind --leak-check=full --show-leak-kinds=all ./bin/vmm -r 64 -t traces/random.trace -a LRUAcceptance:
- 0 bytes definitely lost
- 0 bytes indirectly lost
- All heap blocks freed
Build:
make debugRun:
./bin/vmm_debug -r 32 -t traces/thrashing.trace -a CLOCKAcceptance:
- No heap-buffer-overflow
- No use-after-free
- No memory leaks
Tool: UBSan (built into make debug)
Acceptance:
- No signed integer overflow
- No null pointer dereference
- No unaligned memory access
make testTests Executed:
- Generate sample traces ✓
- Run FIFO algorithm ✓
- Run LRU algorithm ✓
- Run Clock algorithm ✓
- Run OPT algorithm ✓
- TLB size comparison ✓
- Multi-process workload ✓
- Thrashing scenario ✓
- Two-level page table ✓
- Different page sizes ✓
Pass Criteria:
- All 10 tests pass
- Exit code: 0
==================== SIMULATION SUMMARY ====================
Memory Accesses:
Total: 10000
Reads: 8000 (80.0%)
Writes: 2000 (20.0%)
Page Faults:
Total: 324
Major: 42 (required swap-in)
Minor: 282 (no I/O)
Fault Rate: 3.24%
TLB Performance:
Hits: 9456
Misses: 544
Hit Rate: 94.56%
Swap I/O:
Swap-ins: 42
Swap-outs: 38
Replacements: 324
Average Memory Access Time:
AMT: 145.32 ns
Slowdown: 145.3x (vs TLB hit)
Simulation Time:
Wall time: 2.345 ms
Throughput: 4264.4 accesses/ms
============================================================
All test traces available in traces/ directory:
simple.trace- 15 accesses, 2 processes (manual verification)sequential.trace- 1000 accesses, sequential patternrandom.trace- 5000 accesses, uniform randomworking_set.trace- 10000 accesses, 90% localitylocality.trace- 8000 accesses, temporal/spatial localitythrashing.trace- 15000 accesses, exceeds RAM capacity
- All unit tests pass
- All integration tests pass
- All functional tests produce expected metrics (±10%)
- No memory leaks (valgrind clean)
- No undefined behavior (UBSan clean)
- No address sanitizer errors
- Code compiles without warnings (
-Wall -Wextra) - Deterministic behavior with same seed
- Documentation matches implementation
- Examples in README.md work as described
- Single-threaded: No concurrent memory access simulation
- No actual data: Pages don't store data, only metadata
- Simplified page tables: Real CPUs use more complex structures
- No TLB shootdown: Multi-core TLB coherency not simulated
These are intentional simplifications for clarity and focus on core concepts.
- Property-based testing (e.g., QuickCheck-style)
- Fuzzing with AFL or libFuzzer
- Comparative testing against other VM simulators
- Stress testing with massive traces (>100M accesses)
- Multi-threaded trace execution
If tests fail, provide:
- Command line used
- Trace file (if custom)
- Expected vs actual metrics
- Log output (use
-Dflag) - Environment (OS, GCC version)