diff --git a/.gitignore b/.gitignore index 40f2a49386..cf080967fe 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,10 @@ Thumbs.db *.dvi *.toc +# Folders created with unit/functional tests +_build/ +_run/ + # Old Files *~ diff --git a/functional_unit_testing/allometry/AllometryUtils.py b/functional_unit_testing/allometry/allometry_utils.py similarity index 70% rename from functional_unit_testing/allometry/AllometryUtils.py rename to functional_unit_testing/allometry/allometry_utils.py index ac0a285672..08814602b6 100644 --- a/functional_unit_testing/allometry/AllometryUtils.py +++ b/functional_unit_testing/allometry/allometry_utils.py @@ -20,24 +20,24 @@ def plot_allometry_var(data, varname, units, save_fig, plot_dir=None): save_fig (bool): whether or not to write out plot plot_dir (str): if saving figure, where to write to """ - df = pd.DataFrame({'dbh': np.tile(data.dbh, len(data.pft)), + data_frame = pd.DataFrame({'dbh': np.tile(data.dbh, len(data.pft)), 'pft': np.repeat(data.pft, len(data.dbh)), data.name: data.values.flatten()}) - maxdbh = df['dbh'].max() - maxvar = round_up(df[data.name].max()) + maxdbh = data_frame['dbh'].max() + maxvar = round_up(data_frame[data.name].max()) colors = get_color_pallete() plt.figure(figsize=(7, 5)) - ax = plt.subplot(111) - ax.spines["top"].set_visible(False) - ax.spines["bottom"].set_visible(False) - ax.spines["right"].set_visible(False) - ax.spines["left"].set_visible(False) + axis = plt.subplot(111) + axis.spines["top"].set_visible(False) + axis.spines["bottom"].set_visible(False) + axis.spines["right"].set_visible(False) + axis.spines["left"].set_visible(False) - ax.get_xaxis().tick_bottom() - ax.get_yaxis().tick_left() + axis.get_xaxis().tick_bottom() + axis.get_yaxis().tick_left() plt.xlim(0.0, maxdbh) plt.ylim(0.0, maxvar) @@ -47,16 +47,16 @@ def plot_allometry_var(data, varname, units, save_fig, plot_dir=None): inc = (int(maxvar) - 0)/20 for i in range(0, 20): - y = 0.0 + i*inc + y_val = 0.0 + i*inc plt.plot(range(math.floor(0), math.ceil(maxdbh)), - [y] * len(range(math.floor(0), math.ceil(maxdbh))), + [y_val] * len(range(math.floor(0), math.ceil(maxdbh))), "--", lw=0.5, color="black", alpha=0.3) plt.tick_params(bottom=False, top=False, left=False, right=False) - pfts = np.unique(df.pft.values) + pfts = np.unique(data_frame.pft.values) for rank, pft in enumerate(pfts): - dat = df[df.pft == pft] + dat = data_frame[data_frame.pft == pft] plt.plot(dat.dbh.values, dat[data.name].values, lw=2, color=colors[rank], label=pft) @@ -66,7 +66,7 @@ def plot_allometry_var(data, varname, units, save_fig, plot_dir=None): plt.legend(loc='upper left', title='PFT') if save_fig: - fig_name = os.path.join(plot_dir, f"allometry_plot_{var}.png") + fig_name = os.path.join(plot_dir, f"allometry_plot_{data.name}.png") plt.savefig(fig_name) def plot_total_biomass(data, save_fig, plot_dir): @@ -75,7 +75,7 @@ def plot_total_biomass(data, save_fig, plot_dir): Args: data (xarray DataSet): the allometry dataset """ - df = pd.DataFrame({'dbh': np.tile(data.dbh, len(data.pft)), + data_frame = pd.DataFrame({'dbh': np.tile(data.dbh, len(data.pft)), 'pft': np.repeat(data.pft, len(data.dbh)), 'total_biomass_parts': data.total_biomass_parts.values.flatten(), 'total_biomass_tissues': data.total_biomass_tissues.values.flatten()}) @@ -83,16 +83,17 @@ def plot_total_biomass(data, save_fig, plot_dir): colors = get_color_pallete() plt.figure(figsize=(7, 5)) - ax = plt.subplot(111) - ax.spines["top"].set_visible(False) - ax.spines["bottom"].set_visible(False) - ax.spines["right"].set_visible(False) - ax.spines["left"].set_visible(False) + axis = plt.subplot(111) + axis.spines["top"].set_visible(False) + axis.spines["bottom"].set_visible(False) + axis.spines["right"].set_visible(False) + axis.spines["left"].set_visible(False) - ax.get_xaxis().tick_bottom() - ax.get_yaxis().tick_left() + axis.get_xaxis().tick_bottom() + axis.get_yaxis().tick_left() - maxbiomass = np.maximum(df['total_biomass_parts'].max(), df['total_biomass_tissues'].max()) + maxbiomass = np.maximum(data_frame['total_biomass_parts'].max(), + data_frame['total_biomass_tissues'].max()) plt.xlim(0.0, maxbiomass) plt.ylim(0.0, maxbiomass) @@ -101,9 +102,9 @@ def plot_total_biomass(data, save_fig, plot_dir): plt.xticks(fontsize=10) plt.tick_params(bottom=False, top=False, left=False, right=False) - pfts = np.unique(df.pft.values) + pfts = np.unique(data_frame.pft.values) for rank, pft in enumerate(pfts): - data = df[df.pft == pft] + data = data_frame[data_frame.pft == pft] plt.scatter(data.total_biomass_parts.values, data.total_biomass_parts.values, color=colors[rank], label=pft) @@ -117,6 +118,14 @@ def plot_total_biomass(data, save_fig, plot_dir): plt.savefig(fig_name) def plot_allometry_dat(run_dir, out_file, save_figs, plot_dir): + """Plots all allometry plots + + Args: + run_dir (str): run directory + out_file (str): output file name + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory to save the figures to + """ # read in allometry data allometry_dat = xr.open_dataset(os.path.join(run_dir, out_file)) @@ -172,8 +181,8 @@ def plot_allometry_dat(run_dir, out_file, save_figs, plot_dir): }, } - for plot in plot_dict: - plot_allometry_var(allometry_dat[plot], plot_dict[plot]['varname'], - plot_dict[plot]['units'], save_figs, plot_dir) + for plot, attributes in plot_dict.items(): + plot_allometry_var(allometry_dat[plot], attributes['varname'], + attributes['units'], save_figs, plot_dir) - plot_total_biomass(allometry_dat, save_figs, plot_dir) \ No newline at end of file + plot_total_biomass(allometry_dat, save_figs, plot_dir) diff --git a/functional_unit_testing/build_fortran_tests.py b/functional_unit_testing/build_fortran_tests.py index ef6d6577e9..722948ed09 100644 --- a/functional_unit_testing/build_fortran_tests.py +++ b/functional_unit_testing/build_fortran_tests.py @@ -1,18 +1,17 @@ +""" +Builds/compiles any tests within the FATES repository +""" import os -import sys import shutil - -_FATES_PYTHON = os.path.join(os.path.dirname(os.path.abspath(__file__))) -sys.path.insert(1, _FATES_PYTHON) - from utils import add_cime_lib_to_path + add_cime_lib_to_path() -from CIME.utils import get_src_root, run_cmd_no_fail, expect, stringify_bool -from CIME.build import CmakeTmpBuildDir -from CIME.XML.machines import Machines -from CIME.BuildTools.configure import configure, FakeCase -from CIME.XML.env_mach_specific import EnvMachSpecific +from CIME.utils import get_src_root, run_cmd_no_fail, expect, stringify_bool # pylint: disable=wrong-import-position,import-error,wrong-import-order +from CIME.build import CmakeTmpBuildDir # pylint: disable=wrong-import-position,import-error,wrong-import-order +from CIME.XML.machines import Machines # pylint: disable=wrong-import-position,import-error,wrong-import-order +from CIME.BuildTools.configure import configure, FakeCase # pylint: disable=wrong-import-position,import-error,wrong-import-order +from CIME.XML.env_mach_specific import EnvMachSpecific # pylint: disable=wrong-import-position,import-error,wrong-import-order _CIMEROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../cime") @@ -30,7 +29,8 @@ def run_cmake(name, test_dir, pfunit_path, netcdf_c_path, netcdf_f_path, cmake_a print(f"Running cmake for {name}.") # directory with cmake modules - cmake_module_dir = os.path.abspath(os.path.join(_CIMEROOT, "CIME", "non_py", "src", "CMake")) + cmake_module_dir = os.path.abspath(os.path.join(_CIMEROOT, "CIME", "non_py", + "src", "CMake")) # directory with genf90 genf90_dir = os.path.join(_CIMEROOT, "CIME", "non_py", "externals", "genf90") @@ -64,7 +64,8 @@ def find_library(caseroot, cmake_args, lib_string): Args: caseroot (str): Directory with pfunit macros - cmake_args (str): The cmake args used to invoke cmake (so that we get the correct makefile vars) + cmake_args (str): The cmake args used to invoke cmake + (so that we get the correct makefile vars) """ with CmakeTmpBuildDir(macroloc=caseroot) as cmaketmp: all_vars = cmaketmp.get_makefile_vars(cmake_args=cmake_args) @@ -92,7 +93,7 @@ def prep_build_dir(build_dir, clean): # create the build directory build_dir_path = os.path.abspath(build_dir) if not os.path.isdir(build_dir_path): - os.mkdir(build_dir_path) + os.mkdir(build_dir_path) # change into that directory os.chdir(build_dir_path) @@ -116,12 +117,12 @@ def clean_cmake_files(): # Clear contents to do with cmake cache for file in cwd_contents: - if ( - file in ("Macros.cmake", "env_mach_specific.xml") - or file.startswith("Depends") - or file.startswith(".env_mach_specific") - ): - os.remove(file) + if ( + file in ("Macros.cmake", "env_mach_specific.xml") + or file.startswith("Depends") + or file.startswith(".env_mach_specific") + ): + os.remove(file) def get_extra_cmake_args(build_dir, mpilib): """Makes a fake case to grab the required cmake arguments @@ -152,23 +153,22 @@ def get_extra_cmake_args(build_dir, mpilib): os_, unit_testing=True, ) - machspecific = EnvMachSpecific(build_dir, unit_testing=True) + EnvMachSpecific(build_dir, unit_testing=True) # make a fake case - fake_case = FakeCase(compiler, mpilib, True, "nuopc", threading=False) - - cmake_args = ( - "{}-DOS={} -DMACH={} -DCOMPILER={} -DDEBUG={} -DMPILIB={} -Dcompile_threaded={} -DCASEROOT={}".format( - "", - os_, - machobj.get_machine_name(), - compiler, - stringify_bool(True), - mpilib, - stringify_bool(False), - build_dir - ) - ) + FakeCase(compiler, mpilib, True, "nuopc", threading=False) + + cmake_args_list = [ + f"-DOS={os_}", + f"-DMACH={machobj.get_machine_name()}", + f"-DCOMPILER={compiler}", + f"-DDEBUG={stringify_bool(True)}", + f"-DMPILIB={mpilib}", + f"-Dcompile_threaded={stringify_bool(False)}", + f"-DCASEROOT={build_dir}" + ] + + cmake_args = " ".join(cmake_args_list) return cmake_args @@ -246,4 +246,3 @@ def build_unit_tests(build_dir, name, cmake_directory, make_j, clean=False): # run cmake and make run_cmake(name, cmake_directory, pfunit_path, netcdf_c_path, netcdf_f_path, cmake_args) run_make(name, make_j, clean=clean) - diff --git a/functional_unit_testing/math_utils/MathUtils.py b/functional_unit_testing/math_utils/MathUtils.py deleted file mode 100644 index 63c68ae820..0000000000 --- a/functional_unit_testing/math_utils/MathUtils.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Utility functions for allometry functional unit tests -""" -import os -import math -import xarray as xr -import pandas as pd -import numpy as np -import matplotlib -import matplotlib.pyplot as plt - -from utils import get_color_pallete - -def plot_quadratic_dat(run_dir, out_file, save_figs, plot_dir): - - # read in quadratic data - quadratic_dat = xr.open_dataset(os.path.join(run_dir, out_file)) - - # plot output - PlotQuadAndRoots(quadratic_dat.a.values, quadratic_dat.b.values, - quadratic_dat.c.values, quadratic_dat.root1.values, - quadratic_dat.root2.values) - -def PlotQuadAndRoots(a, b, c, r1, r2): - - colors = get_color_pallete() - - fig, axs = plt.subplots(ncols=1, nrows=1, figsize=(8,8)) - x = np.linspace(-10.0, 10.0, num=20) - - for i in range(0, len(a)): - y = a[i]*x**2 + b[i]*x + c[i] - plt.plot(x, y, lw=2, color=colors[i]) - plt.scatter(r1[i], r2[i], color=colors[i], s=50) - plt.axhline(y=0.0, color='k', linestyle='dotted') diff --git a/functional_unit_testing/math_utils/math_utils.py b/functional_unit_testing/math_utils/math_utils.py new file mode 100644 index 0000000000..d2ae7d743f --- /dev/null +++ b/functional_unit_testing/math_utils/math_utils.py @@ -0,0 +1,52 @@ +"""Utility functions for allometry functional unit tests +""" +import os +import math +import xarray as xr +import numpy as np +import matplotlib.pyplot as plt + +from utils import get_color_pallete + +def plot_quadratic_dat(run_dir, out_file, save_figs, plot_dir): + """Reads in and plots quadratic formula test output + + Args: + run_dir (str): run directory + out_file (str): output file + save_figs (bool): whether or not to save the figures + plot_dir (str): plot directory + """ + + # read in quadratic data + quadratic_dat = xr.open_dataset(os.path.join(run_dir, out_file)) + + # plot output + plot_quad_and_roots(quadratic_dat.a.values, quadratic_dat.b.values, + quadratic_dat.c.values, quadratic_dat.root1.values, + quadratic_dat.root2.values) + if save_figs: + fig_name = os.path.join(plot_dir, "quadratic_test.png") + plt.savefig(fig_name) + +def plot_quad_and_roots(a_coeff, b_coeff, c_coeff, root1, root2): + """Plots a set of quadratic formulas (ax**2 + bx + c) and their two roots + + Args: + a (float array): set of a coefficients + b (float array): set of b coefficients + c (float array): set of b coefficients + r1 (float array): set of first real roots + r2 (float array): set of second real roots + """ + + colors = get_color_pallete() + + plt.figure(figsize=(7, 5)) + x_vals = np.linspace(-10.0, 10.0, num=20) + + for i in range(len(a_coeff)): + y_vals = a_coeff[i]*x_vals**2 + b_coeff[i]*x_vals + c_coeff[i] + plt.plot(x_vals, y_vals, lw=2, color=colors[i]) + plt.scatter(root1[i], root2[i], color=colors[i], s=50) + plt.axhline(y=0.0, color='k', linestyle='dotted') diff --git a/functional_unit_testing/run_fates_tests.py b/functional_unit_testing/run_fates_tests.py index ed3e64b1bf..2ee2903554 100755 --- a/functional_unit_testing/run_fates_tests.py +++ b/functional_unit_testing/run_fates_tests.py @@ -10,8 +10,9 @@ - matplotlib - pandas -Though this script does not require any host land model code, it does require some CIME and shr code, -so you should still get these repositories as you normally would (i.e., manage_externals, etc.) +Though this script does not require any host land model code, it does require some CIME +and shr code, so you should still get these repositories as you normally would +(i.e., manage_externals, etc.) Additionally, this requires netcdf and netcdff as well as a fortran compiler. @@ -27,17 +28,16 @@ """ import os import argparse -import matplotlib import matplotlib.pyplot as plt from build_fortran_tests import build_unit_tests, build_exists from path_utils import add_cime_lib_to_path -from utils import round_up, copy_file, create_nc_file -from allometry.AllometryUtils import plot_allometry_dat -from math_utils.MathUtils import plot_quadratic_dat +from utils import copy_file, create_nc_file +from allometry.allometry_utils import plot_allometry_dat +from math_utils.math_utils import plot_quadratic_dat add_cime_lib_to_path() -from CIME.utils import run_cmd_no_fail +from CIME.utils import run_cmd_no_fail # pylint: disable=wrong-import-position,import-error,wrong-import-order # Constants for this script DEFAULT_CDL_PATH = os.path.abspath("../parameter_files/fates_params_default.cdl") @@ -46,6 +46,10 @@ # Dictionary with needed constants for running the executables and reading in the # output files - developers who add tests should add things here. + +# NOTE: if the functional test you write requires a parameter file read in as a +# command-line argument, this should be the *first* (or only) argument in the +# command-line argument list test_dict = { "allometry": { "test_dir": "fates_allom_test", @@ -93,6 +97,12 @@ def run_exectuables(build_dir, test_dir, test_exe, run_dir, args): print(out) def make_plotdirs(run_dir, test_list): + """Create plotting directories if they don't already exist + + Args: + run_dir (str): full path to run directory + test_list (list, str): list of test names + """ # make main plot directory plot_dir = os.path.join(run_dir, 'plots') if not os.path.isdir(plot_dir): @@ -106,6 +116,19 @@ def make_plotdirs(run_dir, test_list): os.mkdir(sub_dir) def create_param_file(param_file, run_dir): + """Creates and/or move the default or input parameter file to the run directory + Creates a netcdf file from a cdl file if a cdl file is supplied + + Args: + param_file (str): path to parmaeter file + run_dir (str): full path to run directory + + Raises: + RuntimeError: Supplied parameter file is not netcdf (.cd) or cdl (.cdl) + + Returns: + str: full path to new parameter file name/location + """ if param_file is None: print("Using default parameter file.") param_file = DEFAULT_CDL_PATH @@ -134,9 +157,7 @@ def run_tests(clean, build, run, build_dir, run_dir, make_j, param_file, save_fi make_j (int): number of processors for the build param_file (str): input FATES parameter file save_figs (bool): whether or not to write figures to file - - Raises: - RuntimeError: Parameter file is not the correct file type + test_list(str, list): list of test names to run """ # absolute path to desired build directory @@ -156,19 +177,24 @@ def run_tests(clean, build, run, build_dir, run_dir, make_j, param_file, save_fi # move parameter file to correct location (creates nc file if cdl supplied) param_file = create_param_file(param_file, run_dir) + # compile code if build: build_unit_tests(build_dir, NAME, CMAKE_BASE_DIR, make_j, clean=clean) + # run executables for each test in test list if run: for test in test_list: + # we don't run executables for pfunit tests if not test_dict[test]['unit_test']: + # prepend parameter file (if required) to argument list args = test_dict[test]['other_args'] if test_dict[test]['use_param_file']: args.insert(0, param_file) + # run run_exectuables(build_dir_path, test_dict[test]['test_dir'], test_dict[test]['test_exe'], run_dir_path, args) - # plot output + # plot output for relevant tests for test in test_list: if test_dict[test]['plotting_function'] is not None: test_dict[test]['plotting_function'](run_dir_path, @@ -177,11 +203,46 @@ def run_tests(clean, build, run, build_dir, run_dir, make_j, param_file, save_fi plt.show() def out_file_exists(run_dir, out_file): + """Checks to see if the file out_file exists in the run_dir + + Args: + run_dir (str): full path to run directory + out_file (str): output file name + + Returns: + bool: yes/no file exists in correct location + """ if not os.path.isfile(os.path.join(run_dir, out_file)): return False return True +def parse_test_list(test_string): + """Parses the input test list and checks for errors + + Args: + test (str): user-supplied comma-separated list of test names + + Returns: + str, list: list of test names to run + + Raises: + RuntimeError: Invalid test name supplied + """ + valid_test_names = test_dict.keys() + + if test_string != "all": + test_list = test_string.split(',') + for test in test_list: + if test not in valid_test_names: + raise argparse.ArgumentTypeError("Invalid test supplied, must supply one of:\n" + f"{', '.join(valid_test_names)}\n" + "or do not supply a test name to run all tests.") + else: + test_list = [test for test in valid_test_names] + + return test_list + def commandline_args(): """Parse and return command-line arguments""" @@ -193,7 +254,6 @@ def commandline_args(): ./run_fates_tests -f parameter_file.nc """ - parser = argparse.ArgumentParser( description=description, formatter_class=argparse.RawTextHelpFormatter ) @@ -201,6 +261,7 @@ def commandline_args(): parser.add_argument( "-f", "--parameter-file", + type=str, default=DEFAULT_CDL_PATH, help="Parameter file to run the FATES tests with.\n" "Can be a netcdf (.nc) or cdl (.cdl) file.\n" @@ -211,6 +272,7 @@ def commandline_args(): parser.add_argument( "-b", "--build-dir", + type=str, default="../_build", help="Directory where tests are built.\n" "Will be created if it does not exist.\n", @@ -219,6 +281,7 @@ def commandline_args(): parser.add_argument( "-r", "--run-dir", + type=str, default="../_run", help="Directory where tests are run.\n" "Will be created if it does not exist.\n", @@ -265,16 +328,68 @@ def commandline_args(): parser.add_argument( "-t", - "--test", + "--test-list", + action="store", + dest="test_list", + type=parse_test_list, + default="all", help="Test(s) to run. Comma-separated list of test names, or 'all'\n" "for all tests. If not supplied, will run all tests." ) args = parser.parse_args() - test_list = check_arg_validity(args) + check_arg_validity(args) + + return args - return args, test_list +def check_param_file(param_file): + """Checks to see if param_file exists and is of the correct form (.nc or .cdl) + + Args: + param_file (str): path to parameter file + + Raises: + IOError: Parameter file is not of the correct form (.nc or .cdl) + IOError: Can't find parameter file + """ + file_suffix = os.path.basename(param_file).split(".")[-1] + if not file_suffix in ['cdl', 'nc']: + raise argparse.ArgumentError("Must supply parameter file with .cdl or .nc ending.") + if not os.path.isfile(param_file): + raise argparse.ArgumentError(f"Cannot find file {param_file}.") + +def check_build_dir(build_dir, test_list): + """Checks to see if all required build directories and executables are present + + Args: + build_dir (str): build directory + test_list (list, str): list of test names + + Raises: + RuntimeError: Can't find a required build directory or executable + """ + for test in test_list: + if not build_exists(build_dir, test_dict[test]['test_dir'], + test_dict[test]['test_exe']): + raise argparse.ArgumentError("Build directory or executable does not exist.\n" + "Re-run script without --skip-build.") + +def check_out_files(run_dir, test_list): + """Checks to see that required output files are present in the run directory + + Args: + run_dir (str): run directory + test_list (str, list): list of test names + + Raises: + RuntimeError: Can't find a required output file + """ + for test in test_list: + if test_dict[test]['out_file'] is not None: + if not out_file_exists(os.path.abspath(run_dir), test_dict[test]['out_file']): + raise argparse.ArgumentError(f"Required file for {test} test does not exist.\n" + "Re-run script without --skip-run.") def check_arg_validity(args): """Checks validity of input script arguments @@ -282,63 +397,33 @@ def check_arg_validity(args): Args: args (parse_args): input arguments - Raises: - IOError: Can't find input parameter file, or parameter file is not correct form - RuntimeError: Invalid test name or test list - RuntimeError: Can't find required build directories or executables - RuntimeError: Can't find required output files for plotting """ # check to make sure parameter file exists and is one of the correct forms if args.parameter_file is not None: - if not os.path.isfile(args.parameter_file): - raise IOError(f"Cannot find file {args.parameter_file}.") - else: - file_suffix = os.path.basename(args.parameter_file).split(".")[-1] - if not file_suffix in ['cdl', 'nc']: - raise IOError("Must supply parameter file with .cdl or .nc ending.") - - # check test names - valid_test_names = test_dict.keys() - if args.test is not None: - test_list = args.test.split(',') - for test in test_list: - if test not in valid_test_names: - raise RuntimeError("Invalid test supplied, must supply one of:\n" - f"{', '.join(valid_test_names)}\n" - "or do not supply a test name to run all tests.") + check_param_file(args.parameter_file) else: - test_list = valid_test_names + check_param_file(DEFAULT_CDL_PATH) # make sure build directory exists if args.skip_build: - for test in test_list: - if not build_exists(args.build_dir, test_dict[test]['test_dir'], - test_dict[test]['test_exe']): - raise RuntimeError("Build directory or executable does not exist.\n" - "Re-run script without --skip-build.") + check_build_dir(args.build_dir, args.test_list) # make sure relevant output files exist: if args.skip_run: - for test in test_list: - if test_dict[test]['out_file'] is not None: - if not out_file_exists(os.path.abspath(args.run_dir), test_dict[test]['out_file']): - raise RuntimeError(f"Required file for {test} test does not exist.\n" - "Re-run script without --skip-run.") - - return test_list + check_out_files(args.run_dir, args.test_list) def main(): """Main script Reads in command-line arguments and then runs the tests. """ - args, test_list = commandline_args() + args = commandline_args() build = not args.skip_build run = not args.skip_run run_tests(args.clean, build, run, args.build_dir, args.run_dir, args.make_j, - args.parameter_file, args.save_figs, test_list) + args.parameter_file, args.save_figs, args.test_list) if __name__ == "__main__": diff --git a/functional_unit_testing/utils.py b/functional_unit_testing/utils.py index 025938c1d8..4388ea5eb9 100644 --- a/functional_unit_testing/utils.py +++ b/functional_unit_testing/utils.py @@ -7,13 +7,31 @@ add_cime_lib_to_path() -from CIME.utils import run_cmd_no_fail +from CIME.utils import run_cmd_no_fail # pylint: disable=wrong-import-position,import-error,wrong-import-order def round_up(num, decimals=0): + """Rounds a number up + + Args: + num (float): number to round + decimals (int, optional): number of decimals to round to. Defaults to 0. + + Returns: + float: input number rounded up + """ multiplier = 10**decimals return math.ceil(num * multiplier)/multiplier def truncate(num, decimals=0): + """Rounds a number down + + Args: + num (float): number to round + decimals (int, optional): Decimals to round down to. Defaults to 0. + + Returns: + float: number rounded down + """ multiplier = 10**decimals return int(num * multiplier)/multiplier @@ -37,7 +55,7 @@ def create_nc_file(cdl_path, run_dir): return file_nc_name -def copy_file(file_path, dir): +def copy_file(file_path, directory): """Copies a file file to a desired directory Args: @@ -49,7 +67,7 @@ def copy_file(file_path, dir): file_copy_command = [ "cp", os.path.abspath(file_path), - os.path.abspath(dir) + os.path.abspath(directory) ] run_cmd_no_fail(" ".join(file_copy_command), combine_output=True) @@ -68,8 +86,6 @@ def get_color_pallete(): (227, 119, 194), (247, 182, 210), (127, 127, 127), (199, 199, 199), (188, 189, 34), (219, 219, 141), (23, 190, 207), (158, 218, 229)] - for i in range(len(colors)): - r, g, b = colors[i] - colors[i] = (r/255., g/255., b/255.) + colors = [(red/255.0, green/255.0, blue/255.0) for red, green, blue in colors] - return colors \ No newline at end of file + return colors