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

Add support for NODE_PRESERVE_SYMLINKS and --preserve-symlinks behavior #9976

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -32,6 +32,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))
- `[jest-cli, jest-init]` Add `coverageProvider` to `jest --init` prompts ([#10044](https://github.com/facebook/jest/pull/10044))
- `[*]` 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 @@ -78,8 +80,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 H from '../constants';
import * as fastPath from '../lib/fast_path';
import type {
Expand All @@ -17,6 +18,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 @@ -75,7 +78,7 @@ function find(
}

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

Expand All @@ -92,7 +95,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 @@ -129,7 +132,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 @@ -23,5 +23,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