Skip to content

Commit

Permalink
Merge a085b69 into 8589744
Browse files Browse the repository at this point in the history
  • Loading branch information
connectdotz committed May 31, 2022
2 parents 8589744 + a085b69 commit 73f691d
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 53 deletions.
37 changes: 31 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Happy testing!
---

## Release Notes

### Pre-Release: [v4.7.0](https://github.com/jest-community/vscode-jest/releases/tag/v4.6.0) <!-- omit in toc -->
- expand the ["jest.shell"](#shell) setting to support LoginShell config. This can resolve the runtime environment issues due to vscode incomplete initialization, which often showed `env: node: No such file or directory` or `env: yarn: No such file or directory`.
- Enhance troubleshooting for [resolve runtime environment issues]().
### Stable: [v4.6.0](https://github.com/jest-community/vscode-jest/releases/tag/v4.6.0) <!-- omit in toc -->

- added a new setting ["jest.showTerminalOnLaunch"](#showTerminalOnLaunch) to control if test explorer terminal should be automatically opened upon launch. Default is true.
Expand Down Expand Up @@ -168,6 +172,7 @@ Content
- [coverageColors](#coveragecolors)
- [autoRun](#autorun)
- [testExplorer](#testexplorer)
- [shell](#shell)
- [Debug Config](#debug-config)
- [Debug Config v2](#debug-config-v2)
- [Commands](#commands)
Expand Down Expand Up @@ -361,7 +366,7 @@ Users can use the following settings to tailor the extension for their environme
|autoEnable :x:|Automatically start Jest for this project|true|Please use `autoRun` instead|
|[jestCommandLine](#jestCommandLine)|The command line to start jest tests|undefined|`"jest.jestCommandLine": "npm test -"` or `"jest.jestCommandLine": "yarn test"` or `"jest.jestCommandLine": "node_modules/.bin/jest --config custom-config.js"`|
|nodeEnv|Add additional env variables to spawned jest process|null|`"jest.nodeEnv": {"PORT": "9800", "BAR":"true"}` |
|shell|Custom shell (path) for jest process|null|`"jest.shell": "/bin/bash"` or `"jest.shell": "powershell"` |
|shell|Custom shell (path or LoginShell) for executing jest|null|`"jest.shell": "/bin/bash"` or `"jest.shell": "powershell"` or `"jest.shell": {"path": "/bin/bash"; args: ["--login"]}` |
|[autoRun](#autorun)|Controls when and what tests should be run|undefined|`"jest.autoRun": "off"` or `"jest.autoRun": {"watch": true, "onStartup": ["all-tests"]}` or `"jest.autoRun": false, onSave:"test-only"}`|
|pathToJest :x:|The path to the Jest binary, or an npm/yarn command to run tests|undefined|Please use `jestCommandLine` instead|
|pathToConfig :x:|The path to your Jest configuration file"|""|Please use `jestCommandLine` instead|
Expand Down Expand Up @@ -507,7 +512,21 @@ for example:
- `showInlineError`: (optional) show vscode style inline error and error message viewer. Default is false.

4.1 default is `"jest.testExplorer": {"enabled": true}`
>


##### shell
```ts
shell = string | LoginShell;

interface LoginShell
{
path: string;
args: string[];
}
```
By default, jest command is executed in default shell ('cmd' for windows, '/bin/sh' for non-windows). Users can use the `"jest.shell"` setting to either pass the path of another shell (e.g. "/bin/zsh") or a LoginShell config, basically a shell path and login arguments (e.g. `{"path": "/bin/bash", "args": ["--login"]}`)

Note the LoginShell is only applicable for non-windows platform and could cause a bit more overhead.
### Debug Config

This extension looks for jest specific debug config (`"vscode-jest-tests"` or `"vscode-jest-tests.v2"`) in the workspace `.vscode/launch.json`. If not found, it will attempt to generate a default config that should work for most standard jest or projects bootstrapped by `create-react-app`.
Expand Down Expand Up @@ -624,12 +643,18 @@ Sorry you are having trouble with the extension. If your issue did not get resol

If you can run jest manually in the terminal but the extension showed error like "xxx ended unexpectedly", following are the most common causes (see [self-diagnosis](#how-to-see-more-debug-info-self-diagnosis) if you need more debug info):

- <a id="trouble-shell-env"></a>runtime environment issue: such as the shell env is not fully initialized upon vscode start up. A good indicator is messages prefixed with **"env:"**, such as `env: node: No such file or directory`
- Most likely the child_process environment the extension used to run jest is not correctly initialized. There are many possible causes, sometimes restarting vscode from a terminal will fix it, otherwise feel free to check out a more in-depth explanation and suggestions [here](https://github.com/jest-community/vscode-jest/issues/741#issuecomment-921222851).
- <a id="trouble-jest-cmdline"></a>jest command line issue: such as you usually run `yarn test` but the extension uses the default `jest` instead.
- <a id="trouble-shell-env"></a>**runtime environment issue**: such as the shell env is not fully initialized upon vscode start up. A good indicator is messages prefixed with **"env:"**, or node/yarn/npm command not found, such as `env: node: No such file or directory`
- This should only happened in Linux or MacOS and is caused by vscode not able to fully initialize the shell env when it starts up (more details [here](https://code.visualstudio.com/docs/supporting/faq#_resolving-shell-environment-fails)).
- The extension usually spawns the jest process with a non-login/not-interactive shell, which inherits the vscode env. If vscode env is not complete, the jest process could fail for example the `PATH` env variable is not correct. (more details [here](https://github.com/jest-community/vscode-jest/issues/741#issuecomment-921222851))
- There are many ways to workaround such issues:
- simply restart vscode sometimes can fix it
- start vscode from a terminal
- add `PATH` directly to `jest.nodeEnv` settings, if that is the only problem.
- force the jest command to be executed in a login shell by setting ["jest.shell"](#shell) to a LoginShell. Note this might have some slight performance overhead.
- <a id="trouble-jest-cmdline"></a>***jest command line issue**: such as you usually run `yarn test` but the extension uses the default `jest` instead.
- Try configuring the [jest.jestCommandLine](#jestcommandline) to mimic how you run jest from the terminal, such as `yarn test` or `npm run test --`. The extension can auto-config common configurations like create react apps but not custom scripts like [CRACO](https://github.com/gsoft-inc/craco).
- or you can use the **"Run Setup Wizard"** button in the error panel to resolve the configuration issue, see [Setup Wizard](setup-wizard.md).
- monorepo project issue: you have a monorepo project but not setup as a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces).
- **monorepo project issue**: you have a monorepo project but not setup as a [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces).
- see [monorepo projects](#how-to-use-the-extension-with-monorepo-projects) on how to set it up correctly.

There could be other causes, such as jest test root path is different from the project's, which can be fixed by setting [jest.rootPath](#rootPath). Feel free to check out the [customization](#customization) section to manually adjust the extension if needed.
Expand Down
Binary file added images/jest-env-error.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vscode-jest",
"displayName": "Jest",
"description": "Use Facebook's Jest With Pleasure.",
"version": "4.6.0",
"version": "4.7.0",
"publisher": "Orta",
"engines": {
"vscode": "^1.63.0"
Expand Down Expand Up @@ -118,9 +118,10 @@
"scope": "resource"
},
"jest.shell": {
"markdownDescription": "The shell path to override jest runner process default shell (see Node [child_process.spawn()](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options)) for more detail)",
"markdownDescription": "The shell path or a login-shell to override jest runner process default shell (see Node [child_process.spawn()](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options)) for more detail)",
"type": [
"string",
"object",
"null"
],
"default": null,
Expand Down Expand Up @@ -458,7 +459,7 @@
"dependencies": {
"istanbul-lib-coverage": "^3.0.0",
"istanbul-lib-source-maps": "^4.0.0",
"jest-editor-support": "^30.0.2",
"jest-editor-support": "^30.1.0",
"vscode-codicons": "^0.0.4"
},
"devDependencies": {
Expand Down
37 changes: 35 additions & 2 deletions src/JestExt/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import * as vscode from 'vscode';
import * as path from 'path';
import { ProjectWorkspace } from 'jest-editor-support';
import { ProjectWorkspace, LoginShell } from 'jest-editor-support';
import { JestProcessRequest } from '../JestProcessManagement';
import {
PluginResourceSettings,
Expand All @@ -16,6 +16,7 @@ import { pathToJest, pathToConfig, toFilePath } from '../helpers';
import { workspaceLogging } from '../logging';
import { AutoRunAccessor, JestExtContext, RunnerWorkspaceOptions } from './types';
import { CoverageColors } from '../Coverage';
import { platform } from 'os';

export const isWatchRequest = (request: JestProcessRequest): boolean =>
request.type === 'watch-tests' || request.type === 'watch-all-tests';
Expand Down Expand Up @@ -108,8 +109,40 @@ export const createJestExtContext = (
};
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isLoginShell = (arg: any): arg is LoginShell =>
arg && typeof arg.path === 'string' && Array.isArray(arg.args);

const getShell = (config: vscode.WorkspaceConfiguration): string | LoginShell | undefined => {
console.log('inside getShell');

const shell = config.get<string | LoginShell>('shell');

console.log('shell=', shell);
if (!shell || typeof shell === 'string') {
return shell;
}

console.log('before calling isLoginShell shell=', shell);
if (isLoginShell(shell)) {
if (platform() === 'win32') {
console.error(`LoginShell is not supported for windows currently.`);
return;
}
if (shell.args.length <= 0) {
console.error(
'Invalid login-shell arguments. Expect arguments like "--login" or "-l", but got:',
shell.args.length
);
return;
}
return shell;
}
};

export const getExtensionResourceSettings = (uri: vscode.Uri): PluginResourceSettings => {
const config = vscode.workspace.getConfiguration('jest', uri);

return {
showTerminalOnLaunch: config.get<boolean>('showTerminalOnLaunch') ?? true,
autoEnable: config.get<boolean>('autoEnable'),
Expand All @@ -130,7 +163,7 @@ export const getExtensionResourceSettings = (uri: vscode.Uri): PluginResourceSet
autoRun: config.get<JestExtAutoRunConfig>('autoRun'),
testExplorer: config.get<TestExplorerConfig>('testExplorer') ?? { enabled: true },
nodeEnv: config.get<NodeEnv | null>('nodeEnv') ?? undefined,
shell: config.get<string | null>('shell') ?? undefined,
shell: getShell(config) ?? undefined,
};
};

Expand Down
4 changes: 2 additions & 2 deletions src/Settings/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TestState } from '../DebugCodeLens';
import { CoverageColors } from '../Coverage/CoverageOverlay';
import { ProjectWorkspace } from 'jest-editor-support';
import { LoginShell, ProjectWorkspace } from 'jest-editor-support';

export type JestTestProcessType =
| 'all-tests'
Expand Down Expand Up @@ -44,7 +44,7 @@ export interface PluginResourceSettings {
autoRun?: JestExtAutoRunConfig;
testExplorer: TestExplorerConfig;
nodeEnv?: NodeEnv;
shell?: string;
shell?: string | LoginShell;
}

export interface PluginWindowSettings {
Expand Down
14 changes: 12 additions & 2 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ExtensionContext } from 'vscode';
import { PluginResourceSettings, hasUserSetPathToJest } from './Settings';
import { TestIdentifier } from './TestResults';
import { TestStats } from './types';
import { LoginShell } from 'jest-editor-support';

/**
* Known binary names of `react-scripts` forks
Expand Down Expand Up @@ -212,6 +213,15 @@ export const emptyTestStats = (): TestStats => {
return { success: 0, fail: 0, unknown: 0 };
};

const getShellPath = (shell?: string | LoginShell): string | undefined => {
if (!shell) {
return;
}
if (typeof shell === 'string') {
return shell;
}
return shell.path;
};
/**
* quoting a given string for it to be used as shell command arguments.
*
Expand All @@ -221,8 +231,8 @@ export const emptyTestStats = (): TestStats => {
*
**/

export const shellQuote = (str: string, shell?: string): string => {
const targetShell = shell && shell.trim().toLowerCase();
export const shellQuote = (str: string, shell?: string | LoginShell): string => {
const targetShell = getShellPath(shell)?.trim().toLowerCase();

// try to determine the shell type
let shellType: 'powershell' | 'cmd' | 'sh';
Expand Down
61 changes: 50 additions & 11 deletions tests/JestExt/helper.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
jest.unmock('../../src/JestExt/helper');
jest.unmock('../test-helper');

const mockPlatform = jest.fn();
jest.mock('os', () => ({ platform: mockPlatform }));

import * as vscode from 'vscode';
import { readFileSync } from 'fs';
import {
Expand All @@ -14,6 +18,8 @@ import { mockProjectWorkspace } from '../test-helper';
import { toFilePath } from '../../src/helpers';
import { RunnerWorkspaceOptions } from '../../src/JestExt/types';

jest.mock('jest-editor-support', () => ({ isLoginShell: jest.fn(), ProjectWorkspace: jest.fn() }));

describe('createJestExtContext', () => {
const workspaceFolder: any = { name: 'workspace' };
describe('autoRun', () => {
Expand Down Expand Up @@ -191,17 +197,50 @@ describe('getExtensionResourceSettings()', () => {
showTerminalOnLaunch: true,
});
});
it('can read user settings', () => {
userSettings = {
testExplorer: { enable: false },
nodeEnv: { whatever: '1' },
shell: '/bin/bash',
};
const uri: any = { fsPath: 'workspaceFolder1' };
expect(getExtensionResourceSettings(uri)).toEqual(
expect.objectContaining({
...userSettings,
})
describe('can read user settings', () => {
it('with nodeEnv and shell path', () => {
userSettings = {
testExplorer: { enable: false },
nodeEnv: { whatever: '1' },
shell: '/bin/bash',
};
const uri: any = { fsPath: 'workspaceFolder1' };
expect(getExtensionResourceSettings(uri)).toEqual(
expect.objectContaining({
...userSettings,
})
);
});
it.each`
platform | args | supported
${'win32'} | ${[]} | ${false}
${'linux'} | ${['-l']} | ${true}
${'darwin'} | ${['-l']} | ${true}
${'darwin'} | ${[]} | ${false}
`(
'supports loginShell with $args in $platform => $supported',
({ platform, supported, args }) => {
mockPlatform.mockReturnValue(platform);

userSettings = {
shell: { path: '/bin/zsh', args },
};
const uri: any = { fsPath: 'workspaceFolder1' };

if (supported) {
expect(getExtensionResourceSettings(uri)).toEqual(
expect.objectContaining({
...userSettings,
})
);
} else {
expect(getExtensionResourceSettings(uri)).not.toEqual(
expect.objectContaining({
...userSettings,
})
);
}
}
);
});
});
49 changes: 26 additions & 23 deletions tests/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,29 +296,32 @@ describe('toUpperCaseDriveLetter', () => {

describe('shellQuote', () => {
it.each`
platform | shell | str | expected
${'win32'} | ${undefined} | ${'plain text'} | ${'"plain text"'}
${'linux'} | ${undefined} | ${'plain text'} | ${'"plain text"'}
${'win32'} | ${'powershell'} | ${"with 'single quote'"} | ${"'with ''single quote'''"}
${'win32'} | ${'cmd.exe'} | ${"with 'single quote'"} | ${'"with \'single quote\'"'}
${'linux'} | ${'bash'} | ${"with 'single quote'"} | ${'"with \\\'single quote\\\'"'}
${'win32'} | ${undefined} | ${"with 'single quote'"} | ${'"with \'single quote\'"'}
${'linux'} | ${undefined} | ${"with 'single quote'"} | ${'"with \\\'single quote\\\'"'}
${'win32'} | ${'powershell'} | ${'with "double quote"'} | ${'\'with ""double quote""\''}
${'win32'} | ${'cmd.exe'} | ${'with "double quote"'} | ${'"with ""double quote"""'}
${'linux'} | ${'bash'} | ${'with "double quote"'} | ${'"with \\"double quote\\""'}
${'win32'} | ${'powershell'} | ${'with \\$name\\.txt'} | ${"'with \\$name\\.txt'"}
${'win32'} | ${'cmd.exe'} | ${'with \\$name\\.txt'} | ${'"with \\$name\\.txt"'}
${'linux'} | ${'bash'} | ${'with \\$name\\.txt'} | ${'"with \\\\\\$name\\\\.txt"'}
${'win32'} | ${'powershell'} | ${''} | ${"''"}
${'win32'} | ${undefined} | ${''} | ${'""'}
${'darwin'} | ${undefined} | ${''} | ${'""'}
${'win32'} | ${'powershell'} | ${'with \\ and \\\\'} | ${"'with \\ and \\\\\\\\'"}
${'win32'} | ${undefined} | ${'with \\ and \\\\'} | ${'"with \\ and \\\\\\\\"'}
${'linux'} | ${undefined} | ${'with \\ and \\\\'} | ${'"with \\\\ and \\\\\\\\"'}
${'win32'} | ${'powershell'} | ${'something\\'} | ${"'something\\'"}
${'win32'} | ${undefined} | ${'something\\'} | ${'something\\'}
${'darwin'} | ${undefined} | ${'something\\'} | ${'something\\\\'}
platform | shell | str | expected
${'win32'} | ${undefined} | ${'plain text'} | ${'"plain text"'}
${'linux'} | ${undefined} | ${'plain text'} | ${'"plain text"'}
${'win32'} | ${'powershell'} | ${"with 'single quote'"} | ${"'with ''single quote'''"}
${'win32'} | ${'cmd.exe'} | ${"with 'single quote'"} | ${'"with \'single quote\'"'}
${'linux'} | ${'/bin/bash'} | ${"with 'single quote'"} | ${'"with \\\'single quote\\\'"'}
${'darwin'} | ${'/bin/zsh'} | ${"with 'single quote'"} | ${'"with \\\'single quote\\\'"'}
${'darwin'} | ${{ path: '/bin/zsh', args: ['-l'] }} | ${"with 'single quote'"} | ${'"with \\\'single quote\\\'"'}
${'win32'} | ${undefined} | ${"with 'single quote'"} | ${'"with \'single quote\'"'}
${'linux'} | ${undefined} | ${"with 'single quote'"} | ${'"with \\\'single quote\\\'"'}
${'win32'} | ${'powershell'} | ${'with "double quote"'} | ${'\'with ""double quote""\''}
${'win32'} | ${'cmd.exe'} | ${'with "double quote"'} | ${'"with ""double quote"""'}
${'linux'} | ${'bash'} | ${'with "double quote"'} | ${'"with \\"double quote\\""'}
${'win32'} | ${'powershell'} | ${'with \\$name\\.txt'} | ${"'with \\$name\\.txt'"}
${'win32'} | ${'cmd.exe'} | ${'with \\$name\\.txt'} | ${'"with \\$name\\.txt"'}
${'linux'} | ${'bash'} | ${'with \\$name\\.txt'} | ${'"with \\\\\\$name\\\\.txt"'}
${'linux'} | ${{ path: '/bin/sh', args: ['--login'] }} | ${'with \\$name\\.txt'} | ${'"with \\\\\\$name\\\\.txt"'}
${'win32'} | ${'powershell'} | ${''} | ${"''"}
${'win32'} | ${undefined} | ${''} | ${'""'}
${'darwin'} | ${undefined} | ${''} | ${'""'}
${'win32'} | ${'powershell'} | ${'with \\ and \\\\'} | ${"'with \\ and \\\\\\\\'"}
${'win32'} | ${undefined} | ${'with \\ and \\\\'} | ${'"with \\ and \\\\\\\\"'}
${'linux'} | ${undefined} | ${'with \\ and \\\\'} | ${'"with \\\\ and \\\\\\\\"'}
${'win32'} | ${'powershell'} | ${'something\\'} | ${"'something\\'"}
${'win32'} | ${undefined} | ${'something\\'} | ${'something\\'}
${'darwin'} | ${undefined} | ${'something\\'} | ${'something\\\\'}
`('can quote "$str" for $shell on $platform', ({ platform, shell, str, expected }) => {
jest.resetAllMocks();
mockPlatform.mockReturnValueOnce(platform);
Expand Down
Loading

0 comments on commit 73f691d

Please sign in to comment.