nip/tests/test_bootstrap_integration.nim

415 lines
12 KiB
Nim

## Integration tests for the bootstrap system
## Tests end-to-end bootstrap installation flow
import std/[os, osproc, strutils, json, times]
import ../src/nimpak/build/recipe_manager
import ../src/nimpak/build/download_manager
import ../src/nimpak/build/installation_manager
import ../src/nimpak/build/recipe_parser
const
TestDir = "/tmp/nip-bootstrap-test"
RecipeRepoUrl = "https://git.maiwald.work/Nexus/nip-recipes.git"
type
TestResult = object
name: string
passed: bool
duration: float
error: string
var testResults: seq[TestResult]
proc startTest(name: string): float =
echo "\n🧪 Testing: ", name
result = cpuTime()
proc endTest(name: string, startTime: float, passed: bool, error: string = "") =
let duration = cpuTime() - startTime
testResults.add(TestResult(
name: name,
passed: passed,
duration: duration,
error: error
))
if passed:
echo "✅ PASSED (", duration.formatFloat(ffDecimal, 3), "s)"
else:
echo "❌ FAILED (", duration.formatFloat(ffDecimal, 3), "s)"
if error.len > 0:
echo " Error: ", error
proc setupTestEnvironment() =
echo "🔧 Setting up test environment..."
# Clean up any previous test runs
if dirExists(TestDir):
removeDir(TestDir)
createDir(TestDir)
createDir(TestDir / "bootstrap")
createDir(TestDir / "cache")
createDir(TestDir / "recipes")
echo "✅ Test environment ready: ", TestDir
proc cleanupTestEnvironment() =
echo "\n🧹 Cleaning up test environment..."
if dirExists(TestDir):
removeDir(TestDir)
echo "✅ Cleanup complete"
proc testRecipeManagerInit(): bool =
let start = startTest("RecipeManager initialization")
try:
let manager = newRecipeManager(TestDir / "recipes")
result = true
endTest("RecipeManager initialization", start, true)
except Exception as e:
endTest("RecipeManager initialization", start, false, e.msg)
result = false
proc testRecipeLoading(): bool =
let start = startTest("Recipe loading and parsing")
try:
let manager = newRecipeManager(TestDir / "recipes")
# Try to load a recipe (will fail if repo not cloned, which is expected)
# This tests the error handling
try:
discard manager.loadRecipe("nix")
# If we get here, recipe loaded successfully
result = true
except RecipeError:
# Expected if recipes not available
echo " Note: Recipe not available (expected in test environment)"
result = true
endTest("Recipe loading and parsing", start, result)
except Exception as e:
endTest("Recipe loading and parsing", start, false, e.msg)
result = false
proc testDownloadManagerInit(): bool =
let start = startTest("DownloadManager initialization")
try:
let manager = newDownloadManager(TestDir / "cache")
result = true
endTest("DownloadManager initialization", start, true)
except Exception as e:
endTest("DownloadManager initialization", start, false, e.msg)
result = false
proc testChecksumVerification(): bool =
let start = startTest("Checksum verification")
try:
# Create a test file with known content
let testFile = TestDir / "test.txt"
writeFile(testFile, "Hello, NIP Bootstrap!")
let manager = newDownloadManager(TestDir / "cache")
# Calculate checksum
let checksum = manager.calculateChecksum(testFile)
# Verify it's in the correct format (blake2b-512 multihash)
if checksum.startsWith("blake2b-"):
result = true
else:
result = false
endTest("Checksum verification", start, false, "Invalid checksum format: " & checksum)
return
# Verify the checksum
let verified = manager.verifyChecksum(testFile, checksum)
result = verified
if verified:
endTest("Checksum verification", start, true)
else:
endTest("Checksum verification", start, false, "Checksum verification failed")
except Exception as e:
endTest("Checksum verification", start, false, e.msg)
result = false
proc testInstallationManagerInit(): bool =
let start = startTest("InstallationManager initialization")
try:
let manager = newInstallationManager(TestDir / "bootstrap")
result = true
endTest("InstallationManager initialization", start, true)
except Exception as e:
endTest("InstallationManager initialization", start, false, e.msg)
result = false
proc testArchiveExtraction(): bool =
let start = startTest("Archive extraction")
try:
# Create a test tar.gz archive
let testDir = TestDir / "archive-test"
let extractDir = TestDir / "extracted"
createDir(testDir)
writeFile(testDir / "test.txt", "Test content")
writeFile(testDir / "test2.txt", "More content")
# Create tar.gz
let archivePath = TestDir / "test.tar.gz"
let cmd = "tar -czf " & archivePath & " -C " & testDir & " ."
let (output, exitCode) = execCmdEx(cmd)
if exitCode != 0:
endTest("Archive extraction", start, false, "Failed to create test archive")
return false
# Test extraction
let manager = newInstallationManager(TestDir / "bootstrap")
manager.extractArchive(archivePath, extractDir)
# Verify files were extracted
result = fileExists(extractDir / "test.txt") and
fileExists(extractDir / "test2.txt")
if result:
endTest("Archive extraction", start, true)
else:
endTest("Archive extraction", start, false, "Extracted files not found")
except Exception as e:
endTest("Archive extraction", start, false, e.msg)
result = false
proc testScriptExecution(): bool =
let start = startTest("Script execution")
try:
# Create a test script
let scriptPath = TestDir / "test-script.sh"
writeFile(scriptPath, """#!/bin/bash
echo "Test script executed"
exit 0
""")
# Make executable
setFilePermissions(scriptPath, {fpUserExec, fpUserRead, fpUserWrite})
# Execute script
let manager = newInstallationManager(TestDir / "bootstrap")
let (output, exitCode) = manager.executeScript(scriptPath, TestDir)
result = exitCode == 0 and "Test script executed" in output
if result:
endTest("Script execution", start, true)
else:
endTest("Script execution", start, false, "Script execution failed or wrong output")
except Exception as e:
endTest("Script execution", start, false, e.msg)
result = false
proc testRecipeValidation(): bool =
let start = startTest("Recipe validation")
try:
# Create a test recipe
let recipePath = TestDir / "test-recipe.kdl"
writeFile(recipePath, """
recipe "test-tool" {
version "1.0.0"
description "Test tool for integration testing"
platform "linux-x86_64" {
url "https://example.com/test-tool.tar.gz"
checksum "blake2b-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
install-script "install.sh"
verify-script "verify.sh"
}
}
""")
# Parse and validate
let recipe = parseRecipe(recipePath)
result = recipe.name == "test-tool" and
recipe.version == "1.0.0" and
recipe.platforms.len > 0
if result:
endTest("Recipe validation", start, true)
else:
endTest("Recipe validation", start, false, "Recipe validation failed")
except Exception as e:
endTest("Recipe validation", start, false, e.msg)
result = false
proc testPlatformSelection(): bool =
let start = startTest("Platform selection")
try:
# Create a recipe with multiple platforms
let recipePath = TestDir / "multi-platform.kdl"
writeFile(recipePath, """
recipe "multi-tool" {
version "1.0.0"
description "Multi-platform test tool"
platform "linux-x86_64" {
url "https://example.com/linux-x86_64.tar.gz"
checksum "blake2b-1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
}
platform "linux-aarch64" {
url "https://example.com/linux-aarch64.tar.gz"
checksum "blake2b-2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222"
}
}
""")
let recipe = parseRecipe(recipePath)
# Test platform selection
when defined(amd64) or defined(x86_64):
let platform = recipe.selectPlatform()
result = platform.isSome and "x86_64" in platform.get().name
else:
# On other architectures, just check that selection works
result = true
if result:
endTest("Platform selection", start, true)
else:
endTest("Platform selection", start, false, "Platform selection failed")
except Exception as e:
endTest("Platform selection", start, false, e.msg)
result = false
proc testErrorHandling(): bool =
let start = startTest("Error handling")
try:
var allPassed = true
# Test 1: Invalid recipe file
try:
let invalidRecipe = TestDir / "invalid.kdl"
writeFile(invalidRecipe, "this is not valid KDL {{{")
discard parseRecipe(invalidRecipe)
allPassed = false # Should have thrown
except RecipeError:
discard # Expected
# Test 2: Missing file
try:
discard parseRecipe(TestDir / "nonexistent.kdl")
allPassed = false # Should have thrown
except IOError, OSError:
discard # Expected
# Test 3: Invalid checksum
let manager = newDownloadManager(TestDir / "cache")
let testFile = TestDir / "test-checksum.txt"
writeFile(testFile, "content")
let verified = manager.verifyChecksum(testFile, "blake2b-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
if verified:
allPassed = false # Should have failed
result = allPassed
if result:
endTest("Error handling", start, true)
else:
endTest("Error handling", start, false, "Error handling not working correctly")
except Exception as e:
endTest("Error handling", start, false, e.msg)
result = false
proc testCaching(): bool =
let start = startTest("Download caching")
try:
let manager = newDownloadManager(TestDir / "cache")
# Create a "downloaded" file in cache
let cacheDir = TestDir / "cache"
let testUrl = "https://example.com/test-file.tar.gz"
let cachedFile = manager.getCachePath(testUrl)
createDir(cachedFile.parentDir())
writeFile(cachedFile, "cached content")
# Check if cache hit works
let isCached = manager.isCached(testUrl)
result = isCached
if result:
endTest("Download caching", start, true)
else:
endTest("Download caching", start, false, "Cache detection failed")
except Exception as e:
endTest("Download caching", start, false, e.msg)
result = false
proc printTestSummary() =
echo "\n" & "=".repeat(60)
echo "TEST SUMMARY"
echo "=".repeat(60)
var passed = 0
var failed = 0
var totalDuration = 0.0
for result in testResults:
totalDuration += result.duration
if result.passed:
inc passed
echo "", result.name
else:
inc failed
echo "", result.name
if result.error.len > 0:
echo " ", result.error
echo ""
echo "Total: ", testResults.len, " tests"
echo "Passed: ", passed, " (", (passed * 100 div testResults.len), "%)"
echo "Failed: ", failed
echo "Duration: ", totalDuration.formatFloat(ffDecimal, 3), "s"
echo "=".repeat(60)
if failed > 0:
quit(1)
proc main() =
echo "NIP Bootstrap Integration Tests"
echo "================================\n"
setupTestEnvironment()
# Run all tests
discard testRecipeManagerInit()
discard testRecipeLoading()
discard testDownloadManagerInit()
discard testChecksumVerification()
discard testInstallationManagerInit()
discard testArchiveExtraction()
discard testScriptExecution()
discard testRecipeValidation()
discard testPlatformSelection()
discard testErrorHandling()
discard testCaching()
cleanupTestEnvironment()
printTestSummary()
when isMainModule:
main()