2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-08-03 16:06:59 +00:00
Files
xcat-core/github_action_xcat_test.pl
T
Daniel Hilst 7fe2244d9d fix(xcat-core): run every xCAT-test unit test in the GitHub CI
The 47 test files under xCAT-test/unit/ were shipped but almost never
executed on a pull request. Only three hand-written xcattest cases
reached them -- dhcp_unit, ipmi_unit and xcatprobe_unit -- and each
proved a single glob against the installed copy, so the majority of the
suite had never run at all. Real drift went unnoticed as a result:
ubuntu_subiquity_template.t still asserted the pre-86e77bcd7 shape of
compute.subiquity.tmpl and failed against the current template.

Run `prove xCAT-test/unit/*.t` directly from github_action_xcat_test.pl.
The tests resolve xCAT modules and fixture files relative to the repo
root through FindBin, so they must be proved from the checkout and not
from /opt/xcat/share/xcat/tools/autotest/unit; install_xcat() chdir's
away, hence the getcwd() captured up front. The step runs after the
install because the suite needs the perl dependencies xCAT pulls in
(Net::DNS, XML::Simple) and a usable xCAT database.

Drop the three prove testcases so their tests do not run twice, and
refresh the two stale ubuntu_subiquity_template.t assertions: the
identity section is now intentional (86e77bcd7) and the MAC
normalization gained cut filters ahead of the tr (c6e38483f), which the
loosened regex plus a new assertion for the suffix stripping now cover.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
(cherry picked from commit a0d2322b71)
2026-07-24 12:56:39 -03:00

648 lines
22 KiB
Perl

# IBM(c) 2022 EPL license http://www.eclipse.org/legal/epl-v10.html
use strict;
use warnings;
use Getopt::Long;
use Data::Dumper;
use Time::Local;
use File::Basename;
use File::Path;
use File::Find;
use LWP::UserAgent;
use HTTP::Request;
use Encode;
use Encode::CN;
use JSON;
use URI::Escape;
use LWP::Simple;
use Cwd;
use Term::ANSIColor qw(:constants);
$Term::ANSIColor::AUTORESET = 1;
#---Global attributes---
my $rst = 0;
my $retries = 5; # Try this many times to get response
my $check_result_str="``CI CHECK RESULT`` : ";
my $last_func_start = timelocal(localtime());
my $GITHUB_API = "https://api.github.com";
# The workflow starts us in the checked out source tree, but install_xcat()
# chdir's away from it. Remember the checkout up front: the unit tests under
# xCAT-test/unit resolve xCAT modules and fixture files relative to it.
my $srcdir = getcwd();
#--------------------------------------------------------
# Fuction name: runcmd
# Description: run a command after 'cmd' label in one case
# Attributes:
# Return code:
# $::RUNCMD_RC : the return code of command
# @$outref : the output of command
#--------------------------------------------------------
sub runcmd
{
my ($cmd) = @_;
my $rc = 0;
$::RUNCMD_RC = 0;
my $outref = [];
@$outref = `$cmd 2>&1`;
if ($?)
{
$rc = $?;
$rc = $rc >> 8;
$::RUNCMD_RC = $rc;
}
chomp(@$outref);
return @$outref;
}
#--------------------------------------------------------
# Fuction name: get_files_recursive
# Description: Search all file in one directory recursively
# Attributes:
# $dir (input attribute)
# The target scan directory
# $files_path_ref (output attribute)
# the reference of array where save all vaild files under $dir
# Return code:
#--------------------------------------------------------
sub get_files_recursive
{
my $dir = shift;
my $files_path_ref = shift;
my $fd = undef;
if(!opendir($fd, $dir)){
print "[get_files_recursive]: failed to open $dir :$!\n";
return 1;
}
for (; ;)
{
my $direntry = readdir($fd);
last unless (defined($direntry));
next if ($direntry =~ m/^\.\w*/);
next if ($direntry eq '..');
my $target = "$dir/$direntry";
if (-d $target) {
get_files_recursive($target, $files_path_ref);
} else {
push(@{$files_path_ref}, glob("$target\n"));
}
}
closedir($fd);
return 0;
}
#--------------------------------------------------------
# Fuction name: check_pr_format
# Description:
# Attributes:
# Return code:
#--------------------------------------------------------
sub check_pr_format{
if($ENV{'TRAVIS_EVENT_TYPE'} eq "pull_request"){
my $pr_url = "$GITHUB_API/repos/$ENV{'TRAVIS_REPO_SLUG'}/pulls/$ENV{'TRAVIS_PULL_REQUEST'}";
my $pr_url_resp;
my $counter = 1;
while($counter <= $retries) {
$pr_url_resp = get($pr_url);
if ($pr_url_resp) {
last; # Got response, no more retries
} else {
sleep($counter*2); # Sleep and try again
print "[check_pr_format] $counter Did not get response, sleeping ". $counter*2 . "\n";
$counter++;
}
}
unless ($pr_url_resp) {
print "[check_pr_format] After $retries retries, not able to get response from $pr_url \n";
# Failed after trying a few times, return error
return $counter;
}
my $pr_content = decode_json($pr_url_resp);
my $pr_title = $pr_content->{title};
my $pr_body = $pr_content->{body};
my $pr_milestone = $pr_content->{milestone};
my $pr_labels_len = @{$pr_content->{labels}};
#print "[check_pr_format] Dumper pr_content:\n";
#print Dumper $pr_content;
print "[check_pr_format] pr title = $pr_title\n";
print "[check_pr_format] pr body = $pr_body \n";
my $checkrst="";
if(! $pr_title){
$checkrst.="Missing title.";
}
if(! $pr_body){
$checkrst.="Missing description.";
}
if(! $pr_milestone){
$checkrst.="Missing milestone.";
}
if(! $pr_labels_len){
$checkrst.="Missing labels.";
}
# Guard against root user making commits
$checkrst.=check_commit_owner('root');
if(length($checkrst) == 0){
$check_result_str .= "> **PR FORMAT CORRECT**";
#send_back_comment("$check_result_str");
}else{
# Warning if missing milestone or labels, others are errors
if($checkrst =~ /milestone/ || $checkrst =~ /labels/){
$check_result_str .= "> **PR FORMAT WARNING** : $checkrst";
#send_back_comment("$check_result_str");
}else{
$check_result_str .= "> **PR FORMAT ERROR** : $checkrst";
#send_back_comment("$check_result_str");
return 1;
}
}
}
return 0;
}
#--------------------------------------------------------
# Fuction name: check_commit_owner
# Description: Verify commits are not done by specified user
# Attributes: user login to reject
# Return:
# Error string -User rejected,
# Empty string -User not rejected
#--------------------------------------------------------
sub check_commit_owner{
my $invalid_user = shift;
if($ENV{'TRAVIS_EVENT_TYPE'} eq "pull_request"){
my $commits_content;
my $commits_len = 0;
my $json = new JSON;
my $commits_url = "$GITHUB_API/repos/$ENV{'TRAVIS_REPO_SLUG'}/pulls/$ENV{'TRAVIS_PULL_REQUEST'}/commits";
my $commits_url_resp;
my $counter = 1;
while($counter <= $retries) {
$commits_url_resp = get($commits_url);
if ($commits_url_resp) {
last; # Got response, no more retries
} else {
sleep($counter*2); # Sleep and try again
print "[check_commit_owner] $counter Did not get response, sleeping ". $counter*2 . "\n";
$counter++;
}
}
if ($commits_url_resp) {
$commits_content = $json->decode($commits_url_resp);
$commits_len = @$commits_content;
} else {
print "[check_commit_owner] After $retries retries, not able to get response from $commits_url \n";
return "Unable to verify login of committer.";
}
if($commits_len > 0) {
foreach my $commit (@{$commits_content}){
my $committer = $commit->{committer};
my $committer_login = $committer->{login};
print "[check_commit_owner] Committer login $committer_login \n";
if($committer_login =~ /^$invalid_user$/) {
# Committer logins matches
return "Commits by $invalid_user not allowed";
}
}
}
}
return "";
}
#--------------------------------------------------------
# Fuction name: send_back_comment
# Description: Append to comment of the PR passed $message
# Attributes: Message to append to PR
# Return code:
#--------------------------------------------------------
sub send_back_comment{
my $message = shift;
my $comment_url = "$GITHUB_API/repos/$ENV{'TRAVIS_REPO_SLUG'}/issues/$ENV{'TRAVIS_PULL_REQUEST'}/comments";
my $json = new JSON;
my $comment_len = 0;
my $comment_content;
my $comment_url_resp;
my $counter = 1;
while($counter <= $retries) {
$comment_url_resp = get($comment_url);
if ($comment_url_resp) {
last; # Got response, no more retries
} else {
sleep($counter*2); # Sleep and try again
print "[send_back_comment] $counter Did not get response, sleeping ". $counter*2 . "\n";
$counter++;
}
}
unless ($comment_url_resp) {
print "[send_back_comment] After $retries retries, not able to get response from $comment_url \n";
# Failed after trying a few times, return
return;
}
print "\n\n>>>>>Dumper comment_url_resp:\n";
print Dumper $comment_url_resp;
$comment_content = $json->decode($comment_url_resp);
$comment_len = @$comment_content;
my $post_url = $comment_url;
my $post_method = "POST";
if($comment_len > 0){
foreach my $comment (@{$comment_content}){
if($comment->{'body'} =~ /CI CHECK RESULT/) {
$post_url = $comment->{'url'};
$post_method = "PATCH";
}
}
}
print "[send_back_comment] method = $post_method to $post_url. Message = $message\n";
if ( $ENV{'xcatbotuser'} and $ENV{'xcatbotpw'}) {
`curl -u "$ENV{'xcatbotuser'}:$ENV{'xcatbotpw'}" -X $post_method -d '{"body":"$message"}' $post_url`;
}
else {
print "Not able to update pull request with message: $message\n";
}
}
#--------------------------------------------------------
# Fuction name: build_xcat_core
# Description:
# Attributes:
# Return code:
#--------------------------------------------------------
sub build_xcat_core{
my @output;
my $cmd = "sudo ./build-ubunturepo -c UP=0 BUILDALL=1 GPGSIGN=0";
@output = runcmd("$cmd");
if($::RUNCMD_RC){
my $lastline = $output[-1];
$lastline =~ s/[\r\n\t\\"']*//g;
print "[build_xcat_core] $cmd ....[Failed]\n";
$check_result_str .= "> **BUILD ERROR**, Please click ``Details`` label in ``Merge pull request`` box for detailed information";
print $check_result_str;
return 1;
}else{
print "[build_xcat_core] $cmd ....[Pass]\n";
$check_result_str .= "> **BUILD SUCCESSFUL** ";
print $check_result_str;
}
return 0;
}
#--------------------------------------------------------
# Fuction name: install_xcat
# Description:
# Attributes:
# Return code:
#--------------------------------------------------------
sub install_xcat{
my @cmds = ("sudo ./mklocalrepo.sh",
"sudo chmod 777 /etc/apt/sources.list",
"sudo echo \"deb [arch=amd64 allow-insecure=yes] http://xcat.org/files/xcat/repos/apt/latest/xcat-dep noble main\" >> /etc/apt/sources.list",
"sudo echo \"deb [arch=ppc64el allow-insecure=yes] http://xcat.org/files/xcat/repos/apt/latest/xcat-dep noble main\" >> /etc/apt/sources.list",
"sudo apt-get -qq --allow-insecure-repositories update");
chdir $ENV{RUNNER_WORKSPACE};;
my @output;
foreach my $cmd (@cmds){
print "[install_xcat] running $cmd\n";
@output = runcmd("$cmd");
if($::RUNCMD_RC){
print RED "[install_xcat] $cmd. ...[Failed]\n";
print "[install_xcat] error message:\n";
print Dumper \@output;
$check_result_str .= "> **INSTALL XCAT ERROR** : Please click ``Details`` label in ``Merge pull request`` box for detailed information ";
print $check_result_str;
return 1;
}
}
my $cmd = "sudo apt-get install xcat --allow-remove-essential --allow-unauthenticated";
@output = runcmd("$cmd");
if($::RUNCMD_RC){
my $lastline = $output[-1];
$lastline =~ s/[\r\n\t\\"']*//g;
print "[install_xcat] $cmd ....[Failed]\n";
print ">>>>>Dumper the output of '$cmd'\n";
print Dumper \@output;
$check_result_str .= "> **INSTALL XCAT ERROR** : Please click ``Details`` label in ``Merge pull request`` box for detailed information";
print $check_result_str;
return 1;
}else{
print "[install_xcat] $cmd ....[Pass]\n";
print "\n------Config xcat and verify xcat is working correctly-----\n";
@cmds = ("sudo -s /opt/xcat/share/xcat/scripts/setup-local-client.sh -f runner",
"sudo -s /opt/xcat/sbin/chtab priority=1.1 policy.name=runner policy.rule=allow",
". /etc/profile.d/xcat.sh && tabdump policy",
". /etc/profile.d/xcat.sh && tabdump site",
". /etc/profile.d/xcat.sh && lsxcatd -a",
"ls /opt/xcat/sbin",
"service xcatd status");
my $ret = 0;
foreach my $cmd (@cmds){
print "\n[install_xcat] running $cmd.....\n";
@output = runcmd("$cmd");
print Dumper \@output;
if($::RUNCMD_RC){
print RED "[install_xcat] $cmd. ...[Failed]\n";
#print Dumper \@output;
$ret = 1;
}else{
print "[install_xcat] $cmd....[Pass]\n";
}
}
$cmd = "sudo apt-get install xcat-probe --allow-remove-essential --allow-unauthenticated";
@output = runcmd("$cmd");
if($::RUNCMD_RC){
print RED "[install_xcat] $cmd ....[Failed]\n";
print Dumper \@output;
$ret = 1;
}else{
print "[install_xcat] $cmd ....[Pass]:\n";
}
if($ret){
$check_result_str .= "> **INSTALL XCAT ERROR** : Please click ``Details`` label in ``Merge pull request`` box for detailed information\n";
print $check_result_str;
return 1;
}
$check_result_str .= "> **INSTALL XCAT SUCCESSFUL**";
print $check_result_str;
}
return 0;
}
#--------------------------------------------------------
# Fuction name: run_unit_tests
# Description: Run every Perl unit test under xCAT-test/unit with prove.
# The tests are source tree tests: they pull xCAT modules and
# fixture files out of the checkout through FindBin, so they
# have to be proved from the checkout root rather than from the
# copy installed under /opt/xcat/share/xcat/tools/autotest.
# Attributes:
# Return code: 0 all tests passed, 1 otherwise
#--------------------------------------------------------
sub run_unit_tests{
my $cmd = "cd $srcdir && prove xCAT-test/unit/*.t";
print "[run_unit_tests] running $cmd\n";
my @output = runcmd("$cmd");
print Dumper \@output;
if($::RUNCMD_RC){
print RED "[run_unit_tests] $cmd ....[Failed]\n";
$check_result_str .= "> **UNIT TESTS Failed** : Please click ``Details`` label in ``Merge pull request`` box for detailed information\n";
print $check_result_str;
return 1;
}
print "[run_unit_tests] $cmd ....[Pass]\n";
$check_result_str .= "> **UNIT TESTS Successful**\n";
print $check_result_str;
return 0;
}
#--------------------------------------------------------
# Fuction name: check_syntax
# Description:
# Attributes:
# Return code:
#--------------------------------------------------------
sub check_syntax{
my @output;
my @syntax_err;
my $ret = 0;
my @target_dirs=("/opt/xcat",
"/install");
foreach my $dir (@target_dirs){
my @files = ();
get_files_recursive("$dir", \@files);
foreach my $file (@files) {
next if($file =~ /\/opt\/xcat\/share\/xcat\/netboot\/genesis\//);
next if($file =~ /\/opt\/xcat\/probe\//);
@output = runcmd("file $file");
if($output[0] =~ /perl /i){
@output = runcmd("sudo bash -c '. /etc/profile.d/xcat.sh && perl -I /opt/xcat/lib/perl -I /opt/xcat/lib -I /usr/lib/perl5 -I /usr/share/perl -c $file'");
if($::RUNCMD_RC){
push @syntax_err, @output;
$ret = 1;
}
}
}
}
if(@syntax_err){
print "[check_syntax] syntax checking ....[Failed]\n";
print "[check_syntax] Dumper error message:\n";
print Dumper @syntax_err;
$check_result_str .= "> **CODE SYNTAX ERROR** : Please click ``Details`` label in ``Merge pull request`` box for detailed information";
print $check_result_str;
}else{
print "[check_syntax] syntax checking ....[Pass]\n";
$check_result_str .= "> **CODE SYNTAX CORRECT**";
print $check_result_str;
}
return $ret;
}
#--------------------------------------------------------
# Fuction name: run_fast_regression_test
# Description:
# Attributes:
# Return code:
#--------------------------------------------------------
sub run_fast_regression_test{
my $cmd = "sudo apt-get install xcat-test --allow-remove-essential --allow-unauthenticated";
my @output = runcmd("$cmd");
if($::RUNCMD_RC){
print RED "[run_fast_regression_test] $cmd ....[Failed]\n";
print Dumper \@output;
return 1;
}else{
print "[run_fast_regression_test] $cmd .....:\n";
print Dumper \@output;
}
$cmd = "sudo bash -c '. /etc/profile.d/xcat.sh && xcattest -h'";
@output = runcmd("$cmd");
if($::RUNCMD_RC){
print RED "[run_fast_regression_test] $cmd ....[Failed]\n";
print "[run_fast_regression_test] error dumper:\n";
print Dumper \@output;
return 1;
}else{
print "[run_fast_regression_test] $cmd .....:\n";
print Dumper \@output;
}
my $hostname = `hostname`;
chomp($hostname);
print "hostname = $hostname\n";
my $conf_file = "$ENV{'PWD'}/regression.conf";
system "sudo touch $conf_file";
system "sudo chmod 777 $conf_file";
open(my $fh, '>', $conf_file) or die "Could not open test configuration file $!";
print $fh "[System]\nMN=$hostname\n[Table_site]\nkey=domain\nvalue=pok.stglabs.ibm.com\nkey=dhcpbackend\nvalue=isc\n";
close($fh);
print "Dumper regression conf file:\n";
@output = runcmd("cat $conf_file");
print Dumper \@output;
$cmd = "sudo bash -c '. /etc/profile.d/xcat.sh && xcattest -s \"ci_test\" -l'";
my @caseslist = runcmd("$cmd");
if($::RUNCMD_RC){
print RED "[run_fast_regression_test] $cmd ....[Failed]\n";
print "[run_fast_regression_test] error dumper:\n";
print Dumper \@caseslist;
return 1;
}else{
print "[run_fast_regression_test] $cmd .....:\n";
print Dumper \@caseslist;
}
my $casenum = @caseslist;
my $x = 0;
my @failcase;
my $passnum = 0;
my $failnum = 0;
foreach my $case (@caseslist){
++$x;
$cmd = "sudo bash -c '. /etc/profile.d/xcat.sh && xcattest -f $conf_file -t $case'";
print "[run_fast_regression_test] run $x: $cmd\n";
@output = runcmd("$cmd");
#print Dumper \@output;
for(my $i = $#output; $i>-1; --$i){
if($output[$i] =~ /------END::(.+)::Failed/){
push @failcase, $1;
++$failnum;
print Dumper \@output;
last;
}elsif ($output[$i] =~ /------END::(.+)::Passed/){
++$passnum;
last;
}
}
}
if($failnum){
my $log_str = join (",", @failcase );
$check_result_str .= "> **FAST REGRESSION TEST Failed**: Totalcase $casenum Passed $passnum Failed $failnum FailedCases: $log_str. Please click ``Details`` label in ``Merge pull request`` box for detailed information\n";
print $check_result_str;
return 1;
}else{
$check_result_str .= "> **FAST REGRESSION TEST Successful**: Totalcase $casenum Passed $passnum Failed $failnum\n";
print $check_result_str;
}
return 0;
}
#--------------------------------------------------------
# Fuction name: mark_time
# Description:
# Attributes:
# Return code:
#--------------------------------------------------------
sub mark_time{
my $func_name=shift;
my $nowtime = timelocal(localtime());
my $nowtime_str = scalar(localtime());
my $duration = $nowtime - $last_func_start;
$last_func_start = $nowtime;
print "[mark_time] $nowtime_str, ElapsedTime of $func_name is $duration s\n";
}
#===============Main Process=============================
my @os_info = runcmd("cat /etc/os-release");
print "Current OS information:\n";
print Dumper \@os_info;
my @perl_vserion = runcmd("perl -v");
print "Current perl information:\n";
print Dumper \@perl_vserion;
my @disk = runcmd("df -h");
print "Disk information:\n";
print Dumper \@disk;
# Hacking the netmask. Not sure if we need to recover it after finish xcattest
# Note: Here has an assumption from Travis VM: only 1 UP Ethernet interface available (CHANGEME if it not as is)
my @intfinfo = runcmd("ip -o link |grep 'link/ether'|grep 'state UP' |awk -F ':' '{print \$2}'|head -1");
foreach my $nic (@intfinfo) {
print "Hacking the netmask length to 16 if it is 32: $nic\n";
runcmd("ip -4 addr show $nic|grep 'inet'|grep -q '/32' && sudo ip addr add \$(hostname -I|awk '{print \$1}')/16 dev $nic");
}
my @ipinfo = runcmd("ip addr");
print "Networking information:\n";
print Dumper \@ipinfo;
#Start to build xcat core
print GREEN "\n------ Building xCAT core package ------\n";
$rst = build_xcat_core();
if($rst){
print RED "Build of xCAT core package failed\n";
exit $rst;
}
mark_time("build_xcat_core");
#Start to install xcat
print GREEN "\n------Installing xCAT ------\n";
$rst = install_xcat();
if($rst){
print RED "Install of xCAT failed\n";
exit $rst;
}
mark_time("install_xcat");
#Run the xCAT-test unit tests. They need the perl dependencies xCAT pulls in
#(Net::DNS, XML::Simple) and a usable xCAT database, so they run after install.
print GREEN "\n------Running xCAT-test unit tests ------\n";
$rst = run_unit_tests();
if($rst){
print RED "Run of xCAT-test unit tests failed\n";
exit $rst;
}
mark_time("run_unit_tests");
#Check the syntax of changing code
print GREEN "\n------ Checking the syntax of changed code------\n";
$rst = check_syntax();
if($rst){
print RED "Check syntax of changed code failed\n";
exit $rst;
}
mark_time("check_syntax");
#run fast regression
print GREEN "\n------Running fast regression test ------\n";
$rst = run_fast_regression_test();
if($rst){
print RED "Run of fast regression test failed\n";
exit $rst;
}
mark_time("run_fast_regression_test");
exit 0;