diff --git a/docs/changelog/2208.bugfix.rst b/docs/changelog/2208.bugfix.rst new file mode 100644 index 000000000..699f1819f --- /dev/null +++ b/docs/changelog/2208.bugfix.rst @@ -0,0 +1,2 @@ +Prevent tox from using a truncated interpreter when using +``TOX_LIMITED_SHEBANG`` -- by :user:`jdknight`. diff --git a/docs/config.rst b/docs/config.rst index ada5f19e9..da10f38c9 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -1097,9 +1097,9 @@ Handle interpreter directives with long lengths For systems supporting executable text files (scripts with a shebang), the system will attempt to parse the interpreter directive to determine the program to execute on the target text file. When ``tox`` prepares a virtual environment -in a file container which has a large length (e.x. using Jenkins Pipelines), the +in a file container which has a large length (e.g. using Jenkins Pipelines), the system might not be able to invoke shebang scripts which define interpreters -beyond system limits (e.x. Linux as a limit of 128; ``BINPRM_BUF_SIZE``). To +beyond system limits (e.g. Linux has a limit of 128; ``BINPRM_BUF_SIZE``). To workaround an environment which suffers from an interpreter directive limit, a user can bypass the system's interpreter parser by defining the ``TOX_LIMITED_SHEBANG`` environment variable before invoking ``tox``:: diff --git a/src/tox/venv.py b/src/tox/venv.py index a28fd83e9..7ba743f80 100644 --- a/src/tox/venv.py +++ b/src/tox/venv.py @@ -19,6 +19,9 @@ from .config import DepConfig +#: maximum parsed shebang interpreter length (see: prepend_shebang_interpreter) +MAXINTERP = 2048 + class CreationConfig: def __init__( @@ -672,7 +675,7 @@ def prepend_shebang_interpreter(args): # # When preparing virtual environments in a file container which has large # length, the system might not be able to invoke shebang scripts which - # define interpreters beyond system limits (e.x. Linux as a limit of 128; + # define interpreters beyond system limits (e.g. Linux has a limit of 128; # BINPRM_BUF_SIZE). This method can be used to check if the executable is # a script containing a shebang line. If so, extract the interpreter (and # possible optional argument) and prepend the values to the provided @@ -682,8 +685,9 @@ def prepend_shebang_interpreter(args): try: with open(args[0], "rb") as f: if f.read(1) == b"#" and f.read(1) == b"!": - MAXINTERP = 2048 - interp = f.readline(MAXINTERP).rstrip().decode("UTF-8") + interp = f.readline(MAXINTERP + 1).rstrip().decode("UTF-8") + if len(interp) > MAXINTERP: # avoid a truncated interpreter + return args interp_args = interp.split(None, 1)[:2] return interp_args + args except (UnicodeDecodeError, IOError): diff --git a/tests/unit/test_venv.py b/tests/unit/test_venv.py index 22e76cf54..37c471f3e 100644 --- a/tests/unit/test_venv.py +++ b/tests/unit/test_venv.py @@ -9,6 +9,7 @@ from tox.interpreters import NoInterpreterInfo from tox.session.commands.run.sequential import installpkg, runtestenv from tox.venv import ( + MAXINTERP, CreationConfig, VirtualEnv, getdigest, @@ -1149,6 +1150,18 @@ def test_tox_testenv_interpret_shebang_long_example(tmpdir): assert args == expected + base_args +@pytest.mark.skipif("sys.platform == 'win32'", reason="no shebang on Windows") +def test_tox_testenv_interpret_shebang_skip_truncated(tmpdir): + testfile = tmpdir.join("check_shebang_truncation.py") + original_args = [str(testfile), "arg1", "arg2", "arg3"] + + # interpreter (too long example) + testfile.write("#!" + ("x" * (MAXINTERP + 1))) + args = prepend_shebang_interpreter(original_args) + + assert args == original_args + + @pytest.mark.parametrize("download", [True, False, None]) def test_create_download(mocksession, newconfig, download): config = newconfig(