Repository: ESMCI/cime

Issue:
case.st_archive getting wrong wall clock time
Reported by Gustavo and confirmed in cesm2_3_alpha10d if you create a case on cheyenne It reports the default wall clock times as:
```
 ./xmlquery JOB_WALLCLOCK_TIME

Results in group case.run
	JOB_WALLCLOCK_TIME: 12:00:00

Results in group case.st_archive
	JOB_WALLCLOCK_TIME: 00:20:00
```
But when you submit (or run preview_run) you get the full 12 hours for st_archive, which then gives an error:
```
 SUBMIT CMD:
      qsub -q share  -l walltime=12:00:00  -A P93300606  -W depend=afterok:0 -v ARGS_FOR_SCRIPT='--resubmit' case.st_archive
```
The error occurs because 12 hrs is beyond the queue limit for the share queue.



Discussion:

Resolving patch:
diff --git a/CIME/XML/env_batch.py b/CIME/XML/env_batch.py
--- a/CIME/XML/env_batch.py
+++ b/CIME/XML/env_batch.py
@@ -654,14 +654,24 @@ def _get_argument(self, case, arg):
 
     def _resolve_argument(self, case, flag, name, job):
         submitargs = ""
-
-        if name.startswith("$"):
-            name = name[1:]
+        logger.debug("name is {}".format(name))
+        # if name.startswith("$"):
+        #    name = name[1:]
 
         if "$" in name:
-            # We have a complex expression and must rely on get_resolved_value.
-            # Hopefully, none of the values require subgroup
-            val = case.get_resolved_value(name)
+            parts = name.split("$")
+            logger.debug("parts are {}".format(parts))
+            val = ""
+            for part in parts:
+                if part != "":
+                    logger.debug("part is {}".format(part))
+                    resolved = case.get_value(part, subgroup=job)
+                    if resolved:
+                        val += resolved
+                    else:
+                        val += part
+            logger.debug("val is {}".format(name))
+            val = case.get_resolved_value(val)
         else:
             val = case.get_value(name, subgroup=job)
 
diff --git a/CIME/case/case.py b/CIME/case/case.py
--- a/CIME/case/case.py
+++ b/CIME/case/case.py
@@ -2459,6 +2459,7 @@ def preview_run(self, write, job):
             job = self.get_first_job()
 
         job_id_to_cmd = self.submit_jobs(dry_run=True, job=job)
+
         env_batch = self.get_env("batch")
         for job_id, cmd in job_id_to_cmd:
             write("  FOR JOB: {}".format(job_id))
diff --git a/CIME/case/case_submit.py b/CIME/case/case_submit.py
--- a/CIME/case/case_submit.py
+++ b/CIME/case/case_submit.py
@@ -8,7 +8,7 @@
 """
 import configparser
 from CIME.XML.standard_module_setup import *
-from CIME.utils import expect, run_and_log_case_status, CIMEError
+from CIME.utils import expect, run_and_log_case_status, CIMEError, get_time_in_seconds
 from CIME.locked_files import unlock_file, lock_file
 from CIME.test_status import *
 
@@ -51,12 +51,7 @@ def _submit(
 
     # Check if CONTINUE_RUN value makes sense
     # if submitted with a prereq don't do this check
-    if (
-        job != "case.test"
-        and case.get_value("CONTINUE_RUN")
-        and hasMediator
-        and not prereq
-    ):
+    if case.get_value("CONTINUE_RUN") and hasMediator and not prereq:
         rundir = case.get_value("RUNDIR")
         expect(
             os.path.isdir(rundir),
@@ -67,7 +62,8 @@ def _submit(
             rpointer = "rpointer.cpl"
         else:
             rpointer = "rpointer.drv"
-        if case.get_value("MULTI_DRIVER"):
+        # Variable MULTI_DRIVER is always true for nuopc so we need to also check NINST > 1
+        if case.get_value("MULTI_DRIVER") and case.get_value("NINST") > 1:
             rpointer = rpointer + "_0001"
         expect(
             os.path.exists(os.path.join(rundir, rpointer)),
@@ -297,6 +293,54 @@ def check_case(self, skip_pnl=False, chksum=False):
         # the ww3 buildnml has dependencies on inputdata so we must run it again
         self.create_namelists(component="WAV")
 
+    if self.get_value("COMP_INTERFACE") == "nuopc":
+        #
+        # Check that run length is a multiple of the longest component
+        # coupling interval. The longest interval is smallest NCPL value.
+        # models using the nuopc interface will fail at initialization unless
+        # ncpl follows these rules, other models will only fail later and so
+        # this test is skipped so that short tests can be run without adjusting NCPL
+        #
+        maxncpl = 10000
+        minncpl = 0
+        maxcomp = None
+        for comp in self.get_values("COMP_CLASSES"):
+            if comp == "CPL":
+                continue
+            ncpl = self.get_value("{}_NCPL".format(comp))
+            if ncpl and maxncpl > ncpl:
+                maxncpl = ncpl
+                maxcomp = comp
+            if ncpl and minncpl < ncpl:
+                minncpl = ncpl
+
+        ncpl_base_period = self.get_value("NCPL_BASE_PERIOD")
+        if ncpl_base_period == "hour":
+            coupling_secs = 3600 / maxncpl
+            timestep = 3600 / minncpl
+        elif ncpl_base_period == "day":
+            coupling_secs = 86400 / maxncpl
+            timestep = 86400 / minncpl
+        elif ncpl_base_period == "year":
+            coupling_secs = 31536000 / maxncpl
+            timestep = 31536000 / minncpl
+        elif ncpl_base_period == "decade":
+            coupling_secs = 315360000 / maxncpl
+            timestep = 315360000 / minncpl
+        stop_option = self.get_value("STOP_OPTION")
+        stop_n = self.get_value("STOP_N")
+        if stop_option == "nsteps":
+            stop_option = "seconds"
+            stop_n = stop_n * timestep
+
+        runtime = get_time_in_seconds(stop_n, stop_option)
+        expect(
+            runtime >= coupling_secs and runtime % coupling_secs == 0,
+            " Runtime ({0} s) must be a multiple of the longest coupling interval {1}_NCPL ({2}s).  Adjust runtime or {1}_NCPL".format(
+                runtime, maxcomp, coupling_secs
+            ),
+        )
+
     expect(
         self.get_value("BUILD_COMPLETE"),
         "Build complete is " "not True please rebuild the model by calling case.build",


Test patch:
diff --git a/CIME/tests/test_sys_create_newcase.py b/CIME/tests/test_sys_create_newcase.py
--- a/CIME/tests/test_sys_create_newcase.py
+++ b/CIME/tests/test_sys_create_newcase.py
@@ -742,12 +742,18 @@ def test_m_createnewcase_alternate_drivers(self):
             alternative_driver = ("moab",)
 
         for driver in alternative_driver:
-            if not os.path.exists(
+            if driver == "moab" and not os.path.exists(
                 os.path.join(utils.get_cime_root(), "src", "drivers", driver)
             ):
                 self.skipTest(
                     "Skipping driver test for {}, driver not found".format(driver)
                 )
+            if driver == "nuopc" and not os.path.exists(
+                os.path.join(utils.get_src_root(), "components", "cmeps")
+            ):
+                self.skipTest(
+                    "Skipping driver test for {}, driver not found".format(driver)
+                )
 
             testdir = os.path.join(cls._testroot, "testcreatenewcase.{}".format(driver))
             if os.path.exists(testdir):
diff --git a/CIME/tests/test_unit_xml_env_batch.py b/CIME/tests/test_unit_xml_env_batch.py
--- a/CIME/tests/test_unit_xml_env_batch.py
+++ b/CIME/tests/test_unit_xml_env_batch.py
@@ -58,12 +58,13 @@ def test_get_submit_args_job_queue(self):
 
             case.get_value.side_effect = ("long", "long", None)
 
+            case.get_resolved_value.return_value = None
+
             case.filename = mock.PropertyMock(return_value=tfile.name)
 
             submit_args = batch.get_submit_args(case, ".case.run")
 
             expected_args = "  -w default -w long"
-
             assert submit_args == expected_args
 
     @mock.patch.dict(os.environ, {"TEST": "GOOD"})
@@ -141,10 +142,14 @@ def test_get_submit_args(self):
                 "00:30:00",
                 "long",
                 "CIME",
+                "/test",
             ]
 
+            def my_get_resolved_value(val):
+                return val
+
             # value for --path
-            case.get_resolved_value.return_value = "/test"
+            case.get_resolved_value.side_effect = my_get_resolved_value
 
             case.filename = mock.PropertyMock(return_value=tfile.name)