Skip to content

Commit

Permalink
fix: check find binary supports the -iname parameter (#10308)
Browse files Browse the repository at this point in the history
  • Loading branch information
richardlau committed Jul 24, 2020
1 parent 7eff69b commit 6ad3a74
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 49 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Expand Up @@ -9,7 +9,8 @@

- `[expect]` Match symbols and bigints in `any()` ([#10223](https://github.com/facebook/jest/pull/10223))
- `[jest-changed-files]` Use `git diff` instead of `git log` for `--changedSince` ([#10155](https://github.com/facebook/jest/pull/10155))
- `[jest-console]` Add missing console.timeLog for compatability with Node ([#10209](https://github.com/facebook/jest/pull/10209))
- `[jest-console]` Add missing `console.timeLog` for compatibility with Node ([#10209](https://github.com/facebook/jest/pull/10209))
- `[jest-haste-map]` Check `find` binary supports the `-iname` parameter ([#10308](https://github.com/facebook/jest/pull/10308))
- `[jest-snapshot]` Strip added indentation for inline error snapshots ([#10217](https://github.com/facebook/jest/pull/10217))

### Chore & Maintenance
Expand Down
5 changes: 2 additions & 3 deletions packages/jest-haste-map/package.json
Expand Up @@ -21,16 +21,15 @@
"jest-worker": "^26.1.0",
"micromatch": "^4.0.2",
"sane": "^4.0.3",
"walker": "^1.0.7",
"which": "^2.0.2"
"walker": "^1.0.7"
},
"devDependencies": {
"@jest/test-utils": "^26.0.0",
"@types/anymatch": "^1.3.1",
"@types/fb-watchman": "^2.0.0",
"@types/micromatch": "^4.0.0",
"@types/sane": "^2.0.0",
"@types/which": "^1.3.2"
"slash": "^3.0.0"
},
"optionalDependencies": {
"fsevents": "^2.1.2"
Expand Down
74 changes: 35 additions & 39 deletions packages/jest-haste-map/src/crawlers/__tests__/node.test.js
Expand Up @@ -8,12 +8,15 @@

'use strict';

import {skipSuiteOnWindows} from '@jest/test-utils';

jest.mock('child_process', () => ({
spawn: jest.fn((cmd, args) => {
let closeCallback;
return {
on: jest.fn().mockImplementation((event, callback) => {
if (event === 'exit') {
callback(mockSpawnExit, null);
}
}),
stdout: {
on: jest.fn().mockImplementation((event, callback) => {
if (event === 'data') {
Expand All @@ -34,17 +37,18 @@ jest.mock('child_process', () => ({
let mockHasReaddirWithFileTypesSupport = false;

jest.mock('graceful-fs', () => {
const slash = require('slash');
let mtime = 32;
const size = 42;
const stat = (path, callback) => {
setTimeout(
() =>
callback(null, {
isDirectory() {
return path.endsWith('/directory');
return slash(path).endsWith('/directory');
},
isSymbolicLink() {
return path.endsWith('symlink');
return slash(path).endsWith('symlink');
},
mtime: {
getTime() {
Expand All @@ -70,7 +74,7 @@ jest.mock('graceful-fs', () => {
}

if (mockHasReaddirWithFileTypesSupport) {
if (dir === '/project/fruits') {
if (slash(dir) === '/project/fruits') {
setTimeout(
() =>
callback(null, [
Expand All @@ -92,7 +96,7 @@ jest.mock('graceful-fs', () => {
]),
0,
);
} else if (dir === '/project/fruits/directory') {
} else if (slash(dir) === '/project/fruits/directory') {
setTimeout(
() =>
callback(null, [
Expand All @@ -104,18 +108,18 @@ jest.mock('graceful-fs', () => {
]),
0,
);
} else if (dir == '/error') {
} else if (slash(dir) == '/error') {
setTimeout(() => callback({code: 'ENOTDIR'}, undefined), 0);
}
} else {
if (dir === '/project/fruits') {
if (slash(dir) === '/project/fruits') {
setTimeout(
() => callback(null, ['directory', 'tomato.js', 'symlink']),
0,
);
} else if (dir === '/project/fruits/directory') {
} else if (slash(dir) === '/project/fruits/directory') {
setTimeout(() => callback(null, ['strawberry.js']), 0);
} else if (dir == '/error') {
} else if (slash(dir) == '/error') {
setTimeout(() => callback({code: 'ENOTDIR'}, undefined), 0);
}
}
Expand All @@ -124,35 +128,32 @@ jest.mock('graceful-fs', () => {
};
});

jest.mock('which', () => jest.fn().mockResolvedValue());

const pearMatcher = path => /pear/.test(path);
const createMap = obj => new Map(Object.keys(obj).map(key => [key, obj[key]]));
const normalize = path =>
process.platform === 'win32' ? path.replace(/\//g, '\\') : path;
const createMap = obj =>
new Map(Object.keys(obj).map(key => [normalize(key), obj[key]]));

const rootDir = '/project';
let mockResponse;
let mockSpawnExit;
let nodeCrawl;
let childProcess;

describe('node crawler', () => {
skipSuiteOnWindows();

beforeEach(() => {
jest.resetModules();

// Remove the "process.platform" property descriptor so it can be writable.
delete process.platform;

mockResponse = [
'/project/fruits/pear.js',
'/project/fruits/strawberry.js',
'/project/fruits/tomato.js',
].join('\n');

mockSpawnExit = 0;
});

it('crawls for files based on patterns', () => {
process.platform = 'linux';

childProcess = require('child_process');
nodeCrawl = require('../node');

Expand Down Expand Up @@ -203,8 +204,6 @@ describe('node crawler', () => {
});

it('updates only changed files', () => {
process.platform = 'linux';

nodeCrawl = require('../node');

// In this test sample, strawberry is changed and tomato is unchanged
Expand All @@ -229,15 +228,13 @@ describe('node crawler', () => {
);

// Make sure it is the *same* unchanged object.
expect(hasteMap.files.get('fruits/tomato.js')).toBe(tomato);
expect(hasteMap.files.get(normalize('fruits/tomato.js'))).toBe(tomato);

expect(removedFiles).toEqual(new Map());
});
});

it('returns removed files', () => {
process.platform = 'linux';

nodeCrawl = require('../node');

// In this test sample, previouslyExisted was present before and will not be
Expand Down Expand Up @@ -269,8 +266,10 @@ describe('node crawler', () => {
});
});

it('uses node fs APIs on windows', () => {
process.platform = 'win32';
it('uses node fs APIs with incompatible find binary', () => {
mockResponse = '';
mockSpawnExit = 1;
childProcess = require('child_process');

nodeCrawl = require('../node');

Expand All @@ -283,6 +282,7 @@ describe('node crawler', () => {
rootDir,
roots: ['/project/fruits'],
}).then(({hasteMap, removedFiles}) => {
expect(childProcess.spawn).lastCalledWith('find', ['.', '-iname', "''"]);
expect(hasteMap.files).toEqual(
createMap({
'fruits/directory/strawberry.js': ['', 33, 42, 0, '', null],
Expand All @@ -293,11 +293,11 @@ describe('node crawler', () => {
});
});

it('uses node fs APIs on Unix based OS without find binary', () => {
process.platform = 'linux';
const which = require('which');
which.mockReturnValueOnce(Promise.reject());

it('uses node fs APIs without find binary', () => {
childProcess = require('child_process');
childProcess.spawn.mockImplementationOnce(() => {
throw new Error();
});
nodeCrawl = require('../node');

return nodeCrawl({
Expand All @@ -316,13 +316,11 @@ describe('node crawler', () => {
}),
);
expect(removedFiles).toEqual(new Map());
expect(which).toBeCalledWith('find');
});
});

it('uses node fs APIs if "forceNodeFilesystemAPI" is set to true, regardless of platform', () => {
process.platform = 'linux';

childProcess = require('child_process');
nodeCrawl = require('../node');

const files = new Map();
Expand All @@ -334,6 +332,7 @@ describe('node crawler', () => {
rootDir,
roots: ['/project/fruits'],
}).then(({hasteMap, removedFiles}) => {
expect(childProcess.spawn).toHaveBeenCalledTimes(0);
expect(hasteMap.files).toEqual(
createMap({
'fruits/directory/strawberry.js': ['', 33, 42, 0, '', null],
Expand All @@ -345,8 +344,6 @@ describe('node crawler', () => {
});

it('completes with empty roots', () => {
process.platform = 'win32';

nodeCrawl = require('../node');

const files = new Map();
Expand All @@ -364,14 +361,13 @@ describe('node crawler', () => {
});

it('completes with fs.readdir throwing an error', () => {
process.platform = 'win32';

nodeCrawl = require('../node');

const files = new Map();
return nodeCrawl({
data: {files},
extensions: ['js'],
forceNodeFilesystemAPI: true,
ignore: pearMatcher,
rootDir,
roots: ['/error'],
Expand Down
16 changes: 12 additions & 4 deletions packages/jest-haste-map/src/crawlers/node.ts
Expand Up @@ -8,7 +8,6 @@
import * as path from 'path';
import {spawn} from 'child_process';
import * as fs from 'graceful-fs';
import which = require('which');
import H from '../constants';
import * as fastPath from '../lib/fast_path';
import type {
Expand All @@ -25,13 +24,22 @@ type Callback = (result: Result) => void;
async function hasNativeFindSupport(
forceNodeFilesystemAPI: boolean,
): Promise<boolean> {
if (forceNodeFilesystemAPI || process.platform === 'win32') {
if (forceNodeFilesystemAPI) {
return false;
}

try {
await which('find');
return true;
return await new Promise(resolve => {
// Check the find binary supports the non-POSIX -iname parameter.
const args = ['.', '-iname', "''"];
const child = spawn('find', args);
child.on('error', () => {
resolve(false);
});
child.on('exit', code => {
resolve(code === 0);
});
});
} catch {
return false;
}
Expand Down
3 changes: 1 addition & 2 deletions yarn.lock
Expand Up @@ -11208,7 +11208,6 @@ fsevents@^1.2.7:
"@types/micromatch": ^4.0.0
"@types/node": "*"
"@types/sane": ^2.0.0
"@types/which": ^1.3.2
anymatch: ^3.0.3
fb-watchman: ^2.0.0
fsevents: ^2.1.2
Expand All @@ -11218,8 +11217,8 @@ fsevents@^1.2.7:
jest-worker: ^26.1.0
micromatch: ^4.0.2
sane: ^4.0.3
slash: ^3.0.0
walker: ^1.0.7
which: ^2.0.2
dependenciesMeta:
fsevents:
optional: true
Expand Down

0 comments on commit 6ad3a74

Please sign in to comment.