Files
kaiju/testingScripts/buildTest.py
2024-03-20 08:38:53 -06:00

408 lines
13 KiB
Python

#!/usr/bin/env python
"""Run MAGE build regression tests.
This script runs a series of builds of the MAGE software using sets of
modules listed in files under:
$KAIJUHOME/testingScripts/mage_build_test_modules
This script reads the file build_test.lst from this directory, and
uses the contents as a list of module list files to use for MAGE build
tests.
NOTE: These tests are performed on a load-balance-assigned login node on
derecho. No PBS job is submitted.
Authors
-------
Jeff Garretson
Eric Winter
"""
# Import standard modules.
import datetime
import glob
import os
import shutil
import subprocess
import sys
# Import 3rd-party modules.
# Import project modules.
from kaipy.testing import common
# Program constants
# Program description.
DESCRIPTION = 'Script for MAGE build testing'
# Home directory of kaiju installation
KAIJUHOME = os.environ['KAIJUHOME']
# Prefix for naming build test directories
BUILD_TEST_DIRECTORY_PREFIX = 'build_'
# glob pattern for naming build test directories
BUILD_TEST_DIRECTORY_GLOB_PATTERN = 'build_*'
# Path to directory to use for building executable list
EXECUTABLE_LIST_BUILD_DIRECTORY = os.path.join(KAIJUHOME, 'testFolder')
# Path to directory containing the test scripts
TEST_SCRIPTS_DIRECTORY = os.path.join(KAIJUHOME, 'testingScripts')
# Path to directory containing module lists
MODULE_LIST_DIRECTORY = os.path.join(TEST_SCRIPTS_DIRECTORY,
'mage_build_test_modules')
# Path to module list file to use when generating the list of executables
EXECUTABLE_LIST_MODULE_LIST = os.path.join(MODULE_LIST_DIRECTORY, '01.lst')
# Path to file containing list of module sets to use for build tests
BUILD_TEST_LIST_FILE = os.path.join(MODULE_LIST_DIRECTORY, 'build_test.lst')
# Name of subdirectory of current build subdirectory containing binaries
BUILD_BIN_DIR = 'bin'
def main():
"""Begin main program.
This is the main program code.
Parameters
----------
None
Returns
-------
None
Raises
------
subprocess.CalledProcessError
If an exception occurs in subprocess.run()
"""
# Set up the command-line parser.
parser = common.create_command_line_parser(DESCRIPTION)
# Parse the command-line arguments.
args = parser.parse_args()
if args.debug:
print(f"args = {args}")
debug = args.debug
be_loud = args.loud
is_test = args.test
verbose = args.verbose
# -------------------------------------------------------------------------
if debug:
print(f"Starting {sys.argv[0]} at {datetime.datetime.now()}")
print(f"Current directory is {os.getcwd()}")
# -------------------------------------------------------------------------
# Set up for communication with Slack.
slack_client = common.slack_create_client()
if debug:
print(f"slack_client = {slack_client}")
# -------------------------------------------------------------------------
# Move to the MAGE installation directory.
os.chdir(KAIJUHOME)
# -------------------------------------------------------------------------
# Clean up from previous tests.
if verbose:
print('Cleaning up from previous tests.')
directories = glob.glob(BUILD_TEST_DIRECTORY_GLOB_PATTERN)
directories.append(EXECUTABLE_LIST_BUILD_DIRECTORY)
for directory in directories:
try:
shutil.rmtree(directory)
except FileNotFoundError:
pass # EXECUTABLE_LIST_BUILD_DIRECTORY may not exist.
# <HACK>
# Remove the pFUnit compiled code to prevent using it during the
# build test. If PFUNIT-4.2 is in kaiju/external during a build,
# make will try to build the unit test code even if it is not
# requested, which causes fatal errors when building with a module
# set that uses a non-Intel compioler, since pFUnit was built with
# the Intel compiler.
directories = [
'FARGPARSE-1.1',
'GFTL-1.3',
'GFTL_SHARED-1.2',
'PFUNIT-4.2',
]
for directory in directories:
path = os.path.join(KAIJUHOME, 'external', directory)
try:
shutil.rmtree(path)
except FileNotFoundError:
pass # These directories may not exist.
# </HACK>
# -------------------------------------------------------------------------
# Find the current branch.
git_branch_name = common.git_get_branch_name()
if debug:
print(f"git_branch_name = {git_branch_name}")
# -------------------------------------------------------------------------
# Do a preliminary cmake run to generate the list of executables.
if verbose:
print(f"Generating list of executables for branch {git_branch_name}.")
# Create and move to the preliminary build folder.
os.mkdir(EXECUTABLE_LIST_BUILD_DIRECTORY)
os.chdir(EXECUTABLE_LIST_BUILD_DIRECTORY)
# Read the module list file for building the executable list.
if verbose:
print(f"Reading {EXECUTABLE_LIST_MODULE_LIST} to determine"
' modules and build options for generating list of executables'
' to build.')
module_names, cmake_environment, cmake_options = (
common.read_build_module_list_file(EXECUTABLE_LIST_MODULE_LIST)
)
if debug:
print(f"module_names = {module_names}")
print(f"cmake_environment = {cmake_environment}")
print(f"cmake_options = {cmake_options}")
# Assemble the commands to load the listed modules.
module_cmd = (
'module --force purge'
f"; module load {' '.join(module_names)}"
)
if debug:
print(f"module_cmd = {module_cmd}")
# Run cmake to build the Makefile.
if verbose:
print('Running cmake for executable list generation.')
cmd = (
f"{module_cmd}"
f"; {cmake_environment} cmake {cmake_options}"
f" {KAIJUHOME}"
' >& cmake.out'
)
if debug:
print(f"cmd = {cmd}")
try:
_ = subprocess.run(cmd, shell=True, check=True)
except subprocess.CalledProcessError as e:
print(
'ERROR: cmake for building executable list failed.\n'
f"e.cmd = {e.cmd}"
f"e.returncode = {e.returncode}\n"
f"See {os.path.join(EXECUTABLE_LIST_BUILD_DIRECTORY, 'cmake.out')}"
' for output from cmake.\n'
'Unable to generate executable list.',
file=sys.stderr
)
raise
# Run make to build the list of executable targets.
if verbose:
print('Running make for executable list generation.')
cmd = f"{module_cmd}; make help | grep '\.x'"
if debug:
print(f"cmd = {cmd}")
try:
cproc = subprocess.run(cmd, shell=True, check=True,
text=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
path = os.path.join(
EXECUTABLE_LIST_BUILD_DIRECTORY, 'make_to_grep.out'
)
with open(path, 'w', encoding='utf-8') as f:
f.write(e.stdout)
print(
'ERROR: make for building executable list failed.\n'
f"e.cmd = {e.cmd}\n"
f"e.returncode = {e.returncode}\n"
f"See {path} for output from make piped to grep.\n"
'Unable to generate executable list.',
file=sys.stderr
)
raise
executable_list = cproc.stdout.splitlines()
# Remove the first four characters (dots and spaces).
executable_list = [_[4:] for _ in executable_list]
if debug:
print(f"executable_list = {executable_list}")
# Create the make command to build the executable list.
make_cmd = f"make {' '.join(executable_list)}"
if debug:
print(f"make_cmd = {make_cmd}")
# -------------------------------------------------------------------------
# Make a list of module sets to build with.
# Read the list of module sets to use for build tests.
with open(BUILD_TEST_LIST_FILE, encoding='utf-8') as f:
lines = f.readlines()
module_list_files = [_.rstrip() for _ in lines]
if debug:
print(f"module_list_files = {module_list_files}")
# -------------------------------------------------------------------------
# Initalize test results for all module sets to False (failed).
test_passed = [False]*len(module_list_files)
# Do a build with each set of modules.
for (i_test, module_list_file) in enumerate(module_list_files):
if verbose:
print('Performing build test with module set '
f"{module_list_file}")
# Extract the name of the list.
module_set_name = module_list_file.rstrip('.lst')
if debug:
print(f"module_set_name = {module_set_name}.")
# Read this module list file, extracting cmake environment and
# options, if any.
path = os.path.join(MODULE_LIST_DIRECTORY, module_list_file)
if verbose:
print(f"Reading {path}.")
module_names, cmake_environment, cmake_options = (
common.read_build_module_list_file(path)
)
if debug:
print(f"module_names = {module_names}")
print(f"cmake_environment = {cmake_environment}")
print(f"cmake_options = {cmake_options}")
# Assemble the commands to load the listed modules.
module_cmd = (
f"module --force purge"
f"; module load {' '.join(module_names)}"
)
if debug:
print(f"module_cmd = {module_cmd}")
# Make a directory for this build, and go there.
dir_name = f"{BUILD_TEST_DIRECTORY_PREFIX}{module_set_name}"
build_directory = os.path.join(KAIJUHOME, dir_name)
if debug:
print(f"build_directory = {build_directory}")
os.mkdir(build_directory)
os.chdir(build_directory)
# Run cmake to build the Makefile.
if verbose:
print(
'Running cmake to create Makefile for module set'
f" {module_set_name}."
)
cmd = (
f"{module_cmd}"
f"; {cmake_environment} cmake {cmake_options}"
f" {KAIJUHOME}"
'>& cmake.out'
)
if debug:
print(f"cmd = {cmd}")
try:
_ = subprocess.run(cmd, shell=True, check=True)
except subprocess.CalledProcessError as e:
print(
f"ERROR: cmake for module set {module_set_name} failed.\n"
f"e.cmd = {e.cmd}\n"
f"e.returncode = {e.returncode}\n"
f"See {os.path.join(build_directory, 'cmake.out')}"
' for output from cmake.\n'
f"Skipping remaining steps for module set {module_set_name}",
file=sys.stderr
)
continue
# Run the build.
if verbose:
print(
'Running make to build kaiju for module set'
f" {module_set_name}."
)
cmd = f"{module_cmd}; {make_cmd} >& make.out"
if debug:
print(f"cmd = {cmd}")
try:
_ = subprocess.run(cmd, shell=True, check=True)
except subprocess.CalledProcessError as e:
print(
f"ERROR: make for module set {module_set_name} failed.\n"
f"e.cmd = {e.cmd}\n"
f"e.returncode = {e.returncode}\n"
f"See {os.path.join(build_directory, 'make.out')}"
' for output from make.\n'
f"Skipping remaining steps for module set {module_set_name}",
file=sys.stderr
)
continue
# Check for all executables.
if verbose:
print(
f"Checking build results for module set {module_set_name}."
)
missing = []
for executable in executable_list:
path = os.path.join(build_directory, BUILD_BIN_DIR, executable)
if not os.path.isfile(path):
missing.append(executable)
if len(missing) > 0:
for executable in missing:
print(f"ERROR: Did not build {executable}.")
else:
test_passed[i_test] = True
# End of loop over module sets.
# -------------------------------------------------------------------------
# Summarize the test results
test_summary_message = 'Results of build tests (`buildTest.py`):\n'
for (i_test, module_list_file) in enumerate(module_list_files):
module_set_name = module_list_file.rstrip('.lst')
test_summary_message += f"Module set `{module_set_name}`: "
if test_passed[i_test]:
test_summary_message += 'PASSED\n'
else:
test_summary_message += '*FAILED*\n'
print(test_summary_message)
# If loud mode is on, post report to Slack.
if be_loud:
common.slack_send_message(slack_client, test_summary_message,
is_test=is_test)
# -------------------------------------------------------------------------
if debug:
print(f"Ending {sys.argv[0]} at {datetime.datetime.now()}")
if __name__ == '__main__':
"""Call main program function."""
main()