Repository: theacodes/nox (python)

Issue:
Selecting parameterized tests with CLI does not respect order of parameterize arguments
**Describe the bug**

When selecting parameterized tests with multiple parameters using the command line, the full session name does not respect the order of parameters. Instead, the arguments are ordered alphabetically.

I believe this line is to blame: https://github.com/theacodes/nox/blob/aa9290713a4b664ea4ef4da0554afb09a626d7e1/nox/_parametrize.py#L48

**How to reproduce**

1. Install nox in a python=3.7 environment.
2. Copy the following `noxfile.py` to a test folder.
```python
import nox

@nox.session(venv_backend="conda")
@nox.parametrize(
    ("a", "c", "b"),
    [
        ("a1", "c1", "b1"),
        ("a1", "c1", "b2"),
        ("a1", "c2", "b1"),
        ("a1", "c2", "b2"),
        ("a2", "c1", "b1"),
        ("a2", "c1", "b2"),
        ("a2", "c2", "b1"),
        ("a2", "c2", "b2"),
    ],
)
def test(session, a, b, c):
     print(f"running a={a}, b={b}, c={c}")
```
3. Run `nox --list`  or `nox` to observe the name of the sessions. Sample output is below.

**Expected behavior**

The session names should have arguments in the same order as in the parameterize call, ie `test(a="a1", c="c1", b="b1")`. Instead, they are run in alphabetical order.

Output of `nox --list`:
```
Sessions defined in /nox_bug_report/noxfile.py:

* test(a='a1', b='b1', c='c1')
* test(a='a1', b='b2', c='c1')
* test(a='a1', b='b1', c='c2')
* test(a='a1', b='b2', c='c2')
* test(a='a2', b='b1', c='c1')
* test(a='a2', b='b2', c='c1')
* test(a='a2', b='b1', c='c2')
* test(a='a2', b='b2', c='c2')

sessions marked with * are selected, sessions marked with - are skipped.
```

Output of `nox`:
```
nox > Running session test(a='a1', b='b1', c='c1')
nox > Creating conda env in .nox/test-a-a1-b-b1-c-c1 with python
running a=a1, b=b1, c=c1
nox > Session test(a='a1', b='b1', c='c1') was successful.
nox > Running session test(a='a1', b='b2', c='c1')
nox > Creating conda env in .nox/test-a-a1-b-b2-c-c1 with python
running a=a1, b=b2, c=c1
nox > Session test(a='a1', b='b2', c='c1') was successful.
nox > Running session test(a='a1', b='b1', c='c2')
nox > Creating conda env in .nox/test-a-a1-b-b1-c-c2 with python
running a=a1, b=b1, c=c2
nox > Session test(a='a1', b='b1', c='c2') was successful.
nox > Running session test(a='a1', b='b2', c='c2')
nox > Creating conda env in .nox/test-a-a1-b-b2-c-c2 with python
running a=a1, b=b2, c=c2
nox > Session test(a='a1', b='b2', c='c2') was successful.
nox > Running session test(a='a2', b='b1', c='c1')
nox > Creating conda env in .nox/test-a-a2-b-b1-c-c1 with python
running a=a2, b=b1, c=c1
nox > Session test(a='a2', b='b1', c='c1') was successful.
nox > Running session test(a='a2', b='b2', c='c1')
nox > Creating conda env in .nox/test-a-a2-b-b2-c-c1 with python
running a=a2, b=b2, c=c1
nox > Session test(a='a2', b='b2', c='c1') was successful.
nox > Running session test(a='a2', b='b1', c='c2')
nox > Creating conda env in .nox/test-a-a2-b-b1-c-c2 with python
running a=a2, b=b1, c=c2
nox > Session test(a='a2', b='b1', c='c2') was successful.
nox > Running session test(a='a2', b='b2', c='c2')
nox > Creating conda env in .nox/test-a-a2-b-b2-c-c2 with python
running a=a2, b=b2, c=c2
nox > Session test(a='a2', b='b2', c='c2') was successful.
nox > Ran multiple sessions:
nox > * test(a='a1', b='b1', c='c1'): success
nox > * test(a='a1', b='b2', c='c1'): success
nox > * test(a='a1', b='b1', c='c2'): success
nox > * test(a='a1', b='b2', c='c2'): success
nox > * test(a='a2', b='b1', c='c1'): success
nox > * test(a='a2', b='b2', c='c1'): success
nox > * test(a='a2', b='b1', c='c2'): success
nox > * test(a='a2', b='b2', c='c2'): success
```

Pull request:
Preserve the order of parameterized arguments
Parameterized jobs will now display in the same order as they are written.

```python
"""Example noxfile.py"""
import nox

@nox.session()
@nox.parameterize(
  ("z", "a"),
  [
    (1, 2),
    (3, 4),
  ]
)
def job(z, a):
    ...
```

### Before

```sh
% nox --list

* job(a=2, z=1)
* job(a=4, z=3)
```

### After

```sh
% nox --list

* job(z=1, a=2)
* job(z=3, a=4)
```

Closes #264

---

Thanks for the great library!

Patch:
diff --git a/nox/_parametrize.py b/nox/_parametrize.py
index 9f2cd63..be226e4 100644
--- a/nox/_parametrize.py
+++ b/nox/_parametrize.py
@@ -51,8 +51,7 @@ class Param:
             return self.id
         else:
             call_spec = self.call_spec
-            keys = sorted(call_spec.keys(), key=str)
-            args = ["{}={}".format(k, repr(call_spec[k])) for k in keys]
+            args = ["{}={}".format(k, repr(call_spec[k])) for k in call_spec.keys()]
             return ", ".join(args)
 
     __repr__ = __str__
diff --git a/noxfile.py b/noxfile.py
index 24e41a7..802f6dc 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -90,6 +90,7 @@ def lint(session):
     session.install("flake8==3.7.8", "black==19.3b0", "isort==4.3.21", "mypy==0.720")
     session.run(
         "mypy",
+        "--config-file=",
         "--disallow-untyped-defs",
         "--warn-unused-ignores",
         "--ignore-missing-imports",


Test patch:
diff --git a/tests/test__parametrize.py b/tests/test__parametrize.py
index 2a7f69b..d130174 100644
--- a/tests/test__parametrize.py
+++ b/tests/test__parametrize.py
@@ -200,19 +200,19 @@ def test_generate_calls_multiple_args():
     f = mock.Mock()
     f.__name__ = "f"
 
-    arg_names = ("abc", "foo")
+    arg_names = ("foo", "abc")
     call_specs = [
-        _parametrize.Param(1, "a", arg_names=arg_names),
-        _parametrize.Param(2, "b", arg_names=arg_names),
-        _parametrize.Param(3, "c", arg_names=arg_names),
+        _parametrize.Param("a", 1, arg_names=arg_names),
+        _parametrize.Param("b", 2, arg_names=arg_names),
+        _parametrize.Param("c", 3, arg_names=arg_names),
     ]
 
     calls = _decorators.Call.generate_calls(f, call_specs)
 
     assert len(calls) == 3
-    assert calls[0].session_signature == "(abc=1, foo='a')"
-    assert calls[1].session_signature == "(abc=2, foo='b')"
-    assert calls[2].session_signature == "(abc=3, foo='c')"
+    assert calls[0].session_signature == "(foo='a', abc=1)"
+    assert calls[1].session_signature == "(foo='b', abc=2)"
+    assert calls[2].session_signature == "(foo='c', abc=3)"
 
     calls[0]()
     f.assert_called_with(abc=1, foo="a")