Repository: theacodes/nox
Pull request #463 (2021-07-05 20:31:50)

Add friendlier message if no noxfile.py
#462 

Add a friendlier message on the specific case that user is calling
nox from within a directory with no noxfile.

Existing test for this case modified and two additional tests
added to ensure lower level errors are still handled further down.


Patch:
diff --git a/nox/tasks.py b/nox/tasks.py
index afb50a8..ad11b5c 100644
--- a/nox/tasks.py
+++ b/nox/tasks.py
@@ -52,6 +52,7 @@ def load_nox_module(global_config: Namespace) -> Union[types.ModuleType, int]:
             # Be sure to expand variables
             os.path.expandvars(global_config.noxfile)
         )
+        noxfile_parent_dir = os.path.realpath(os.path.dirname(global_config.noxfile))
 
         # Check ``nox.needs_version`` by parsing the AST.
         check_nox_version(global_config.noxfile)
@@ -60,7 +61,7 @@ def load_nox_module(global_config: Namespace) -> Union[types.ModuleType, int]:
         # This will ensure that the Noxfile's path is on sys.path, and that
         # import-time path resolutions work the way the Noxfile author would
         # guess.
-        os.chdir(os.path.realpath(os.path.dirname(global_config.noxfile)))
+        os.chdir(noxfile_parent_dir)
         return importlib.machinery.SourceFileLoader(
             "user_nox_module", global_config.noxfile
         ).load_module()
@@ -68,6 +69,11 @@ def load_nox_module(global_config: Namespace) -> Union[types.ModuleType, int]:
     except (VersionCheckFailed, InvalidVersionSpecifier) as error:
         logger.error(str(error))
         return 2
+    except FileNotFoundError:
+        logger.error(
+            f"Failed to load Noxfile {global_config.noxfile}, no such file exists."
+        )
+        return 2
     except (IOError, OSError):
         logger.exception("Failed to load Noxfile {}".format(global_config.noxfile))
         return 2


Test patch:
diff --git a/tests/test_tasks.py b/tests/test_tasks.py
index 4031724..c8edb8a 100644
--- a/tests/test_tasks.py
+++ b/tests/test_tasks.py
@@ -18,6 +18,7 @@ import io
 import json
 import os
 import platform
+from pathlib import Path
 from textwrap import dedent
 from unittest import mock
 
@@ -76,9 +77,48 @@ def test_load_nox_module_expandvars():
     assert noxfile_module.SIGIL == "123"
 
 
-def test_load_nox_module_not_found():
-    config = _options.options.namespace(noxfile="bogus.py")
+def test_load_nox_module_not_found(caplog, tmp_path):
+    bogus_noxfile = tmp_path / "bogus.py"
+    config = _options.options.namespace(noxfile=str(bogus_noxfile))
+
     assert tasks.load_nox_module(config) == 2
+    assert (
+        f"Failed to load Noxfile {bogus_noxfile}, no such file exists." in caplog.text
+    )
+
+
+def test_load_nox_module_IOError(caplog):
+
+    # Need to give it a noxfile that exists so load_nox_module can progress
+    # past FileNotFoundError
+    # use our own noxfile.py for this
+    our_noxfile = Path(__file__).parent.parent.joinpath("noxfile.py")
+    config = _options.options.namespace(noxfile=str(our_noxfile))
+
+    with mock.patch(
+        "nox.tasks.importlib.machinery.SourceFileLoader.load_module"
+    ) as mock_load:
+        mock_load.side_effect = IOError
+
+        assert tasks.load_nox_module(config) == 2
+        assert "Failed to load Noxfile" in caplog.text
+
+
+def test_load_nox_module_OSError(caplog):
+
+    # Need to give it a noxfile that exists so load_nox_module can progress
+    # past FileNotFoundError
+    # use our own noxfile.py for this
+    our_noxfile = Path(__file__).parent.parent.joinpath("noxfile.py")
+    config = _options.options.namespace(noxfile=str(our_noxfile))
+
+    with mock.patch(
+        "nox.tasks.importlib.machinery.SourceFileLoader.load_module"
+    ) as mock_load:
+        mock_load.side_effect = OSError
+
+        assert tasks.load_nox_module(config) == 2
+        assert "Failed to load Noxfile" in caplog.text
 
 
 @pytest.fixture