415 lines
12 KiB
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()
|