Skip to content

Commit

Permalink
Add support for NODE_PRESERVE_SYMLINKS and --preserve-symlinks behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
Giancarlo Anemone committed May 13, 2020
1 parent 9842735 commit d1ca473
Show file tree
Hide file tree
Showing 16 changed files with 209 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/examples/*/node_modules/
/examples/mongodb/globalConfig.json

/e2e/preserve-symlinks/*
/e2e/*/node_modules
/e2e/*/.pnp
/e2e/*/.pnp.js
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- `[jest-config]` Support config files exporting (`async`) `function`s ([#10001](https://github.com/facebook/jest/pull/10001))
- `[jest-cli, jest-core]` Add `--selectProjects` CLI argument to filter test suites by project name ([#8612](https://github.com/facebook/jest/pull/8612))
- `[*]` Add support for NODE_PRESERVE_SYMLINKS and --preserve-symlinks behavior

### Fixes

Expand Down
95 changes: 95 additions & 0 deletions e2e/__tests__/preserveSymlinks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {join, resolve} from 'path';
import {
existsSync,
mkdirSync,
rmdirSync,
symlinkSync,
unlinkSync,
} from 'graceful-fs';

import runJest from '../runJest';
import {extractSummary} from '../Utils';

const destRoot = resolve(__dirname, '../preserve-symlinks');
const srcRoot = resolve(__dirname, '../symlinked-source-dir');

const files = [
'package.json',
'a.js',
'b.js',
'ab.js',
'__tests__/a.test.js',
'__tests__/b.test.js',
'__tests__/ab.test.js',
];

function cleanup() {
files
.map(f => join(destRoot, f))
.filter(f => existsSync(f))
.forEach(f => {
unlinkSync(f);
});
if (existsSync(join(destRoot, '__tests__'))) {
rmdirSync(join(destRoot, '__tests__'));
}
if (existsSync(destRoot)) {
rmdirSync(destRoot);
}
}

beforeAll(() => {
cleanup();
mkdirSync(destRoot);
mkdirSync(join(destRoot, '__tests__'));
files.forEach(f => {
symlinkSync(join(srcRoot, f), join(destRoot, f));
});
});

afterAll(() => {
cleanup();
});

test('preserving symlinks with environment variable', () => {
const {stderr, exitCode} = runJest('preserve-symlinks', ['--no-watchman'], {
preserveSymlinks: '1',
});
const {summary, rest} = extractSummary(stderr);
expect(exitCode).toEqual(0);
expect(rest.split('\n').length).toEqual(3);
expect(rest).toMatch('PASS __tests__/ab.test.js');
expect(rest).toMatch('PASS __tests__/a.test.js');
expect(rest).toMatch('PASS __tests__/b.test.js');
expect(summary).toMatch('Test Suites: 3 passed, 3 total');
expect(summary).toMatch('Tests: 3 passed, 3 total');
expect(summary).toMatch('Snapshots: 0 total');
});

test('preserving symlinks with --preserve-symlinks node flag', () => {
const {stderr, exitCode} = runJest('preserve-symlinks', ['--no-watchman'], {
nodeFlags: ['--preserve-symlinks'],
});
const {summary, rest} = extractSummary(stderr);
expect(exitCode).toEqual(0);
expect(rest.split('\n').length).toEqual(3);
expect(rest).toMatch('PASS __tests__/ab.test.js');
expect(rest).toMatch('PASS __tests__/a.test.js');
expect(rest).toMatch('PASS __tests__/b.test.js');
expect(summary).toMatch('Test Suites: 3 passed, 3 total');
expect(summary).toMatch('Tests: 3 passed, 3 total');
expect(summary).toMatch('Snapshots: 0 total');
});

test('no preserve symlinks configuration', () => {
const {exitCode, stdout} = runJest('preserve-symlinks', ['--no-watchman']);
expect(exitCode).toEqual(1);
expect(stdout).toMatch('No tests found, exiting with code 1');
});
7 changes: 7 additions & 0 deletions e2e/runJest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {normalizeIcons} from './Utils';
const JEST_PATH = path.resolve(__dirname, '../packages/jest-cli/bin/jest.js');

type RunJestOptions = {
preserveSymlinks?: string;
nodeFlags?: Array<string>;
nodeOptions?: string;
nodePath?: string;
skipPkgJsonCheck?: boolean; // don't complain if can't find package.json
Expand Down Expand Up @@ -77,8 +79,13 @@ function spawnJest(

if (options.nodeOptions) env['NODE_OPTIONS'] = options.nodeOptions;
if (options.nodePath) env['NODE_PATH'] = options.nodePath;
if (options.preserveSymlinks)
env['NODE_PRESERVE_SYMLINKS'] = options.preserveSymlinks;

const spawnArgs = [JEST_PATH, ...args];
if (options.nodeFlags) {
spawnArgs.unshift(...options.nodeFlags);
}
const spawnOptions = {
cwd: dir,
env,
Expand Down
12 changes: 12 additions & 0 deletions e2e/symlinked-source-dir/__tests__/a.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
const a = require('../a');

test('a', () => {
expect(a()).toEqual('a');
});
12 changes: 12 additions & 0 deletions e2e/symlinked-source-dir/__tests__/ab.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
const ab = require('../ab');

test('ab', () => {
expect(ab()).toEqual('ab');
});
12 changes: 12 additions & 0 deletions e2e/symlinked-source-dir/__tests__/b.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
const b = require('../b');

test('b', () => {
expect(b()).toEqual('b');
});
10 changes: 10 additions & 0 deletions e2e/symlinked-source-dir/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
module.exports = function a() {
return 'a';
};
13 changes: 13 additions & 0 deletions e2e/symlinked-source-dir/ab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
const a = require('./a');
const b = require('./b');

module.exports = function ab() {
return a() + b();
};
10 changes: 10 additions & 0 deletions e2e/symlinked-source-dir/b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
module.exports = function b() {
return 'b';
};
5 changes: 5 additions & 0 deletions e2e/symlinked-source-dir/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"jest": {
"testEnvironment": "node"
}
}
3 changes: 2 additions & 1 deletion packages/jest-config/src/Defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import type {Config} from '@jest/types';
import {replacePathSepForRegex} from 'jest-regex-util';
import {shouldPreserveSymlinks} from 'jest-util';
import {NODE_MODULES} from './constants';
import getCacheDirectory from './getCacheDirectory';

Expand Down Expand Up @@ -66,7 +67,7 @@ const defaultOptions: Config.DefaultOptions = {
useStderr: false,
watch: false,
watchPathIgnorePatterns: [],
watchman: true,
watchman: !shouldPreserveSymlinks(),
};

export default defaultOptions;
15 changes: 12 additions & 3 deletions packages/jest-haste-map/src/crawlers/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import * as path from 'path';
import {spawn} from 'child_process';
import * as fs from 'graceful-fs';
import {shouldPreserveSymlinks} from 'jest-util';
import which = require('which');
import H from '../constants';
import * as fastPath from '../lib/fast_path';
Expand All @@ -18,6 +19,8 @@ import type {
InternalHasteMap,
} from '../types';

const preserveSymlinks = shouldPreserveSymlinks();

type Result = Array<[/* id */ string, /* mtime */ number, /* size */ number]>;

type Callback = (result: Result) => void;
Expand Down Expand Up @@ -67,7 +70,7 @@ function find(
}

if (typeof entry !== 'string') {
if (entry.isSymbolicLink()) {
if (!preserveSymlinks && entry.isSymbolicLink()) {
return;
}

Expand All @@ -84,7 +87,7 @@ function find(

// This logic is unnecessary for node > v10.10, but leaving it in
// since we need it for backwards-compatibility still.
if (!err && stat && !stat.isSymbolicLink()) {
if (!err && stat && (preserveSymlinks || !stat.isSymbolicLink())) {
if (stat.isDirectory()) {
search(file);
} else {
Expand Down Expand Up @@ -121,7 +124,13 @@ function findNative(
callback: Callback,
): void {
const args = Array.from(roots);
args.push('-type', 'f');
if (preserveSymlinks) {
// follow symlinks to determine file type
args.unshift('-L');
args.push('( -not -type d )');
} else {
args.push('-type', 'f');
}
if (extensions.length) {
args.push('(');
}
Expand Down
1 change: 1 addition & 0 deletions packages/jest-util/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ import * as preRunMessage from './preRunMessage';
export {default as pluralize} from './pluralize';
export {default as formatTime} from './formatTime';
export {default as tryRealpath} from './tryRealpath';
export {default as shouldPreserveSymlinks} from './shouldPreserveSymlinks';

export {preRunMessage, specialChars};
10 changes: 10 additions & 0 deletions packages/jest-util/src/shouldPreserveSymlinks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

export default (): boolean =>
Boolean(process.env.NODE_PRESERVE_SYMLINKS) ||
process.execArgv.includes('--preserve-symlinks');
6 changes: 6 additions & 0 deletions packages/jest-util/src/tryRealpath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@

import {realpathSync} from 'graceful-fs';
import type {Config} from '@jest/types';
import shouldPreserveSymlinks from './shouldPreserveSymlinks';

const preserveSymlinks = shouldPreserveSymlinks();

export default function tryRealpath(path: Config.Path): Config.Path {
if (preserveSymlinks) {
return path;
}
try {
path = realpathSync.native(path);
} catch (error) {
Expand Down

0 comments on commit d1ca473

Please sign in to comment.