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 c45469c
Show file tree
Hide file tree
Showing 7 changed files with 351 additions and 5 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);
}

}
196 changes: 194 additions & 2 deletions e2e/__tests__/jestChangedFiles.test.ts
Expand Up @@ -7,16 +7,18 @@

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,6 +93,47 @@ testIfHg('gets hg 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',
'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\/?$/,
);
});

test('gets git SCM roots and dedupes them', async () => {
writeFiles(DIR, {
'first-repo/file1.txt': 'file1',
Expand Down Expand Up @@ -129,41 +172,52 @@ test('gets git SCM roots and dedupes them', async () => {
);
});

testIfHg('gets mixed git and hg SCM roots and dedupes them', async () => {
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 () => {
Expand Down Expand Up @@ -459,6 +513,106 @@ 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 +632,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 +669,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'");
});
46 changes: 45 additions & 1 deletion e2e/__tests__/onlyChanged.test.ts
Expand Up @@ -8,7 +8,7 @@
import {tmpdir} from 'os';
import * as path from 'path';
import semver = require('semver');
import {cleanup, run, testIfHg, writeFiles} from '../Utils';
import {cleanup, run, testIfHg, testIfSl, writeFiles} from '../Utils';
import runJest from '../runJest';

const DIR = path.resolve(tmpdir(), 'jest_only_changed');
Expand Down Expand Up @@ -354,6 +354,50 @@ testIfHg('gets changed files for hg', async () => {
expect(stderr).toMatch(/PASS __tests__(\/|\\)file3.test.js/);
});

const SL = 'sl --config ui.username=jest_test';
testIfSl('gets changed files for sl', async () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'__tests__/file1.test.js': "require('../file1'); test('file1', () => {});",
'file1.js': 'module.exports = {}',
'package.json': JSON.stringify({jest: {testEnvironment: 'node'}}),
});

run(`${SL} init --git`, DIR);
run(`${SL} add .`, DIR);
run(`${SL} commit -m "test"`, DIR);

let stdout;
let stderr;

({stdout} = runJest(DIR, ['-o']));
expect(stdout).toMatch('No tests found related to files changed');

writeFiles(DIR, {
'__tests__/file2.test.js': "require('../file2'); test('file2', () => {});",
'file2.js': 'module.exports = {}',
'file3.js': "require('./file2')",
});

({stderr} = runJest(DIR, ['-o']));
expect(stderr).toMatch(/PASS __tests__(\/|\\)file2.test.js/);

run(`${SL} add .`, DIR);
run(`${SL} commit -m "test2"`, DIR);

writeFiles(DIR, {
'__tests__/file3.test.js': "require('../file3'); test('file3', () => {});",
});

({stdout, stderr} = runJest(DIR, ['-o']));
expect(stderr).toMatch(/PASS __tests__(\/|\\)file3.test.js/);
expect(stderr).not.toMatch(/PASS __tests__(\/|\\)file2.test.js/);

({stdout, stderr} = runJest(DIR, ['-o', '--changedFilesWithAncestor']));
expect(stderr).toMatch(/PASS __tests__(\/|\\)file2.test.js/);
expect(stderr).toMatch(/PASS __tests__(\/|\\)file3.test.js/);
});

test('path on Windows is case-insensitive', () => {
if (process.platform !== 'win32') {
// This test is Windows specific, skip it on other platforms.
Expand Down
1 change: 1 addition & 0 deletions e2e/snapshot-escape/__tests__/snapshot.test.js
Expand Up @@ -8,3 +8,4 @@

// prettier-ignore
test('escape strings', () => expect('one: \\\'').toMatchSnapshot());
test('escape strings two', () => expect('two: \'"').toMatchSnapshot());

0 comments on commit c45469c

Please sign in to comment.