Repository: theacodes/nox (python)

Issue:
Regression: Garbled error message when session not found
<!-- Thank you for filing a bug! Please feel free to answer as much or as little of this template as you can. -->

**Describe the bug**
Nox prints a garbled error message when a session is not found.

More precisely, instead of the offending session name, Nox prints the representation of an AST tree.

This is a regression on the main branch, no release is affected.

**How to reproduce**
In the Nox repository, pass a bogus session name.

```python
$ nox -s bogus
nox > Error while collecting sessions.
nox > Sessions not found: Module(body=[Expr(value=Name(id='bogus', ctx=Load()))], type_ignores=[])
```

**Expected behavior**
```python
$ nox -s bogus
nox > Error while collecting sessions.
nox > Sessions not found: bogus
```


Pull request description:
Fix garbled error message when session not found
Fixes #433

Patch:
diff --git a/nox/manifest.py b/nox/manifest.py
index 8265de2..dbc3304 100644
--- a/nox/manifest.py
+++ b/nox/manifest.py
@@ -150,10 +150,11 @@ class Manifest:
                 ),
             )
         )
-        normalized_specified_sessions = [
-            _normalize_arg(session_name) for session_name in specified_sessions
+        missing_sessions = [
+            session_name
+            for session_name in specified_sessions
+            if _normalize_arg(session_name) not in all_sessions
         ]
-        missing_sessions = set(normalized_specified_sessions) - all_sessions
         if missing_sessions:
             raise KeyError("Sessions not found: {}".format(", ".join(missing_sessions)))
 


Test patch:
diff --git a/tests/resources/noxfile_normalization.py b/tests/resources/noxfile_normalization.py
new file mode 100644
index 0000000..d1a07fe
--- /dev/null
+++ b/tests/resources/noxfile_normalization.py
@@ -0,0 +1,16 @@
+import datetime
+
+import nox
+
+
+class Foo:
+    pass
+
+
+@nox.session
+@nox.parametrize(
+    "arg",
+    ["Jane", "Joe's", '"hello world"', datetime.datetime(1980, 1, 1), [42], Foo()],
+)
+def test(session, arg):
+    pass
diff --git a/tests/test_main.py b/tests/test_main.py
index 3539581..9f41ac5 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -370,6 +370,78 @@ def test_main_session_with_names(capsys, monkeypatch):
         sys_exit.assert_called_once_with(0)
 
 
+@pytest.fixture
+def run_nox(capsys, monkeypatch):
+    def _run_nox(*args):
+        monkeypatch.setattr(sys, "argv", ["nox", *args])
+
+        with mock.patch("sys.exit") as sys_exit:
+            nox.__main__.main()
+            stdout, stderr = capsys.readouterr()
+            returncode = sys_exit.call_args[0][0]
+
+        return returncode, stdout, stderr
+
+    return _run_nox
+
+
+@pytest.mark.parametrize(
+    ("normalized_name", "session"),
+    [
+        ("test(arg='Jane')", "test(arg='Jane')"),
+        ("test(arg='Jane')", 'test(arg="Jane")'),
+        ("test(arg='Jane')", 'test(arg = "Jane")'),
+        ("test(arg='Jane')", 'test ( arg = "Jane" )'),
+        ('test(arg="Joe\'s")', 'test(arg="Joe\'s")'),
+        ('test(arg="Joe\'s")', "test(arg='Joe\\'s')"),
+        ("test(arg='\"hello world\"')", "test(arg='\"hello world\"')"),
+        ("test(arg='\"hello world\"')", 'test(arg="\\"hello world\\"")'),
+        ("test(arg=[42])", "test(arg=[42])"),
+        ("test(arg=[42])", "test(arg=[42,])"),
+        ("test(arg=[42])", "test(arg=[ 42 ])"),
+        ("test(arg=[42])", "test(arg=[0x2a])"),
+        (
+            "test(arg=datetime.datetime(1980, 1, 1, 0, 0))",
+            "test(arg=datetime.datetime(1980, 1, 1, 0, 0))",
+        ),
+        (
+            "test(arg=datetime.datetime(1980, 1, 1, 0, 0))",
+            "test(arg=datetime.datetime(1980,1,1,0,0))",
+        ),
+        (
+            "test(arg=datetime.datetime(1980, 1, 1, 0, 0))",
+            "test(arg=datetime.datetime(1980, 1, 1, 0, 0x0))",
+        ),
+    ],
+)
+def test_main_with_normalized_session_names(run_nox, normalized_name, session):
+    noxfile = os.path.join(RESOURCES, "noxfile_normalization.py")
+    returncode, _, stderr = run_nox(f"--noxfile={noxfile}", f"--session={session}")
+    assert returncode == 0
+    assert normalized_name in stderr
+
+
+@pytest.mark.parametrize(
+    "session",
+    [
+        "syntax error",
+        "test(arg=Jane)",
+        "test(arg='Jane ')",
+        "_test(arg='Jane')",
+        "test(arg=42)",
+        "test(arg=[42.0])",
+        "test(arg=[43])",
+        "test(arg=<user_nox_module.Foo object at 0x123456789abc>)",
+        "test(arg=datetime.datetime(1980, 1, 1))",
+    ],
+)
+def test_main_with_bad_session_names(run_nox, session):
+    noxfile = os.path.join(RESOURCES, "noxfile_normalization.py")
+    returncode, _, stderr = run_nox(f"--noxfile={noxfile}", f"--session={session}")
+    assert returncode != 0
+    assert session in stderr
+
+
 def test_main_noxfile_options(monkeypatch):
     monkeypatch.setattr(
         sys,