mirror of https://github.com/renovatebot/renovate
557 lines
17 KiB
TypeScript
557 lines
17 KiB
TypeScript
import _findUp from 'find-up';
|
|
import fs from 'fs-extra';
|
|
import type { DirectoryResult } from 'tmp-promise';
|
|
import tmp from 'tmp-promise';
|
|
import { join, resolve } from 'upath';
|
|
import { mockedFunction } from '../../../test/util';
|
|
import { GlobalConfig } from '../../config/global';
|
|
import {
|
|
cachePathExists,
|
|
cachePathIsFile,
|
|
chmodLocalFile,
|
|
createCacheReadStream,
|
|
createCacheWriteStream,
|
|
deleteLocalFile,
|
|
ensureCacheDir,
|
|
ensureDir,
|
|
ensureLocalDir,
|
|
findLocalSiblingOrParent,
|
|
findUpLocal,
|
|
getLocalFiles,
|
|
getParentDir,
|
|
getSiblingFileName,
|
|
isValidLocalPath,
|
|
listCacheDir,
|
|
localPathExists,
|
|
localPathIsFile,
|
|
localPathIsSymbolicLink,
|
|
outputCacheFile,
|
|
privateCacheDir,
|
|
readCacheFile,
|
|
readLocalDirectory,
|
|
readLocalFile,
|
|
readLocalSymlink,
|
|
readSystemFile,
|
|
renameLocalFile,
|
|
rmCache,
|
|
statCacheFile,
|
|
statLocalFile,
|
|
writeLocalFile,
|
|
writeSystemFile,
|
|
} from '.';
|
|
|
|
jest.mock('../exec/env');
|
|
jest.mock('find-up');
|
|
jest.mock('../git');
|
|
|
|
const findUp = mockedFunction(_findUp);
|
|
|
|
describe('util/fs/index', () => {
|
|
let localDirResult: DirectoryResult;
|
|
let localDir: string;
|
|
|
|
let cacheDirResult: DirectoryResult;
|
|
let cacheDir: string;
|
|
|
|
let tmpDirResult: DirectoryResult;
|
|
let tmpDir: string;
|
|
|
|
beforeEach(async () => {
|
|
localDirResult = await tmp.dir({ unsafeCleanup: true });
|
|
localDir = localDirResult.path;
|
|
|
|
cacheDirResult = await tmp.dir({ unsafeCleanup: true });
|
|
cacheDir = cacheDirResult.path;
|
|
|
|
tmpDirResult = await tmp.dir({ unsafeCleanup: true });
|
|
tmpDir = tmpDirResult.path;
|
|
|
|
GlobalConfig.set({ localDir, cacheDir });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await localDirResult?.cleanup();
|
|
await cacheDirResult?.cleanup();
|
|
await tmpDirResult?.cleanup();
|
|
});
|
|
|
|
describe('getParentDir', () => {
|
|
it.each`
|
|
dir | expected
|
|
${'/foo/bar/'} | ${'/foo'}
|
|
${'/foo/bar'} | ${'/foo'}
|
|
${'/foo/'} | ${'/'}
|
|
${'/foo'} | ${'/'}
|
|
${'foo/bar/'} | ${'foo'}
|
|
${'foo/bar'} | ${'foo'}
|
|
${'foo/'} | ${''}
|
|
${'foo'} | ${''}
|
|
${''} | ${''}
|
|
${'.'} | ${''}
|
|
${'..'} | ${''}
|
|
${'./foo'} | ${'.'}
|
|
${'../foo'} | ${'..'}
|
|
`(`('$dir') -> '$expected'`, ({ dir, expected }) => {
|
|
expect(getParentDir(dir)).toBe(expected);
|
|
});
|
|
});
|
|
|
|
describe('getSiblingFileName', () => {
|
|
it.each`
|
|
file | sibling | expected
|
|
${'/foo/bar'} | ${'baz'} | ${'/foo/baz'}
|
|
${'foo/bar'} | ${'baz'} | ${'foo/baz'}
|
|
${'foo/'} | ${'baz'} | ${'baz'}
|
|
${'foo'} | ${'baz'} | ${'baz'}
|
|
${'./foo'} | ${'baz'} | ${'baz'}
|
|
${'../foo'} | ${'baz'} | ${'../baz'}
|
|
`(`('$file', '$sibling') -> '$expected'`, ({ file, sibling, expected }) => {
|
|
expect(getSiblingFileName(file, sibling)).toBe(expected);
|
|
});
|
|
});
|
|
|
|
describe('readLocalFile', () => {
|
|
it('reads buffer', async () => {
|
|
await fs.outputFile(`${localDir}/file.txt`, 'foobar');
|
|
const res = await readLocalFile('file.txt');
|
|
expect(res).toBeInstanceOf(Buffer);
|
|
});
|
|
|
|
it('reads string', async () => {
|
|
await fs.outputFile(`${localDir}/file.txt`, 'foobar');
|
|
const res = await readLocalFile('file.txt', 'utf8');
|
|
expect(res).toBe('foobar');
|
|
});
|
|
|
|
it('returns null if file is not found', async () => {
|
|
expect(await readLocalFile('foobar')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('writeLocalFile', () => {
|
|
it('outputs file', async () => {
|
|
await writeLocalFile('foo/bar/file.txt', 'foobar');
|
|
|
|
const path = `${localDir}/foo/bar/file.txt`;
|
|
expect(await fs.pathExists(path)).toBeTrue();
|
|
expect(await fs.readFile(path, 'utf8')).toBe('foobar');
|
|
});
|
|
});
|
|
|
|
describe('deleteLocalFile', () => {
|
|
it('throws if platform is local', async () => {
|
|
GlobalConfig.set({ platform: 'local' });
|
|
await expect(deleteLocalFile('foo/bar/file.txt')).rejects.toThrow();
|
|
});
|
|
|
|
it('deletes file', async () => {
|
|
const filePath = `${localDir}/foo/bar/file.txt`;
|
|
await fs.outputFile(filePath, 'foobar');
|
|
|
|
expect(await fs.pathExists(filePath)).toBeTrue();
|
|
await deleteLocalFile('foo/bar/file.txt');
|
|
expect(await fs.pathExists(filePath)).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('renameLocalFile', () => {
|
|
it('renames file', async () => {
|
|
const sourcePath = `${localDir}/foo.txt`;
|
|
const targetPath = `${localDir}/bar.txt`;
|
|
await fs.outputFile(sourcePath, 'foobar');
|
|
|
|
expect(await fs.pathExists(sourcePath)).toBeTrue();
|
|
expect(await fs.pathExists(targetPath)).toBeFalse();
|
|
await renameLocalFile('foo.txt', 'bar.txt');
|
|
expect(await fs.pathExists(sourcePath)).toBeFalse();
|
|
expect(await fs.pathExists(targetPath)).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('ensureDir', () => {
|
|
it('creates directory', async () => {
|
|
const path = `${localDir}/foo/bar`;
|
|
|
|
await ensureDir(path);
|
|
const stat = await fs.stat(path);
|
|
expect(stat.isDirectory()).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('ensureLocalDir', () => {
|
|
it('creates local directory', async () => {
|
|
const path = `${localDir}/foo/bar`;
|
|
|
|
await ensureLocalDir('foo/bar');
|
|
const stat = await fs.stat(path);
|
|
expect(stat.isDirectory()).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('ensureCacheDir', () => {
|
|
it('prefers environment variables over global config', async () => {
|
|
const res = await ensureCacheDir('bundler');
|
|
const path = join(cacheDir, 'others/bundler');
|
|
expect(res).toEqual(path);
|
|
expect(await fs.pathExists(path)).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('privateCacheDir', () => {
|
|
it('returns cache dir', () => {
|
|
GlobalConfig.set({ cacheDir: '/tmp/foo/bar' });
|
|
expect(privateCacheDir()).toBe(`/tmp/foo/bar/__renovate-private-cache`);
|
|
});
|
|
});
|
|
|
|
describe('localPathExists', () => {
|
|
it('returns true for file', async () => {
|
|
const path = `${localDir}/file.txt`;
|
|
await fs.outputFile(path, 'foobar');
|
|
expect(await localPathExists('file.txt')).toBeTrue();
|
|
});
|
|
|
|
it('returns true for directory', async () => {
|
|
expect(await localPathExists('.')).toBeTrue();
|
|
});
|
|
|
|
it('returns false', async () => {
|
|
expect(await localPathExists('file.txt')).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('isLocalPath', () => {
|
|
it('returns true for valid local path', () => {
|
|
expect(isValidLocalPath('./foo/...')).toBeTrue();
|
|
});
|
|
|
|
it('returns false', () => {
|
|
expect(isValidLocalPath('/file.txt')).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('readLocalSymlink', () => {
|
|
it('reads symlink', async () => {
|
|
await writeLocalFile('test/test.txt', '');
|
|
await fs.symlink(
|
|
join(localDir, 'test/test.txt'),
|
|
join(localDir, 'test/test'),
|
|
);
|
|
|
|
const result = await readLocalSymlink('test/test');
|
|
|
|
expect(result).not.toBeNull();
|
|
});
|
|
|
|
it('return null when link not exists', async () => {
|
|
await writeLocalFile('test/test.txt', '');
|
|
await fs.symlink(
|
|
join(localDir, 'test/test.txt'),
|
|
join(localDir, 'test/test'),
|
|
);
|
|
|
|
const notExistsResult = await readLocalSymlink('test/not-exists');
|
|
|
|
expect(notExistsResult).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('findLocalSiblingOrParent', () => {
|
|
it('returns path for file', async () => {
|
|
await writeLocalFile('crates/one/Cargo.toml', 'foo');
|
|
await writeLocalFile('Cargo.lock', 'bar');
|
|
|
|
expect(
|
|
await findLocalSiblingOrParent('crates/one/Cargo.toml', 'Cargo.lock'),
|
|
).toBe('Cargo.lock');
|
|
expect(
|
|
await findLocalSiblingOrParent('crates/one/Cargo.toml', 'Cargo.mock'),
|
|
).toBeNull();
|
|
|
|
await writeLocalFile('crates/one/Cargo.lock', '');
|
|
|
|
expect(
|
|
await findLocalSiblingOrParent('crates/one/Cargo.toml', 'Cargo.lock'),
|
|
).toBe('crates/one/Cargo.lock');
|
|
expect(await findLocalSiblingOrParent('crates/one', 'Cargo.lock')).toBe(
|
|
'Cargo.lock',
|
|
);
|
|
expect(
|
|
await findLocalSiblingOrParent('crates/one/Cargo.toml', 'Cargo.mock'),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('immediately returns null when either path is absolute', async () => {
|
|
expect(await findLocalSiblingOrParent('/etc/hosts', 'other')).toBeNull();
|
|
expect(await findLocalSiblingOrParent('other', '/etc/hosts')).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('readLocalDirectory', () => {
|
|
it('returns dir content', async () => {
|
|
await writeLocalFile('test/Cargo.toml', '');
|
|
await writeLocalFile('test/Cargo.lock', '');
|
|
|
|
const result = await readLocalDirectory('test');
|
|
expect(result).not.toBeNull();
|
|
expect(result).toBeArrayOfSize(2);
|
|
expect(result).toMatchSnapshot();
|
|
|
|
await writeLocalFile('Cargo.lock', '');
|
|
await writeLocalFile('test/subdir/Cargo.lock', '');
|
|
|
|
const resultWithAdditionalFiles = await readLocalDirectory('test');
|
|
expect(resultWithAdditionalFiles).not.toBeNull();
|
|
expect(resultWithAdditionalFiles).toBeArrayOfSize(3);
|
|
expect(resultWithAdditionalFiles).toMatchSnapshot();
|
|
});
|
|
|
|
it('return empty array for non existing directory', async () => {
|
|
await expect(readLocalDirectory('somedir')).rejects.toThrow();
|
|
});
|
|
|
|
it('return empty array for a existing but empty directory', async () => {
|
|
await ensureLocalDir('somedir');
|
|
const result = await readLocalDirectory('somedir');
|
|
expect(result).not.toBeNull();
|
|
expect(result).toBeArrayOfSize(0);
|
|
});
|
|
});
|
|
|
|
describe('createCacheWriteStream', () => {
|
|
it('creates write stream', async () => {
|
|
const path = `${cacheDir}/file.txt`;
|
|
await fs.outputFile(path, 'foo');
|
|
|
|
const stream = createCacheWriteStream('file.txt');
|
|
expect(stream).toBeInstanceOf(fs.WriteStream);
|
|
|
|
const write = new Promise((resolve, reject) => {
|
|
stream.write('bar');
|
|
stream.close(resolve);
|
|
});
|
|
await write;
|
|
expect(await fs.readFile(path, 'utf8')).toBe('bar');
|
|
});
|
|
});
|
|
|
|
describe('createCacheReadStream', () => {
|
|
it('creates read stream', async () => {
|
|
const path = `${cacheDir}/file.txt`;
|
|
const fileContent = 'foo';
|
|
await fs.outputFile(path, fileContent);
|
|
|
|
const stream = createCacheReadStream('file.txt');
|
|
expect(stream).toBeInstanceOf(fs.ReadStream);
|
|
|
|
let data = '';
|
|
stream.on('data', (chunk) => {
|
|
data += chunk.toString();
|
|
});
|
|
|
|
await new Promise((resolve, reject) => {
|
|
stream.on('end', resolve);
|
|
stream.on('error', reject);
|
|
});
|
|
|
|
expect(data).toBe(fileContent);
|
|
});
|
|
});
|
|
|
|
describe('localPathIsFile', () => {
|
|
it('returns true for file', async () => {
|
|
const path = `${localDir}/file.txt`;
|
|
await fs.outputFile(path, 'foo');
|
|
expect(await localPathIsFile('file.txt')).toBeTrue();
|
|
});
|
|
|
|
it('returns false for directory', async () => {
|
|
const path = resolve(`${localDir}/foobar`);
|
|
await fs.mkdir(path);
|
|
expect(await localPathIsFile(path)).toBeFalse();
|
|
});
|
|
|
|
it('returns false for non-existing path', async () => {
|
|
expect(await localPathIsFile(resolve(`${localDir}/foobar`))).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('localPathIsSymbolicLink', () => {
|
|
it('returns false for file', async () => {
|
|
const path = `${localDir}/file.txt`;
|
|
await fs.outputFile(path, 'foobar');
|
|
expect(await localPathIsSymbolicLink(path)).toBeFalse();
|
|
});
|
|
|
|
it('returns false for directory', async () => {
|
|
const path = `${localDir}/foobar`;
|
|
await fs.mkdir(path);
|
|
expect(await localPathIsSymbolicLink(path)).toBeFalse();
|
|
});
|
|
|
|
it('returns false for non-existing path', async () => {
|
|
const path = `${localDir}/file.txt`;
|
|
expect(await localPathIsSymbolicLink(path)).toBeFalse();
|
|
});
|
|
|
|
it('returns true for symlink', async () => {
|
|
const source = `${localDir}/test/test.txt`;
|
|
const target = `${localDir}/test/test`;
|
|
await fs.outputFile(source, 'foobar');
|
|
await fs.symlink(source, target);
|
|
|
|
const result = await localPathIsSymbolicLink('test/test');
|
|
expect(result).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('findUpLocal', () => {
|
|
beforeEach(() => {
|
|
GlobalConfig.set({ localDir: '/abs/path/to/local/dir' });
|
|
});
|
|
|
|
it('returns relative path for file', async () => {
|
|
findUp.mockResolvedValueOnce('/abs/path/to/local/dir/subdir/file.json');
|
|
const res = await findUpLocal('file.json', 'subdir/subdir2');
|
|
expect(res).toBe('subdir/file.json');
|
|
});
|
|
|
|
it('returns null if nothing found', async () => {
|
|
findUp.mockResolvedValueOnce(undefined);
|
|
const res = await findUpLocal('file.json', 'subdir/subdir2');
|
|
expect(res).toBeNull();
|
|
});
|
|
|
|
it('returns undefined if found a file outside of localDir', async () => {
|
|
findUp.mockResolvedValueOnce('/abs/path/to/file.json');
|
|
const res = await findUpLocal('file.json', 'subdir/subdir2');
|
|
expect(res).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('chmodLocalFile', () => {
|
|
it('changes file mode', async () => {
|
|
await writeLocalFile('foo', 'bar');
|
|
let stat = await statLocalFile('foo');
|
|
const oldMode = stat!.mode & 0o777;
|
|
const newMode = oldMode & 0o555; // Remove `write` attributes (Windows-compatible)
|
|
|
|
await chmodLocalFile('foo', newMode);
|
|
stat = await statLocalFile('foo');
|
|
expect(stat!.mode & 0o777).toBe(newMode);
|
|
|
|
await chmodLocalFile('foo', oldMode);
|
|
stat = await statLocalFile('foo');
|
|
expect(stat!.mode & 0o777).toBe(oldMode);
|
|
});
|
|
});
|
|
|
|
describe('statLocalFile', () => {
|
|
it('returns stat object', async () => {
|
|
expect(await statLocalFile('foo')).toBeNull();
|
|
|
|
await writeLocalFile('foo', 'bar');
|
|
const stat = await statLocalFile('foo');
|
|
expect(stat).toBeTruthy();
|
|
expect(stat!.isFile()).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('statCacheFile', () => {
|
|
it('returns stat object', async () => {
|
|
expect(await statCacheFile('foo')).toBeNull();
|
|
|
|
await fs.outputFile(`${cacheDir}/foo`, 'foobar');
|
|
const stat = await statCacheFile('foo');
|
|
expect(stat).toBeTruthy();
|
|
expect(stat!.isFile()).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('listCacheDir', () => {
|
|
it('lists directory', async () => {
|
|
await fs.outputFile(`${cacheDir}/foo/bar.txt`, 'foobar');
|
|
expect(await listCacheDir('foo')).toEqual(['bar.txt']);
|
|
});
|
|
});
|
|
|
|
describe('rmCache', () => {
|
|
it('removes cache dir', async () => {
|
|
await fs.outputFile(`${cacheDir}/foo/bar/file.txt`, 'foobar');
|
|
await rmCache(`foo/bar`);
|
|
expect(await fs.pathExists(`${cacheDir}/foo/bar/file.txt`)).toBeFalse();
|
|
expect(await fs.pathExists(`${cacheDir}/foo/bar`)).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('cachePathExists', () => {
|
|
it('reads file', async () => {
|
|
await fs.outputFile(`${cacheDir}/foo/bar/file.txt`, 'foobar');
|
|
expect(await cachePathExists(`foo/bar/file.txt1`)).toBeFalse();
|
|
expect(await cachePathExists(`foo/bar/file.txt`)).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('cachePathIsFile', () => {
|
|
it('returns false if does not exist', async () => {
|
|
await expect(cachePathIsFile(`a/a/file.txt`)).resolves.toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('readCacheFile', () => {
|
|
it('reads file', async () => {
|
|
await fs.outputFile(`${cacheDir}/foo/bar/file.txt`, 'foobar');
|
|
expect(await readCacheFile(`foo/bar/file.txt`, 'utf8')).toBe('foobar');
|
|
expect(await readCacheFile(`foo/bar/file.txt`)).toEqual(
|
|
Buffer.from('foobar'),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('outputCacheFile', () => {
|
|
it('outputs file', async () => {
|
|
await outputCacheFile('file.txt', 'foobar');
|
|
const res = await fs.readFile(`${cacheDir}/file.txt`, 'utf8');
|
|
expect(res).toBe('foobar');
|
|
});
|
|
});
|
|
|
|
describe('readSystemFile', () => {
|
|
it('reads file', async () => {
|
|
const path = `${tmpDir}/file.txt`;
|
|
await fs.outputFile(path, 'foobar', { encoding: 'utf8' });
|
|
expect(await readSystemFile(path, 'utf8')).toBe('foobar');
|
|
expect(await readSystemFile(path)).toEqual(Buffer.from('foobar'));
|
|
});
|
|
});
|
|
|
|
describe('writeSystemFile', () => {
|
|
it('writes file', async () => {
|
|
const path = `${tmpDir}/file.txt`;
|
|
await writeSystemFile(path, 'foobar');
|
|
expect(await readSystemFile(path)).toEqual(Buffer.from('foobar'));
|
|
});
|
|
});
|
|
|
|
describe('getLocalFiles', () => {
|
|
it('reads list of files from local fs', async () => {
|
|
const fileContentMap = {
|
|
file1: 'foobar',
|
|
file2: 'foobar2',
|
|
};
|
|
|
|
await fs.outputFile(`${localDir}/file1`, fileContentMap.file1);
|
|
await fs.outputFile(`${localDir}/file2`, fileContentMap.file2);
|
|
const res = await getLocalFiles(Object.keys(fileContentMap));
|
|
expect(res).toStrictEqual(fileContentMap);
|
|
});
|
|
|
|
it('returns null as content if file is not found', async () => {
|
|
const res = await getLocalFiles(['invalidfile']);
|
|
expect(res).toStrictEqual({
|
|
invalidfile: null,
|
|
});
|
|
});
|
|
});
|
|
});
|