diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 9badbdf673..e56311635e 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -70,5 +70,8 @@ cdf40d265cc82775607a1bf25f5f527bacc97405
4ad46f46de7dde753b4653c15f05326f55116b73
75db098206b064b8b7b2a0604d3f0bf8fdb950cc
84609494b54ea9732f64add43b2f1dd035632b4c
+7eb17f3ef0b9829fb55e0e3d7f02e157b0e41cfb
+62d7711506a0fb9a3ad138ceceffbac1b79a6caa
+49ad0f7ebe0b07459abc00a5c33c55a646f1e7e0
ac03492012837799b7111607188acff9f739044a
d858665d799690d73b56bcb961684382551193f4
diff --git a/cime_config/SystemTests/setparamfile.py b/cime_config/SystemTests/setparamfile.py
new file mode 100644
index 0000000000..f106a9505a
--- /dev/null
+++ b/cime_config/SystemTests/setparamfile.py
@@ -0,0 +1,95 @@
+"""
+CTSM-specific test that first runs the set_paramfile tool and then ensures that CTSM does not fail
+using the just-generated parameter file
+"""
+
+import os
+import sys
+import logging
+import re
+from CIME.SystemTests.system_tests_common import SystemTestsCommon
+
+# In case we need to import set_paramfile later
+_CTSM_PYTHON = os.path.join(
+ os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir, "python"
+)
+sys.path.insert(1, _CTSM_PYTHON)
+
+logger = logging.getLogger(__name__)
+
+
+class SETPARAMFILE(SystemTestsCommon):
+ def __init__(self, case):
+ """
+ initialize an object interface to the SMS system test
+ """
+ SystemTestsCommon.__init__(self, case)
+
+ # Create out-of-the-box lnd_in to obtain paramfile
+ case.create_namelists(component="lnd")
+
+ # Find the paramfile to modify
+ lnd_in_path = os.path.join(self._get_caseroot(), "CaseDocs", "lnd_in")
+ self._paramfile_in = None
+ with open(lnd_in_path, "r", encoding="utf-8") as lnd_in:
+ for line in lnd_in:
+ paramfile_in = re.match(r" *paramfile *= *'(.*)'", line)
+ if paramfile_in:
+ self._paramfile_in = paramfile_in.group(1)
+ break
+ if not self._paramfile_in:
+ raise RuntimeError(f"paramfile not found in {lnd_in_path}")
+
+ # Get the output file
+ self.paramfile_out = os.path.join(self._get_caseroot(), "paramfile.nc")
+
+ # Define set_paramfile command
+ self.set_paramfile_cmd = [
+ "set_paramfile",
+ "-i",
+ self._paramfile_in,
+ "-o",
+ self.paramfile_out,
+ # Change two parameters for one PFT
+ "-p",
+ "needleleaf_deciduous_boreal_tree",
+ "rswf_min=0.35",
+ "rswf_max=0.7",
+ ]
+
+ def build_phase(self, sharedlib_only=False, model_only=False):
+ """
+ Run set_paramfile and then build the model
+ """
+
+ # Run set_paramfile.
+ # build_phase gets called twice:
+ # - once with sharedlib_only = True and
+ # - once with model_only = True
+ # Because we only need set_paramfile run once, we only do it for the sharedlib_only call.
+ # We could also check for the existence of the set_paramfile outputs, but that might lead to
+ # a situation where the user expects set_paramfile to be called but it's not. Better to run
+ # unnecessarily (e.g., if you fixed some FORTRAN code and just need to rebuild).
+ if sharedlib_only:
+ self._run_set_paramfile()
+
+ # Do the build
+ self.build_indv(sharedlib_only=sharedlib_only, model_only=model_only)
+
+ def _run_set_paramfile(self):
+ """
+ Run set_paramfile
+ """
+ # Import set_paramfile. Do it here rather than at top because otherwise the import will
+ # be attempted even during RUN phase.
+ # pylint: disable=wrong-import-position,import-outside-toplevel
+ from ctsm.param_utils.set_paramfile import main as set_paramfile
+
+ # Run set_paramfile
+ sys.argv = self.set_paramfile_cmd
+ set_paramfile()
+
+ # Append
+ user_nl_clm_path = os.path.join(self._get_caseroot(), "user_nl_clm")
+ with open(user_nl_clm_path, "a", encoding="utf-8") as user_nl_clm:
+ user_nl_clm.write(f"paramfile = '{self.paramfile_out}'\n")
diff --git a/cime_config/config_tests.xml b/cime_config/config_tests.xml
index 12859b9131..6df0f357e4 100644
--- a/cime_config/config_tests.xml
+++ b/cime_config/config_tests.xml
@@ -155,6 +155,16 @@ This defines various CTSM-specific system tests
$STOP_N
+
+ Modify a copy of the paramfile and run with it.
+ 1
+ FALSE
+ FALSE
+ never
+ $STOP_OPTION
+ $STOP_N
+
+