mirror of https://github.com/facebook/jest.git
499 lines
15 KiB
TypeScript
499 lines
15 KiB
TypeScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
import {tmpdir} from 'os';
|
|
import * as path from 'path';
|
|
import semver = require('semver');
|
|
import slash = require('slash');
|
|
import {findRepos, getChangedFilesForRoots} from 'jest-changed-files';
|
|
import {cleanup, run, testIfHg, writeFiles} from '../Utils';
|
|
import runJest from '../runJest';
|
|
|
|
const DIR = path.resolve(tmpdir(), 'jest-changed-files-test-dir');
|
|
|
|
const GIT = 'git -c user.name=jest_test -c user.email=jest_test@test.com';
|
|
const HG = 'hg --config ui.username=jest_test';
|
|
|
|
const gitVersionSupportsInitialBranch = (() => {
|
|
const {stdout} = run(`${GIT} --version`);
|
|
const gitVersion = stdout.trim();
|
|
|
|
const match = gitVersion.match(/^git version (?<version>\d+\.\d+\.\d+)/);
|
|
|
|
if (match?.groups?.version == null) {
|
|
throw new Error(`Unable to parse git version from string "${gitVersion}"`);
|
|
}
|
|
|
|
const {version} = match.groups;
|
|
|
|
return semver.gte(version, '2.28.0');
|
|
})();
|
|
|
|
const mainBranchName = gitVersionSupportsInitialBranch ? 'main' : 'master';
|
|
|
|
function gitInit(dir: string) {
|
|
const initCommand = gitVersionSupportsInitialBranch
|
|
? `${GIT} init --initial-branch=${mainBranchName}`
|
|
: `${GIT} init`;
|
|
|
|
run(initCommand, dir);
|
|
}
|
|
|
|
function gitCreateBranch(branchName: string, dir: string) {
|
|
run(`git branch ${branchName}`, dir);
|
|
}
|
|
|
|
jest.retryTimes(3);
|
|
|
|
beforeEach(() => cleanup(DIR));
|
|
afterEach(() => cleanup(DIR));
|
|
|
|
testIfHg('gets hg SCM roots and dedupes them', async () => {
|
|
writeFiles(DIR, {
|
|
'first-repo/file1.txt': 'file1',
|
|
'first-repo/nested-dir/file2.txt': 'file2',
|
|
'first-repo/nested-dir/second-nested-dir/file3.txt': 'file3',
|
|
'second-repo/file1.txt': 'file1',
|
|
'second-repo/nested-dir/file2.txt': 'file2',
|
|
'second-repo/nested-dir/second-nested-dir/file3.txt': 'file3',
|
|
});
|
|
|
|
run(`${HG} init`, path.resolve(DIR, 'first-repo'));
|
|
run(`${HG} init`, path.resolve(DIR, 'second-repo'));
|
|
|
|
const roots = [
|
|
'',
|
|
'first-repo/nested-dir',
|
|
'first-repo/nested-dir/second-nested-dir',
|
|
'second-repo/nested-dir',
|
|
'second-repo/nested-dir/second-nested-dir',
|
|
].map(filename => path.resolve(DIR, filename));
|
|
|
|
const repos = await findRepos(roots);
|
|
expect(repos.git.size).toBe(0);
|
|
|
|
const hgRepos = Array.from(repos.hg);
|
|
|
|
// it's not possible to match the exact path because it will resolve
|
|
// differently on different platforms.
|
|
// NOTE: This test can break if you have a .hg repo initialized inside your
|
|
// os tmp directory.
|
|
expect(hgRepos).toHaveLength(2);
|
|
expect(slash(hgRepos[0])).toMatch(
|
|
/\/jest-changed-files-test-dir\/first-repo\/?$/,
|
|
);
|
|
expect(slash(hgRepos[1])).toMatch(
|
|
/\/jest-changed-files-test-dir\/second-repo\/?$/,
|
|
);
|
|
});
|
|
|
|
test('gets git SCM roots and dedupes them', async () => {
|
|
writeFiles(DIR, {
|
|
'first-repo/file1.txt': 'file1',
|
|
'first-repo/nested-dir/file2.txt': 'file2',
|
|
'first-repo/nested-dir/second-nested-dir/file3.txt': 'file3',
|
|
'second-repo/file1.txt': 'file1',
|
|
'second-repo/nested-dir/file2.txt': 'file2',
|
|
'second-repo/nested-dir/second-nested-dir/file3.txt': 'file3',
|
|
});
|
|
|
|
gitInit(path.resolve(DIR, 'first-repo'));
|
|
gitInit(path.resolve(DIR, 'second-repo'));
|
|
|
|
const roots = [
|
|
'',
|
|
'first-repo/nested-dir',
|
|
'first-repo/nested-dir/second-nested-dir',
|
|
'second-repo/nested-dir',
|
|
'second-repo/nested-dir/second-nested-dir',
|
|
].map(filename => path.resolve(DIR, filename));
|
|
|
|
const repos = await findRepos(roots);
|
|
expect(repos.hg.size).toBe(0);
|
|
const gitRepos = Array.from(repos.git);
|
|
|
|
// it's not possible to match the exact path because it will resolve
|
|
// differently on different platforms.
|
|
// NOTE: This test can break if you have a .git repo initialized inside your
|
|
// os tmp directory.
|
|
expect(gitRepos).toHaveLength(2);
|
|
expect(slash(gitRepos[0])).toMatch(
|
|
/\/jest-changed-files-test-dir\/first-repo\/?$/,
|
|
);
|
|
expect(slash(gitRepos[1])).toMatch(
|
|
/\/jest-changed-files-test-dir\/second-repo\/?$/,
|
|
);
|
|
});
|
|
|
|
testIfHg('gets mixed git and hg SCM roots and dedupes them', async () => {
|
|
writeFiles(DIR, {
|
|
'first-repo/file1.txt': 'file1',
|
|
'first-repo/nested-dir/file2.txt': 'file2',
|
|
'first-repo/nested-dir/second-nested-dir/file3.txt': 'file3',
|
|
'second-repo/file1.txt': 'file1',
|
|
'second-repo/nested-dir/file2.txt': 'file2',
|
|
'second-repo/nested-dir/second-nested-dir/file3.txt': 'file3',
|
|
});
|
|
|
|
gitInit(path.resolve(DIR, 'first-repo'));
|
|
run(`${HG} init`, path.resolve(DIR, 'second-repo'));
|
|
|
|
const roots = [
|
|
'',
|
|
'first-repo/nested-dir',
|
|
'first-repo/nested-dir/second-nested-dir',
|
|
'second-repo/nested-dir',
|
|
'second-repo/nested-dir/second-nested-dir',
|
|
].map(filename => path.resolve(DIR, filename));
|
|
|
|
const repos = await findRepos(roots);
|
|
const hgRepos = Array.from(repos.hg);
|
|
const gitRepos = Array.from(repos.git);
|
|
|
|
// NOTE: This test can break if you have a .git or .hg repo initialized
|
|
// inside your os tmp directory.
|
|
expect(gitRepos).toHaveLength(1);
|
|
expect(hgRepos).toHaveLength(1);
|
|
expect(slash(gitRepos[0])).toMatch(
|
|
/\/jest-changed-files-test-dir\/first-repo\/?$/,
|
|
);
|
|
expect(slash(hgRepos[0])).toMatch(
|
|
/\/jest-changed-files-test-dir\/second-repo\/?$/,
|
|
);
|
|
});
|
|
|
|
test('gets changed files for git', async () => {
|
|
writeFiles(DIR, {
|
|
'file1.txt': 'file1',
|
|
'nested-dir/file2.txt': 'file2',
|
|
'nested-dir/second-nested-dir/file3.txt': 'file3',
|
|
});
|
|
|
|
gitInit(DIR);
|
|
|
|
const roots = [
|
|
// same first root name with existing branch name makes pitfall that
|
|
// causes "ambiguous argument" git error.
|
|
'nested-dir',
|
|
'nested-dir/second-nested-dir',
|
|
'',
|
|
].map(filename => path.resolve(DIR, filename));
|
|
|
|
let {changedFiles: files} = await getChangedFilesForRoots(roots, {});
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file1.txt', 'file2.txt', 'file3.txt']);
|
|
|
|
run(`${GIT} add .`, DIR);
|
|
|
|
// Uses multiple `-m` to make the commit message have multiple
|
|
// paragraphs. This is done to ensure that `changedFiles` only
|
|
// returns files and not parts of commit messages.
|
|
run(`${GIT} commit --no-gpg-sign -m "test" -m "extra-line"`, DIR);
|
|
|
|
gitCreateBranch('nested-dir', DIR);
|
|
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {}));
|
|
expect(Array.from(files)).toEqual([]);
|
|
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {
|
|
lastCommit: true,
|
|
}));
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file1.txt', 'file2.txt', 'file3.txt']);
|
|
|
|
writeFiles(DIR, {
|
|
'file1.txt': 'modified file1',
|
|
});
|
|
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {}));
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file1.txt']);
|
|
|
|
run(`${GIT} add -A`, DIR);
|
|
|
|
// staged files should be included
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {}));
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file1.txt']);
|
|
|
|
run(`${GIT} commit --no-gpg-sign -am "test2"`, DIR);
|
|
|
|
writeFiles(DIR, {
|
|
'file4.txt': 'file4',
|
|
});
|
|
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {
|
|
withAncestor: true,
|
|
}));
|
|
// Returns files from current uncommitted state + the last commit
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file1.txt', 'file4.txt']);
|
|
|
|
run(`${GIT} add file4.txt`, DIR);
|
|
run(`${GIT} commit --no-gpg-sign -m "test3"`, DIR);
|
|
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {
|
|
changedSince: 'HEAD^^',
|
|
}));
|
|
// Returns files from the last 2 commits
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file1.txt', 'file4.txt']);
|
|
|
|
run(`${GIT} checkout HEAD^^ -b feature-branch`, DIR);
|
|
|
|
writeFiles(DIR, {
|
|
'file5.txt': 'file5',
|
|
});
|
|
run(`${GIT} add file5.txt`, DIR);
|
|
run(`${GIT} commit --no-gpg-sign -m "test5"`, DIR);
|
|
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {
|
|
changedSince: mainBranchName,
|
|
}));
|
|
// Returns files from this branch but not ones that only exist on mainBranchName
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file5.txt']);
|
|
});
|
|
|
|
test('monitors only root paths for git', async () => {
|
|
writeFiles(DIR, {
|
|
'file1.txt': 'file1',
|
|
'nested-dir/file2.txt': 'file2',
|
|
'nested-dir/second-nested-dir/file3.txt': 'file3',
|
|
});
|
|
|
|
gitInit(DIR);
|
|
|
|
const roots = [path.resolve(DIR, 'nested-dir')];
|
|
|
|
const {changedFiles: files} = await getChangedFilesForRoots(roots, {});
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file2.txt', 'file3.txt']);
|
|
});
|
|
|
|
it('does not find changes in files with no diff, for git', async () => {
|
|
const roots = [path.resolve(DIR)];
|
|
|
|
// create an empty file, commit it to "mainBranchName"
|
|
writeFiles(DIR, {'file1.txt': ''});
|
|
gitInit(DIR);
|
|
run(`${GIT} add file1.txt`, DIR);
|
|
run(`${GIT} commit --no-gpg-sign -m "initial"`, DIR);
|
|
|
|
// check out a new branch, jestChangedFilesSpecBase, to use later in diff
|
|
run(`${GIT} checkout -b jestChangedFilesSpecBase`, DIR);
|
|
|
|
// check out second branch, jestChangedFilesSpecMod, modify file & commit
|
|
run(`${GIT} checkout -b jestChangedFilesSpecMod`, DIR);
|
|
writeFiles(DIR, {
|
|
'file1.txt': 'modified file1',
|
|
});
|
|
run(`${GIT} add file1.txt`, DIR);
|
|
run(`${GIT} commit --no-gpg-sign -m "modified"`, DIR);
|
|
|
|
// still on jestChangedFilesSpecMod branch, "revert" back to empty file and commit
|
|
writeFiles(DIR, {
|
|
'file1.txt': '',
|
|
});
|
|
run(`${GIT} add file1.txt`, DIR);
|
|
run(`${GIT} commit --no-gpg-sign -m "removemod"`, DIR);
|
|
|
|
// check that passing in no changedSince arg doesn't return any unstaged / other changes
|
|
const {changedFiles: files} = await getChangedFilesForRoots(roots, {});
|
|
expect(Array.from(files)).toEqual([]);
|
|
|
|
// check that in diff from `jestChangedFilesSpecBase` branch, no changed files are reported
|
|
const {changedFiles: filesExplicitBaseBranch} = await getChangedFilesForRoots(
|
|
roots,
|
|
{
|
|
changedSince: 'jestChangedFilesSpecBase',
|
|
},
|
|
);
|
|
expect(Array.from(filesExplicitBaseBranch)).toEqual([]);
|
|
});
|
|
|
|
test('handles a bad revision for "changedSince", for git', async () => {
|
|
writeFiles(DIR, {
|
|
'.watchmanconfig': '',
|
|
'__tests__/file1.test.js': "require('../file1'); test('file1', () => {});",
|
|
'file1.js': 'module.exports = {}',
|
|
'package.json': '{}',
|
|
});
|
|
|
|
gitInit(DIR);
|
|
run(`${GIT} add .`, DIR);
|
|
run(`${GIT} commit --no-gpg-sign -m "first"`, DIR);
|
|
|
|
const {exitCode, stderr} = runJest(DIR, ['--changedSince=^blablabla']);
|
|
|
|
expect(exitCode).toBe(1);
|
|
expect(stderr).toContain('Test suite failed to run');
|
|
expect(stderr).toContain("fatal: bad revision '^blablabla...HEAD'");
|
|
});
|
|
|
|
testIfHg('gets changed files for hg', async () => {
|
|
// file1.txt is used to make a multi-line commit message
|
|
// with `hg commit -l file1.txt`.
|
|
// This is done to ensure that `changedFiles` only returns files
|
|
// and not parts of commit messages.
|
|
writeFiles(DIR, {
|
|
'file1.txt': 'file1\n\nextra-line',
|
|
'nested-dir/file2.txt': 'file2',
|
|
'nested-dir/second-nested-dir/file3.txt': 'file3',
|
|
});
|
|
|
|
run(`${HG} init`, DIR);
|
|
|
|
const roots = ['', 'nested-dir', 'nested-dir/second-nested-dir'].map(
|
|
filename => path.resolve(DIR, filename),
|
|
);
|
|
|
|
let {changedFiles: files} = await getChangedFilesForRoots(roots, {});
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file1.txt', 'file2.txt', 'file3.txt']);
|
|
|
|
run(`${HG} add .`, DIR);
|
|
run(`${HG} commit -l file1.txt`, DIR);
|
|
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {}));
|
|
expect(Array.from(files)).toEqual([]);
|
|
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {
|
|
lastCommit: true,
|
|
}));
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file1.txt', 'file2.txt', 'file3.txt']);
|
|
|
|
writeFiles(DIR, {
|
|
'file1.txt': 'modified file1',
|
|
});
|
|
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {}));
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file1.txt']);
|
|
|
|
run(`${HG} commit -m "test2"`, DIR);
|
|
|
|
writeFiles(DIR, {
|
|
'file4.txt': 'file4',
|
|
});
|
|
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {
|
|
withAncestor: true,
|
|
}));
|
|
// Returns files from current uncommitted state + the last commit
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file1.txt', 'file4.txt']);
|
|
|
|
run(`${HG} add file4.txt`, DIR);
|
|
run(`${HG} commit -m "test3"`, DIR);
|
|
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {
|
|
changedSince: '-3',
|
|
}));
|
|
// Returns files from the last 2 commits
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file1.txt', 'file4.txt']);
|
|
|
|
run(`${HG} bookmark main`, DIR);
|
|
// Back up and develop on a different branch
|
|
run(`${HG} checkout --rev=-2`, DIR);
|
|
|
|
writeFiles(DIR, {
|
|
'file5.txt': 'file5',
|
|
});
|
|
run(`${HG} add file5.txt`, DIR);
|
|
run(`${HG} commit -m "test4"`, DIR);
|
|
|
|
({changedFiles: files} = await getChangedFilesForRoots(roots, {
|
|
changedSince: 'main',
|
|
}));
|
|
// Returns files from this branch but not ones that only exist on main
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file5.txt']);
|
|
});
|
|
|
|
testIfHg('monitors only root paths for hg', async () => {
|
|
writeFiles(DIR, {
|
|
'file1.txt': 'file1',
|
|
'nested-dir/file2.txt': 'file2',
|
|
'nested-dir/second-nested-dir/file3.txt': 'file3',
|
|
});
|
|
|
|
run(`${HG} init`, DIR);
|
|
|
|
const roots = [path.resolve(DIR, 'nested-dir')];
|
|
|
|
const {changedFiles: files} = await getChangedFilesForRoots(roots, {});
|
|
expect(
|
|
Array.from(files)
|
|
.map(filePath => path.basename(filePath))
|
|
.sort(),
|
|
).toEqual(['file2.txt', 'file3.txt']);
|
|
});
|
|
|
|
testIfHg('handles a bad revision for "changedSince", for hg', async () => {
|
|
writeFiles(DIR, {
|
|
'.watchmanconfig': '',
|
|
'__tests__/file1.test.js': "require('../file1'); test('file1', () => {});",
|
|
'file1.js': 'module.exports = {}',
|
|
'package.json': '{}',
|
|
});
|
|
|
|
run(`${HG} init`, DIR);
|
|
run(`${HG} add .`, DIR);
|
|
run(`${HG} commit -m "first"`, DIR);
|
|
|
|
const {exitCode, stderr} = runJest(DIR, ['--changedSince=blablabla']);
|
|
|
|
expect(exitCode).toBe(1);
|
|
expect(stderr).toContain('Test suite failed to run');
|
|
expect(stderr).toContain("abort: unknown revision 'blablabla'");
|
|
});
|