diff --git a/CONTRIBUTORS b/CONTRIBUTORS index a95197ffb..39d848b4f 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -8,6 +8,7 @@ Anthon van der Neuth Anthony Sottile Ashley Whetter Asmund Grammeltwedt +Barney Gale Barry Warsaw Bartolome Sanchez Salado Benoit Pierre diff --git a/docs/changelog/1336.bugfix.rst b/docs/changelog/1336.bugfix.rst new file mode 100644 index 000000000..17f9dd556 --- /dev/null +++ b/docs/changelog/1336.bugfix.rst @@ -0,0 +1,2 @@ +tox used Windows shell rules on non-Windows platforms when transforming +positional arguments to a string - by :user:`barneygale`. diff --git a/src/tox/config/__init__.py b/src/tox/config/__init__.py index 19820d033..4437268e3 100644 --- a/src/tox/config/__init__.py +++ b/src/tox/config/__init__.py @@ -39,6 +39,12 @@ from .parallel import add_parallel_config, add_parallel_flags from .reporter import add_verbosity_commands +try: + from shlex import quote as shlex_quote +except ImportError: + from pipes import quote as shlex_quote + + hookimpl = tox.hookimpl """DEPRECATED - REMOVE - this is left for compatibility with plugins importing this from here. @@ -1674,7 +1680,10 @@ def getargvlist(cls, reader, value, replace=True): @classmethod def processcommand(cls, reader, command, replace=True): posargs = getattr(reader, "posargs", "") - posargs_string = list2cmdline([x for x in posargs if x]) + if sys.platform.startswith("win"): + posargs_string = list2cmdline([x for x in posargs if x]) + else: + posargs_string = " ".join([shlex_quote(x) for x in posargs if x]) # Iterate through each word of the command substituting as # appropriate to construct the new command string. This diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index 7c1fa6045..2f019b791 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -730,8 +730,18 @@ def test_argvlist_posargs_with_quotes(self, newconfig): cmd1 -f {posargs} """ ) + # The operating system APIs for launching processes differ between + # Windows and other OSs. On Windows, the command line is passed as a + # string (and not a list of strings). Python uses the MS C runtime + # rules for splitting this string into `sys.argv`, and those rules + # differ from POSIX shell rules in their treatment of quoted arguments. + if sys.platform.startswith("win"): + substitutions = ["foo", "'bar", "baz'"] + else: + substitutions = ["foo", "bar baz"] + reader = SectionReader("section", config._cfg) - reader.addsubstitutions(["foo", "'bar", "baz'"]) + reader.addsubstitutions(substitutions) assert reader.getargvlist("key1") == [] x = reader.getargvlist("key2") assert x == [["cmd1", "-f", "foo", "bar baz"]]