# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os
import subprocess
import sys
from unittest
import mock
import pytest
from buildconfig
import topsrcdir
from mozunit
import main
from mach.requirements
import MachEnvRequirements, RequirementsTxtSpecifier
from mach.site
import (
PIP_NETWORK_INSTALL_RESTRICTED_VIRTUALENVS,
ExternalPythonSite,
MozSiteMetadata,
PythonVirtualenv,
SitePackagesSource,
_create_venv_with_pthfile,
resolve_requirements,
)
@pytest.mark.parametrize(
"env_native_package_source,env_use_system_python,env_moz_automation,expected",
[
(
"system",
False,
False, SitePackagesSource.SYSTEM),
(
"pip",
False,
False, SitePackagesSource.VENV),
(
"none",
False,
False, SitePackagesSource.
NONE),
(
None,
False,
False, SitePackagesSource.VENV),
(
None,
False,
True, SitePackagesSource.
NONE),
(
None,
True,
False, SitePackagesSource.
NONE),
(
None,
True,
True, SitePackagesSource.
NONE),
],
)
def test_resolve_package_source(
env_native_package_source, env_use_system_python, env_moz_automation, expected
):
with mock.patch.dict(
os.environ,
{
"MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE": env_native_package_source
or "",
"MACH_USE_SYSTEM_PYTHON":
"1" if env_use_system_python
else "",
"MOZ_AUTOMATION":
"1" if env_moz_automation
else "",
},
):
assert SitePackagesSource.for_mach() == expected
def test_all_restricted_sites_dont_have_pypi_requirements():
for site_name
in PIP_NETWORK_INSTALL_RESTRICTED_VIRTUALENVS:
requirements = resolve_requirements(topsrcdir, site_name)
assert not requirements.pypi_requirements, (
'Sites that must be able to operate without "pip install" must not have any '
f
'mandatory "pypi requirements". However, the "{site_name}" site depends on: '
f
"{requirements.pypi_requirements}"
)
@pytest.fixture
def parse_requirements_txt(tmp_path):
"""Fixture to test requirements-txt parsing."""
def inner(site_content, requirements_txt_content=
None):
# Create site packages file
site_file = tmp_path /
"test-site.txt"
site_file.write_text(site_content)
# Create requirements.txt if provided
if requirements_txt_content
is not None:
req_file = tmp_path /
"requirements.txt"
req_file.write_text(requirements_txt_content)
# Parse the site file
requirements = MachEnvRequirements.from_requirements_definition(
str(tmp_path),
is_thunderbird=
False,
only_strict_requirements=
False,
requirements_definition=str(site_file),
)
return requirements
return inner
def test_requirements_txt_parsed(parse_requirements_txt):
requirements = parse_requirements_txt(
"requires-python:>=3.9\nrequirements-txt:requirements.txt\n",
"package1==1.0.0 --hash=sha256:abc123\npackage2==2.0.0 --hash=sha256:def456\n",
)
assert len(requirements.requirements_txt_files) ==
1
assert "requirements.txt" in requirements.requirements_txt_files[
0].path
def test_requirements_txt_missing_file(parse_requirements_txt):
with pytest.raises(Exception, match=
"requirements.txt file not found"):
parse_requirements_txt(
"requires-python:>=3.8\nrequirements-txt:nonexistent.txt\n"
)
@pytest.fixture
def run_create_venv_with_pthfile(tmp_path):
def inner(requirements_content):
req_file = tmp_path /
"requirements.txt"
req_file.write_text(requirements_content)
requirements = MachEnvRequirements()
requirements.requirements_txt_files.append(
RequirementsTxtSpecifier(str(req_file))
)
venv_root = tmp_path /
"venv"
external_python = ExternalPythonSite(sys.executable)
metadata = MozSiteMetadata(
sys.hexversion,
"test",
SitePackagesSource.VENV,
external_python,
str(venv_root),
)
venv = PythonVirtualenv(str(venv_root))
_create_venv_with_pthfile(venv, [],
True, requirements, metadata)
return venv
return inner
def test_requirements_txt_install_requires_hashes(
monkeypatch, run_create_venv_with_pthfile
):
monkeypatch.delenv(
"MACH_SHOW_PIP_OUTPUT", raising=
False)
try:
run_create_venv_with_pthfile(
"certifi==2021.5.30\n")
pytest.fail(
"Expected CalledProcessError to be raised due to missing hashes")
except subprocess.CalledProcessError
as e:
error_output = e.stderr
if e.stderr
else ""
assert "hash" in error_output.lower(), (
f
"Expected hash error in stderr, got: {error_output}"
)
def test_requirements_txt_installs_with_hashes(run_create_venv_with_pthfile):
requirements_content = (
"certifi==2021.5.30 "
"--hash=sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8\n"
)
venv = run_create_venv_with_pthfile(requirements_content)
site_packages = venv.resolve_sysconfig_packages_path(
"purelib")
assert os.path.exists(site_packages), f
"site-packages not found: {site_packages}"
certifi_path = os.path.join(site_packages,
"certifi")
assert os.path.exists(certifi_path), f
"certifi package not found in {site_packages}"
if __name__ ==
"__main__":
main()