0
0
mirror of https://github.com/renovatebot/renovate.git synced 2024-12-22 13:38:32 +00:00
renovatebot_renovate/lib/modules/manager/pep621/extract.spec.ts
2024-10-29 18:56:50 +00:00

610 lines
18 KiB
TypeScript

import { codeBlock } from 'common-tags';
import { Fixtures } from '../../../../test/fixtures';
import { fs } from '../../../../test/util';
import { GitRefsDatasource } from '../../datasource/git-refs';
import { depTypes } from './utils';
import { extractPackageFile } from '.';
jest.mock('../../../util/fs');
const pdmPyProject = Fixtures.get('pyproject_with_pdm.toml');
const pdmSourcesPyProject = Fixtures.get('pyproject_pdm_sources.toml');
describe('modules/manager/pep621/extract', () => {
describe('extractPackageFile()', () => {
it('should return null for empty content', async () => {
const result = await extractPackageFile('', 'pyproject.toml');
expect(result).toBeNull();
});
it('should return null for invalid toml', async () => {
const result = await extractPackageFile(
codeBlock`
[project]
name =
`,
'pyproject.toml',
);
expect(result).toBeNull();
});
it('should return dependencies for valid content', async () => {
const result = await extractPackageFile(pdmPyProject, 'pyproject.toml');
expect(result).toMatchObject({
extractedConstraints: {
python: '>=3.7',
},
});
const dependencies = result?.deps.filter(
(dep) => dep.depType === 'project.dependencies',
);
expect(dependencies).toEqual([
{
packageName: 'blinker',
depName: 'blinker',
datasource: 'pypi',
depType: 'project.dependencies',
skipReason: 'unspecified-version',
},
{
packageName: 'packaging',
depName: 'packaging',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '>=20.9,!=22.0',
},
{
packageName: 'rich',
depName: 'rich',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '>=12.3.0',
},
{
packageName: 'virtualenv',
depName: 'virtualenv',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '==20.0.0',
currentVersion: '20.0.0',
},
{
packageName: 'pyproject-hooks',
depName: 'pyproject-hooks',
datasource: 'pypi',
depType: 'project.dependencies',
skipReason: 'unspecified-version',
},
{
packageName: 'unearth',
depName: 'unearth',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '>=0.9.0',
},
{
packageName: 'tomlkit',
depName: 'tomlkit',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '>=0.11.1,<1',
},
{
packageName: 'installer',
depName: 'installer',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '<0.8,>=0.7',
},
{
packageName: 'cachecontrol',
depName: 'cachecontrol',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '>=0.12.11',
},
{
packageName: 'tomli',
depName: 'tomli',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '>=1.1.0',
},
{
packageName: 'typing-extensions',
depName: 'typing-extensions',
datasource: 'pypi',
depType: 'project.dependencies',
skipReason: 'unspecified-version',
},
{
packageName: 'importlib-metadata',
depName: 'importlib-metadata',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '>=3.6',
},
]);
const optionalDependencies = result?.deps.filter(
(dep) => dep.depType === 'project.optional-dependencies',
);
expect(optionalDependencies).toEqual([
{
packageName: 'pytest',
datasource: 'pypi',
depType: 'project.optional-dependencies',
currentValue: '>12',
depName: 'pytest',
managerData: { depGroup: 'pytest' },
},
{
packageName: 'pytest-mock',
datasource: 'pypi',
depType: 'project.optional-dependencies',
skipReason: 'unspecified-version',
depName: 'pytest-mock',
managerData: { depGroup: 'pytest' },
},
]);
const dependenciesFromDependencyGroups = result?.deps.filter(
(dep) => dep.depType === 'dependency-groups',
);
expect(dependenciesFromDependencyGroups).toEqual([
{
packageName: 'mypy',
datasource: 'pypi',
depType: 'dependency-groups',
currentValue: '==1.13.0',
currentVersion: '1.13.0',
depName: 'mypy',
managerData: { depGroup: 'typing' },
},
{
packageName: 'types-requests',
datasource: 'pypi',
depType: 'dependency-groups',
skipReason: 'unspecified-version',
depName: 'types-requests',
managerData: { depGroup: 'typing' },
},
{
packageName: 'pytest-cov',
datasource: 'pypi',
depType: 'dependency-groups',
currentValue: '==5.0.0',
currentVersion: '5.0.0',
depName: 'pytest-cov',
managerData: { depGroup: 'coverage' },
},
{
packageName: 'click',
datasource: 'pypi',
depType: 'dependency-groups',
currentValue: '==8.1.7',
currentVersion: '8.1.7',
depName: 'click',
managerData: { depGroup: 'all' },
},
]);
const pdmDevDependencies = result?.deps.filter(
(dep) => dep.depType === 'tool.pdm.dev-dependencies',
);
expect(pdmDevDependencies).toEqual([
{
packageName: 'pdm',
datasource: 'pypi',
depType: 'tool.pdm.dev-dependencies',
skipReason: 'unspecified-version',
depName: 'pdm',
managerData: { depGroup: 'test' },
},
{
packageName: 'pytest-rerunfailures',
datasource: 'pypi',
depType: 'tool.pdm.dev-dependencies',
currentValue: '>=10.2',
depName: 'pytest-rerunfailures',
managerData: { depGroup: 'test' },
},
{
packageName: 'tox',
datasource: 'pypi',
depType: 'tool.pdm.dev-dependencies',
skipReason: 'unspecified-version',
depName: 'tox',
managerData: { depGroup: 'tox' },
},
{
packageName: 'tox-pdm',
datasource: 'pypi',
depType: 'tool.pdm.dev-dependencies',
currentValue: '>=0.5',
depName: 'tox-pdm',
managerData: { depGroup: 'tox' },
},
]);
});
it('should return dependencies with overwritten pypi registryUrl', async () => {
const result = await extractPackageFile(
pdmSourcesPyProject,
'pyproject.toml',
);
expect(result?.deps).toEqual([
{
packageName: 'blinker',
depName: 'blinker',
datasource: 'pypi',
depType: 'project.dependencies',
skipReason: 'unspecified-version',
registryUrls: [
'https://private-site.org/pypi/simple',
'https://private.pypi.org/simple',
],
},
{
packageName: 'packaging',
depName: 'packaging',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '>=20.9,!=22.0',
registryUrls: [
'https://private-site.org/pypi/simple',
'https://private.pypi.org/simple',
],
},
{
packageName: 'pytest',
datasource: 'pypi',
depType: 'project.optional-dependencies',
currentValue: '>12',
depName: 'pytest',
registryUrls: [
'https://private-site.org/pypi/simple',
'https://private.pypi.org/simple',
],
managerData: { depGroup: 'pytest' },
},
{
packageName: 'pytest-rerunfailures',
datasource: 'pypi',
depType: 'tool.pdm.dev-dependencies',
currentValue: '>=10.2',
depName: 'pytest-rerunfailures',
registryUrls: [
'https://private-site.org/pypi/simple',
'https://private.pypi.org/simple',
],
managerData: { depGroup: 'test' },
},
{
packageName: 'tox-pdm',
datasource: 'pypi',
depType: 'tool.pdm.dev-dependencies',
currentValue: '>=0.5',
depName: 'tox-pdm',
registryUrls: [
'https://private-site.org/pypi/simple',
'https://private.pypi.org/simple',
],
managerData: { depGroup: 'tox' },
},
]);
});
it('should return dependencies with original pypi registryUrl', async () => {
const result = await extractPackageFile(
codeBlock`
[project]
dependencies = [
"packaging>=20.9,!=22.0",
]
[[tool.pdm.source]]
url = "https://private-site.org/pypi/simple"
verify_ssl = true
name = "internal"
`,
'pyproject.toml',
);
expect(result?.deps).toEqual([
{
packageName: 'packaging',
depName: 'packaging',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '>=20.9,!=22.0',
registryUrls: [
'https://pypi.org/pypi/',
'https://private-site.org/pypi/simple',
],
},
]);
});
it('should skip dependencies with unsupported uv sources', async () => {
const result = await extractPackageFile(
codeBlock`
[project]
dependencies = [
"dep1",
"dep2",
"dep3",
"dep4",
"dep5",
"dep6",
"dep-with_NORMALIZATION",
]
[tool.uv.sources]
dep2 = { git = "https://github.com/foo/bar" }
dep3 = { path = "/local-dep.whl" }
dep4 = { url = "https://example.com" }
dep5 = { workspace = true }
dep_WITH-normalization = { workspace = true }
`,
'pyproject.toml',
);
expect(result?.deps).toMatchObject([
{
depName: 'dep1',
},
{
depName: 'dep2',
depType: depTypes.uvSources,
datasource: GitRefsDatasource.id,
packageName: 'https://github.com/foo/bar',
currentValue: undefined,
skipReason: 'unspecified-version',
},
{
depName: 'dep3',
depType: depTypes.uvSources,
skipReason: 'path-dependency',
},
{
depName: 'dep4',
depType: depTypes.uvSources,
skipReason: 'unsupported-url',
},
{
depName: 'dep5',
depType: depTypes.uvSources,
skipReason: 'inherited-dependency',
},
{
depName: 'dep6',
},
{
depName: 'dep-with_NORMALIZATION',
depType: depTypes.uvSources,
skipReason: 'inherited-dependency',
},
]);
});
it('should extract dependencies from hatch environments', async () => {
const hatchPyProject = Fixtures.get('pyproject_with_hatch.toml');
const result = await extractPackageFile(hatchPyProject, 'pyproject.toml');
expect(result?.deps).toEqual([
{
currentValue: '==2.30.0',
currentVersion: '2.30.0',
datasource: 'pypi',
depName: 'requests',
depType: 'project.dependencies',
packageName: 'requests',
},
{
datasource: 'pypi',
depName: 'hatchling',
depType: 'build-system.requires',
packageName: 'hatchling',
skipReason: 'unspecified-version',
},
{
currentValue: '==6.5',
currentVersion: '6.5',
datasource: 'pypi',
depName: 'coverage',
depType: 'tool.hatch.envs.default',
packageName: 'coverage',
},
{
datasource: 'pypi',
depName: 'pytest',
depType: 'tool.hatch.envs.default',
packageName: 'pytest',
skipReason: 'unspecified-version',
},
{
currentValue: '>=23.1.0',
datasource: 'pypi',
depName: 'black',
depType: 'tool.hatch.envs.lint',
packageName: 'black',
},
{
datasource: 'pypi',
depName: 'baz',
depType: 'tool.hatch.envs.experimental',
packageName: 'baz',
skipReason: 'unspecified-version',
},
]);
});
it('should extract project version', async () => {
const content = codeBlock`
[project]
name = "test"
version = "0.0.2"
dependencies = [ "requests==2.30.0" ]
`;
const res = await extractPackageFile(content, 'pyproject.toml');
expect(res?.packageFileVersion).toBe('0.0.2');
});
it('should extract dependencies from build-system.requires', async () => {
const content = codeBlock`
[build-system]
requires = ["hatchling==1.18.0", "setuptools==69.0.3"]
build-backend = "hatchling.build"
[project]
name = "test"
version = "0.0.2"
dependencies = [ "requests==2.30.0" ]
`;
const result = await extractPackageFile(content, 'pyproject.toml');
expect(result?.deps).toEqual([
{
currentValue: '==2.30.0',
currentVersion: '2.30.0',
datasource: 'pypi',
depName: 'requests',
depType: 'project.dependencies',
packageName: 'requests',
},
{
currentValue: '==1.18.0',
currentVersion: '1.18.0',
datasource: 'pypi',
depName: 'hatchling',
depType: 'build-system.requires',
packageName: 'hatchling',
},
{
currentValue: '==69.0.3',
currentVersion: '69.0.3',
datasource: 'pypi',
depName: 'setuptools',
depType: 'build-system.requires',
packageName: 'setuptools',
},
]);
});
it('should resolve lockedVersions from pdm.lock', async () => {
fs.readLocalFile.mockResolvedValue(
Fixtures.get('pyproject_pdm_lockedversion.lock'),
);
const res = await extractPackageFile(
Fixtures.get('pyproject_pdm_lockedversion.toml'),
'pyproject.toml',
);
expect(res).toMatchObject({
extractedConstraints: { python: '>=3.11' },
deps: [
{
packageName: 'jwcrypto',
depName: 'jwcrypto',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '>=1.4.1',
lockedVersion: '1.4.1',
},
{
packageName: 'pdm-backend',
depName: 'pdm-backend',
datasource: 'pypi',
depType: 'build-system.requires',
skipReason: 'unspecified-version',
},
],
});
});
it('should resolve lockedVersions from uv.lock', async () => {
fs.readLocalFile.mockResolvedValue(
codeBlock`
version = 1
requires-python = ">=3.11"
[[package]]
name = "attrs"
version = "24.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 },
]
[[package]]
name = "pep621-uv"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "attrs" },
]
[package.metadata]
requires-dist = [{ name = "attrs", specifier = ">=24.1.0" }]
`,
);
const res = await extractPackageFile(
codeBlock`
[project]
name = "pep621-uv"
version = "0.1.0"
dependencies = ["attrs>=24.1.0"]
requires-python = ">=3.11"
`,
'pyproject.toml',
);
expect(res).toMatchObject({
extractedConstraints: { python: '>=3.11' },
deps: [
{
packageName: 'attrs',
depName: 'attrs',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '>=24.1.0',
lockedVersion: '24.2.0',
},
],
});
});
it('should resolve dependencies without locked versions on invalid uv.lock', async () => {
fs.readLocalFile.mockResolvedValue(codeBlock`invalid_toml`);
const res = await extractPackageFile(
codeBlock`
[project]
name = "pep621-uv"
version = "0.1.0"
dependencies = ["attrs>=24.1.0"]
requires-python = ">=3.11"
`,
'pyproject.toml',
);
expect(res).toMatchObject({
extractedConstraints: { python: '>=3.11' },
deps: [
{
packageName: 'attrs',
depName: 'attrs',
datasource: 'pypi',
depType: 'project.dependencies',
currentValue: '>=24.1.0',
},
],
});
});
});
});