Skip to content

Commit 9e2746a

Browse files
committed
test(utils): add test for virtualenv
This test is written as to allow for an easy extension, so that testing for unconventional environments can be integrated rapidly. It mocks all parts necessary to ensure that all combinations can be tested, without being too verbose.
1 parent f4c9ba6 commit 9e2746a

File tree

1 file changed

+84
-1
lines changed

1 file changed

+84
-1
lines changed

tests/unit/test_utils.py

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import os
2+
import sys
3+
import sysconfig
4+
from contextlib import ExitStack
5+
import importlib
26
from unittest import mock
7+
from unittest.mock import MagicMock, patch
38

49
import pytest
510

611
from pipenv.exceptions import PipenvUsageError
7-
from pipenv.utils import dependencies, indexes, internet, shell, toml
12+
from pipenv.utils import dependencies, indexes, internet, shell, toml, virtualenv
813

914
# Pipfile format <-> requirements.txt format.
1015
DEP_PIP_PAIRS = [
@@ -547,3 +552,81 @@ def test_is_env_truthy_does_not_exisxt(self, monkeypatch):
547552
name = "ZZZ"
548553
monkeypatch.delenv(name, raising=False)
549554
assert shell.is_env_truthy(name) is False
555+
556+
@pytest.mark.utils
557+
@pytest.mark.parametrize(
558+
"os_name, platform, version, preferred_schemes, expected",
559+
[
560+
# MSYS2 compiled against MinGW-w64 with Microsoft Visual C++ Runtime
561+
("nt", "win32", "any",{'prefix': 'posix_prefix'}, 'foobar/bin'),
562+
("nt", "win32", "GCC",{'prefix': 'posix_prefix'}, 'foobar/bin'),
563+
("nt", "win32", "GCC", {'prefix': 'nt'}, 'foobar/bin'),
564+
("nt", "win32", "any", {'prefix': 'nt'}, 'foobar/Scripts'),
565+
("nt", "win32", "any", {'prefix': 'venv'}, 'foobar/Scripts'),
566+
("nt", "win32", "GCC", {'prefix': 'venv'}, 'foobar/bin'),
567+
]
568+
)
569+
def test_virtualenv_scripts_dir(self, os_name, platform, version, preferred_schemes, expected):
570+
"""Test for CPython compiled against various platforms
571+
572+
To simulate the differing environments as best as possible, we're
573+
mocking `os.name`, and `sys.platform`, and
574+
`sysconfig._get_preferred_schemes` as this is the lowest we must go to
575+
have full control for mocking all possible combinations relevant for the
576+
function we're testing here. MSYS2 is the most extreme edge-case I could
577+
find, since it patches sysconfig at compile time.
578+
579+
References (CPython main branch is also applicable to 3.12 branch):
580+
581+
- https://github.com/msys2/MINGW-packages/blob/master/mingw-w64-python/0017-sysconfig-treat-MINGW-builds-as-POSIX-builds.patch
582+
- https://github.com/python/cpython/blob/main/Lib/sysconfig/__init__.py#L28
583+
- https://github.com/python/cpython/blob/main/Include/cpython/initconfig.h#L209
584+
- https://github.com/python/cpython/blob/main/Python/sysmodule.c#L3894
585+
- https://github.com/python/cpython/blob/main/Modules/getpath.py#L227
586+
587+
:param os_name: os.name
588+
:param platform: sys.platform
589+
:param version: sys.version
590+
:param preferred_schemes: sysconfig._get_preferred_schemes()
591+
"""
592+
with ExitStack() as stack:
593+
stack.enter_context(patch.object(os, 'name', os_name))
594+
stack.enter_context(patch.object(sys, 'platform', platform))
595+
stack.enter_context(patch.object(sys, 'version', version))
596+
597+
# ensure, that when the test suite is executed from within a venv,
598+
# that this does not trigger defaulting to choosing the venv prefix
599+
if preferred_schemes['prefix'] != 'venv':
600+
stack.enter_context(patch.object(sys, 'prefix', sys.base_prefix))
601+
602+
# we need to unload sysconfig, as well as the virtualenv util
603+
# module, as a constant dependent on os.name
604+
# and sys.version is being initialized during import, which we need
605+
# to rewrite to be able to simulate different platforms and patches
606+
# applied by CPython distributors.
607+
if "sysconfig" in importlib.sys.modules:
608+
del importlib.sys.modules["sysconfig"]
609+
if "pipenv.utils.virtualenv" in importlib.sys.modules:
610+
del importlib.sys.modules["pipenv.utils.virtualenv"]
611+
612+
sysconfig = importlib.import_module("sysconfig")
613+
virtualenv = importlib.import_module("pipenv.utils.virtualenv")
614+
615+
# MSYS2 specific reversion of PATCH #23, which seems to be
616+
# unnecessary, as the logic for correct platform switching is
617+
# already handled by PATCH #17 (see references of this test) and
618+
# also introduces inconsistencies. This overwrite only applies to a
619+
# single parameter set though. As soon as the issue is resolved,
620+
# this MSYS2 specific override can be removed
621+
#
622+
# The issue is tracked here: https://github.com/msys2/MINGW-packages/issues/23992
623+
# MSYS2-specific PATCH #23: https://github.com/msys2/MINGW-packages/blob/c8e881f77f804dd1721f7ff90e2931593eb6ac1a/mingw-w64-python/0023-sysconfig-mingw-sysconfig-like-posix.patch#L24
624+
if preferred_schemes['prefix'] == 'nt' and 'GCC' not in version:
625+
sysconfig._INSTALL_SCHEMES['nt']['scripts'] = '{base}/Scripts'
626+
627+
stack.enter_context(patch.object(
628+
sysconfig, "_get_preferred_schemes", return_value=preferred_schemes
629+
))
630+
631+
assert str(virtualenv.virtualenv_scripts_dir('foobar')) == expected
632+

0 commit comments

Comments
 (0)