Skip to content

Commit 5ac5f7d

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 5ac5f7d

File tree

1 file changed

+82
-1
lines changed

1 file changed

+82
-1
lines changed

tests/unit/test_utils.py

+82-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import os
2+
import sys
3+
import sysconfig
4+
from contextlib import ExitStack
5+
import importlib
26
from unittest import mock
37

48
import pytest
59

610
from pipenv.exceptions import PipenvUsageError
7-
from pipenv.utils import dependencies, indexes, internet, shell, toml
11+
from pipenv.utils import dependencies, indexes, internet, shell, toml, virtualenv
812

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

0 commit comments

Comments
 (0)