Skip to content

Commit 564ee83

Browse files
TobiaszSMLdus7ehrarkinsviceice
authored
feat(conan): Add support for lockfile maintenance (#28174)
Co-authored-by: Damian E <[email protected]> Co-authored-by: Rhys Arkins <[email protected]> Co-authored-by: Michael Kriese <[email protected]> Co-authored-by: Michael Kriese <[email protected]>
1 parent 8a93407 commit 564ee83

File tree

5 files changed

+401
-1
lines changed

5 files changed

+401
-1
lines changed

docs/usage/configuration-options.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2352,6 +2352,7 @@ Supported lock files:
23522352
- `Cargo.lock`
23532353
- `Chart.lock`
23542354
- `composer.lock`
2355+
- `conan.lock`
23552356
- `flake.lock`
23562357
- `Gemfile.lock`
23572358
- `gradle.lockfile`
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import { join } from 'upath';
2+
import { GlobalConfig } from '../../../config/global';
3+
import type { RepoGlobalConfig } from '../../../config/types';
4+
import { TEMPORARY_ERROR } from '../../../constants/error-messages';
5+
import { logger } from '../../../logger';
6+
import type { UpdateArtifactsConfig } from '../types';
7+
import * as conan from '.';
8+
import { envMock, mockExecAll } from '~test/exec-util';
9+
import { env, fs } from '~test/util';
10+
11+
vi.mock('../../../util/exec/env');
12+
vi.mock('../../../util/fs');
13+
14+
process.env.CONTAINERBASE = 'true';
15+
const config: UpdateArtifactsConfig = {};
16+
17+
const adminConfig: RepoGlobalConfig = {
18+
localDir: join('/tmp/github/some/repo'),
19+
cacheDir: join('/tmp/cache'),
20+
containerbaseDir: join('/tmp/cache/containerbase'),
21+
dockerSidecarImage: 'ghcr.io/containerbase/sidecar',
22+
};
23+
24+
describe('modules/manager/conan/artifacts', () => {
25+
beforeEach(() => {
26+
env.getChildProcessEnv.mockReturnValue(envMock.basic);
27+
GlobalConfig.set(adminConfig);
28+
});
29+
30+
it('returns null if updatedDeps are empty and lockFileMaintenance is turned off', async () => {
31+
expect(
32+
await conan.updateArtifacts({
33+
packageFileName: 'conanfile.py',
34+
updatedDeps: [],
35+
newPackageFileContent: '',
36+
config,
37+
}),
38+
).toBeNull();
39+
expect(logger.trace).toHaveBeenCalledWith(
40+
'No conan.lock dependencies to update',
41+
);
42+
});
43+
44+
it('returns null if conan.lock was not found', async () => {
45+
const updatedDeps = [
46+
{
47+
depName: 'dep',
48+
},
49+
];
50+
51+
expect(
52+
await conan.updateArtifacts({
53+
packageFileName: 'conanfile.py',
54+
updatedDeps,
55+
newPackageFileContent: '',
56+
config,
57+
}),
58+
).toBeNull();
59+
expect(logger.trace).toHaveBeenCalledWith('No conan.lock found');
60+
});
61+
62+
it('returns null if conan.lock read operation failed', async () => {
63+
const updatedDeps = [
64+
{
65+
depName: 'dep',
66+
},
67+
];
68+
69+
fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock');
70+
71+
expect(
72+
await conan.updateArtifacts({
73+
packageFileName: 'conanfile.py',
74+
updatedDeps,
75+
newPackageFileContent: '',
76+
config,
77+
}),
78+
).toBeNull();
79+
expect(logger.debug).toHaveBeenCalledWith(
80+
'conan.lock read operation failed',
81+
);
82+
});
83+
84+
it('returns null if read operation failed for new conan.lock', async () => {
85+
const updatedDeps = [
86+
{
87+
depName: 'dep',
88+
},
89+
];
90+
const expectedInSnapshot = [
91+
{
92+
cmd: 'conan lock create conanfile.py',
93+
},
94+
];
95+
96+
fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock');
97+
fs.readLocalFile.mockResolvedValueOnce('Original conan.lock');
98+
const execSnapshots = mockExecAll();
99+
100+
expect(
101+
await conan.updateArtifacts({
102+
packageFileName: 'conanfile.py',
103+
updatedDeps,
104+
newPackageFileContent: '',
105+
config,
106+
}),
107+
).toBeNull();
108+
expect(execSnapshots).toMatchObject(expectedInSnapshot);
109+
expect(logger.debug).toHaveBeenCalledWith(
110+
'New conan.lock read operation failed',
111+
);
112+
});
113+
114+
it('returns null if original and updated conan.lock files are the same', async () => {
115+
const updatedDeps = [
116+
{
117+
depName: 'dep',
118+
},
119+
];
120+
const expectedInSnapshot = [
121+
{
122+
cmd: 'conan lock create conanfile.py',
123+
},
124+
];
125+
126+
fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock');
127+
fs.readLocalFile.mockResolvedValueOnce('Original conan.lock');
128+
const execSnapshots = mockExecAll();
129+
fs.readLocalFile.mockResolvedValueOnce('Original conan.lock');
130+
131+
expect(
132+
await conan.updateArtifacts({
133+
packageFileName: 'conanfile.py',
134+
updatedDeps,
135+
newPackageFileContent: '',
136+
config,
137+
}),
138+
).toBeNull();
139+
expect(execSnapshots).toMatchObject(expectedInSnapshot);
140+
expect(logger.trace).toHaveBeenCalledWith('conan.lock is unchanged');
141+
});
142+
143+
it('returns updated conan.lock for conanfile.txt', async () => {
144+
const updatedDeps = [
145+
{
146+
depName: 'dep',
147+
},
148+
];
149+
const expectedInSnapshot = [
150+
{
151+
cmd: 'conan lock create conanfile.txt',
152+
},
153+
];
154+
155+
fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock');
156+
fs.readLocalFile.mockResolvedValueOnce('Original conan.lock');
157+
const execSnapshots = mockExecAll();
158+
fs.readLocalFile.mockResolvedValueOnce('Updated conan.lock');
159+
160+
expect(
161+
await conan.updateArtifacts({
162+
packageFileName: 'conanfile.txt',
163+
updatedDeps,
164+
newPackageFileContent: '',
165+
config,
166+
}),
167+
).toEqual([
168+
{
169+
file: {
170+
contents: 'Updated conan.lock',
171+
path: 'conan.lock',
172+
type: 'addition',
173+
},
174+
},
175+
]);
176+
expect(execSnapshots).toMatchObject(expectedInSnapshot);
177+
});
178+
179+
it('returns updated conan.lock when updateType are not empty', async () => {
180+
const updatedDeps = [
181+
{
182+
depName: 'dep',
183+
},
184+
];
185+
const expectedInSnapshot = [
186+
{
187+
cmd: 'conan lock create conanfile.py',
188+
},
189+
];
190+
191+
fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock');
192+
fs.readLocalFile.mockResolvedValueOnce('Original conan.lock');
193+
const execSnapshots = mockExecAll();
194+
fs.readLocalFile.mockResolvedValueOnce('Updated conan.lock');
195+
196+
expect(
197+
await conan.updateArtifacts({
198+
packageFileName: 'conanfile.py',
199+
updatedDeps,
200+
newPackageFileContent: '',
201+
config,
202+
}),
203+
).toEqual([
204+
{
205+
file: {
206+
contents: 'Updated conan.lock',
207+
path: 'conan.lock',
208+
type: 'addition',
209+
},
210+
},
211+
]);
212+
expect(execSnapshots).toMatchObject(expectedInSnapshot);
213+
});
214+
215+
it('returns updated conan.lock when updateType are empty, but isLockFileMaintenance is true', async () => {
216+
const expectedInSnapshot = [
217+
{
218+
cmd: 'conan lock create conanfile.py --lockfile=""',
219+
},
220+
];
221+
222+
fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock');
223+
fs.readLocalFile.mockResolvedValueOnce('Original conan.lock');
224+
const execSnapshots = mockExecAll();
225+
fs.readLocalFile.mockResolvedValueOnce('Updated conan.lock');
226+
227+
expect(
228+
await conan.updateArtifacts({
229+
packageFileName: 'conanfile.py',
230+
updatedDeps: [],
231+
newPackageFileContent: '',
232+
config: { ...config, isLockFileMaintenance: true },
233+
}),
234+
).toEqual([
235+
{
236+
file: {
237+
contents: 'Updated conan.lock',
238+
path: 'conan.lock',
239+
type: 'addition',
240+
},
241+
},
242+
]);
243+
expect(execSnapshots).toMatchObject(expectedInSnapshot);
244+
});
245+
246+
it('rethrows temporary error', async () => {
247+
const updatedDeps = [
248+
{
249+
depName: 'dep',
250+
},
251+
];
252+
253+
fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock');
254+
fs.readLocalFile.mockResolvedValueOnce('Original conan.lock');
255+
mockExecAll(new Error(TEMPORARY_ERROR));
256+
257+
await expect(
258+
conan.updateArtifacts({
259+
packageFileName: 'conanfile.py',
260+
updatedDeps,
261+
newPackageFileContent: '',
262+
config: { ...config, updateType: 'lockFileMaintenance' },
263+
}),
264+
).rejects.toThrow(TEMPORARY_ERROR);
265+
});
266+
267+
it('returns an artifact error when conan.lock update fails', async () => {
268+
const updatedDeps = [
269+
{
270+
depName: 'dep',
271+
},
272+
];
273+
const errorMessage = 'conan.lock update execution failure message';
274+
275+
fs.findLocalSiblingOrParent.mockResolvedValueOnce('conan.lock');
276+
fs.readLocalFile.mockResolvedValueOnce('Original conan.lock');
277+
mockExecAll(new Error(errorMessage));
278+
279+
expect(
280+
await conan.updateArtifacts({
281+
packageFileName: 'conanfile.py',
282+
updatedDeps,
283+
newPackageFileContent: '',
284+
config: { ...config, updateType: 'lockFileMaintenance' },
285+
}),
286+
).toEqual([
287+
{ artifactError: { lockFile: 'conan.lock', stderr: errorMessage } },
288+
]);
289+
});
290+
});

0 commit comments

Comments
 (0)