Skip to content

Commit

Permalink
feat(vcs): Support Sapling in jest-changed-files
Browse files Browse the repository at this point in the history
Summary: Currently, jest only supports git and hg repositories.  This commit
adds sl to the support version-control system

Test Plan:
```sh
$ yarn jest e2e/__tests__/jestChangedFiles.test.ts e2e/__tests/onlyChanged.test.ts
```
  • Loading branch information
vegerot committed Feb 21, 2023
1 parent 1eb3bb5 commit c77e80d
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 20 deletions.
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);
}
};
237 changes: 220 additions & 17 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 @@ -91,7 +100,8 @@ testIfHg('gets hg SCM roots and dedupes them', async () => {
);
});

test('gets git SCM roots and dedupes them', async () => {
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',
Expand All @@ -101,8 +111,8 @@ test('gets git SCM roots and dedupes them', async () => {
'second-repo/nested-dir/second-nested-dir/file3.txt': 'file3',
});

gitInit(path.resolve(DIR, 'first-repo'));
gitInit(path.resolve(DIR, 'second-repo'));
run(`${SL} init --git`, path.resolve(DIR, 'first-repo'));
run(`${SL} init --git`, path.resolve(DIR, 'second-repo'));

const roots = [
'',
Expand All @@ -113,23 +123,25 @@ test('gets git SCM roots and dedupes them', async () => {
].map(filename => path.resolve(DIR, filename));

const repos = await findRepos(roots);
expect(repos.git.size).toBe(0);
expect(repos.hg.size).toBe(0);
const gitRepos = Array.from(repos.git);

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 .git repo initialized inside your
// NOTE: This test can break if you have a .sl repo initialized inside your
// os tmp directory.
expect(gitRepos).toHaveLength(2);
expect(slash(gitRepos[0])).toMatch(
expect(slRepos).toHaveLength(2);
expect(slash(slRepos[0])).toMatch(
/\/jest-changed-files-test-dir\/first-repo\/?$/,
);
expect(slash(gitRepos[1])).toMatch(
expect(slash(slRepos[1])).toMatch(
/\/jest-changed-files-test-dir\/second-repo\/?$/,
);
});

testIfHg('gets mixed git and hg SCM roots and dedupes them', async () => {
test('gets git SCM roots and dedupes them', async () => {
writeFiles(DIR, {
'first-repo/file1.txt': 'file1',
'first-repo/nested-dir/file2.txt': 'file2',
Expand All @@ -140,7 +152,7 @@ testIfHg('gets mixed git and hg SCM roots and dedupes them', async () => {
});

gitInit(path.resolve(DIR, 'first-repo'));
run(`${HG} init`, path.resolve(DIR, 'second-repo'));
gitInit(path.resolve(DIR, 'second-repo'));

const roots = [
'',
Expand All @@ -151,21 +163,73 @@ testIfHg('gets mixed git and hg SCM roots and dedupes them', async () => {
].map(filename => path.resolve(DIR, filename));

const repos = await findRepos(roots);
const hgRepos = Array.from(repos.hg);
expect(repos.hg.size).toBe(0);
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);
// 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(hgRepos[0])).toMatch(
expect(slash(gitRepos[1])).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, {
'file1.txt': 'file1',
Expand Down Expand Up @@ -459,6 +523,107 @@ testIfHg('gets changed files for hg', async () => {
).toEqual(['file5.txt']);
});

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']);
});

testIfHg('monitors only root paths for hg', async () => {
writeFiles(DIR, {
'file1.txt': 'file1',
Expand All @@ -478,6 +643,25 @@ testIfHg('monitors only root paths for hg', async () => {
).toEqual(['file2.txt', 'file3.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']);
});

testIfHg('handles a bad revision for "changedSince", for hg', async () => {
writeFiles(DIR, {
'.watchmanconfig': '',
Expand All @@ -496,3 +680,22 @@ 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('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'");
});

0 comments on commit c77e80d

Please sign in to comment.