Repository: ESMCI/cime

Issue:
Should the case directory be removed when create_newcase has an error?
First let me say: cime is now much better than the scripts used to be about not creating a case directory if there was an error in create_newcase. However, some errors generated late in the create_newcase process can still leave you with a possibly-invalid case directory.

An example is errors in case.py: apply_user_mods, e.g., triggered with this artificial change:

```diff
diff --git a/scripts/lib/CIME/case.py b/scripts/lib/CIME/case.py
index b9bb56e..792f222 100644
--- a/scripts/lib/CIME/case.py
+++ b/scripts/lib/CIME/case.py
@@ -1083,6 +1083,7 @@ class Case(object):
         User mods can be specified on the create_newcase command line (usually when called from create test)
         or they can be in the compset definition, or both.
         """
+        expect(False, 'throw an error')
         all_user_mods = []
         for comp in self._component_classes:
             component = str(self.get_value("COMP_{}".format(comp)))
```

These generate an ERROR statement in stdout, but you still have a case directory. So if you're not looking carefully, you might miss that error message. Or, you might think, "it says error, but I see a case directory here, so I guess it worked okay".

Should we somehow catch these errors and remove the case directory in cases like this?

cc @mvertens @jedwards4b @jgfouca @JeremyFyke


Discussion:
I think if there is an error - a case should not be created.

On Fri, Jun 9, 2017 at 4:39 PM, Bill Sacks <notifications@github.com> wrote:

> First let me say: cime is now much better than the scripts used to be
> about not creating a case directory if there was an error in
> create_newcase. However, some errors generated late in the create_newcase
> process can still leave you with a possibly-invalid case directory.
>
> An example is errors in case.py: apply_user_mods, e.g., triggered with
> this artificial change:
>
> diff --git a/scripts/lib/CIME/case.py b/scripts/lib/CIME/case.py
> index b9bb56e..792f222 100644--- a/scripts/lib/CIME/case.py+++ b/scripts/lib/CIME/case.py@@ -1083,6 +1083,7 @@ class Case(object):
>          User mods can be specified on the create_newcase command line (usually when called from create test)
>          or they can be in the compset definition, or both.
>          """+        expect(False, 'throw an error')
>          all_user_mods = []
>          for comp in self._component_classes:
>              component = str(self.get_value("COMP_{}".format(comp)))
>
> These generate an ERROR statement in stdout, but you still have a case
> directory. So if you're not looking carefully, you might miss that error
> message. Or, you might think, "it says error, but I see a case directory
> here, so I guess it worked okay".
>
> Should we somehow catch these errors and remove the case directory in
> cases like this?
>
> cc @mvertens <https://github.com/mvertens> @jedwards4b
> <https://github.com/jedwards4b> @jgfouca <https://github.com/jgfouca>
> @JeremyFyke <https://github.com/jeremyfyke>
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/ESMCI/cime/issues/1674>, or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AHlxE3YXmcp3yo8n0_jl8WHLszX5VpEaks5sCcmhgaJpZM4N15ss>
> .
>

@jedwards4b says sometimes leaving the case is useful for debugging. Leave it if create_test, delete if create_newcase?
Leave it if `--debug` is set?
I realized @jedwards4b 's suggestion of leaving the case directory in place for create_test is critical, so that you keep around the TestStatus file that has the FAIL line in it.
@billsacks you're right. So only delete for create_newcase and not --debug.

Resolving patch:
diff --git a/scripts/lib/CIME/case.py b/scripts/lib/CIME/case.py
--- a/scripts/lib/CIME/case.py
+++ b/scripts/lib/CIME/case.py
@@ -1418,3 +1418,39 @@ def get_latest_cpl_log(self):
             return cpllog
         else:
             return None
+
+    def create(self, casename, srcroot, compset_name, grid_name, user_mods_dir=None, machine_name=None,
+               project=None, pecount=None, compiler=None, mpilib=None,
+               pesfile=None,user_grid=False, gridfile=None, ninst=1, test=False,
+               walltime=None, queue=None, output_root=None, run_unsupported=False, answer=None,
+               input_dir=None):
+        try:
+            # Set values for env_case.xml
+            self.set_lookup_value("CASE", os.path.basename(casename))
+            self.set_lookup_value("CASEROOT", self._caseroot)
+            self.set_lookup_value("SRCROOT", srcroot)
+
+            # Configure the Case
+            self.configure(compset_name, grid_name, machine_name=machine_name, project=project,
+                           pecount=pecount, compiler=compiler, mpilib=mpilib,
+                           pesfile=pesfile,user_grid=user_grid, gridfile=gridfile, ninst=ninst, test=test,
+                           walltime=walltime, queue=queue, output_root=output_root,
+                           run_unsupported=run_unsupported, answer=answer,
+                           input_dir=input_dir)
+
+            self.create_caseroot()
+
+            # Write out the case files
+            self.flush(flushall=True)
+            self.apply_user_mods(user_mods_dir)
+
+            # Lock env_case.xml
+            lock_file("env_case.xml", self._caseroot)
+        except:
+            if os.path.exists(self._caseroot) and not logger.isEnabledFor(logging.DEBUG) and not test:
+                logger.warn("Failed to setup case, removing {}\nUse --debug to force me to keep caseroot".format(self._caseroot))
+                shutil.rmtree(self._caseroot)
+            else:
+                logger.warn("Leaving broken case dir {}".format(self._caseroot))
+
+            raise


Test patch: