Subversion Repositories havirt

Rev

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;
}