diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cef5dc1cc..88ac0b7e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.7', '3.8', '3.9', '3.10'] + python-version: ['3.7', '3.8', '3.9', '3.10', 'pypy-3.8'] steps: - uses: actions/checkout@v2 diff --git a/hatch.toml b/hatch.toml index 0aade8b9b..0b01669d3 100644 --- a/hatch.toml +++ b/hatch.toml @@ -4,12 +4,14 @@ dependencies = [ "pytest", "pytest-cov", "pytest-mock", + "pytest-randomly", + "pytest-rerunfailures", ] post-install-commands = [ "python -m pip install --disable-pip-version-check -q -e ./backend", ] [envs.default.scripts] -full = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=src/hatch --cov=backend/src/hatchling --cov=tests" +full = "pytest --reruns 5 --reruns-delay 1 --cov-report=term-missing --cov-config=pyproject.toml --cov=src/hatch --cov=backend/src/hatchling --cov=tests" dev = "pytest --no-cov" [[envs.test.matrix]] diff --git a/tests/backend/builders/test_wheel.py b/tests/backend/builders/test_wheel.py index d5dd11467..0f8c9c6be 100644 --- a/tests/backend/builders/test_wheel.py +++ b/tests/backend/builders/test_wheel.py @@ -10,6 +10,12 @@ from hatchling.builders.wheel import WheelBuilder from hatchling.metadata.utils import DEFAULT_METADATA_VERSION, get_core_metadata_constructors +# https://github.com/python/cpython/pull/26184 +fixed_pathlib_resolution = pytest.mark.skipif( + platform.system() == 'Windows' and (sys.version_info < (3, 8) or sys.implementation.name == 'pypy'), + reason='pathlib.Path.resolve has bug on Windows', +) + def get_python_versions_tag(): return '.'.join(f'py{major_version}' for major_version in get_known_python_major_versions()) @@ -918,9 +924,7 @@ def initialize(self, version, build_data): ) helpers.assert_files(extraction_directory, expected_files, check_contents=True) - @pytest.mark.skipif( - platform.system() == 'Windows' and sys.version_info < (3, 8), reason='pathlib.Path.resolve has bug on Windows' - ) + @fixed_pathlib_resolution def test_editable_standard(self, hatch, helpers, temp_dir): project_name = 'My App' @@ -977,9 +981,7 @@ def test_editable_standard(self, hatch, helpers, temp_dir): zip_info = zip_archive.getinfo(f'{metadata_directory}/WHEEL') assert zip_info.date_time == (2020, 2, 2, 0, 0, 0) - @pytest.mark.skipif( - platform.system() == 'Windows' and sys.version_info < (3, 8), reason='pathlib.Path.resolve has bug on Windows' - ) + @fixed_pathlib_resolution def test_editable_pth(self, hatch, helpers, temp_dir): project_name = 'My App' diff --git a/tests/cli/env/test_create.py b/tests/cli/env/test_create.py index 7cdcf8bf9..7b5a96932 100644 --- a/tests/cli/env/test_create.py +++ b/tests/cli/env/test_create.py @@ -640,7 +640,9 @@ def test_incompatible_matrix_partial(hatch, helpers, temp_dir, config_file): assert env_dirs[0].name == 'test.42' -def test_install_project_default_dev_mode(hatch, helpers, temp_dir, platform, config_file): +def test_install_project_default_dev_mode( + hatch, helpers, temp_dir, platform, config_file, default_virtualenv_installed_packages +): config_file.model.template.plugins['default']['tests'] = False config_file.save() @@ -693,6 +695,8 @@ def test_install_project_default_dev_mode(hatch, helpers, temp_dir, platform, co with VirtualEnv(env_path, platform): output = platform.run_command(['pip', 'freeze'], check=True, capture_output=True).stdout.decode('utf-8') lines = output.strip().splitlines() + for package in default_virtualenv_installed_packages: + lines.remove(package) assert len(lines) == 3 assert lines[0].startswith('editables==') @@ -700,7 +704,9 @@ def test_install_project_default_dev_mode(hatch, helpers, temp_dir, platform, co assert lines[2].lower() == f'-e {str(project_path).lower()}' -def test_install_project_no_dev_mode(hatch, helpers, temp_dir, platform, config_file): +def test_install_project_no_dev_mode( + hatch, helpers, temp_dir, platform, config_file, default_virtualenv_installed_packages +): config_file.model.template.plugins['default']['tests'] = False config_file.save() @@ -754,6 +760,8 @@ def test_install_project_no_dev_mode(hatch, helpers, temp_dir, platform, config_ with VirtualEnv(env_path, platform): output = platform.run_command(['pip', 'freeze'], check=True, capture_output=True).stdout.decode('utf-8') lines = output.strip().splitlines() + for package in default_virtualenv_installed_packages: + lines.remove(package) assert len(lines) == 1 assert lines[0].startswith('my-app @') @@ -912,7 +920,7 @@ def test_post_install_commands_error(hatch, helpers, temp_dir, config_file): ) -def test_sync_dependencies(hatch, helpers, temp_dir, platform, config_file): +def test_sync_dependencies(hatch, helpers, temp_dir, platform, config_file, default_virtualenv_installed_packages): config_file.model.template.plugins['default']['tests'] = False config_file.save() @@ -977,6 +985,8 @@ def test_sync_dependencies(hatch, helpers, temp_dir, platform, config_file): with VirtualEnv(env_path, platform): output = platform.run_command(['pip', 'freeze'], check=True, capture_output=True).stdout.decode('utf-8') lines = output.strip().splitlines() + for package in default_virtualenv_installed_packages: + lines.remove(package) assert len(lines) == 4 assert lines[0].startswith('binary==') @@ -985,7 +995,7 @@ def test_sync_dependencies(hatch, helpers, temp_dir, platform, config_file): assert lines[3].lower() == f'-e {str(project_path).lower()}' -def test_features(hatch, helpers, temp_dir, platform, config_file): +def test_features(hatch, helpers, temp_dir, platform, config_file, default_virtualenv_installed_packages): config_file.model.template.plugins['default']['tests'] = False config_file.save() @@ -1042,6 +1052,8 @@ def test_features(hatch, helpers, temp_dir, platform, config_file): with VirtualEnv(env_path, platform): output = platform.run_command(['pip', 'freeze'], check=True, capture_output=True).stdout.decode('utf-8') lines = output.strip().splitlines() + for package in default_virtualenv_installed_packages: + lines.remove(package) assert len(lines) == 4 assert lines[0].startswith('binary==') diff --git a/tests/cli/env/test_prune.py b/tests/cli/env/test_prune.py index 83b3bd9e3..2f53421ae 100644 --- a/tests/cli/env/test_prune.py +++ b/tests/cli/env/test_prune.py @@ -31,7 +31,7 @@ def test_unknown_type(hatch, helpers, temp_dir_data, config_file): ) -def test_all(hatch, helpers, temp_dir_data, config_file): +def test_all(hatch, helpers, temp_dir_data, config_file, default_virtualenv_installed_packages): project_name = 'My App' with temp_dir_data.as_cwd(): diff --git a/tests/conftest.py b/tests/conftest.py index 28c766ca2..10cfcb88b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -15,6 +15,7 @@ from hatch.utils.fs import Path, temp_directory from hatch.utils.platform import Platform from hatch.utils.structures import EnvVars +from hatch.venv.core import TempVirtualEnv from hatchling.cli import hatchling from .helpers.templates.licenses import MIT, Apache_2_0 @@ -149,6 +150,20 @@ def config_file(tmp_path) -> ConfigFile: return config +@pytest.fixture(scope='session') +def default_virtualenv_installed_packages(): + # PyPy installs extra packages by default + from virtualenv import cli_run + + with temp_directory() as d: + cli_run([str(d / 'venv'), '--no-download', '--no-periodic-update', '-qqq']) + with TempVirtualEnv(sys.executable, PLATFORM): + output = PLATFORM.run_command(['pip', 'freeze'], check=True, capture_output=True).stdout.decode('utf-8') + lines = output.strip().splitlines() + + yield lines + + @pytest.fixture def mock_backend_process(request, mocker): if 'allow_backend_process' in request.keywords: diff --git a/tests/venv/test_core.py b/tests/venv/test_core.py index 871138798..23716c7f1 100644 --- a/tests/venv/test_core.py +++ b/tests/venv/test_core.py @@ -98,7 +98,7 @@ def test_activation_path_env_var_missing(temp_dir, platform): assert os.environ[env_var] == 'foo' -def test_context_manager(temp_dir, platform): +def test_context_manager(temp_dir, platform, default_virtualenv_installed_packages): venv_dir = temp_dir / 'venv' venv = VirtualEnv(venv_dir, platform) venv.create(sys.executable) @@ -117,7 +117,7 @@ def test_context_manager(temp_dir, platform): # Run here while we have cleanup output = platform.run_command(['pip', 'freeze'], check=True, capture_output=True).stdout.decode('utf-8') - assert output.strip().count('==') == 0 + assert output.strip().count('==') == len(default_virtualenv_installed_packages) assert os.environ['PATH'] == str(temp_dir) assert os.environ['VIRTUAL_ENV'] == 'foo'