Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Installation test always fails on native Windows #1651

Closed
EliahKagan opened this issue Sep 10, 2023 · 0 comments · Fixed by #1654
Closed

Installation test always fails on native Windows #1651

EliahKagan opened this issue Sep 10, 2023 · 0 comments · Fixed by #1654

Comments

@EliahKagan
Copy link
Contributor

EliahKagan commented Sep 10, 2023

GitPython supports and can be installed on native Windows systems (not just Cygwin), but the TestInstallation.test_installation test in test/test_installation.py always fails with a FileNotFoundError. This is not caught on CI because, for Windows, only Cygwin currently has CI coverage. The test failure happens for two reasons:

  • The test relies on the virtual environment having a bin directory, but on Windows this is called Scripts. This is the immediate cause of the error, but if only this is fixed, a second, closely related incompatibility will cause the test to fail.
  • The test relies on the virtual environment having python3 and pip3 commands, but Windows does not have these in virtual environments. Python virtual environments on all systems have python and pip commands (even on systems such as Debian and Ubuntu where those commands are not available globally or whose global versions are part of a legacy Python 2 installation).

The following output is produced on Windows 10 using Python 3.11.5, installing GitPython from the tip of the main branch (c8e303f), but I believe the test would fail on all Windows systems with all versions of Python in the same way:

(.venv) C:\Users\ek\source\repos\GitPython [main ≡]> pytest --no-cov -k test_installation
Test session starts (platform: win32, Python 3.11.5, pytest 7.4.2, pytest-sugar 0.9.7)
rootdir: C:\Users\ek\source\repos\GitPython
configfile: pyproject.toml
testpaths: test
plugins: cov-4.1.0, sugar-0.9.7
collected 504 items / 503 deselected / 1 selected


――――――――――――――――――――――――――――――――――――――――― TestInstallation.test_installation ――――――――――――――――――――――――――――――――――――――――――

self = <test.test_installation.TestInstallation testMethod=test_installation>
rw_dir = 'C:\\Users\\ek\\AppData\\Local\\Temp\\test_installatione9rg39ma'

    @with_rw_directory
    def test_installation(self, rw_dir):
        self.setUp_venv(rw_dir)
>       result = subprocess.run(
            [self.pip, "install", "-r", "requirements.txt"],
            stdout=subprocess.PIPE,
            cwd=self.sources,
        )

test\test_installation.py:24:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.1520.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:548: in run
    with Popen(*popenargs, **kwargs) as process:
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.1520.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:1026: in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <Popen: returncode: None args: ['C:\\Users\\ek\\AppData\\Local\\Temp\\test_i...>
args = 'C:\\Users\\ek\\AppData\\Local\\Temp\\test_installatione9rg39ma\\bin/pip3 install -r requirements.txt'
executable = None, preexec_fn = None, close_fds = False, pass_fds = ()
cwd = 'C:\\Users\\ek\\AppData\\Local\\Temp\\test_installatione9rg39ma\\src', env = None
startupinfo = <subprocess.STARTUPINFO object at 0x000001B4D625B390>, creationflags = 0, shell = False
p2cread = Handle(1292), p2cwrite = -1, c2pread = 15, c2pwrite = Handle(1396), errread = -1, errwrite = Handle(1416)
unused_restore_signals = True, unused_gid = None, unused_gids = None, unused_uid = None, unused_umask = -1
unused_start_new_session = False, unused_process_group = -1

    def _execute_child(self, args, executable, preexec_fn, close_fds,
                       pass_fds, cwd, env,
                       startupinfo, creationflags, shell,
                       p2cread, p2cwrite,
                       c2pread, c2pwrite,
                       errread, errwrite,
                       unused_restore_signals,
                       unused_gid, unused_gids, unused_uid,
                       unused_umask,
                       unused_start_new_session, unused_process_group):
        """Execute program (MS Windows version)"""

        assert not pass_fds, "pass_fds not supported on Windows."

        if isinstance(args, str):
            pass
        elif isinstance(args, bytes):
            if shell:
                raise TypeError('bytes args is not allowed on Windows')
            args = list2cmdline([args])
        elif isinstance(args, os.PathLike):
            if shell:
                raise TypeError('path-like args is not allowed when '
                                'shell is true')
            args = list2cmdline([args])
        else:
            args = list2cmdline(args)

        if executable is not None:
            executable = os.fsdecode(executable)

        # Process startup details
        if startupinfo is None:
            startupinfo = STARTUPINFO()
        else:
            # bpo-34044: Copy STARTUPINFO since it is modified above,
            # so the caller can reuse it multiple times.
            startupinfo = startupinfo.copy()

        use_std_handles = -1 not in (p2cread, c2pwrite, errwrite)
        if use_std_handles:
            startupinfo.dwFlags |= _winapi.STARTF_USESTDHANDLES
            startupinfo.hStdInput = p2cread
            startupinfo.hStdOutput = c2pwrite
            startupinfo.hStdError = errwrite

        attribute_list = startupinfo.lpAttributeList
        have_handle_list = bool(attribute_list and
                                "handle_list" in attribute_list and
                                attribute_list["handle_list"])

        # If we were given an handle_list or need to create one
        if have_handle_list or (use_std_handles and close_fds):
            if attribute_list is None:
                attribute_list = startupinfo.lpAttributeList = {}
            handle_list = attribute_list["handle_list"] = \
                list(attribute_list.get("handle_list", []))

            if use_std_handles:
                handle_list += [int(p2cread), int(c2pwrite), int(errwrite)]

            handle_list[:] = self._filter_handle_list(handle_list)

            if handle_list:
                if not close_fds:
                    warnings.warn("startupinfo.lpAttributeList['handle_list'] "
                                  "overriding close_fds", RuntimeWarning)

                # When using the handle_list we always request to inherit
                # handles but the only handles that will be inherited are
                # the ones in the handle_list
                close_fds = False

        if shell:
            startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW
            startupinfo.wShowWindow = _winapi.SW_HIDE
            if not executable:
                # gh-101283: without a fully-qualified path, before Windows
                # checks the system directories, it first looks in the
                # application directory, and also the current directory if
                # NeedCurrentDirectoryForExePathW(ExeName) is true, so try
                # to avoid executing unqualified "cmd.exe".
                comspec = os.environ.get('ComSpec')
                if not comspec:
                    system_root = os.environ.get('SystemRoot', '')
                    comspec = os.path.join(system_root, 'System32', 'cmd.exe')
                    if not os.path.isabs(comspec):
                        raise FileNotFoundError('shell not found: neither %ComSpec% nor %SystemRoot% is set')
                if os.path.isabs(comspec):
                    executable = comspec
            else:
                comspec = executable

            args = '{} /c "{}"'.format (comspec, args)

        if cwd is not None:
            cwd = os.fsdecode(cwd)

        sys.audit("subprocess.Popen", executable, args, cwd, env)

        # Start the process
        try:
>           hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
                                     # no special security
                                     None, None,
                                     int(not close_fds),
                                     creationflags,
                                     env,
                                     cwd,
                                     startupinfo)
E                                    FileNotFoundError: [WinError 2] The system cannot find the file specified

C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.1520.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:1538: FileNotFoundError

 test/test_installation.py ⨯                                                                             100% ██████████
=============================================== short test summary info ===============================================
FAILED test/test_installation.py::TestInstallation::test_installation - FileNotFoundError: [WinError 2] The system cannot find the file specified

Results (7.00s):
       1 failed
         - test/test_installation.py:21 TestInstallation.test_installation
     503 deselected

I have also verified that this occurs on Python 3.12.0rc2 on Windows. #1640 affects Windows as much as other systems, but this specific issue causes the test to fail first.

I think it would make sense to fix this together with #1640, since a fix for that would include related changes to test_installation. I've opened #1654 for this (which also fixes #1652 and #1653).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

2 participants