Blame | Last modification | View Log | Download | RSS feed
#!/usr/bin/env perl
# test_integration.pl
# Integration test script for havirt
# Tests the havirt modules and functions directly
#
# Usage: ./test_integration.pl [--verbose] [--dryrun]
#
# Copyright 2026 Daily Data, Inc.
use strict;
use warnings;
use FindBin;
use File::Spec;
use Cwd 'abs_path';
use File::Basename;
# Add parent directory to lib path (tests/../ to find modules)
use lib dirname( dirname( abs_path( __FILE__ ) ) );
use Data::Dumper;
use Getopt::Long;
# Load havirt modules
use havirt;
use domain;
use node;
use cluster;
# Test framework variables
my $tests_run = 0;
my $tests_passed = 0;
my $tests_failed = 0;
my @failed_tests;
# Command line options
my $verbose = 0;
my $dryrun = 1; # Default to dryrun for safety
GetOptions(
'verbose|v+' => \$verbose,
'dryrun|n!' => \$dryrun,
) or die "Error parsing command line\n";
# Color codes for output
my $RED = "\033[0;31m";
my $GREEN = "\033[0;32m";
my $YELLOW = "\033[1;33m";
my $BLUE = "\033[0;34m";
my $NC = "\033[0m";
# Logging functions
sub log_info {
print "${BLUE}[INFO]${NC} ", @_, "\n";
}
sub log_success {
print "${GREEN}[PASS]${NC} ", @_, "\n";
$tests_passed++;
}
sub log_failure {
my $msg = shift;
print "${RED}[FAIL]${NC} $msg\n";
$tests_failed++;
push @failed_tests, $msg;
}
sub log_test {
print "${YELLOW}[TEST]${NC} ", @_, "\n";
$tests_run++;
}
# Test function wrapper
sub run_test {
my ($test_name, $code) = @_;
log_test($test_name);
eval {
my $result = $code->();
if (defined $result) {
log_success($test_name);
print " Result: $result\n" if $verbose && $result;
} else {
log_success($test_name);
}
};
if ($@) {
log_failure("$test_name - Error: $@");
}
}
# Test that a function returns defined value
sub test_returns_defined {
my ($test_name, $code) = @_;
log_test($test_name);
eval {
my $result = $code->();
if (defined $result) {
log_success($test_name);
print " Result: " . (ref($result) ? Dumper($result) : $result) . "\n" if $verbose > 1;
} else {
log_failure("$test_name - returned undefined");
}
};
if ($@) {
log_failure("$test_name - Error: $@");
}
}
print "=" x 50 . "\n";
print " havirt Integration Test Suite\n";
print "=" x 50 . "\n\n";
log_info("Running in " . ($dryrun ? "DRY-RUN" : "LIVE") . " mode");
log_info("Verbose level: $verbose");
print "\n";
# Initialize config (in parent directory)
my $configFileName = dirname($FindBin::RealBin) . '/config.yaml';
my $config;
print "=" x 50 . "\n";
print "Section 1: Configuration Tests\n";
print "=" x 50 . "\n\n";
run_test("Check config file exists", sub {
return -f $configFileName ? "Config file found" : undef;
});
test_returns_defined("Load configuration", sub {
$config = havirt::readConfig($configFileName);
return $config ? "Config loaded" : undef;
});
# Set test flags
if ($config) {
$config->{'flags'}->{'dryrun'} = $dryrun;
$config->{'flags'}->{'verbose'} = $verbose;
$main::config = $config;
}
print "\n";
# Test havirt.pm functions
print "=" x 50 . "\n";
print "Section 2: havirt.pm Core Functions\n";
print "=" x 50 . "\n\n";
my $statusDB;
test_returns_defined("Read status database", sub {
$statusDB = havirt::readDB($config);
$main::statusDB = $statusDB;
return $statusDB ? "Status DB loaded" : undef;
});
if ($statusDB) {
run_test("Check nodes in status DB", sub {
my $node_count = scalar(keys %{$statusDB->{'node'}});
return "Found $node_count nodes";
});
run_test("Check domains in status DB", sub {
my $domain_count = scalar(keys %{$statusDB->{'domain'}});
return "Found $domain_count domains";
});
}
test_returns_defined("Test makeCommand function", sub {
my $cmd = havirt::makeCommand("localhost", "test command");
return $cmd;
});
run_test("Test diffArray function", sub {
my @array1 = ('a', 'b', 'c', 'd');
my @array2 = ('b', 'd', 'e');
my @diff = havirt::diffArray(\@array1, \@array2);
my $diff_str = join(',', @diff);
return "Diff result: $diff_str" if @diff;
return "Empty diff";
});
print "\n";
# Test node.pm functions
print "=" x 50 . "\n";
print "Section 3: node.pm Module Tests\n";
print "=" x 50 . "\n\n";
test_returns_defined("Node help", sub {
my $help = node::help();
return $help ? "Help returned" : undef;
});
test_returns_defined("Node list", sub {
my $list = node::list();
return $list ? "Node list returned" : undef;
});
if ($statusDB && $statusDB->{'node'}) {
my @nodes = keys %{$statusDB->{'node'}};
if (@nodes) {
my $test_node = $nodes[0];
log_info("Using node '$test_node' for tests");
run_test("Get domains on node $test_node", sub {
my @domains = havirt::getDomainsOnNode($statusDB, $test_node);
return "Found " . scalar(@domains) . " domains on $test_node";
});
}
}
print "\n";
# Test domain.pm functions
print "=" x 50 . "\n";
print "Section 4: domain.pm Module Tests\n";
print "=" x 50 . "\n\n";
test_returns_defined("Domain help", sub {
my $help = domain::help();
return $help ? "Help returned" : undef;
});
test_returns_defined("Domain list", sub {
my $list = domain::list();
return $list ? "Domain list returned" : undef;
});
# Test MAC address generation
run_test("Test MAC address generation", sub {
my $mac = domain::makeMac();
return $mac =~ /^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/ ? "Generated MAC: $mac" : undef;
});
# Test VNC port finding
run_test("Test VNC port finding", sub {
my $port = domain::findVNCPort($statusDB);
return $port && $port >= 5900 ? "Found VNC port: $port" : "Default port: 5900";
});
# Test domain parsing if config files exist
my $conf_dir = $config->{'config dir'} if $config;
if ($conf_dir && -d $conf_dir) {
my @xml_files = glob("$conf_dir/*.xml");
if (@xml_files) {
my $test_xml = $xml_files[0];
my $domain_name = basename($test_xml, '.xml');
run_test("Parse domain config: $domain_name", sub {
my $parsed = domain::parseDomain($test_xml);
return $parsed ? "Parsed successfully" : undef;
});
}
}
print "\n";
# Test cluster.pm functions
print "=" x 50 . "\n";
print "Section 5: cluster.pm Module Tests\n";
print "=" x 50 . "\n\n";
test_returns_defined("Cluster help", sub {
my $help = cluster::help();
return $help ? "Help returned" : undef;
});
test_returns_defined("Cluster status", sub {
my $status = cluster::status();
return $status ? "Status returned" : undef;
});
run_test("Get cluster stats", sub {
my $stats = cluster::getClusterStats($statusDB);
if ($stats) {
my $node_count = scalar(@{$stats->{'nodes'}}) if $stats->{'nodes'};
return "Cluster stats retrieved" . ($node_count ? " ($node_count nodes)" : "");
}
return undef;
});
run_test("Test humanReadable function", sub {
my $hr = cluster::humanReadable(1073741824); # 1GB in bytes
return "1GB = $hr";
});
run_test("Test percent function", sub {
my $pct = cluster::percent(50, 200);
return "50/200 = $pct";
});
print "\n";
# Test data structure integrity
print "=" x 50 . "\n";
print "Section 6: Data Structure Validation\n";
print "=" x 50 . "\n\n";
if ($statusDB) {
run_test("Validate node data structure", sub {
foreach my $node_name (keys %{$statusDB->{'node'}}) {
my $node = $statusDB->{'node'}{$node_name};
return undef unless ref($node) eq 'HASH';
}
return "All node structures valid";
});
run_test("Validate domain data structure", sub {
foreach my $domain_name (keys %{$statusDB->{'domain'}}) {
my $domain = $statusDB->{'domain'}{$domain_name};
return undef unless ref($domain) eq 'HASH';
}
return "All domain structures valid";
});
run_test("Check for orphaned domains", sub {
my @orphaned;
foreach my $domain_name (keys %{$statusDB->{'domain'}}) {
my $domain = $statusDB->{'domain'}{$domain_name};
if ($domain->{'node'} && !exists $statusDB->{'node'}{$domain->{'node'}}) {
push @orphaned, $domain_name;
}
}
if (@orphaned) {
return "Found " . scalar(@orphaned) . " orphaned domains: " . join(', ', @orphaned);
}
return "No orphaned domains";
});
}
print "\n";
# Final summary
print "=" x 50 . "\n";
print " Test Summary\n";
print "=" x 50 . "\n\n";
print "Total tests run: $tests_run\n";
print "Tests passed: ${GREEN}$tests_passed${NC}\n";
print "Tests failed: ${RED}$tests_failed${NC}\n";
print "\n";
if ($tests_failed > 0) {
print "${RED}Failed tests:${NC}\n";
foreach my $failed (@failed_tests) {
print " - $failed\n";
}
print "\n";
}
if ($tests_failed == 0) {
print "${GREEN}All tests passed!${NC}\n";
exit 0;
} else {
print "${RED}Some tests failed.${NC}\n";
exit 1;
}