Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vcs): Support Sapling in jest-changed-files #13941

Merged
merged 1 commit into from Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

### Features

- `[jest-changed-files]` Support Sapling ([#13941](https://github.com/facebook/jest/pull/13941))
- `[jest-cli, jest-config, @jest/core, jest-haste-map, @jest/reporters, jest-runner, jest-runtime, @jest/types]` Add `workerThreads` configuration option to allow using [worker threads](https://nodejs.org/dist/latest/docs/api/worker_threads.html) for parallelization ([#13939](https://github.com/facebook/jest/pull/13939))
- `[jest-worker]` Add `start` method to worker farms ([#13937](https://github.com/facebook/jest/pull/13937))

Expand Down
33 changes: 33 additions & 0 deletions e2e/Utils.ts
Expand Up @@ -336,3 +336,36 @@ export const testIfHg = (...args: Parameters<typeof test>) => {
test.skip(...args);
}
};

// Certain environments (like CITGM and GH Actions) do not come with sapling installed
let slIsInstalled: boolean | null = null;
export const testIfSl = (...args: Parameters<typeof test>) => {
if (slIsInstalled === null) {
slIsInstalled = which.sync('sl', {nothrow: true}) !== null;
}

if (slIsInstalled) {
test(...args);
} else {
console.warn('Sapling (sl) is not installed - skipping some tests');
test.skip(...args);
}
};

export const testIfSlAndHg = (...args: Parameters<typeof test>) => {
if (slIsInstalled === null) {
slIsInstalled = which.sync('sl', {nothrow: true}) !== null;
}
if (hgIsInstalled === null) {
hgIsInstalled = which.sync('hg', {nothrow: true}) !== null;
}

if (slIsInstalled && hgIsInstalled) {
test(...args);
} else {
console.warn(
'Sapling (sl) or Mercurial (hg) is not installed - skipping some tests',
);
test.skip(...args);
}
};
277 changes: 240 additions & 37 deletions e2e/__tests__/jestChangedFiles.test.ts
Expand Up @@ -7,16 +7,25 @@

import {tmpdir} from 'os';
import * as path from 'path';
import * as fs from 'graceful-fs';
import semver = require('semver');
import slash = require('slash');
import {findRepos, getChangedFilesForRoots} from 'jest-changed-files';
import {cleanup, run, testIfHg, writeFiles} from '../Utils';
import {
cleanup,
run,
testIfHg,
testIfSl,
testIfSlAndHg,
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 SL = 'sl --config ui.username=jest_test';

const gitVersionSupportsInitialBranch = (() => {
const {stdout} = run(`${GIT} --version`);
Expand Down Expand Up @@ -129,42 +138,56 @@ test('gets git SCM roots and dedupes them', async () => {
);
});

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\/?$/,
);
});
testIfSlAndHg(
'gets mixed git, hg, and sl 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',
'third-repo/file1.txt': 'file1',
'third-repo/nested-dir/file2.txt': 'file2',
'third-repo/nested-dir/second-nested-dir/file3.txt': 'file3',
});

gitInit(path.resolve(DIR, 'first-repo'));
run(`${HG} init`, path.resolve(DIR, 'second-repo'));
run(`${SL} init --git`, path.resolve(DIR, 'third-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',
'third-repo/nested-dir',
'third-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);
const slRepos = Array.from(repos.sl);

// 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(slRepos).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\/?$/,
);
expect(slash(slRepos[0])).toMatch(
/\/jest-changed-files-test-dir\/third-repo\/?$/,
);
},
);

test('gets changed files for git', async () => {
writeFiles(DIR, {
Expand Down Expand Up @@ -496,3 +519,183 @@ testIfHg('handles a bad revision for "changedSince", for hg', async () => {
expect(stderr).toContain('Test suite failed to run');
expect(stderr).toContain("abort: unknown revision 'blablabla'");
});

testIfSl('gets sl SCM roots and dedupes them', async () => {
fs.mkdirSync(path.resolve(DIR, 'first-repo'), {recursive: true});
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(`${SL} init --git`, path.resolve(DIR, 'first-repo'));
run(`${SL} init --git`, 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);
expect(repos.hg.size).toBe(0);

const slRepos = Array.from(repos.sl);

// 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 .sl repo initialized inside your
// os tmp directory.
expect(slRepos).toHaveLength(2);
expect(slash(slRepos[0])).toMatch(
/\/jest-changed-files-test-dir\/first-repo\/?$/,
);
expect(slash(slRepos[1])).toMatch(
/\/jest-changed-files-test-dir\/second-repo\/?$/,
);
});

testIfSl('gets changed files for sl', async () => {
// file1.txt is used to make a multi-line commit message
// with `sl 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(`${SL} init --git`, 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(`${SL} add .`, DIR);
run(`${SL} 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(`${SL} 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(`${SL} add file4.txt`, DIR);
run(`${SL} commit -m "test3"`, DIR);

({changedFiles: files} = await getChangedFilesForRoots(roots, {
changedSince: '.~2',
}));
// Returns files from the last 2 commits
expect(
Array.from(files)
.map(filePath => path.basename(filePath))
.sort(),
).toEqual(['file1.txt', 'file4.txt']);

run(`${SL} bookmark main_branch`, DIR);
// Back up and develop on a different branch
run(`${SL}`, DIR);
run(`${SL} go prev(2)`, DIR);

writeFiles(DIR, {
'file5.txt': 'file5',
});
run(`${SL} add file5.txt`, DIR);
run(`${SL} commit -m "test4"`, DIR);

({changedFiles: files} = await getChangedFilesForRoots(roots, {
changedSince: 'main_branch',
}));
// 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']);
});

testIfSl('monitors only root paths for sl', async () => {
writeFiles(DIR, {
'file1.txt': 'file1',
'nested-dir/file2.txt': 'file2',
'nested-dir/second-nested-dir/file3.txt': 'file3',
});

run(`${SL} init --git`, 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']);
});

testIfSl('handles a bad revision for "changedSince", for sl', async () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'__tests__/file1.test.js': "require('../file1'); test('file1', () => {});",
'file1.js': 'module.exports = {}',
'package.json': '{}',
});

run(`${SL} init --git`, DIR);
run(`${SL} add .`, DIR);
run(`${SL} 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'");
});