Skip to content

Commit 32ff0cc

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 32ff0cc

File tree

1 file changed

+83
-1
lines changed

1 file changed

+83
-1
lines changed

tests/unit/test_utils.py

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

0 commit comments

Comments
 (0)