refactor: simplify split-cache handling

Restore cache entries sequentially, keep saves parallel with per-entry error handling, and simplify the cache test setup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
qmuntalandCopilot committed 2026-11-09 12:29:31 +01:00
1 parent aa2cac01fd
commit 57097ea729
9 files changed
+74 -421

No files matched your search

+1 -144
View File
@@ -1,14 +1,4 @@
import {
jest,
describe,
it,
expect,
beforeEach,
afterEach,
afterAll
} from '@jest/globals';
import fs from 'fs';
import path from 'path';
import {jest, describe, it, expect, beforeEach, afterAll} from '@jest/globals';
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
@@ -212,139 +202,6 @@ describe('getCacheDirectoryPath', () => {
});
});
describe('canRestoreCachesInParallel', () => {
const originalPlatform = Object.getOwnPropertyDescriptor(
process,
'platform'
)!;
const directoryStat = fs.lstatSync(process.cwd());
beforeEach(() => {
Object.defineProperty(process, 'platform', {value: 'linux'});
jest.spyOn(fs, 'lstatSync').mockReturnValue(directoryStat);
jest
.spyOn(fs, 'realpathSync')
.mockImplementation(directory => path.resolve(directory.toString()));
});
afterEach(() => {
Object.defineProperty(process, 'platform', originalPlatform);
jest.restoreAllMocks();
});
it.each([
{modules: 'cache/modules', build: 'cache/build', parallel: true},
{modules: 'cache', build: 'cache-build', parallel: true},
{modules: 'cache', build: 'cache', parallel: false},
{modules: 'cache', build: 'cache/build', parallel: false},
{modules: 'cache/modules', build: 'cache', parallel: false},
{modules: 'cache', build: 'cache/../cache', parallel: false}
])(
'checks overlapping paths: $modules and $build',
({modules, build, parallel}) => {
expect(
cacheUtils.canRestoreCachesInParallel([
{kind: 'modules', path: modules},
{kind: 'build', path: build}
])
).toBe(parallel);
}
);
it('allows concurrent Windows restores with disjoint paths', () => {
Object.defineProperty(process, 'platform', {value: 'win32'});
expect(
cacheUtils.canRestoreCachesInParallel([
{kind: 'modules', path: 'cache/modules'},
{kind: 'build', path: 'cache/build'}
])
).toBe(true);
});
it.each(['darwin', 'win32'])(
'handles case-insensitive paths conservatively on %s',
platform => {
Object.defineProperty(process, 'platform', {value: platform});
expect(
cacheUtils.canRestoreCachesInParallel([
{kind: 'modules', path: 'cache'},
{kind: 'build', path: 'CACHE/build'}
])
).toBe(false);
}
);
it.each([true, false])(
'serializes symlink aliases with missing cache directories (overlap=%s)',
overlap => {
const alias = path.resolve('alias');
const target = path.resolve('target');
jest.spyOn(fs, 'lstatSync').mockImplementation(directory => {
if (directory !== alias && directory !== target) {
throw Object.assign(new Error('Missing path'), {code: 'ENOENT'});
}
return directoryStat;
});
jest.spyOn(fs, 'realpathSync').mockReturnValue(target);
expect(
cacheUtils.canRestoreCachesInParallel([
{kind: 'modules', path: path.join(alias, 'modules')},
{
kind: 'build',
path: path.join(target, overlap ? 'modules/build' : 'build')
}
])
).toBe(false);
}
);
it.each(['nested alias', 'alias through module cache'])(
'serializes restores when the %s resolves outside the module cache',
scenario => {
const modules = path.resolve('modules');
const target = path.resolve('external-build');
const alias = path.join(modules, 'build-link');
const build =
scenario === 'nested alias'
? path.join(alias, 'cache')
: path.resolve('build-alias', 'cache');
jest.spyOn(fs, 'realpathSync').mockImplementation(directory => {
const absolute = path.resolve(directory.toString());
if (absolute === build) {
return path.join(target, 'cache');
}
if (absolute === path.dirname(build)) {
return scenario === 'nested alias' ? target : alias;
}
return absolute;
});
expect(
cacheUtils.canRestoreCachesInParallel([
{kind: 'modules', path: modules},
{kind: 'build', path: build}
])
).toBe(false);
}
);
it('falls back to sequential restores when path resolution fails', () => {
jest.spyOn(fs, 'realpathSync').mockImplementation(() => {
throw new Error('Cannot resolve path');
});
expect(
cacheUtils.canRestoreCachesInParallel([
{kind: 'modules', path: 'cache/modules'},
{kind: 'build', path: 'cache/build'}
])
).toBe(false);
});
});
describe('isCacheFeatureAvailable', () => {
//Arrange
const isFeatureAvailableSpy = cache.isFeatureAvailable as jest.Mock<