Repository: ESMCI/cime

Issue:
Calling new_hash fails on some systems where HOSTNAME is not defined
Method `new_hash` fails if HOSTNAME does not exists.

https://github.com/ESMCI/cime/blob/d224588ef7af63138d0f479398ef9ee8f0bed20b/scripts/lib/CIME/case/case.py#L1733-L1743

Should switch to `socket.getfqdn` which is already used in CIME.


Discussion:

Resolving patch:
diff --git a/scripts/lib/CIME/case/case.py b/scripts/lib/CIME/case/case.py
--- a/scripts/lib/CIME/case/case.py
+++ b/scripts/lib/CIME/case/case.py
@@ -5,7 +5,7 @@
 through the Case module.
 """
 from copy import deepcopy
-import glob, os, shutil, math, six, time, hashlib
+import glob, os, shutil, math, six, time, hashlib, socket, getpass
 from CIME.XML.standard_module_setup import *
 #pylint: disable=import-error,redefined-builtin
 from six.moves import input
@@ -1735,8 +1735,8 @@ def new_hash(self):
         """
         args = "".join(sys.argv)
         ctime = time.strftime("%Y-%m-%d %H:%M:%S")
-        hostname = os.environ["HOSTNAME"]
-        user = os.environ["USER"]
+        hostname = socket.getfqdn()
+        user = getpass.getuser()
 
         data = "{}{}{}{}".format(args, ctime, hostname, user)
 


Test patch:
diff --git a/scripts/lib/CIME/tests/test_case.py b/scripts/lib/CIME/tests/test_case.py
--- a/scripts/lib/CIME/tests/test_case.py
+++ b/scripts/lib/CIME/tests/test_case.py
@@ -1,35 +1,29 @@
 import os
-import sys
 import unittest
+import tempfile
+from unittest import mock
 
 from CIME.case import Case
 from CIME import utils as cime_utils
 
-from . import utils
-
 class TestCase(unittest.TestCase):
 
     def setUp(self):
         self.srcroot = os.path.abspath(cime_utils.get_cime_root())
-        self.tempdir = utils.TemporaryDirectory()
-
-        self.mock = utils.Mocker()
-        self.mock.patch(Case, "read_xml")
-        Case._force_read_only = False # pylint: disable=protected-access
-        self._time_strftime = self.mock.patch('time.strftime', ret='00:00:00')
-        self.mock.patch("sys.argv",
-                        ret=[
-                            "/src/create_newcase",
-                            "--machine",
-                            "docker"
-                        ], is_property=True)
-
-    def test_new_hash(self):
+        self.tempdir = tempfile.TemporaryDirectory()
+
+    @mock.patch("CIME.case.case.Case.read_xml")
+    @mock.patch("sys.argv", [
+        "/src/create_newcase",
+        "--machine",
+        "docker"
+    ])
+    @mock.patch("time.strftime", return_value="00:00:00")
+    @mock.patch("socket.getfqdn", return_value="host1")
+    @mock.patch("getpass.getuser", side_effect=["root", "root", "johndoe"])
+    def test_new_hash(self, getuser, getfqdn, strftime, read_xml): # pylint: disable=unused-argument
         with self.tempdir as tempdir:
             with Case(tempdir) as case:
-                os.environ["USER"] = "root"
-                os.environ["HOSTNAME"] = "host1"
-
                 expected = "134a939f62115fb44bf08a46bfb2bd13426833b5c8848cf7c4884af7af05b91a"
 
                 # Check idempotency
@@ -39,8 +33,6 @@ def test_new_hash(self):
                     self.assertTrue(value == expected,
                                     "{} != {}".format(value, expected))
 
-                os.environ["USER"] = "johndoe"
-
                 expected = "bb59f1c473ac07e9dd30bfab153c0530a777f89280b716cf42e6fe2f49811a6e"
 
                 value = case.new_hash()
@@ -48,105 +40,120 @@ def test_new_hash(self):
                 self.assertTrue(value == expected,
                                 "{} != {}".format(value, expected))
 
-    def test_copy(self):
+    @mock.patch("CIME.case.case.Case.read_xml")
+    @mock.patch("sys.argv", [
+        "/src/create_newcase",
+        "--machine",
+        "docker"
+    ])
+    @mock.patch("time.strftime", return_value="00:00:00")
+    @mock.patch("CIME.case.case.lock_file")
+    @mock.patch("CIME.case.case.Case.set_lookup_value")
+    @mock.patch("CIME.case.case.Case.apply_user_mods")
+    @mock.patch("CIME.case.case.Case.create_caseroot")
+    @mock.patch("CIME.case.case.Case.configure")
+    @mock.patch("socket.getfqdn", return_value="host1")
+    @mock.patch("getpass.getuser", return_value="root")
+    def test_copy(self, getuser, getfqdn, configure, create_caseroot, # pylint: disable=unused-argument
+                  apply_user_mods, set_lookup_value, lock_file, strftime, # pylint: disable=unused-argument
+                  read_xml): # pylint: disable=unused-argument
         expected_first_hash = "134a939f62115fb44bf08a46bfb2bd13426833b5c8848cf7c4884af7af05b91a"
         expected_second_hash = "3561339a49daab999e3c4ea2f03a9c6acc33296a5bc35f1bfb82e7b5e10bdf38"
 
         with self.tempdir as tempdir:
             caseroot = os.path.join(tempdir, "test1")
             with Case(caseroot, read_only=False) as case:
-                os.environ["USER"] = "root"
-                os.environ["HOSTNAME"] = "host1"
                 os.environ["CIME_MODEL"] = "cesm"
 
-                # Need to mock all of these to prevent errors with xml files
-                # just want to ensure `create` calls `set_lookup_value`
-                # correctly.
-                _configure = self.mock.patch(case, "configure")
-                _create_caseroot = self.mock.patch(case, "create_caseroot")
-                _apply_user_mods = self.mock.patch(case, "apply_user_mods")
-                _lock_file = self.mock.patch("CIME.case.case.lock_file")
-                _set_lookup_value = self.mock.patch(case, "set_lookup_value")
-
                 srcroot = os.path.abspath(os.path.join(
                     os.path.dirname(__file__), "../../../../../"))
                 case.create("test1", srcroot, "A", "f19_g16_rx1",
                             machine_name="ubuntu-latest")
 
                 # Check that they're all called
-                _configure.assert_called_with(args=["A", "f19_g16_rx1"])
-                _create_caseroot.assert_called()
-                _apply_user_mods.assert_called()
-                _lock_file.assert_called()
-
-                self.assertTrue(_set_lookup_value.calls[-1]['args'][0] ==
-                                "CASE_HASH")
-                self.assertTrue(_set_lookup_value.calls[-1]['args'][1] ==
-                                expected_first_hash)
-
-                _set_value = self.mock.patch(case, "set_value",
-                                             ret=utils.Mocker())
-
-                # simulate change
-                self._time_strftime.ret = "10:00:00"
-                self.mock.patch("sys.argv",
-                                ret=[
-                                    "/src/create_clone"
-                                ], is_property=True, update_value_only=True)
-
-                case.copy("test2", "{}_2".format(tempdir))
-
-                self.assertTrue(_set_value.calls[-1]['args'][0] ==
-                                "CASE_HASH")
-                self.assertTrue(_set_value.calls[-1]['args'][1] ==
-                                expected_second_hash)
-
-
-    def test_create(self):
+                configure.assert_called_with("A", "f19_g16_rx1",
+                                             machine_name="ubuntu-latest",
+                                             project=None, pecount=None,
+                                             compiler=None, mpilib=None,
+                                             pesfile=None, gridfile=None,
+                                             multi_driver=False, ninst=1,
+                                             test=False, walltime=None,
+                                             queue=None, output_root=None,
+                                             run_unsupported=False, answer=None,
+                                             input_dir=None, driver=None,
+                                             workflowid="default",
+                                             non_local=False,
+                                             extra_machines_dir=None,
+                                             case_group=None)
+                create_caseroot.assert_called()
+                apply_user_mods.assert_called()
+                lock_file.assert_called()
+
+                set_lookup_value.assert_called_with("CASE_HASH",
+                                                    expected_first_hash)
+
+                strftime.return_value = "10:00:00"
+                with mock.patch("CIME.case.case.Case.set_value") as set_value, \
+                        mock.patch("sys.argv", ["/src/create_clone"]):
+                    case.copy("test2", "{}_2".format(tempdir))
+
+                    set_value.assert_called_with("CASE_HASH",
+                                                expected_second_hash)
+
+    @mock.patch("CIME.case.case.Case.read_xml")
+    @mock.patch("sys.argv", [
+        "/src/create_newcase",
+        "--machine",
+        "docker"
+    ])
+    @mock.patch("time.strftime", return_value="00:00:00")
+    @mock.patch("CIME.case.case.lock_file")
+    @mock.patch("CIME.case.case.Case.set_lookup_value")
+    @mock.patch("CIME.case.case.Case.apply_user_mods")
+    @mock.patch("CIME.case.case.Case.create_caseroot")
+    @mock.patch("CIME.case.case.Case.configure")
+    @mock.patch("socket.getfqdn", return_value="host1")
+    @mock.patch("getpass.getuser", return_value="root")
+    def test_create(self, get_user, getfqdn, configure, create_caseroot, # pylint: disable=unused-argument
+                    apply_user_mods, set_lookup_value, lock_file, strftime, # pylint: disable=unused-argument
+                    read_xml): # pylint: disable=unused-argument
         with self.tempdir as tempdir:
             caseroot = os.path.join(tempdir, "test1")
             with Case(caseroot, read_only=False) as case:
-                os.environ["USER"] = "root"
-                os.environ["HOSTNAME"] = "host1"
                 os.environ["CIME_MODEL"] = "cesm"
 
-                # Need to mock all of these to prevent errors with xml files
-                # just want to ensure `create` calls `set_lookup_value`
-                # correctly.
-                _configure = self.mock.patch(case, 'configure')
-                _create_caseroot = self.mock.patch(case, 'create_caseroot')
-                _apply_user_mods = self.mock.patch(case, 'apply_user_mods')
-                _lock_file = self.mock.patch('CIME.case.case.lock_file')
-                _set_lookup_value = self.mock.patch(case, "set_lookup_value")
-
                 srcroot = os.path.abspath(os.path.join(
                     os.path.dirname(__file__), "../../../../../"))
                 case.create("test1", srcroot, "A", "f19_g16_rx1",
                             machine_name="ubuntu-latest")
 
                 # Check that they're all called
-                _configure.assert_called_with(args=["A", "f19_g16_rx1"])
-                _create_caseroot.assert_called()
-                _apply_user_mods.assert_called()
-                _lock_file.assert_called()
-
-                self.assertTrue(_set_lookup_value.calls[-1]['args'][0] ==
-                                "CASE_HASH")
-                self.assertTrue(_set_lookup_value.calls[-1]['args'][1] ==
-                                "134a939f62115fb44bf08a46bfb2bd13426833b5c8848cf7c4884af7af05b91a")
+                configure.assert_called_with("A", "f19_g16_rx1",
+                                             machine_name="ubuntu-latest",
+                                             project=None, pecount=None,
+                                             compiler=None, mpilib=None,
+                                             pesfile=None, gridfile=None,
+                                             multi_driver=False, ninst=1,
+                                             test=False, walltime=None,
+                                             queue=None, output_root=None,
+                                             run_unsupported=False, answer=None,
+                                             input_dir=None, driver=None,
+                                             workflowid="default",
+                                             non_local=False,
+                                             extra_machines_dir=None,
+                                             case_group=None)
+                create_caseroot.assert_called()
+                apply_user_mods.assert_called()
+                lock_file.assert_called()
+
+                set_lookup_value.assert_called_with("CASE_HASH",
+                                                    "134a939f62115fb44bf08a46bfb2bd13426833b5c8848cf7c4884af7af05b91a")
 
 class TestCase_RecordCmd(unittest.TestCase):
 
     def setUp(self):
         self.srcroot = os.path.abspath(cime_utils.get_cime_root())
-        self.tempdir = utils.TemporaryDirectory()
-
-        self.mock = utils.Mocker()
-        self.mock.patch(Case, "__init__", ret=None)
-        self.mock.patch(Case, "flush", ret=utils.Mocker())
-        # Case.__init__ = utils.Mocker()
-        # Case.flush = utils.Mocker()
-        Case._force_read_only = False # pylint: disable=protected-access
+        self.tempdir = tempfile.TemporaryDirectory()
 
     def assert_calls_match(self, calls, expected):
         self.assertTrue(len(calls) == len(expected), calls)
@@ -154,54 +161,58 @@ def assert_calls_match(self, calls, expected):
         for x, y in zip(calls, expected):
             self.assertTrue(x == y, calls)
 
-    def test_init(self):
-        with self.tempdir as tempdir:
-            mock = utils.Mocker()
-            open_mock = mock.patch(
-                "builtins.open" if sys.version_info.major > 2 else
-                    "__builtin__.open",
-                ret=utils.Mocker())
-            mock.patch("time.strftime", ret="00:00:00")
-            mock.patch("sys.argv", ret=["/src/create_newcase"], is_property=True)
+    @mock.patch("CIME.case.case.Case.__init__", return_value=None)
+    @mock.patch("CIME.case.case.Case.flush")
+    @mock.patch("CIME.case.case.Case.get_value")
+    @mock.patch("CIME.case.case.open", mock.mock_open())
+    @mock.patch("time.strftime", return_value="00:00:00")
+    @mock.patch("sys.argv", ["/src/create_newcase"])
+    def test_init(self, strftime, get_value, flush, init): # pylint: disable=unused-argument
+        Case._force_read_only = False # pylint: disable=protected-access
 
+        mocked_open = mock.mock_open()
+
+        with self.tempdir as tempdir, mock.patch("CIME.case.case.open",
+                                                 mocked_open):
             with Case(tempdir) as case:
-                case.get_value = utils.Mocker(
-                    side_effect=[tempdir, "/src"]
-                )
+                get_value.side_effect = [
+                    tempdir,
+                    "/src"
+                ]
 
                 case.record_cmd(init=True)
 
-        self.assertTrue(open_mock.calls[0]["args"] ==
-                        ("{}/replay.sh".format(tempdir), "a"))
+        mocked_open.assert_called_with(f"{tempdir}/replay.sh", "a")
 
-        expected = [
+        handle = mocked_open()
+
+        handle.writelines.assert_called_with([
             "#!/bin/bash\n\n",
             "set -e\n\n",
             "# Created 00:00:00\n\n",
             "CASEDIR=\"{}\"\n\n".format(tempdir),
             "/src/create_newcase\n\n",
             "cd \"${CASEDIR}\"\n\n",
-        ]
-
-        calls = open_mock.ret.method_calls["writelines"][0]["args"][0] # pylint: disable=no-member
-
-        self.assert_calls_match(calls, expected)
+        ])
+
+    @mock.patch("CIME.case.case.Case.__init__", return_value=None)
+    @mock.patch("CIME.case.case.Case.flush")
+    @mock.patch("CIME.case.case.Case.get_value")
+    @mock.patch("CIME.case.case.open", mock.mock_open())
+    @mock.patch("time.strftime", return_value="00:00:00")
+    @mock.patch("sys.argv", ["/src/scripts/create_newcase"])
+    def test_sub_relative(self, strftime, get_value, flush, init): # pylint: disable=unused-argument
+        Case._force_read_only = False # pylint: disable=protected-access
 
-    def test_sub_relative(self):
-        with self.tempdir as tempdir:
-            mock = utils.Mocker()
-            open_mock = mock.patch(
-                "builtins.open" if sys.version_info.major > 2 else
-                    "__builtin__.open",
-                ret=utils.Mocker()
-            )
-            mock.patch("time.strftime", ret="00:00:00")
-            mock.patch("sys.argv", ret=["./create_newcase"], is_property=True)
+        mocked_open = mock.mock_open()
 
+        with self.tempdir as tempdir, mock.patch("CIME.case.case.open",
+                                                 mocked_open):
             with Case(tempdir) as case:
-                case.get_value = utils.Mocker(
-                    side_effect=[tempdir, "/src"]
-                )
+                get_value.side_effect = [
+                    tempdir,
+                    "/src"
+                ]
 
                 case.record_cmd(init=True)
 
@@ -214,23 +225,24 @@ def test_sub_relative(self):
             "cd \"${CASEDIR}\"\n\n",
         ]
 
-        calls = open_mock.ret.method_calls["writelines"][0]["args"][0] # pylint: disable=no-member
+        handle = mocked_open()
+        handle.writelines.assert_called_with(expected)
 
-        self.assert_calls_match(calls, expected)
+    @mock.patch("CIME.case.case.Case.__init__", return_value=None)
+    @mock.patch("CIME.case.case.Case.flush")
+    @mock.patch("CIME.case.case.Case.get_value")
+    def test_cmd_arg(self, get_value, flush, init): # pylint: disable=unused-argument
+        Case._force_read_only = False # pylint: disable=protected-access
 
-    def test_cmd_arg(self):
-        with self.tempdir as tempdir:
-            mock = utils.Mocker()
-            open_mock = mock.patch(
-                "builtins.open" if sys.version_info.major > 2 else
-                    "__builtin__.open",
-                ret=utils.Mocker()
-            )
+        mocked_open = mock.mock_open()
 
+        with self.tempdir as tempdir, mock.patch("CIME.case.case.open",
+                                                 mocked_open):
             with Case(tempdir) as case:
-                case.get_value = utils.Mocker(
-                    side_effect=[tempdir, "/src"]
-                )
+                get_value.side_effect = [
+                    tempdir,
+                    "/src",
+                ]
 
                 case.record_cmd(["/some/custom/command", "arg1"])
 
@@ -238,9 +250,8 @@ def test_cmd_arg(self):
             "/some/custom/command arg1\n\n",
         ]
 
-        calls = open_mock.ret.method_calls["writelines"][0]["args"][0] # pylint: disable=no-member
-
-        self.assert_calls_match(calls, expected)
+        handle = mocked_open()
+        handle.writelines.assert_called_with(expected)
 
 if __name__ == '__main__':
     unittest.main()