421 lines
12 KiB
Nim
421 lines
12 KiB
Nim
## Integration tests for the bootstrap system
|
|
## Tests end-to-end bootstrap installation flow
|
|
|
|
import std/[os, osproc, strutils, times, options]
|
|
import ../src/nimpak/build/recipe_manager
|
|
import ../src/nimpak/build/download_manager
|
|
import ../src/nimpak/build/installation_manager
|
|
import ../src/nimpak/build/recipe_parser
|
|
import ../src/nimpak/cas
|
|
|
|
const
|
|
TestDir = "/tmp/nip-bootstrap-test"
|
|
|
|
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 = manager != nil
|
|
endTest("RecipeManager initialization", start, result)
|
|
except Exception as e:
|
|
endTest("RecipeManager initialization", start, false, e.msg)
|
|
result = false
|
|
|
|
proc testDownloadManagerInit(): bool =
|
|
let start = startTest("DownloadManager initialization")
|
|
|
|
try:
|
|
let manager = newDownloadManager(TestDir / "cache")
|
|
result = manager != nil and dirExists(manager.cacheDir)
|
|
endTest("DownloadManager initialization", start, result)
|
|
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!")
|
|
|
|
# Calculate checksum using the standalone function
|
|
let checksumResult = calculateBlake2b(testFile)
|
|
|
|
if checksumResult.isErr:
|
|
endTest("Checksum verification", start, false, "Failed to calculate checksum")
|
|
return false
|
|
|
|
let checksum = checksumResult.value
|
|
|
|
# Verify it's in the correct format (blake2b-512 multihash)
|
|
if not checksum.startsWith("blake2b-"):
|
|
endTest("Checksum verification", start, false, "Invalid checksum format: " & checksum)
|
|
return false
|
|
|
|
# Verify the checksum using the standalone function
|
|
let verified = 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 = manager != nil and dirExists(manager.toolsDir)
|
|
endTest("InstallationManager initialization", start, result)
|
|
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")
|
|
let (success, message) = manager.extractArchive(archivePath, extractDir)
|
|
|
|
# Verify files were extracted
|
|
result = success and
|
|
fileExists(extractDir / "test.txt") and
|
|
fileExists(extractDir / "test2.txt")
|
|
|
|
if result:
|
|
endTest("Archive extraction", start, true)
|
|
else:
|
|
endTest("Archive extraction", start, false, message)
|
|
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 (success, output) = manager.executeScript(scriptPath, TestDir)
|
|
|
|
result = success 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 recipeContent = readFile(recipePath)
|
|
let recipeOpt = parseRecipe(recipeContent)
|
|
|
|
if recipeOpt.isNone:
|
|
endTest("Recipe validation", start, false, "Failed to parse recipe")
|
|
return false
|
|
|
|
let recipe = recipeOpt.get()
|
|
|
|
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 recipeContent = readFile(recipePath)
|
|
let recipeOpt = parseRecipe(recipeContent)
|
|
|
|
if recipeOpt.isNone:
|
|
endTest("Platform selection", start, false, "Failed to parse recipe")
|
|
return false
|
|
|
|
let recipe = recipeOpt.get()
|
|
|
|
# Test platform selection
|
|
when defined(amd64) or defined(x86_64):
|
|
let platform = recipe.selectPlatform(paX86_64, "linux")
|
|
if platform.isSome:
|
|
result = platform.get().arch == paX86_64
|
|
else:
|
|
result = false
|
|
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 - parseRecipe returns None on error
|
|
let invalidRecipe = TestDir / "invalid.kdl"
|
|
writeFile(invalidRecipe, "this is not valid KDL {{{")
|
|
let invalidContent = readFile(invalidRecipe)
|
|
let invalidResult = parseRecipe(invalidContent)
|
|
if invalidResult.isSome:
|
|
allPassed = false # Should have returned None
|
|
|
|
# Test 2: Missing file - should throw IOError
|
|
try:
|
|
discard readFile(TestDir / "nonexistent.kdl")
|
|
allPassed = false # Should have thrown
|
|
except IOError, OSError:
|
|
discard # Expected
|
|
|
|
# Test 3: Invalid checksum
|
|
let testFile = TestDir / "test-checksum.txt"
|
|
writeFile(testFile, "content")
|
|
|
|
let verified = 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 testToolManagement(): bool =
|
|
let start = startTest("Tool management")
|
|
|
|
try:
|
|
let manager = newInstallationManager(TestDir / "bootstrap")
|
|
|
|
# Test tool path generation
|
|
let toolPath = manager.getToolPath("test-tool")
|
|
if not toolPath.contains("test-tool"):
|
|
endTest("Tool management", start, false, "Tool path generation failed")
|
|
return false
|
|
|
|
# Test tool installation check
|
|
let isInstalled = manager.isToolInstalled("test-tool")
|
|
if isInstalled:
|
|
endTest("Tool management", start, false, "Tool should not be installed yet")
|
|
return false
|
|
|
|
# Create a fake tool installation
|
|
createDir(toolPath)
|
|
|
|
# Check again
|
|
let nowInstalled = manager.isToolInstalled("test-tool")
|
|
if not nowInstalled:
|
|
endTest("Tool management", start, false, "Tool should be detected as installed")
|
|
return false
|
|
|
|
# Test getting installed tools
|
|
let tools = manager.getInstalledTools()
|
|
if "test-tool" notin tools:
|
|
endTest("Tool management", start, false, "Installed tool not in list")
|
|
return false
|
|
|
|
result = true
|
|
endTest("Tool management", start, true)
|
|
except Exception as e:
|
|
endTest("Tool management", 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 testDownloadManagerInit()
|
|
discard testChecksumVerification()
|
|
discard testInstallationManagerInit()
|
|
discard testArchiveExtraction()
|
|
discard testScriptExecution()
|
|
discard testRecipeValidation()
|
|
discard testPlatformSelection()
|
|
discard testErrorHandling()
|
|
discard testToolManagement()
|
|
|
|
cleanupTestEnvironment()
|
|
printTestSummary()
|
|
|
|
when isMainModule:
|
|
main()
|