Skip to content
Snippets Groups Projects
Unverified Commit 4322ae82 authored by Christoph Ludwig's avatar Christoph Ludwig Committed by GitHub
Browse files

fix(pip_requirements): ignore extras in test for hashes (#16910)

parent ace3095f
No related merge requests found
...@@ -20,9 +20,20 @@ const adminConfig: RepoGlobalConfig = { ...@@ -20,9 +20,20 @@ const adminConfig: RepoGlobalConfig = {
const config: UpdateArtifactsConfig = { constraints: { python: '3.10.2' } }; const config: UpdateArtifactsConfig = { constraints: { python: '3.10.2' } };
const newPackageFileContent = `atomicwrites==1.4.0 \ /*
--hash=sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4 \ * Sample package file content that exhibits dependencies with and without
--hash=sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6`; * "extras" specifications as well as line continuations and additional
* (valid) whitespace.
*/
const newPackageFileContent = `atomicwrites==1.4.0 \\\n\
--hash=sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4 \\\n\
--hash=sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6\n\
boto3-stubs[iam] == 1.24.36.post1 \
--hash=sha256:39acbbc8c87a101bdf46e058fbb012d044b773b43f7ed02cc4c24192a564411e \
--hash=sha256:ca3b3066773fc727fea0dbec252d098098e45fe0def011b22036ef674344def2\n\
botocore==1.27.46 \
--hash=sha256:747b7e94aef41498f063fc0be79c5af102d940beea713965179e1ead89c7e9ec \
--hash=sha256:f66d8305d1f59d83334df9b11b6512bb1e14698ec4d5d6d42f833f39f3304ca7`;
describe('modules/manager/pip_requirements/artifacts', () => { describe('modules/manager/pip_requirements/artifacts', () => {
beforeEach(() => { beforeEach(() => {
...@@ -60,7 +71,7 @@ describe('modules/manager/pip_requirements/artifacts', () => { ...@@ -60,7 +71,7 @@ describe('modules/manager/pip_requirements/artifacts', () => {
expect( expect(
await updateArtifacts({ await updateArtifacts({
packageFileName: 'requirements.txt', packageFileName: 'requirements.txt',
updatedDeps: [{ depName: 'atomicwrites' }], updatedDeps: [{ depName: 'atomicwrites' }, { depName: 'boto3-stubs' }],
newPackageFileContent, newPackageFileContent,
config, config,
}) })
...@@ -71,6 +82,10 @@ describe('modules/manager/pip_requirements/artifacts', () => { ...@@ -71,6 +82,10 @@ describe('modules/manager/pip_requirements/artifacts', () => {
cmd: 'hashin atomicwrites==1.4.0 -r requirements.txt', cmd: 'hashin atomicwrites==1.4.0 -r requirements.txt',
options: { cwd: '/tmp/github/some/repo' }, options: { cwd: '/tmp/github/some/repo' },
}, },
{
cmd: "hashin 'boto3-stubs[iam] == 1.24.36.post1' -r requirements.txt",
options: { cwd: '/tmp/github/some/repo' },
},
]); ]);
}); });
...@@ -80,7 +95,43 @@ describe('modules/manager/pip_requirements/artifacts', () => { ...@@ -80,7 +95,43 @@ describe('modules/manager/pip_requirements/artifacts', () => {
expect( expect(
await updateArtifacts({ await updateArtifacts({
packageFileName: 'requirements.txt', packageFileName: 'requirements.txt',
updatedDeps: [{ depName: 'atomicwrites' }], updatedDeps: [{ depName: 'atomicwrites' }, { depName: 'boto3-stubs' }],
newPackageFileContent,
config,
})
).toEqual([
{
file: {
type: 'addition',
path: 'requirements.txt',
contents: 'new content',
},
},
]);
expect(execSnapshots).toMatchObject([
{
cmd: 'hashin atomicwrites==1.4.0 -r requirements.txt',
options: { cwd: '/tmp/github/some/repo' },
},
{
cmd: "hashin 'boto3-stubs[iam] == 1.24.36.post1' -r requirements.txt",
options: { cwd: '/tmp/github/some/repo' },
},
]);
});
it('ignores falsy depNames', async () => {
fs.readLocalFile.mockResolvedValueOnce('new content');
const execSnapshots = mockExecAll();
expect(
await updateArtifacts({
packageFileName: 'requirements.txt',
updatedDeps: [
{ depName: '' },
{ depName: 'atomicwrites' },
{ depName: undefined },
],
newPackageFileContent, newPackageFileContent,
config, config,
}) })
......
import is from '@sindresorhus/is'; import is from '@sindresorhus/is';
import { quote } from 'shlex';
import { TEMPORARY_ERROR } from '../../../constants/error-messages'; import { TEMPORARY_ERROR } from '../../../constants/error-messages';
import { logger } from '../../../logger'; import { logger } from '../../../logger';
import { exec } from '../../../util/exec'; import { exec } from '../../../util/exec';
import type { ExecOptions } from '../../../util/exec/types'; import type { ExecOptions } from '../../../util/exec/types';
import { ensureCacheDir, readLocalFile } from '../../../util/fs'; import { ensureCacheDir, readLocalFile } from '../../../util/fs';
import { newlineRegex, regEx } from '../../../util/regex'; import { escapeRegExp, regEx } from '../../../util/regex';
import type { UpdateArtifact, UpdateArtifactsResult } from '../types'; import type { UpdateArtifact, UpdateArtifactsResult } from '../types';
import { extrasPattern } from './extract';
/**
* Create a RegExp that matches the first dependency pattern for
* the named dependency that is followed by package hashes.
*
* The regular expression defines a single named group `depConstraint`
* that holds the dependency constraint without the hash specifiers.
* The substring matched by this named group will start with the dependency
* name and end with a non-whitespace character.
*
* @param depName the name of the dependency
*/
function dependencyAndHashPattern(depName: string): RegExp {
const escapedDepName = escapeRegExp(depName);
// extrasPattern covers any whitespace between the dep name and the optional extras specifier,
// but it does not cover any whitespace in front of the equal signs.
//
// Use a non-greedy wildcard for the range pattern; otherwise, we would
// include all but the last hash specifier into depConstraint.
return regEx(
`^\\s*(?<depConstraint>${escapedDepName}${extrasPattern}\\s*==.*?\\S)\\s+--hash=`,
'm'
);
}
export async function updateArtifacts({ export async function updateArtifacts({
packageFileName, packageFileName,
...@@ -21,18 +48,18 @@ export async function updateArtifacts({ ...@@ -21,18 +48,18 @@ export async function updateArtifacts({
try { try {
const cmd: string[] = []; const cmd: string[] = [];
const rewrittenContent = newPackageFileContent.replace(regEx(/\\\n/g), ''); const rewrittenContent = newPackageFileContent.replace(regEx(/\\\n/g), '');
const lines = rewrittenContent
.split(newlineRegex)
.map((line) => line.trim());
for (const dep of updatedDeps) { for (const dep of updatedDeps) {
const hashLine = lines.find( if (!dep.depName) {
(line) => continue;
// TODO: types (#7154) }
line.startsWith(`${dep.depName!}==`) && line.includes('--hash=') const depAndHashMatch = dependencyAndHashPattern(dep.depName).exec(
rewrittenContent
); );
if (hashLine) { if (depAndHashMatch) {
const depConstraint = hashLine.split(' ')[0]; // If there's a match, then the regular expression guarantees
cmd.push(`hashin ${depConstraint} -r ${packageFileName}`); // that the named subgroup deepConstraint did match as well.
const depConstraint = depAndHashMatch.groups!.depConstraint;
cmd.push(`hashin ${quote(depConstraint)} -r ${quote(packageFileName)}`);
} }
} }
if (!cmd.length) { if (!cmd.length) {
......
...@@ -11,7 +11,7 @@ import type { PackageDependency, PackageFile } from '../types'; ...@@ -11,7 +11,7 @@ import type { PackageDependency, PackageFile } from '../types';
export const packagePattern = export const packagePattern =
'[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]'; '[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]';
const extrasPattern = '(?:\\s*\\[[^\\]]+\\])?'; export const extrasPattern = '(?:\\s*\\[[^\\]]+\\])?';
const packageGitRegex = regEx( const packageGitRegex = regEx(
/(?<source>(?:git\+)(?<protocol>git|ssh|https):\/\/(?<gitUrl>(?:(?<user>[^@]+)@)?(?<hostname>[\w.-]+)(?<delimiter>\/)(?<scmPath>.*\/(?<depName>[\w/-]+))(\.git)?(?:@(?<version>.*))))/ /(?<source>(?:git\+)(?<protocol>git|ssh|https):\/\/(?<gitUrl>(?:(?<user>[^@]+)@)?(?<hostname>[\w.-]+)(?<delimiter>\/)(?<scmPath>.*\/(?<depName>[\w/-]+))(\.git)?(?:@(?<version>.*))))/
); );
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment