DEV Community

TiltedLunar123
TiltedLunar123

Posted on

I couldn't test my VM sizing math without spinning up a real VM

WhonixAutoSetup is a PowerShell project i keep poking at while studying for Security+. it stands up Whonix on Windows: one VM runs Tor (the gateway), a second VM routes all its traffic through the first (the workstation), so the workstation never has a path to the internet that isn't Tor. four scripts, run in order.

one of those scripts, configure-vms.ps1, decides how much RAM and how many cores each VM gets. the gateway is fixed at 1 core and 1 GB because Tor doesn't need more. the workstation scales with the host: 2 to 4 cores, 25 to 40 percent of available RAM, with a floor so it never boots into something unusable and a ceiling so it doesn't swallow the whole machine.

that's the only part of the script with real logic in it. tiers, a floor, a ceiling, a rounding step. and until last week it had zero tests.

why it had no tests

the function read the hardware and did the math in the same breath:

function Get-ResourceAllocation {
    $totalRam = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory
    $cores    = (Get-CimInstance Win32_Processor).NumberOfLogicalProcessors
    # ...then ~30 lines of tiering, flooring, and rounding on those two values
}
Enter fullscreen mode Exit fullscreen mode

to exercise that, you need a real Windows host handing back real numbers from CIM. so the only way to check whether, say, the 6 GB tier rounded the way i intended was to find a 6 GB machine and run the whole thing. i don't own a shelf of machines at different RAM sizes. i own my laptop. so the math got checked exactly once, at exactly one memory size, and everyone else got my crossed fingers.

that's backwards. the CIM call is the part i can't control and shouldn't be testing, it's Microsoft's. the arithmetic is the part i wrote and the part that actually breaks. the one piece i cared about was welded to the one thing that made it impossible to test in isolation.

the fix is boring, which is the whole point

pull the math into a function that takes numbers instead of a machine:

function Get-VmResourceAllocation {
    param(
        [long]$TotalRamBytes,
        [int]$CoreCount
    )
    # same tiering, flooring, rounding, now operating on parameters
    # returns the same allocation hashtable as before
}
Enter fullscreen mode Exit fullscreen mode

configure-vms.ps1 still does the CIM reads and the logging. it just hands those two numbers to this helper. same inputs, same hashtable that came out before, so a real run behaves identically. nothing changed for anyone running it for real.

what changed is i can now do this with no VM, no VirtualBox, nothing, on a clean CI runner:

It 'leaves a core for the host and never drops below one' {
    (Get-VmResourceAllocation -TotalRamBytes 8GB -CoreCount 1).Cores | Should -Be 1
    (Get-VmResourceAllocation -TotalRamBytes 8GB -CoreCount 4).Cores | Should -Be 3
}
Enter fullscreen mode Exit fullscreen mode

twelve cases now cover the three RAM tiers, the 2 GB workstation floor, the 8 GB ceiling, the 128 MB rounding step, and the core caps. they run under Pester on every push, and they mock VirtualBox and the filesystem so the suite runs anywhere without a hypervisor installed.

the case i was least sure about was a single-core host, the kind of cheap cloud box someone might try this on. i'd genuinely never confirmed what the old function did when handed one core. writing the test was how i finally checked: the "leave one for the host" branch returns 1, not 0. it does the right thing. but "it probably does the right thing" is not what you want to be shipping in the code that decides whether a VM can boot, and that's the state it had been in.

what's still not great

the CIM reads are still untested. i mock them, which means if Windows ever hands back TotalPhysicalMemory in a different shape or unit, my tests stay green while a real run breaks. i'm trusting the boundary i drew. that's the normal cost of this kind of split, but it's worth saying out loud instead of letting a green checkmark imply more coverage than exists.

the RAM percentages are also still magic numbers sitting inside if-branches. 25 here, 40 there. they're tested now, so changing them is safe, but they belong in a small table at the top of the file, not buried in the logic. haven't done that one yet.

the thing i keep relearning

if a chunk of code can only be tested by running the entire program against real infrastructure, that's usually not a testing problem, it's a seam problem. the logic and the I/O are fused, and the fix is to cut them apart, not to build a bigger test rig around the outside.

i knew that going in. i still shipped the fused version first, because it was fewer lines and it ran fine on my machine. it runs fine on more machines now, and i can prove it without owning them.

repo if you want the actual code: github.com/TiltedLunar123/WhonixAutoSetup

Top comments (0)