/
niceSOFT
/
python3-hatchling
Обзор
Документация
Войти
/
niceSOFT
/
python3-hatchling
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
tests/backend/builders/test_config.py
2 069 строк
87 KB
Dimitri Papadopoulos Orfanos
Upgrade Ruff to 0.13.2 (#1890)
28 сен 2025, 21:20
Не верифицирован
28 сен 2025, 21:20
aff6f39
Код
Авторство
О чём код?
import os import re from os.path import join as pjoin import pathspec import pytest from hatch.utils.structures import EnvVars from hatchling.builders.constants import BuildEnvVars from .utils import MockBuilder class TestDirectory: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.directory == builder.config.directory == str(isolation / "dist") def test_target(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"directory": "bar"}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.directory == str(isolation / "bar") def test_target_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"directory": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.targets.foo.directory` must be a string"): _ = builder.config.directory def test_global(self, isolation): config = {"tool": {"hatch": {"build": {"directory": "bar"}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.directory == str(isolation / "bar") def test_global_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"directory": 9000}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.directory` must be a string"): _ = builder.config.directory def test_target_overrides_global(self, isolation): config = {"tool": {"hatch": {"build": {"directory": "bar", "targets": {"foo": {"directory": "baz"}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.directory == str(isolation / "baz") def test_absolute_path(self, isolation): absolute_path = str(isolation) config = {"tool": {"hatch": {"build": {"targets": {"foo": {"directory": absolute_path}}}}}} builder = MockBuilder(absolute_path, config=config) builder.PLUGIN_NAME = "foo" assert builder.config.directory == absolute_path class TestSkipExcludedDirs: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.skip_excluded_dirs is builder.config.skip_excluded_dirs is False def test_target(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"skip-excluded-dirs": True}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.skip_excluded_dirs is True def test_target_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"skip-excluded-dirs": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Field `tool.hatch.build.targets.foo.skip-excluded-dirs` must be a boolean" ): _ = builder.config.skip_excluded_dirs def test_global(self, isolation): config = {"tool": {"hatch": {"build": {"skip-excluded-dirs": True}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.skip_excluded_dirs is True def test_global_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"skip-excluded-dirs": 9000}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.skip-excluded-dirs` must be a boolean"): _ = builder.config.skip_excluded_dirs def test_target_overrides_global(self, isolation): config = { "tool": { "hatch": {"build": {"skip-excluded-dirs": True, "targets": {"foo": {"skip-excluded-dirs": False}}}} } } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.skip_excluded_dirs is False class TestIgnoreVCS: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.ignore_vcs is builder.config.ignore_vcs is False def test_target(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"ignore-vcs": True}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.ignore_vcs is True def test_target_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"ignore-vcs": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.targets.foo.ignore-vcs` must be a boolean"): _ = builder.config.ignore_vcs def test_global(self, isolation): config = {"tool": {"hatch": {"build": {"ignore-vcs": True}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.ignore_vcs is True def test_global_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"ignore-vcs": 9000}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.ignore-vcs` must be a boolean"): _ = builder.config.ignore_vcs def test_target_overrides_global(self, isolation): config = {"tool": {"hatch": {"build": {"ignore-vcs": True, "targets": {"foo": {"ignore-vcs": False}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.ignore_vcs is False class TestRequireRuntimeDependencies: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.require_runtime_dependencies is builder.config.require_runtime_dependencies is False def test_target(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"require-runtime-dependencies": True}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.require_runtime_dependencies is True def test_target_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"require-runtime-dependencies": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Field `tool.hatch.build.targets.foo.require-runtime-dependencies` must be a boolean", ): _ = builder.config.require_runtime_dependencies def test_global(self, isolation): config = {"tool": {"hatch": {"build": {"require-runtime-dependencies": True}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.require_runtime_dependencies is True def test_global_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"require-runtime-dependencies": 9000}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.require-runtime-dependencies` must be a boolean"): _ = builder.config.require_runtime_dependencies def test_target_overrides_global(self, isolation): config = { "tool": { "hatch": { "build": { "require-runtime-dependencies": True, "targets": {"foo": {"require-runtime-dependencies": False}}, } } } } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.require_runtime_dependencies is False class TestRequireRuntimeFeatures: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.require_runtime_features == builder.config.require_runtime_features == [] def test_target(self, isolation): config = { "project": {"name": "my_app", "version": "0.0.1", "optional-dependencies": {"foo": [], "bar": []}}, "tool": {"hatch": {"build": {"targets": {"foo": {"require-runtime-features": ["foo", "bar"]}}}}}, } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.require_runtime_features == ["foo", "bar"] def test_target_not_array(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"require-runtime-features": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Field `tool.hatch.build.targets.foo.require-runtime-features` must be an array" ): _ = builder.config.require_runtime_features def test_target_feature_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"require-runtime-features": [9000]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Feature #1 of field `tool.hatch.build.targets.foo.require-runtime-features` must be a string", ): _ = builder.config.require_runtime_features def test_target_feature_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"require-runtime-features": [""]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match=( "Feature #1 of field `tool.hatch.build.targets.foo.require-runtime-features` cannot be an empty string" ), ): _ = builder.config.require_runtime_features def test_target_feature_unknown(self, isolation): config = { "project": {"name": "my_app", "version": "0.0.1"}, "tool": {"hatch": {"build": {"targets": {"foo": {"require-runtime-features": ["foo_bar"]}}}}}, } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match=( "Feature `foo-bar` of field `tool.hatch.build.targets.foo.require-runtime-features` is not defined in " "field `project.optional-dependencies`" ), ): _ = builder.config.require_runtime_features def test_global(self, isolation): config = { "project": {"name": "my_app", "version": "0.0.1", "optional-dependencies": {"foo": [], "bar": []}}, "tool": {"hatch": {"build": {"require-runtime-features": ["foo", "bar"]}}}, } builder = MockBuilder(str(isolation), config=config) assert builder.config.require_runtime_features == ["foo", "bar"] def test_global_not_array(self, isolation): config = {"tool": {"hatch": {"build": {"require-runtime-features": 9000}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Field `tool.hatch.build.require-runtime-features` must be an array"): _ = builder.config.require_runtime_features def test_global_feature_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"require-runtime-features": [9000]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( TypeError, match="Feature #1 of field `tool.hatch.build.require-runtime-features` must be a string" ): _ = builder.config.require_runtime_features def test_global_feature_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"require-runtime-features": [""]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( ValueError, match="Feature #1 of field `tool.hatch.build.require-runtime-features` cannot be an empty string", ): _ = builder.config.require_runtime_features def test_global_feature_unknown(self, isolation): config = { "project": {"name": "my_app", "version": "0.0.1"}, "tool": {"hatch": {"build": {"require-runtime-features": ["foo_bar"]}}}, } builder = MockBuilder(str(isolation), config=config) with pytest.raises( ValueError, match=( "Feature `foo-bar` of field `tool.hatch.build.require-runtime-features` is not defined in " "field `project.optional-dependencies`" ), ): _ = builder.config.require_runtime_features def test_target_overrides_global(self, isolation): config = { "project": {"name": "my_app", "version": "0.0.1", "optional-dependencies": {"foo_bar": [], "bar_baz": []}}, "tool": { "hatch": { "build": { "require-runtime-features": ["bar_baz"], "targets": {"foo": {"require-runtime-features": ["foo_bar"]}}, } } }, } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.require_runtime_features == ["foo-bar"] class TestOnlyPackages: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.only_packages is builder.config.only_packages is False def test_target(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"only-packages": True}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.only_packages is True def test_target_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"only-packages": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.targets.foo.only-packages` must be a boolean"): _ = builder.config.only_packages def test_global(self, isolation): config = {"tool": {"hatch": {"build": {"only-packages": True}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.only_packages is True def test_global_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"only-packages": 9000}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.only-packages` must be a boolean"): _ = builder.config.only_packages def test_target_overrides_global(self, isolation): config = {"tool": {"hatch": {"build": {"only-packages": True, "targets": {"foo": {"only-packages": False}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.only_packages is False class TestReproducible: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.reproducible is builder.config.reproducible is True def test_target(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"reproducible": False}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.reproducible is False def test_target_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"reproducible": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.targets.foo.reproducible` must be a boolean"): _ = builder.config.reproducible def test_global(self, isolation): config = {"tool": {"hatch": {"build": {"reproducible": False}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.reproducible is False def test_global_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"reproducible": 9000}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.reproducible` must be a boolean"): _ = builder.config.reproducible def test_target_overrides_global(self, isolation): config = {"tool": {"hatch": {"build": {"reproducible": False, "targets": {"foo": {"reproducible": True}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.reproducible is True class TestDevModeDirs: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.dev_mode_dirs == builder.config.dev_mode_dirs == [] def test_global_invalid_type(self, isolation): config = {"tool": {"hatch": {"build": {"dev-mode-dirs": ""}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Field `tool.hatch.build.dev-mode-dirs` must be an array of strings"): _ = builder.config.dev_mode_dirs def test_global(self, isolation): config = {"tool": {"hatch": {"build": {"dev-mode-dirs": ["foo", "bar/baz"]}}}} builder = MockBuilder(str(isolation), config=config) assert builder.config.dev_mode_dirs == ["foo", "bar/baz"] def test_global_pattern_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"dev-mode-dirs": [0]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Directory #1 in field `tool.hatch.build.dev-mode-dirs` must be a string"): _ = builder.config.dev_mode_dirs def test_global_pattern_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"dev-mode-dirs": [""]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( ValueError, match="Directory #1 in field `tool.hatch.build.dev-mode-dirs` cannot be an empty string" ): _ = builder.config.dev_mode_dirs def test_target(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"dev-mode-dirs": ["foo", "bar/baz"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dev_mode_dirs == ["foo", "bar/baz"] def test_target_pattern_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"dev-mode-dirs": [0]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Directory #1 in field `tool.hatch.build.targets.foo.dev-mode-dirs` must be a string" ): _ = builder.config.dev_mode_dirs def test_target_pattern_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"dev-mode-dirs": [""]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match="Directory #1 in field `tool.hatch.build.targets.foo.dev-mode-dirs` cannot be an empty string", ): _ = builder.config.dev_mode_dirs def test_target_overrides_global(self, isolation): config = { "tool": {"hatch": {"build": {"dev-mode-dirs": ["foo"], "targets": {"foo": {"dev-mode-dirs": ["bar"]}}}}} } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dev_mode_dirs == ["bar"] class TestDevModeExact: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.dev_mode_exact is builder.config.dev_mode_exact is False def test_target(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"dev-mode-exact": True}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dev_mode_exact is True def test_target_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"dev-mode-exact": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.targets.foo.dev-mode-exact` must be a boolean"): _ = builder.config.dev_mode_exact def test_global(self, isolation): config = {"tool": {"hatch": {"build": {"dev-mode-exact": True}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dev_mode_exact is True def test_global_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"dev-mode-exact": 9000}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.dev-mode-exact` must be a boolean"): _ = builder.config.dev_mode_exact def test_target_overrides_global(self, isolation): config = {"tool": {"hatch": {"build": {"dev-mode-exact": True, "targets": {"foo": {"dev-mode-exact": False}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dev_mode_exact is False class TestPackages: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.packages == builder.config.packages == [] def test_global_invalid_type(self, isolation): config = {"tool": {"hatch": {"build": {"packages": ""}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Field `tool.hatch.build.packages` must be an array of strings"): _ = builder.config.packages def test_global(self, isolation): config = {"tool": {"hatch": {"build": {"packages": ["src/foo"]}}}} builder = MockBuilder(str(isolation), config=config) assert len(builder.config.packages) == 1 assert builder.config.packages[0] == pjoin("src", "foo") def test_global_package_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"packages": [0]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Package #1 in field `tool.hatch.build.packages` must be a string"): _ = builder.config.packages def test_global_package_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"packages": [""]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( ValueError, match="Package #1 in field `tool.hatch.build.packages` cannot be an empty string" ): _ = builder.config.packages def test_target(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"packages": ["src/foo"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert len(builder.config.packages) == 1 assert builder.config.packages[0] == pjoin("src", "foo") def test_target_package_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"packages": [0]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Package #1 in field `tool.hatch.build.targets.foo.packages` must be a string" ): _ = builder.config.packages def test_target_package_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"packages": [""]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match="Package #1 in field `tool.hatch.build.targets.foo.packages` cannot be an empty string" ): _ = builder.config.packages def test_target_overrides_global(self, isolation): config = { "tool": {"hatch": {"build": {"packages": ["src/foo"], "targets": {"foo": {"packages": ["pkg/foo"]}}}}} } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert len(builder.config.packages) == 1 assert builder.config.packages[0] == pjoin("pkg", "foo") def test_no_source(self, isolation): config = {"tool": {"hatch": {"build": {"packages": ["foo"]}}}} builder = MockBuilder(str(isolation), config=config) assert len(builder.config.packages) == 1 assert builder.config.packages[0] == pjoin("foo") class TestSources: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.sources == builder.config.sources == {} assert builder.config.get_distribution_path(pjoin("src", "foo", "bar.py")) == pjoin("src", "foo", "bar.py") def test_global_invalid_type(self, isolation): config = {"tool": {"hatch": {"build": {"sources": ""}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Field `tool.hatch.build.sources` must be a mapping or array of strings"): _ = builder.config.sources def test_global_array(self, isolation): config = {"tool": {"hatch": {"build": {"sources": ["src"]}}}} builder = MockBuilder(str(isolation), config=config) assert len(builder.config.sources) == 1 assert builder.config.sources[pjoin("src", "")] == "" assert builder.config.get_distribution_path(pjoin("src", "foo", "bar.py")) == pjoin("foo", "bar.py") def test_global_array_source_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"sources": [0]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Source #1 in field `tool.hatch.build.sources` must be a string"): _ = builder.config.sources def test_global_array_source_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"sources": [""]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(ValueError, match="Source #1 in field `tool.hatch.build.sources` cannot be an empty string"): _ = builder.config.sources def test_global_mapping(self, isolation): config = {"tool": {"hatch": {"build": {"sources": {"src/foo": "renamed"}}}}} builder = MockBuilder(str(isolation), config=config) assert len(builder.config.sources) == 1 assert builder.config.sources[pjoin("src", "foo", "")] == pjoin("renamed", "") assert builder.config.get_distribution_path(pjoin("src", "foo", "bar.py")) == pjoin("renamed", "bar.py") def test_global_mapping_source_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"sources": {"": "renamed"}}}}} builder = MockBuilder(str(isolation), config=config) assert len(builder.config.sources) == 1 assert builder.config.sources[""] == pjoin("renamed", "") assert builder.config.get_distribution_path("bar.py") == pjoin("renamed", "bar.py") assert builder.config.get_distribution_path(pjoin("foo", "bar.py")) == pjoin("renamed", "foo", "bar.py") def test_global_mapping_path_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"sources": {"src/foo": ""}}}}} builder = MockBuilder(str(isolation), config=config) assert len(builder.config.sources) == 1 assert builder.config.sources[pjoin("src", "foo", "")] == "" assert builder.config.get_distribution_path(pjoin("src", "foo", "bar.py")) == "bar.py" def test_global_mapping_replacement_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"sources": {"src/foo": 0}}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( TypeError, match="Path for source `src/foo` in field `tool.hatch.build.sources` must be a string" ): _ = builder.config.sources def test_target_invalid_type(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"sources": ""}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Field `tool.hatch.build.targets.foo.sources` must be a mapping or array of strings" ): _ = builder.config.sources def test_target_array(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"sources": ["src"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert len(builder.config.sources) == 1 assert builder.config.sources[pjoin("src", "")] == "" assert builder.config.get_distribution_path(pjoin("src", "foo", "bar.py")) == pjoin("foo", "bar.py") def test_target_array_source_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"sources": [0]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Source #1 in field `tool.hatch.build.targets.foo.sources` must be a string" ): _ = builder.config.sources def test_target_array_source_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"sources": [""]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match="Source #1 in field `tool.hatch.build.targets.foo.sources` cannot be an empty string" ): _ = builder.config.sources def test_target_mapping(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"sources": {"src/foo": "renamed"}}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert len(builder.config.sources) == 1 assert builder.config.sources[pjoin("src", "foo", "")] == pjoin("renamed", "") assert builder.config.get_distribution_path(pjoin("src", "foo", "bar.py")) == pjoin("renamed", "bar.py") def test_target_mapping_source_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"sources": {"": "renamed"}}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert len(builder.config.sources) == 1 assert builder.config.sources[""] == pjoin("renamed", "") assert builder.config.get_distribution_path(pjoin("bar.py")) == pjoin("renamed", "bar.py") def test_target_mapping_path_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"sources": {"src/foo": ""}}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert len(builder.config.sources) == 1 assert builder.config.sources[pjoin("src", "foo", "")] == "" assert builder.config.get_distribution_path(pjoin("src", "foo", "bar.py")) == "bar.py" def test_target_mapping_replacement_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"sources": {"src/foo": 0}}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Path for source `src/foo` in field `tool.hatch.build.targets.foo.sources` must be a string", ): _ = builder.config.sources def test_target_overrides_global(self, isolation): config = {"tool": {"hatch": {"build": {"sources": ["src"], "targets": {"foo": {"sources": ["pkg"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert len(builder.config.sources) == 1 assert builder.config.sources[pjoin("pkg", "")] == "" assert builder.config.get_distribution_path(pjoin("pkg", "foo", "bar.py")) == pjoin("foo", "bar.py") assert builder.config.get_distribution_path(pjoin("src", "foo", "bar.py")) == pjoin("src", "foo", "bar.py") def test_no_source(self, isolation): config = {"tool": {"hatch": {"build": {"sources": ["bar"]}}}} builder = MockBuilder(str(isolation), config=config) assert len(builder.config.sources) == 1 assert builder.config.sources[pjoin("bar", "")] == "" assert builder.config.get_distribution_path(pjoin("foo", "bar.py")) == pjoin("foo", "bar.py") def test_compatible_with_packages(self, isolation): config = {"tool": {"hatch": {"build": {"sources": {"src/foo": "renamed"}, "packages": ["src/foo"]}}}} builder = MockBuilder(str(isolation), config=config) assert len(builder.config.sources) == 1 assert builder.config.sources[pjoin("src", "foo", "")] == pjoin("renamed", "") assert builder.config.get_distribution_path(pjoin("src", "foo", "bar.py")) == pjoin("renamed", "bar.py") class TestForceInclude: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.force_include == builder.config.force_include == {} def test_global_invalid_type(self, isolation): config = {"tool": {"hatch": {"build": {"force-include": ""}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Field `tool.hatch.build.force-include` must be a mapping"): _ = builder.config.force_include def test_global_absolute(self, isolation): config = {"tool": {"hatch": {"build": {"force-include": {str(isolation / "source"): "/target/"}}}}} builder = MockBuilder(str(isolation), config=config) assert builder.config.force_include == {str(isolation / "source"): "target"} def test_global_relative(self, isolation): config = {"tool": {"hatch": {"build": {"force-include": {"../source": "/target/"}}}}} builder = MockBuilder(str(isolation / "foo"), config=config) assert builder.config.force_include == {str(isolation / "source"): "target"} def test_global_source_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"force-include": {"": "/target/"}}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( ValueError, match="Source #1 in field `tool.hatch.build.force-include` cannot be an empty string" ): _ = builder.config.force_include def test_global_relative_path_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"force-include": {"source": 0}}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( TypeError, match="Path for source `source` in field `tool.hatch.build.force-include` must be a string" ): _ = builder.config.force_include def test_global_relative_path_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"force-include": {"source": ""}}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( ValueError, match="Path for source `source` in field `tool.hatch.build.force-include` cannot be an empty string", ): _ = builder.config.force_include def test_target_invalid_type(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"force-include": ""}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.targets.foo.force-include` must be a mapping"): _ = builder.config.force_include def test_target_absolute(self, isolation): config = { "tool": { "hatch": {"build": {"targets": {"foo": {"force-include": {str(isolation / "source"): "/target/"}}}}} } } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.force_include == {str(isolation / "source"): "target"} def test_target_relative(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"force-include": {"../source": "/target/"}}}}}}} builder = MockBuilder(str(isolation / "foo"), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.force_include == {str(isolation / "source"): "target"} def test_target_source_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"force-include": {"": "/target/"}}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match="Source #1 in field `tool.hatch.build.targets.foo.force-include` cannot be an empty string", ): _ = builder.config.force_include def test_target_relative_path_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"force-include": {"source": 0}}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Path for source `source` in field `tool.hatch.build.targets.foo.force-include` must be a string", ): _ = builder.config.force_include def test_target_relative_path_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"force-include": {"source": ""}}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match=( "Path for source `source` in field `tool.hatch.build.targets.foo.force-include` " "cannot be an empty string" ), ): _ = builder.config.force_include def test_order(self, isolation): config = { "tool": { "hatch": { "build": { "force-include": { "../very-nested": "target1/embedded", "../source1": "/target2/", "../source2": "/target1/", } } } } } builder = MockBuilder(str(isolation / "foo"), config=config) assert builder.config.force_include == { str(isolation / "source2"): "target1", str(isolation / "very-nested"): f"target1{os.sep}embedded", str(isolation / "source1"): "target2", } class TestOnlyInclude: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.only_include == builder.config.only_include == {} def test_global_invalid_type(self, isolation): config = {"tool": {"hatch": {"build": {"only-include": 9000}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Field `tool.hatch.build.only-include` must be an array"): _ = builder.config.only_include def test_global_path_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"only-include": [9000]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Path #1 in field `tool.hatch.build.only-include` must be a string"): _ = builder.config.only_include @pytest.mark.parametrize("path", ["/", "~/foo", "../foo"]) def test_global_not_relative(self, isolation, path): config = {"tool": {"hatch": {"build": {"only-include": [path]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( ValueError, match=f"Path #1 in field `tool.hatch.build.only-include` must be relative: {path}" ): _ = builder.config.only_include def test_global_duplicate(self, isolation): config = {"tool": {"hatch": {"build": {"only-include": ["/foo//bar", "foo//bar/"]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( ValueError, match=re.escape(f"Duplicate path in field `tool.hatch.build.only-include`: foo{os.sep}bar") ): _ = builder.config.only_include def test_global_correct(self, isolation): config = {"tool": {"hatch": {"build": {"only-include": ["/foo//bar/"]}}}} builder = MockBuilder(str(isolation), config=config) assert builder.config.only_include == {f"{isolation}{os.sep}foo{os.sep}bar": f"foo{os.sep}bar"} def test_target_invalid_type(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"only-include": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.targets.foo.only-include` must be an array"): _ = builder.config.only_include def test_target_path_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"only-include": [9000]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Path #1 in field `tool.hatch.build.targets.foo.only-include` must be a string" ): _ = builder.config.only_include @pytest.mark.parametrize("path", ["/", "~/foo", "../foo"]) def test_target_not_relative(self, isolation, path): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"only-include": [path]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match=f"Path #1 in field `tool.hatch.build.targets.foo.only-include` must be relative: {path}" ): _ = builder.config.only_include def test_target_duplicate(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"only-include": ["/foo//bar", "foo//bar/"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match=re.escape(f"Duplicate path in field `tool.hatch.build.targets.foo.only-include`: foo{os.sep}bar"), ): _ = builder.config.only_include def test_target_correct(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"only-include": ["/foo//bar/"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.only_include == {f"{isolation}{os.sep}foo{os.sep}bar": f"foo{os.sep}bar"} class TestVersions: def test_default_known(self, isolation): builder = MockBuilder(str(isolation)) builder.PLUGIN_NAME = "foo" builder.get_version_api = lambda: {"2": str, "1": str} assert builder.config.versions == builder.config.versions == ["2", "1"] def test_default_override(self, isolation): builder = MockBuilder(str(isolation)) builder.PLUGIN_NAME = "foo" builder.get_default_versions = lambda: ["old", "new", "new"] assert builder.config.versions == builder.config.versions == ["old", "new"] def test_invalid_type(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"versions": ""}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Field `tool.hatch.build.targets.foo.versions` must be an array of strings" ): _ = builder.config.versions def test_correct(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"versions": ["3.14", "1", "3.14"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" builder.get_version_api = lambda: {"3.14": str, "42": str, "1": str} assert builder.config.versions == builder.config.versions == ["3.14", "1"] def test_empty_default(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"versions": []}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" builder.get_default_versions = lambda: ["old", "new"] assert builder.config.versions == builder.config.versions == ["old", "new"] def test_version_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"versions": [1]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Version #1 in field `tool.hatch.build.targets.foo.versions` must be a string" ): _ = builder.config.versions def test_version_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"versions": [""]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match="Version #1 in field `tool.hatch.build.targets.foo.versions` cannot be an empty string" ): _ = builder.config.versions def test_unknown_version(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"versions": ["9000", "1", "42"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" builder.get_version_api = lambda: {"1": str} with pytest.raises( ValueError, match="Unknown versions in field `tool.hatch.build.targets.foo.versions`: 42, 9000" ): _ = builder.config.versions class TestHookConfig: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.hook_config == builder.config.hook_config == {} def test_target_not_table(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"hooks": "bar"}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.targets.foo.hooks` must be a table"): _ = builder.config.hook_config def test_target_hook_not_table(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"hooks": {"bar": "baz"}}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.targets.foo.hooks.bar` must be a table"): _ = builder.config.hook_config def test_global_not_table(self, isolation): config = {"tool": {"hatch": {"build": {"hooks": "foo"}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Field `tool.hatch.build.hooks` must be a table"): _ = builder.config.hook_config def test_global_hook_not_table(self, isolation): config = {"tool": {"hatch": {"build": {"hooks": {"foo": "bar"}}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Field `tool.hatch.build.hooks.foo` must be a table"): _ = builder.config.hook_config def test_global(self, isolation): config = {"tool": {"hatch": {"build": {"hooks": {"foo": {"bar": "baz"}}}}}} builder = MockBuilder(str(isolation), config=config) assert builder.config.hook_config["foo"]["bar"] == "baz" def test_order(self, isolation): config = { "tool": { "hatch": { "build": {"hooks": {"foo": {"bar": "baz"}}, "targets": {"foo": {"hooks": {"baz": {"foo": "bar"}}}}} } }, } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.hook_config == {"foo": {"bar": "baz"}, "baz": {"foo": "bar"}} def test_target_overrides_global(self, isolation): config = { "tool": { "hatch": { "build": {"hooks": {"foo": {"bar": "baz"}}, "targets": {"foo": {"hooks": {"foo": {"baz": "bar"}}}}} } }, } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.hook_config["foo"]["baz"] == "bar" def test_env_var_no_hooks(self, isolation): config = {"tool": {"hatch": {"build": {"hooks": {"foo": {"bar": "baz"}}}}}} builder = MockBuilder(str(isolation), config=config) with EnvVars({BuildEnvVars.NO_HOOKS: "true"}): assert builder.config.hook_config == {} def test_enable_by_default(self, isolation): config = { "tool": { "hatch": { "build": { "hooks": { "foo": {"bar": "baz", "enable-by-default": False}, "bar": {"foo": "baz", "enable-by-default": False}, "baz": {"foo": "bar"}, } } } } } builder = MockBuilder(str(isolation), config=config) assert builder.config.hook_config == {"baz": {"foo": "bar"}} def test_env_var_all_override_enable_by_default(self, isolation): config = { "tool": { "hatch": { "build": { "hooks": { "foo": {"bar": "baz", "enable-by-default": False}, "bar": {"foo": "baz", "enable-by-default": False}, "baz": {"foo": "bar"}, } } } } } builder = MockBuilder(str(isolation), config=config) with EnvVars({BuildEnvVars.HOOKS_ENABLE: "true"}): assert builder.config.hook_config == { "foo": {"bar": "baz", "enable-by-default": False}, "bar": {"foo": "baz", "enable-by-default": False}, "baz": {"foo": "bar"}, } def test_env_var_specific_override_enable_by_default(self, isolation): config = { "tool": { "hatch": { "build": { "hooks": { "foo": {"bar": "baz", "enable-by-default": False}, "bar": {"foo": "baz", "enable-by-default": False}, "baz": {"foo": "bar"}, } } } } } builder = MockBuilder(str(isolation), config=config) with EnvVars({f"{BuildEnvVars.HOOK_ENABLE_PREFIX}FOO": "true"}): assert builder.config.hook_config == { "foo": {"bar": "baz", "enable-by-default": False}, "baz": {"foo": "bar"}, } class TestDependencies: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.dependencies == builder.config.dependencies == [] def test_target_not_array(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"dependencies": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.targets.foo.dependencies` must be an array"): _ = builder.config.dependencies def test_target_dependency_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"dependencies": [9000]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Dependency #1 of field `tool.hatch.build.targets.foo.dependencies` must be a string" ): _ = builder.config.dependencies def test_global_not_array(self, isolation): config = {"tool": {"hatch": {"build": {"dependencies": 9000}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Field `tool.hatch.build.dependencies` must be an array"): _ = builder.config.dependencies def test_global_dependency_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"dependencies": [9000]}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Dependency #1 of field `tool.hatch.build.dependencies` must be a string"): _ = builder.config.dependencies def test_hook_require_runtime_dependencies_not_boolean(self, isolation): config = {"tool": {"hatch": {"build": {"hooks": {"foo": {"require-runtime-dependencies": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Option `require-runtime-dependencies` of build hook `foo` must be a boolean" ): _ = builder.config.dependencies def test_hook_require_runtime_features_not_array(self, isolation): config = {"tool": {"hatch": {"build": {"hooks": {"foo": {"require-runtime-features": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Option `require-runtime-features` of build hook `foo` must be an array"): _ = builder.config.dependencies def test_hook_require_runtime_features_feature_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"hooks": {"foo": {"require-runtime-features": [9000]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Feature #1 of option `require-runtime-features` of build hook `foo` must be a string" ): _ = builder.config.dependencies def test_hook_require_runtime_features_feature_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"hooks": {"foo": {"require-runtime-features": [""]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match="Feature #1 of option `require-runtime-features` of build hook `foo` cannot be an empty string", ): _ = builder.config.dependencies def test_hook_require_runtime_features_feature_unknown(self, isolation): config = { "project": {"name": "my_app", "version": "0.0.1"}, "tool": {"hatch": {"build": {"hooks": {"foo": {"require-runtime-features": ["foo_bar"]}}}}}, } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match=( "Feature `foo-bar` of option `require-runtime-features` of build hook `foo` is not defined in " "field `project.optional-dependencies`" ), ): _ = builder.config.dependencies def test_hook_dependencies_not_array(self, isolation): config = {"tool": {"hatch": {"build": {"hooks": {"foo": {"dependencies": 9000}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises(TypeError, match="Option `dependencies` of build hook `foo` must be an array"): _ = builder.config.dependencies def test_hook_dependency_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"hooks": {"foo": {"dependencies": [9000]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Dependency #1 of option `dependencies` of build hook `foo` must be a string" ): _ = builder.config.dependencies def test_correct(self, isolation): config = { "tool": { "hatch": { "build": { "dependencies": ["bar"], "hooks": {"foobar": {"dependencies": ["test1"]}}, "targets": {"foo": {"dependencies": ["baz"], "hooks": {"foobar": {"dependencies": ["test2"]}}}}, } } } } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dependencies == ["baz", "bar", "test2"] def test_require_runtime_dependencies(self, isolation): config = { "project": {"name": "my-app", "version": "0.0.1", "dependencies": ["foo"]}, "tool": { "hatch": { "build": { "require-runtime-dependencies": True, "dependencies": ["bar"], "hooks": {"foobar": {"dependencies": ["test1"]}}, "targets": {"foo": {"dependencies": ["baz"], "hooks": {"foobar": {"dependencies": ["test2"]}}}}, } } }, } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dependencies == ["baz", "bar", "test2", "foo"] def test_require_runtime_features(self, isolation): config = { "project": {"name": "my-app", "version": "0.0.1", "optional-dependencies": {"bar_baz": ["foo"]}}, "tool": { "hatch": { "build": { "require-runtime-features": ["bar-baz"], "dependencies": ["bar"], "hooks": {"foobar": {"dependencies": ["test1"]}}, "targets": {"foo": {"dependencies": ["baz"], "hooks": {"foobar": {"dependencies": ["test2"]}}}}, } } }, } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dependencies == ["baz", "bar", "test2", "foo"] def test_env_var_no_hooks(self, isolation): config = { "tool": { "hatch": { "build": { "hooks": { "foo": {"dependencies": ["foo"]}, "bar": {"dependencies": ["bar"]}, "baz": {"dependencies": ["baz"]}, }, } } } } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with EnvVars({BuildEnvVars.NO_HOOKS: "true"}): assert builder.config.dependencies == [] def test_hooks_enable_by_default(self, isolation): config = { "tool": { "hatch": { "build": { "hooks": { "foo": {"dependencies": ["foo"], "enable-by-default": False}, "bar": {"dependencies": ["bar"], "enable-by-default": False}, "baz": {"dependencies": ["baz"]}, }, } } } } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dependencies == ["baz"] def test_hooks_env_var_all_override_enable_by_default(self, isolation): config = { "tool": { "hatch": { "build": { "hooks": { "foo": {"dependencies": ["foo"], "enable-by-default": False}, "bar": {"dependencies": ["bar"], "enable-by-default": False}, "baz": {"dependencies": ["baz"]}, }, } } } } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with EnvVars({BuildEnvVars.HOOKS_ENABLE: "true"}): assert builder.config.dependencies == ["foo", "bar", "baz"] def test_hooks_env_var_specific_override_enable_by_default(self, isolation): config = { "tool": { "hatch": { "build": { "hooks": { "foo": {"dependencies": ["foo"], "enable-by-default": False}, "bar": {"dependencies": ["bar"], "enable-by-default": False}, "baz": {"dependencies": ["baz"]}, }, } } } } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with EnvVars({f"{BuildEnvVars.HOOK_ENABLE_PREFIX}FOO": "true"}): assert builder.config.dependencies == ["foo", "baz"] def test_hooks_require_runtime_dependencies(self, isolation): config = { "project": {"name": "my-app", "version": "0.0.1", "dependencies": ["baz"]}, "tool": { "hatch": { "build": { "hooks": { "foo": {"dependencies": ["foo"], "enable-by-default": False}, "bar": {"dependencies": ["bar"], "require-runtime-dependencies": True}, }, } } }, } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dependencies == ["bar", "baz"] def test_hooks_require_runtime_dependencies_disabled(self, isolation): config = { "project": {"name": "my-app", "version": "0.0.1", "dependencies": ["baz"]}, "tool": { "hatch": { "build": { "hooks": { "foo": { "dependencies": ["foo"], "enable-by-default": False, "require-runtime-dependencies": True, }, "bar": {"dependencies": ["bar"]}, }, } } }, } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dependencies == ["bar"] def test_hooks_require_runtime_features(self, isolation): config = { "project": {"name": "my-app", "version": "0.0.1", "optional-dependencies": {"foo_bar": ["baz"]}}, "tool": { "hatch": { "build": { "hooks": { "foo": {"dependencies": ["foo"], "enable-by-default": False}, "bar": {"dependencies": ["bar"], "require-runtime-features": ["foo-bar"]}, }, } } }, } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dependencies == ["bar", "baz"] def test_hooks_require_runtime_features_disabled(self, isolation): config = { "project": {"name": "my-app", "version": "0.0.1", "optional-dependencies": {"foo_bar": ["baz"]}}, "tool": { "hatch": { "build": { "hooks": { "foo": { "dependencies": ["foo"], "enable-by-default": False, "require-runtime-features": ["foo-bar"], }, "bar": {"dependencies": ["bar"]}, }, } } }, } builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.dependencies == ["bar"] class TestFileSelectionDefaults: def test_include(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.default_include() == [] def test_exclude(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.default_exclude() == [] def test_packages(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.default_packages() == [] def test_only_include(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.default_only_include() == [] def test_global_exclude(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.default_global_exclude() == ["*.py[cdo]", "/dist"] class TestPatternInclude: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.include_spec is None def test_global_becomes_spec(self, isolation): config = {"tool": {"hatch": {"build": {"include": ["foo"]}}}} builder = MockBuilder(str(isolation), config=config) assert isinstance(builder.config.include_spec, pathspec.GitIgnoreSpec) def test_global_invalid_type(self, isolation): config = {"tool": {"hatch": {"build": {"include": ""}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Field `tool.hatch.build.include` must be an array of strings"): _ = builder.config.include_spec @pytest.mark.parametrize("separator", ["/", "\\"]) def test_global(self, isolation, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") config = {"tool": {"hatch": {"build": {"include": ["foo", "bar/baz"]}}}} builder = MockBuilder(str(isolation), config=config) assert builder.config.include_spec.match_file(f"foo{separator}file.py") assert builder.config.include_spec.match_file(f"bar{separator}baz{separator}file.py") assert not builder.config.include_spec.match_file(f"bar{separator}file.py") def test_global_pattern_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"include": [0]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Pattern #1 in field `tool.hatch.build.include` must be a string"): _ = builder.config.include_spec def test_global_pattern_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"include": [""]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( ValueError, match="Pattern #1 in field `tool.hatch.build.include` cannot be an empty string" ): _ = builder.config.include_spec @pytest.mark.parametrize("separator", ["/", "\\"]) def test_global_packages_included(self, isolation, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") config = {"tool": {"hatch": {"build": {"packages": ["bar"], "include": ["foo"]}}}} builder = MockBuilder(str(isolation), config=config) assert builder.config.include_spec.match_file(f"foo{separator}file.py") assert builder.config.include_spec.match_file(f"bar{separator}baz{separator}file.py") assert not builder.config.include_spec.match_file(f"baz{separator}bar{separator}file.py") @pytest.mark.parametrize("separator", ["/", "\\"]) def test_target(self, isolation, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") config = {"tool": {"hatch": {"build": {"targets": {"foo": {"include": ["foo", "bar/baz"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.include_spec.match_file(f"foo{separator}file.py") assert builder.config.include_spec.match_file(f"bar{separator}baz{separator}file.py") assert not builder.config.include_spec.match_file(f"bar{separator}file.py") def test_target_pattern_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"include": [0]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Pattern #1 in field `tool.hatch.build.targets.foo.include` must be a string" ): _ = builder.config.include_spec def test_target_pattern_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"include": [""]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match="Pattern #1 in field `tool.hatch.build.targets.foo.include` cannot be an empty string" ): _ = builder.config.include_spec @pytest.mark.parametrize("separator", ["/", "\\"]) def test_target_overrides_global(self, isolation, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") config = {"tool": {"hatch": {"build": {"include": ["foo"], "targets": {"foo": {"include": ["bar"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert not builder.config.include_spec.match_file(f"foo{separator}file.py") assert builder.config.include_spec.match_file(f"bar{separator}file.py") @pytest.mark.parametrize("separator", ["/", "\\"]) def test_target_packages_included(self, isolation, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") config = {"tool": {"hatch": {"build": {"targets": {"foo": {"packages": ["bar"], "include": ["foo"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.include_spec.match_file(f"foo{separator}file.py") assert builder.config.include_spec.match_file(f"bar{separator}baz{separator}file.py") assert not builder.config.include_spec.match_file(f"baz{separator}bar{separator}file.py") class TestPatternExclude: @pytest.mark.parametrize("separator", ["/", "\\"]) def test_default(self, isolation, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") builder = MockBuilder(str(isolation)) assert isinstance(builder.config.exclude_spec, pathspec.GitIgnoreSpec) assert builder.config.exclude_spec.match_file(f"dist{separator}file.py") def test_global_invalid_type(self, isolation): config = {"tool": {"hatch": {"build": {"exclude": ""}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Field `tool.hatch.build.exclude` must be an array of strings"): _ = builder.config.exclude_spec @pytest.mark.parametrize("separator", ["/", "\\"]) def test_global(self, isolation, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") config = {"tool": {"hatch": {"build": {"exclude": ["foo", "bar/baz"]}}}} builder = MockBuilder(str(isolation), config=config) assert builder.config.exclude_spec.match_file(f"foo{separator}file.py") assert builder.config.exclude_spec.match_file(f"bar{separator}baz{separator}file.py") assert not builder.config.exclude_spec.match_file(f"bar{separator}file.py") def test_global_pattern_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"exclude": [0]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Pattern #1 in field `tool.hatch.build.exclude` must be a string"): _ = builder.config.exclude_spec def test_global_pattern_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"exclude": [""]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( ValueError, match="Pattern #1 in field `tool.hatch.build.exclude` cannot be an empty string" ): _ = builder.config.exclude_spec @pytest.mark.parametrize("separator", ["/", "\\"]) def test_target(self, isolation, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") config = {"tool": {"hatch": {"build": {"targets": {"foo": {"exclude": ["foo", "bar/baz"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.exclude_spec.match_file(f"foo{separator}file.py") assert builder.config.exclude_spec.match_file(f"bar{separator}baz{separator}file.py") assert not builder.config.exclude_spec.match_file(f"bar{separator}file.py") def test_target_pattern_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"exclude": [0]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Pattern #1 in field `tool.hatch.build.targets.foo.exclude` must be a string" ): _ = builder.config.exclude_spec def test_target_pattern_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"exclude": [""]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match="Pattern #1 in field `tool.hatch.build.targets.foo.exclude` cannot be an empty string" ): _ = builder.config.exclude_spec @pytest.mark.parametrize("separator", ["/", "\\"]) def test_target_overrides_global(self, isolation, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") config = {"tool": {"hatch": {"build": {"exclude": ["foo"], "targets": {"foo": {"exclude": ["bar"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert not builder.config.exclude_spec.match_file(f"foo{separator}file.py") assert builder.config.exclude_spec.match_file(f"bar{separator}file.py") @pytest.mark.parametrize("separator", ["/", "\\"]) def test_vcs_git(self, temp_dir, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") with temp_dir.as_cwd(): config = {"tool": {"hatch": {"build": {"exclude": ["foo"]}}}} builder = MockBuilder(str(temp_dir), config=config) vcs_ignore_file = temp_dir / ".gitignore" vcs_ignore_file.write_text("/bar\n*.pyc") assert builder.config.exclude_spec.match_file(f"foo{separator}file.py") assert builder.config.exclude_spec.match_file(f"bar{separator}file.py") assert builder.config.exclude_spec.match_file(f"baz{separator}bar{separator}file.pyc") assert not builder.config.exclude_spec.match_file(f"baz{separator}bar{separator}file.py") @pytest.mark.parametrize("separator", ["/", "\\"]) def test_ignore_vcs_git(self, temp_dir, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") with temp_dir.as_cwd(): config = {"tool": {"hatch": {"build": {"ignore-vcs": True, "exclude": ["foo"]}}}} builder = MockBuilder(str(temp_dir), config=config) vcs_ignore_file = temp_dir / ".gitignore" vcs_ignore_file.write_text("/bar\n*.pyc") assert builder.config.exclude_spec.match_file(f"foo{separator}file.py") assert not builder.config.exclude_spec.match_file(f"bar{separator}file.py") @pytest.mark.parametrize("separator", ["/", "\\"]) def test_vcs_git_boundary(self, temp_dir, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") project_dir = temp_dir / "project" project_dir.mkdir() (project_dir / ".git").mkdir() with project_dir.as_cwd(): config = {"tool": {"hatch": {"build": {"exclude": ["foo"]}}}} builder = MockBuilder(str(project_dir), config=config) vcs_ignore_file = temp_dir / ".gitignore" vcs_ignore_file.write_text("/bar\n*.pyc") assert builder.config.exclude_spec.match_file(f"foo{separator}file.py") assert not builder.config.exclude_spec.match_file(f"bar{separator}file.py") @pytest.mark.parametrize("separator", ["/", "\\"]) def test_vcs_git_exclude_whitelisted_file(self, temp_dir, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") with temp_dir.as_cwd(): config = {"tool": {"hatch": {"build": {"exclude": ["foo/bar"]}}}} builder = MockBuilder(str(temp_dir), config=config) vcs_ignore_file = temp_dir / ".gitignore" vcs_ignore_file.write_text("foo/*\n!foo/bar") assert builder.config.path_is_excluded(f"foo{separator}deb") is True assert builder.config.path_is_excluded(f"foo{separator}bar") is True @pytest.mark.parametrize("separator", ["/", "\\"]) def test_vcs_mercurial(self, temp_dir, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") with temp_dir.as_cwd(): config = {"tool": {"hatch": {"build": {"exclude": ["foo"]}}}} builder = MockBuilder(str(temp_dir), config=config) vcs_ignore_file = temp_dir / ".hgignore" vcs_ignore_file.write_text("syntax: glob\n/bar\n*.pyc") assert builder.config.exclude_spec.match_file(f"foo{separator}file.py") assert builder.config.exclude_spec.match_file(f"bar{separator}file.py") assert builder.config.exclude_spec.match_file(f"baz{separator}bar{separator}file.pyc") assert not builder.config.exclude_spec.match_file(f"baz{separator}bar{separator}file.py") @pytest.mark.parametrize("separator", ["/", "\\"]) def test_ignore_vcs_mercurial(self, temp_dir, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") with temp_dir.as_cwd(): config = {"tool": {"hatch": {"build": {"ignore-vcs": True, "exclude": ["foo"]}}}} builder = MockBuilder(str(temp_dir), config=config) vcs_ignore_file = temp_dir / ".hgignore" vcs_ignore_file.write_text("syntax: glob\n/bar\n*.pyc") assert builder.config.exclude_spec.match_file(f"foo{separator}file.py") assert not builder.config.exclude_spec.match_file(f"bar{separator}file.py") @pytest.mark.parametrize("separator", ["/", "\\"]) def test_vcs_mercurial_boundary(self, temp_dir, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") project_dir = temp_dir / "project" project_dir.mkdir() (project_dir / ".hg").mkdir() with project_dir.as_cwd(): config = {"tool": {"hatch": {"build": {"exclude": ["foo"]}}}} builder = MockBuilder(str(project_dir), config=config) vcs_ignore_file = temp_dir / ".hgignore" vcs_ignore_file.write_text("syntax: glob\n/bar\n*.pyc") assert builder.config.exclude_spec.match_file(f"foo{separator}file.py") assert not builder.config.exclude_spec.match_file(f"bar{separator}file.py") def test_override_default_global_exclude_patterns(self, isolation): builder = MockBuilder(str(isolation)) builder.config.default_global_exclude = list assert builder.config.exclude_spec is None assert not builder.config.path_is_excluded(".git/file") class TestPatternArtifacts: def test_default(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.artifact_spec is None def test_global_becomes_spec(self, isolation): config = {"tool": {"hatch": {"build": {"artifacts": ["foo"]}}}} builder = MockBuilder(str(isolation), config=config) assert isinstance(builder.config.artifact_spec, pathspec.GitIgnoreSpec) def test_global_invalid_type(self, isolation): config = {"tool": {"hatch": {"build": {"artifacts": ""}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Field `tool.hatch.build.artifacts` must be an array of strings"): _ = builder.config.artifact_spec @pytest.mark.parametrize("separator", ["/", "\\"]) def test_global(self, isolation, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") config = {"tool": {"hatch": {"build": {"artifacts": ["foo", "bar/baz"]}}}} builder = MockBuilder(str(isolation), config=config) assert builder.config.artifact_spec.match_file(f"foo{separator}file.py") assert builder.config.artifact_spec.match_file(f"bar{separator}baz{separator}file.py") assert not builder.config.artifact_spec.match_file(f"bar{separator}file.py") def test_global_pattern_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"artifacts": [0]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises(TypeError, match="Pattern #1 in field `tool.hatch.build.artifacts` must be a string"): _ = builder.config.artifact_spec def test_global_pattern_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"artifacts": [""]}}}} builder = MockBuilder(str(isolation), config=config) with pytest.raises( ValueError, match="Pattern #1 in field `tool.hatch.build.artifacts` cannot be an empty string" ): _ = builder.config.artifact_spec @pytest.mark.parametrize("separator", ["/", "\\"]) def test_target(self, isolation, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") config = {"tool": {"hatch": {"build": {"targets": {"foo": {"artifacts": ["foo", "bar/baz"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert builder.config.artifact_spec.match_file(f"foo{separator}file.py") assert builder.config.artifact_spec.match_file(f"bar{separator}baz{separator}file.py") assert not builder.config.artifact_spec.match_file(f"bar{separator}file.py") def test_target_pattern_not_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"artifacts": [0]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( TypeError, match="Pattern #1 in field `tool.hatch.build.targets.foo.artifacts` must be a string" ): _ = builder.config.artifact_spec def test_target_pattern_empty_string(self, isolation): config = {"tool": {"hatch": {"build": {"targets": {"foo": {"artifacts": [""]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" with pytest.raises( ValueError, match="Pattern #1 in field `tool.hatch.build.targets.foo.artifacts` cannot be an empty string" ): _ = builder.config.artifact_spec @pytest.mark.parametrize("separator", ["/", "\\"]) def test_target_overrides_global(self, isolation, separator, platform): if separator == "\\" and not platform.windows: pytest.skip("Not running on Windows") config = {"tool": {"hatch": {"build": {"artifacts": ["foo"], "targets": {"foo": {"artifacts": ["bar"]}}}}}} builder = MockBuilder(str(isolation), config=config) builder.PLUGIN_NAME = "foo" assert not builder.config.artifact_spec.match_file(f"foo{separator}file.py") assert builder.config.artifact_spec.match_file(f"bar{separator}file.py") class TestPatternMatching: def test_include_explicit(self, isolation): config = {"tool": {"hatch": {"build": {"include": ["foo"]}}}} builder = MockBuilder(str(isolation), config=config) assert builder.config.include_path("foo/file.py") assert not builder.config.include_path("bar/file.py") def test_no_include_greedy(self, isolation): builder = MockBuilder(str(isolation)) assert builder.config.include_path("foo/file.py") assert builder.config.include_path("bar/file.py") def test_exclude_precedence(self, isolation): config = {"tool": {"hatch": {"build": {"include": ["foo"], "exclude": ["foo"]}}}} builder = MockBuilder(str(isolation), config=config) assert not builder.config.include_path("foo/file.py") assert not builder.config.include_path("bar/file.py") def test_artifact_super_precedence(self, isolation): config = {"tool": {"hatch": {"build": {"include": ["foo"], "exclude": ["foo"], "artifacts": ["foo"]}}}} builder = MockBuilder(str(isolation), config=config) assert builder.config.include_path("foo/file.py") assert not builder.config.include_path("bar/file.py")