clm5/python/ctsm/test/test_unit_gen_mksurfdata_jobscript_single.py
2024-05-09 15:14:01 +08:00

252 lines
9.0 KiB
Python

#!/usr/bin/env python3
"""
Unit tests for gen_mksurfdata_jobscript_single.py subroutines:
"""
import unittest
import os
import sys
import shutil
import tempfile
from ctsm import unit_testing
from ctsm.path_utils import path_to_ctsm_root
from ctsm.path_utils import path_to_cime
from ctsm.os_utils import run_cmd_output_on_error
from ctsm.toolchain.gen_mksurfdata_jobscript_single import get_parser
from ctsm.toolchain.gen_mksurfdata_jobscript_single import get_mpirun
from ctsm.toolchain.gen_mksurfdata_jobscript_single import check_parser_args
from ctsm.toolchain.gen_mksurfdata_jobscript_single import write_runscript_part1
def add_args(machine, nodes, tasks):
"""add arguments to sys.argv"""
args_to_add = [
"--machine",
machine,
"--number-of-nodes",
str(nodes),
"--tasks-per-node",
str(tasks),
]
for item in args_to_add:
sys.argv.append(item)
def create_empty_file(filename):
"""create an empty file"""
os.system("touch " + filename)
# Allow test names that pylint doesn't like; otherwise hard to make them
# readable
# pylint: disable=invalid-name
# pylint: disable=protected-access
# pylint: disable=too-many-instance-attributes
class TestFGenMkSurfJobscriptSingle(unittest.TestCase):
"""Tests the gen_mksurfdata_jobscript_single subroutines"""
def setUp(self):
"""Setup for trying out the methods"""
testinputs_path = os.path.join(path_to_ctsm_root(), "python/ctsm/test/testinputs")
self._testinputs_path = testinputs_path
self._previous_dir = os.getcwd()
self._tempdir = tempfile.mkdtemp()
os.chdir(self._tempdir)
self._account = "ACCOUNT_NUMBER"
self._jobscript_file = "output_jobscript"
self._output_compare = """#!/bin/bash
# Edit the batch directives for your batch system
# Below are default batch directives for derecho
#PBS -N mksurfdata
#PBS -j oe
#PBS -k eod
#PBS -S /bin/bash
#PBS -l walltime=12:00:00
#PBS -A ACCOUNT_NUMBER
#PBS -q main
#PBS -l select=1:ncpus=128:mpiprocs=64:mem=218GB
# This is a batch script to run a set of resolutions for mksurfdata_esmf input namelist
# NOTE: THIS SCRIPT IS AUTOMATICALLY GENERATED SO IN GENERAL YOU SHOULD NOT EDIT it!!
"""
self._bld_path = os.path.join(self._tempdir, "tools_bld")
os.makedirs(self._bld_path)
self.assertTrue(os.path.isdir(self._bld_path))
self._nlfile = os.path.join(self._tempdir, "namelist_file")
create_empty_file(self._nlfile)
self.assertTrue(os.path.exists(self._nlfile))
self._mksurf_exe = os.path.join(self._bld_path, "mksurfdata")
create_empty_file(self._mksurf_exe)
self.assertTrue(os.path.exists(self._mksurf_exe))
self._env_mach = os.path.join(self._bld_path, ".env_mach_specific.sh")
create_empty_file(self._env_mach)
self.assertTrue(os.path.exists(self._env_mach))
sys.argv = [
"gen_mksurfdata_jobscript_single",
"--bld-path",
self._bld_path,
"--namelist-file",
self._nlfile,
"--jobscript-file",
self._jobscript_file,
"--account",
self._account,
]
def tearDown(self):
"""
Remove temporary directory
"""
os.chdir(self._previous_dir)
shutil.rmtree(self._tempdir, ignore_errors=True)
def assertFileContentsEqual(self, expected, filepath, msg=None):
"""Asserts that the contents of the file given by 'filepath' are equal to
the string given by 'expected'. 'msg' gives an optional message to be
printed if the assertion fails.
Copied from test_unit_job_launcher_no_batch should go to utils!"""
with open(filepath, "r") as myfile:
contents = myfile.read()
self.assertEqual(expected, contents, msg=msg)
def test_simple_derecho_args(self):
"""test simple derecho arguments"""
machine = "derecho"
nodes = 1
tasks = 64
add_args(machine, nodes, tasks)
args = get_parser().parse_args()
check_parser_args(args)
with open(self._jobscript_file, "w", encoding="utf-8") as runfile:
attribs = write_runscript_part1(
nodes, tasks, machine, self._account, args.walltime, runfile
)
self.assertEqual({"mpilib": "default"}, attribs, msg="attribs not as expected")
self.assertFileContentsEqual(self._output_compare, self._jobscript_file)
def test_derecho_mpirun(self):
"""
test derecho mpirun. This would've helped caught a problem we ran into
It will also be helpful when externals are updated to guide to solutions
to problems
"""
machine = "derecho"
nodes = 4
tasks = 128
add_args(machine, nodes, tasks)
args = get_parser().parse_args()
check_parser_args(args)
self.assertEqual(machine, args.machine)
self.assertEqual(tasks, args.tasks_per_node)
self.assertEqual(nodes, args.number_of_nodes)
self.assertEqual(self._account, args.account)
# Create the env_mach_specific.xml file needed for get_mpirun
# This will catch problems with our usage of CIME objects
# Doing this here will also catch potential issues in the gen_mksurfdata_build script
configure_path = os.path.join(path_to_cime(), "CIME", "scripts", "configure")
self.assertTrue(os.path.exists(configure_path))
options = " --macros-format CMake --silent --compiler intel --machine " + machine
cmd = configure_path + options
cmd_list = cmd.split()
run_cmd_output_on_error(
cmd=cmd_list, errmsg="Trouble running configure", cwd=self._bld_path
)
self.assertTrue(os.path.exists(self._env_mach))
expected_attribs = {"mpilib": "default"}
with open(self._jobscript_file, "w", encoding="utf-8") as runfile:
attribs = write_runscript_part1(
nodes, tasks, machine, self._account, args.walltime, runfile
)
self.assertEqual(attribs, expected_attribs)
(executable, mksurfdata_path, env_mach_path) = get_mpirun(args, attribs)
expected_exe = "time mpibind "
self.assertEqual(executable, expected_exe)
self.assertEqual(mksurfdata_path, self._mksurf_exe)
self.assertEqual(env_mach_path, self._env_mach)
def test_too_many_tasks(self):
"""test trying to use too many tasks"""
machine = "derecho"
nodes = 1
tasks = 129
add_args(machine, nodes, tasks)
args = get_parser().parse_args()
check_parser_args(args)
with open(self._jobscript_file, "w", encoding="utf-8") as runfile:
with self.assertRaisesRegex(
SystemExit,
"Number of tasks per node exceeds the number of processors per node"
+ " on this machine",
):
write_runscript_part1(nodes, tasks, machine, self._account, args.walltime, runfile)
def test_zero_tasks(self):
"""test for fail on zero tasks"""
machine = "derecho"
nodes = 5
tasks = 0
add_args(machine, nodes, tasks)
args = get_parser().parse_args()
with self.assertRaisesRegex(
SystemExit,
"Input argument --tasks_per_node is zero or negative and needs to be positive",
):
check_parser_args(args)
def test_bld_build_path(self):
"""test for bad build path"""
machine = "derecho"
nodes = 10
tasks = 64
add_args(machine, nodes, tasks)
# Remove the build path directory
shutil.rmtree(self._bld_path, ignore_errors=True)
args = get_parser().parse_args()
with self.assertRaisesRegex(SystemExit, "Input Build path .+ does NOT exist, aborting"):
check_parser_args(args)
def test_mksurfdata_exist(self):
"""test fails if mksurfdata does not exist"""
machine = "derecho"
nodes = 10
tasks = 64
add_args(machine, nodes, tasks)
args = get_parser().parse_args()
os.remove(self._mksurf_exe)
with self.assertRaisesRegex(SystemExit, "mksurfdata_esmf executable "):
check_parser_args(args)
def test_env_mach_specific_exist(self):
"""test fails if the .env_mach_specific.sh file does not exist"""
machine = "derecho"
nodes = 10
tasks = 64
add_args(machine, nodes, tasks)
args = get_parser().parse_args()
os.remove(self._env_mach)
with self.assertRaisesRegex(SystemExit, "Environment machine specific file"):
check_parser_args(args)
def test_bad_machine(self):
"""test bad machine name"""
machine = "zztop"
nodes = 1
tasks = 64
add_args(machine, nodes, tasks)
with self.assertRaises(SystemExit):
get_parser().parse_args()
if __name__ == "__main__":
unit_testing.setup_for_tests()
unittest.main()