salmon-ops-recipes-0.1.0.0: test/Test/QemuResolveKernelSpec.hs
{- | Layer 1 (pure filesystem, no root/VM needed): exercises
'Salmon.Builtin.Nodes.Qemu.resolveKernelInitrd''s three cases —
exactly one match, none, and more than one — against a scratch
@boot/@ directory built with 'System.IO.Temp.withSystemTempDirectory'.
Closes @specs/qemu-test-vms-progress.md@ §3 item 3: the prefix-match
assumption was previously only reasoned about from code review, unverified
against a rootfs holding a held-over old kernel (two @vmlinuz-*@\/
@initrd.img-*@ pairs). This confirms 'resolveKernelInitrd' throws a
caller-visible "ambiguous" error rather than silently picking one, and does
so without needing an actual multi-kernel debootstrap chroot.
-}
module Test.QemuResolveKernelSpec (tests) where
import Control.Exception (try)
import Data.List (isInfixOf)
import Salmon.Builtin.Nodes.Qemu (resolveKernelInitrd)
import System.Directory (createDirectoryIfMissing)
import System.FilePath ((</>))
import System.IO (IOMode (WriteMode), withFile)
import System.IO.Error (isUserError)
import System.IO.Temp (withSystemTempDirectory)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, assertFailure, testCase, (@?=))
tests :: TestTree
tests =
testGroup
"Qemu.resolveKernelInitrd"
[ testCase "resolves the single vmlinuz/initrd pair" resolvesSinglePair
, testCase "throws when no kernel is present" (throwsContaining [] "no vmlinuz-")
, testCase
"throws when a held-over old kernel makes the match ambiguous"
(throwsContaining ["vmlinuz-6.1.0-amd64", "initrd.img-6.1.0-amd64", "vmlinuz-5.10.0-amd64", "initrd.img-5.10.0-amd64"] "ambiguous vmlinuz-")
]
touch :: FilePath -> IO ()
touch path = withFile path WriteMode (const (pure ()))
withBoot :: [FilePath] -> (FilePath -> IO a) -> IO a
withBoot bootFiles act =
withSystemTempDirectory "resolveKernelInitrd-spec" $ \rootfs -> do
let bootDir = rootfs </> "boot"
createDirectoryIfMissing True bootDir
mapM_ (touch . (bootDir </>)) bootFiles
act rootfs
resolvesSinglePair :: IO ()
resolvesSinglePair = withBoot ["vmlinuz-6.1.0-amd64", "initrd.img-6.1.0-amd64", "System.map-6.1.0-amd64"] $ \rootfs -> do
(kernel, initrd) <- resolveKernelInitrd rootfs
kernel @?= rootfs </> "boot" </> "vmlinuz-6.1.0-amd64"
initrd @?= rootfs </> "boot" </> "initrd.img-6.1.0-amd64"
-- | Asserts 'resolveKernelInitrd' throws a 'userError' whose message
-- contains @needle@, given a @boot/@ populated with @bootFiles@.
throwsContaining :: [FilePath] -> String -> IO ()
throwsContaining bootFiles needle = withBoot bootFiles $ \rootfs -> do
result <- try (resolveKernelInitrd rootfs)
case result of
Left e | isUserError e -> assertBool ("expected error containing " <> show needle <> ", got: " <> show e) (needle `isInfixOf` show e)
Left e -> assertFailure ("expected a userError, got: " <> show e)
Right r -> assertFailure ("expected resolveKernelInitrd to throw, got: " <> show r)