268 lines
8.0 KiB
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
|