nip/tests/test_filesystem.nim

268 lines
8.0 KiB
Nim

## NimPak Filesystem Management Tests
##
## This module contains unit tests for the filesystem management functionality,
## focusing on the GoboLinux-style directory structure and atomic symlink operations.
import unittest, os, strutils, strformat
import nimpak/[types, filesystem]
# Create a temporary test directory
let testDir = getTempDir() / "nimpak_test_filesystem"
let programsRoot = testDir / "Programs"
let indexRoot = testDir / "System/Index"
let logFile = testDir / "filesystem.log"
# Setup and teardown helpers
proc setupTestDir() =
if dirExists(testDir):
removeDir(testDir)
createDir(programsRoot)
createDir(indexRoot)
proc teardownTestDir() =
if dirExists(testDir):
removeDir(testDir)
suite "FilesystemManager Tests":
setup:
setupTestDir()
let fm = newFilesystemManager(
programsRoot = programsRoot,
indexRoot = indexRoot,
logFile = logFile,
dryRun = false
)
teardown:
teardownTestDir()
test "Create FilesystemManager with custom paths":
let customFm = newFilesystemManager(
programsRoot = "/custom/Programs",
indexRoot = "/custom/Index",
logFile = "/custom/logs/fs.log",
dryRun = true
)
check customFm.programsRoot == "/custom/Programs"
check customFm.indexRoot == "/custom/Index"
check customFm.logFile == "/custom/logs/fs.log"
check customFm.dryRun == true
test "Create program directory structure":
let pkg = PackageId(name: "test", version: "1.0.0", stream: Stable)
let result = fm.createProgramDirectory(pkg)
check result.isOk
let programDir = result.get()
check programDir == joinPath(programsRoot, "Test", "1.0.0")
check dirExists(programDir)
# Check standard subdirectories
for subdir in ["bin", "lib", "share", "etc", "var"]:
check dirExists(joinPath(programDir, subdir))
test "Remove program directory":
let pkg = PackageId(name: "test", version: "1.0.0", stream: Stable)
let createResult = fm.createProgramDirectory(pkg)
check createResult.isOk
let programDir = createResult.get()
check dirExists(programDir)
let removeResult = fm.removeProgramDirectory(pkg)
check removeResult.isOk
check not dirExists(programDir)
test "Create and remove symlink":
# Create a test file
let sourceDir = joinPath(programsRoot, "Test", "1.0.0", "bin")
createDir(sourceDir)
let sourceFile = joinPath(sourceDir, "testapp")
writeFile(sourceFile, "test content")
let targetFile = joinPath(indexRoot, "bin", "testapp")
# Create symlink
let createResult = fm.createSymlink(sourceFile, targetFile)
check createResult.isOk
check symlinkExists(targetFile)
check expandSymlink(targetFile) == sourceFile
# Remove symlink
let removeResult = fm.removeSymlink(targetFile)
check removeResult.isOk
check not symlinkExists(targetFile)
test "Atomic symlink update with rollback":
# Create test files
let sourceDir = joinPath(programsRoot, "Test", "1.0.0", "bin")
createDir(sourceDir)
let sourceFile1 = joinPath(sourceDir, "app1")
let sourceFile2 = joinPath(sourceDir, "app2")
writeFile(sourceFile1, "app1 content")
writeFile(sourceFile2, "app2 content")
let targetFile1 = joinPath(indexRoot, "bin", "app1")
let targetFile2 = joinPath(indexRoot, "bin", "app2")
# Create symlink pairs
let pairs = @[
SymlinkPair(source: sourceFile1, target: targetFile1),
SymlinkPair(source: sourceFile2, target: targetFile2)
]
# Perform atomic update
let result = fm.atomicSymlinkUpdate(pairs)
check result.isOk
# Verify symlinks were created
check symlinkExists(targetFile1)
check symlinkExists(targetFile2)
check expandSymlink(targetFile1) == sourceFile1
check expandSymlink(targetFile2) == sourceFile2
test "Generate symlinks for program directory":
# Create test program directory with files
let programDir = joinPath(programsRoot, "Test", "1.0.0")
createDir(programDir)
for dir in ["bin", "lib", "share", "etc"]:
let dirPath = joinPath(programDir, dir)
createDir(dirPath)
# Create a test file in each directory
writeFile(joinPath(dirPath, fmt"test_{dir}"), fmt"test content for {dir}")
# Generate symlinks
let result = fm.generateSymlinks(programDir)
check result.isOk
let symlinks = result.get()
check symlinks.len == 4 # One for each directory
# Verify symlink structure
for pair in symlinks:
check pair.source.startsWith(programDir)
check pair.target.startsWith(indexRoot)
test "Install and uninstall package":
let pkg = PackageId(name: "testapp", version: "2.0.0", stream: Stable)
# Install package
let installResult = fm.installPackage(pkg)
check installResult.isOk
let location = installResult.get()
check dirExists(location.programDir)
# Create some test files to generate real symlinks
let binDir = joinPath(location.programDir, "bin")
let appPath = joinPath(binDir, "testapp")
writeFile(appPath, "test app content")
# Re-generate symlinks
let symlinksResult = fm.generateSymlinks(location.programDir)
check symlinksResult.isOk
let symlinks = symlinksResult.get()
# Apply symlinks
let updateResult = fm.atomicSymlinkUpdate(symlinks)
check updateResult.isOk
# Verify symlink was created
let indexBinPath = joinPath(indexRoot, "bin", "testapp")
check symlinkExists(indexBinPath)
# Uninstall package
let uninstallResult = fm.uninstallPackage(pkg)
check uninstallResult.isOk
# Verify directory and symlinks were removed
check not dirExists(location.programDir)
check not symlinkExists(indexBinPath)
test "Validate filesystem state":
# Create a broken symlink
let sourceDir = joinPath(programsRoot, "Test", "1.0.0", "bin")
createDir(sourceDir)
let sourceFile = joinPath(sourceDir, "testapp")
writeFile(sourceFile, "test content")
let targetDir = joinPath(indexRoot, "bin")
createDir(targetDir)
let targetFile = joinPath(targetDir, "testapp")
# Create symlink
discard fm.createSymlink(sourceFile, targetFile)
# Remove source file to create broken symlink
removeFile(sourceFile)
# Validate filesystem state
let result = fm.validateFilesystemState()
check result.isOk
let issues = result.get()
if issues.len == 0:
fail()
else:
check issues.len >= 1 # At least one issue (broken symlink)
check issues[0].contains("Broken symlink")
test "Repair filesystem":
# Remove root directories
removeDir(programsRoot)
removeDir(indexRoot)
# Create a broken symlink
# createDir(indexRoot / "bin") # REMOVED THIS LINE
createSymlink("/tmp/nonexistent_path_for_testing", indexRoot / "bin" / "broken")
# Repair filesystem
let result = fm.repairFilesystem()
check result.isOk
let repairs = result.get()
check repairs.len >= 2 # At least created programs root and removed broken symlink
# Verify repairs
check dirExists(programsRoot)
check dirExists(indexRoot)
check not symlinkExists(indexRoot / "bin" / "broken")
test "Dry run mode":
let dryRunFm = newFilesystemManager(
programsRoot = programsRoot,
indexRoot = indexRoot,
logFile = logFile,
dryRun = true
)
# Remove directories to test dry run
removeDir(programsRoot)
removeDir(indexRoot)
let pkg = PackageId(name: "drytest", version: "1.0.0", stream: Stable)
let result = dryRunFm.createProgramDirectory(pkg)
# Should succeed in dry run mode
check result.isOk
# But directory should not actually be created
check not dirExists(joinPath(programsRoot, "Drytest", "1.0.0"))
test "Error handling for invalid paths":
let invalidFm = newFilesystemManager(
programsRoot = "/nonexistent/invalid/path",
indexRoot = indexRoot,
logFile = logFile,
dryRun = false
)
let pkg = PackageId(name: "test", version: "1.0.0", stream: Stable)
let result = invalidFm.createProgramDirectory(pkg)
check result.isErr
let error = result.getError()
check error.kind == DirectoryCreationFailed