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

Preserve symlinks when node is configured to do so #9732

Closed
wants to merge 1 commit 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Features

- `[*]` Respect NODE_PRESERVE_SYMLINKS environment variable and --preserve-symlinks flag when resolving file paths ([#9732](https://github.com/facebook/jest/pull/9732))

### Fixes

- `[jest-jasmine2]` Don't run `beforeAll` / `afterAll` in skipped describe block ([#9931](https://github.com/facebook/jest/pull/9931))
Expand Down
4 changes: 2 additions & 2 deletions e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ FAIL __tests__/index.js
12 | module.exports = () => 'test';
13 |

at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:545:17)
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:561:17)
at Object.require (index.js:10:1)
`;

Expand Down Expand Up @@ -65,6 +65,6 @@ FAIL __tests__/index.js
12 | module.exports = () => 'test';
13 |

at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:545:17)
at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:561:17)
at Object.require (index.js:10:1)
`;
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ FAIL __tests__/test.js
| ^
9 |

at Resolver.resolveModule (../../packages/jest-resolve/build/index.js:299:11)
at Resolver.resolveModule (../../packages/jest-resolve/build/index.js:315:11)
at Object.require (index.js:8:18)
`;
131 changes: 131 additions & 0 deletions e2e/__tests__/preserveSymlinks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* 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';
import HasteMap = require('jest-haste-map');

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('hasteMap finds symlinks correctly', async () => {
const options = {
extensions: ['js'],
forceNodeFilesystemAPI: true,
maxWorkers: 2,
mocksPattern: '',
name: 'tmp',
platforms: [],
preserveSymlinks: true,
retainAllFiles: true,
rootDir: resolve(__dirname, '../preserve-symlinks'),
roots: [resolve(__dirname, '../preserve-symlinks')],
useWatchman: false,
watch: false,
};
const hasteMap = new HasteMap(options);
const result = await hasteMap.build();
const files = result.hasteFS
.getAllFiles()
.map(f => f.split('preserve-symlinks').pop());

const expectedFiles = [
join('/__tests__', 'a.test.js'),
join('/__tests__', 'ab.test.js'),
join('/__tests__', 'b.test.js'),
join('/', 'a.js'),
join('/', 'ab.js'),
join('/', 'b.js'),
];

expectedFiles.forEach(f => {
expect(files).toContain(f);
});
});

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');
});
8 changes: 7 additions & 1 deletion 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;
ganemone marked this conversation as resolved.
Show resolved Hide resolved
nodeFlags?: Array<string>;
nodeOptions?: string;
nodePath?: string;
skipPkgJsonCheck?: boolean; // don't complain if can't find package.json
Expand Down Expand Up @@ -74,11 +76,15 @@ function spawnJest(
);
}
const env = Object.assign({}, process.env, {FORCE_COLOR: '0'});

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"
}
}
1 change: 1 addition & 0 deletions packages/jest-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"jest-validate": "^25.5.0",
"prompts": "^2.0.1",
"realpath-native": "^2.0.0",
"should-preserve-links": "^1.0.4",
"yargs": "^15.3.1"
},
"devDependencies": {
Expand Down
10 changes: 10 additions & 0 deletions packages/jest-cli/should-preserve-links.d.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.
*/

declare module 'should-preserve-links' {
export default function shouldPreserveLinks(): boolean;
}
8 changes: 7 additions & 1 deletion packages/jest-cli/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ import {getVersion, runCLI} from '@jest/core';
import chalk = require('chalk');
import exit = require('exit');
import yargs = require('yargs');
import {sync as realpath} from 'realpath-native';
import {sync as _realpath} from 'realpath-native';
import shouldPreserveSymlinks from 'should-preserve-links';
import init from '../init';
import * as args from './args';

const preserveSymlinks = shouldPreserveSymlinks();
function realpath(p: string) {
return preserveSymlinks ? p : _realpath(p);
}

export async function run(
maybeArgv?: Array<string>,
project?: Config.Path,
Expand Down
8 changes: 7 additions & 1 deletion packages/jest-cli/src/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import * as path from 'path';
import * as fs from 'graceful-fs';
import chalk = require('chalk');
import prompts = require('prompts');
import {sync as realpath} from 'realpath-native';
import {sync as _realpath} from 'realpath-native';
import {constants} from 'jest-config';
import shouldPreserveSymlinks from 'should-preserve-links';
import defaultQuestions, {testScriptQuestion} from './questions';
import {MalformedPackageJsonError, NotFoundPackageJsonError} from './errors';
import generateConfigFile from './generate_config_file';
Expand All @@ -32,6 +33,11 @@ type PromptsResults = {
scripts: boolean;
};

const preserveSymlinks = shouldPreserveSymlinks();
function realpath(p: string) {
return preserveSymlinks ? p : _realpath(p);
}

const getConfigFilename = (ext: string) => JEST_CONFIG_BASE_NAME + ext;

export default async (
Expand Down
3 changes: 2 additions & 1 deletion packages/jest-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"jest-validate": "^25.5.0",
"micromatch": "^4.0.2",
"pretty-format": "^25.5.0",
"realpath-native": "^2.0.0"
"realpath-native": "^2.0.0",
"should-preserve-links": "^1.0.4"
},
"devDependencies": {
"@types/babel__core": "^7.0.4",
Expand Down
10 changes: 10 additions & 0 deletions packages/jest-config/should-preserve-links.d.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.
*/

declare module 'should-preserve-links' {
export default function shouldPreserveLinks(): boolean;
}
9 changes: 8 additions & 1 deletion packages/jest-config/src/getCacheDirectory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@

import * as path from 'path';
import {tmpdir} from 'os';
import {sync as realpath} from 'realpath-native';
import {sync as _realpath} from 'realpath-native';

import shouldPreserveSymlinks from 'should-preserve-links';

const preserveSymlinks = shouldPreserveSymlinks();
function realpath(p: string) {
return preserveSymlinks ? p : _realpath(p);
}

const getCacheDirectory = () => {
const {getuid} = process;
Expand Down
8 changes: 7 additions & 1 deletion packages/jest-config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import * as path from 'path';
import * as fs from 'graceful-fs';
import type {Config} from '@jest/types';
import chalk = require('chalk');
import {sync as realpath} from 'realpath-native';
import {sync as _realpath} from 'realpath-native';
import shouldPreserveSymlinks from 'should-preserve-links';
import {isJSONString, replaceRootDirInPath} from './utils';
import normalize from './normalize';
import resolveConfigPath from './resolveConfigPath';
Expand All @@ -23,6 +24,11 @@ export {default as descriptions} from './Descriptions';
import * as constants from './constants';
export {constants};

const preserveSymlinks = shouldPreserveSymlinks();
function realpath(p: string) {
return preserveSymlinks ? p : _realpath(p);
}

type ReadConfig = {
configPath: Config.Path | null | undefined;
globalConfig: Config.GlobalConfig;
Expand Down
Loading