pypa-hatch/tests/backend/builders/test_config.py

2070 lines
87 KiB
Python

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')