update vscode

This commit is contained in:
Siheng Zhao
2024-01-08 23:09:12 +08:00
parent 2b09b7ce41
commit 4f165cd618
1951 changed files with 303273 additions and 12 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

View File

@@ -0,0 +1,89 @@
### @vscode/test
> This is an experimental command-line test runner is available. Its API may change as we get feedback on it. Please try it out!
This is an configuration-driver command line runner for [VS Code extension tests](https://code.visualstudio.com/api/working-with-extensions/testing-extension). It provides a simple, configuration-driven interface for running tests, and has its own VS Code extension that allows running tests in the UI.
Install with:
```
npm install --save-dev @vscode/test
```
After installing the package, the runner is available as the `vscode-test` CLI. Running it will look for a `.vscode-test.(js/json/mjs)` file relative to the current working directory. You can see the configuration [here](https://github.com/microsoft/vscode-test/blob/main/lib/cli-runner/config.ts). This may be as simple as:
```js
// .vscode-test.mjs
import { defineConfig } from '@vscode/test';
export default defineConfig({ files: 'out/test/**/*.test.js' });
```
Or include more options. For example:
```js
// .vscode-test.mjs
import { defineConfig } from '@vscode/test';
export default defineConfig([
{
// Required: Glob of files to load (can be an array and include absolute paths).
files: 'out/test/**/*.test.js',
// Optional: Version to use, same as the API above, defaults to stable
version: 'insiders',
// Optional: Root path of your extension, same as the API above, defaults
// to the directory this config file is in
extensionDevelopmentPath: __dirname,
// Optional: sample workspace to open
workspaceFolder: `${__dirname}/sampleWorkspace`,
// Optional: additional mocha options to use:
mocha: {
preload: `./out/test-utils.js`,
timeout: 20000,
},
},
// you can specify additional test configurations if necessary
]);
```
Tests included with this command line are run in Mocha. You can run the tests simply by running `vscode-test` on the command line. You can view more options with `vscode-test --help`; this command line is very similar to Mocha. For example, to watch and run only tests named "addition", you can run:
```sh
vscode-test --watch 'out/**/*.js' --grep 'addition'
```
#### Debugging
Add or update an `extensionHost` launch-type config with the path to your test configuration:
```diff
{
"type": "extensionHost",
"request": "launch",
"name": "My extension tests",
+ "testConfiguration": "${workspaceFolder}/.vscode-test.js",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"]
},
```
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Trademarks
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
trademarks or logos is subject to and must follow
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
Any use of third-party trademarks or logos are subject to those third-party's policies.

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
export {};

418
vscodeEvalExtension/node_modules/@vscode/test-cli/out/bin.mjs generated vendored Executable file
View File

@@ -0,0 +1,418 @@
#!/usr/bin/env node
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import * as chokidar from 'chokidar';
import { existsSync, promises as fs } from 'fs';
import { glob } from 'glob';
import { minimatch } from 'minimatch';
import { cpus } from 'os';
import { dirname, isAbsolute, join, resolve } from 'path';
import supportsColor from 'supports-color';
import { fileURLToPath, pathToFileURL } from 'url';
import yargs from 'yargs';
const rulesAndBehavior = 'Mocha: Rules & Behavior';
const reportingAndOutput = 'Mocha: Reporting & Output';
const fileHandling = 'Mocha: File Handling';
const testFilters = 'Mocha: Test Filters';
const vscodeSection = 'VS Code Options';
const configFileDefault = 'nearest .vscode-test.js';
const args = yargs(process.argv)
.epilogue('See https://code.visualstudio.com/api/working-with-extensions/testing-extension for help')
.option('config', {
type: 'string',
description: 'Config file to use',
default: configFileDefault,
group: vscodeSection,
})
.option('label', {
alias: 'l',
type: 'array',
description: 'Specify the test configuration to run based on its label in configuration',
group: vscodeSection,
})
.option('code-version', {
type: 'string',
description: 'Override the VS Code version used to run tests',
group: vscodeSection,
})
//#region Rules & Behavior
.option('bail', {
alias: 'b',
type: 'boolean',
description: 'Abort ("bail") after first test failure',
group: rulesAndBehavior,
})
.option('dry-run', {
type: 'boolean',
description: 'Report tests without executing them',
group: rulesAndBehavior,
})
.option('list-configuration', {
type: 'boolean',
description: 'List configurations and that they woud run, without executing them',
group: rulesAndBehavior,
})
.option('fail-zero', {
type: 'boolean',
description: 'Fail test run if no test(s) encountered',
group: rulesAndBehavior,
})
.option('forbid-only', {
type: 'boolean',
description: 'Fail if exclusive test(s) encountered',
group: rulesAndBehavior,
})
.option('forbid-pending', {
type: 'boolean',
description: 'Fail if pending test(s) encountered',
group: rulesAndBehavior,
})
.option('jobs', {
alias: 'j',
type: 'number',
description: 'Number of concurrent jobs for --parallel; use 1 to run in serial',
default: Math.max(1, cpus().length - 1),
group: rulesAndBehavior,
})
.options('parallel', {
alias: 'p',
type: 'boolean',
description: 'Run tests in parallel',
group: rulesAndBehavior,
})
.option('retries', {
alias: 'r',
type: 'number',
description: 'Number of times to retry failed tests',
group: rulesAndBehavior,
})
.option('slow', {
alias: 's',
type: 'number',
description: 'Specify "slow" test threshold (in milliseconds)',
default: 75,
group: rulesAndBehavior,
})
.option('timeout', {
alias: 't',
type: 'number',
description: 'Specify test timeout threshold (in milliseconds)',
default: 2000,
group: rulesAndBehavior,
})
//#endregion
//#region Reporting & Output
.option('color', {
alias: 'c',
type: 'boolean',
description: 'Force-enable color output',
group: reportingAndOutput,
})
.option('diff', {
type: 'boolean',
description: 'Show diff on failure',
default: true,
group: reportingAndOutput,
})
.option('full-trace', {
type: 'boolean',
description: 'Display full stack traces',
group: reportingAndOutput,
})
.option('inline-diffs', {
type: 'boolean',
description: 'Display actual/expected differences inline within each string',
group: reportingAndOutput,
})
.option('reporter', {
alias: 'R',
type: 'string',
description: 'Specify reporter to use',
default: 'spec',
group: reportingAndOutput,
})
.option('reporter-option', {
alias: 'O',
type: 'array',
description: 'Reporter-specific options (<k=v,[k1=v1,..]>)',
group: reportingAndOutput,
})
//#endregion
//#region File Handling
.option('file', {
type: 'array',
description: 'Specify file(s) to be loaded prior to root suite',
group: fileHandling,
})
.option('ignore', {
alias: 'exclude',
type: 'array',
description: 'Ignore file(s) or glob pattern(s)',
group: fileHandling,
})
.option('watch', {
alias: 'w',
type: 'boolean',
description: 'Watch files in the current working directory for changes',
group: fileHandling,
})
.option('watch-files', {
type: 'array',
description: 'List of paths or globs to watch',
group: fileHandling,
})
.option('watch-ignore', {
type: 'array',
description: 'List of paths or globs to exclude from watching',
group: fileHandling,
})
.option('run', {
type: 'array',
description: 'List of specific files to run',
group: fileHandling,
})
//#endregion
//#region Test Filters
.option('fgrep', {
type: 'string',
alias: 'f',
description: 'Only run tests containing this string',
group: testFilters,
})
.option('grep', {
type: 'string',
alias: 'g',
description: 'Only run tests matching this string or regexp',
group: testFilters,
})
.option('invert', {
alias: 'i',
type: 'boolean',
description: 'Inverts --grep and --fgrep matches',
group: testFilters,
})
.parseSync();
const configFileRules = {
json: (path) => fs.readFile(path, 'utf8').then(JSON.parse),
js: (path) => import(pathToFileURL(path).toString()),
mjs: (path) => import(pathToFileURL(path).toString()),
};
class CliExpectedError extends Error {
}
main();
async function main() {
let code = 0;
try {
let configs = args.config !== configFileDefault
? await tryLoadConfigFile(args.config)
: await loadDefaultConfigFile();
if (args.label?.length) {
configs = args.label.map((label) => {
const found = configs.find((c, i) => typeof label === 'string' ? c.config.label === label : i === label);
if (!found) {
throw new CliExpectedError(`Could not find a configuration with label "${label}"`);
}
return found;
});
}
if (args.watch) {
await watchConfigs(configs);
}
else {
code = await runConfigs(configs);
}
}
catch (e) {
code = 1;
if (e instanceof CliExpectedError) {
console.error(e.message);
}
else {
console.error(e.stack || e);
}
}
finally {
process.exit(code);
}
}
const WATCH_RUN_DEBOUNCE = 500;
async function watchConfigs(configs) {
let debounceRun;
let rerun = false;
let running = true;
const runOrDebounce = () => {
if (debounceRun) {
clearTimeout(debounceRun);
}
debounceRun = setTimeout(async () => {
running = true;
rerun = false;
try {
await runConfigs(configs);
}
finally {
running = false;
if (rerun) {
runOrDebounce();
}
}
}, WATCH_RUN_DEBOUNCE);
};
const watcher = chokidar.watch(args.watchFiles?.length ? args.watchFiles.map(String) : process.cwd(), {
ignored: [
'**/.vscode-test/**',
'**/node_modules/**',
...(args.watchIgnore || []).map(String),
],
ignoreInitial: true,
});
watcher.on('all', (evts) => {
console.log(evts);
if (running) {
rerun = true;
}
else {
runOrDebounce();
}
});
watcher.on('ready', () => {
runOrDebounce();
});
// wait until interrupted
await new Promise(() => {
/* no-op */
});
}
const isDesktop = (config) => !config.platform || config.platform === 'desktop';
const RUNNER_PATH = join(fileURLToPath(new URL('.', import.meta.url)), 'runner.cjs');
/** Runs the given test configurations. */
async function runConfigs(configs) {
const resolvedConfigs = await Promise.all(configs.map(async (c) => {
const files = args.run?.length
? args.run.map((r) => resolve(process.cwd(), String(r)))
: await gatherFiles(c);
const env = {};
if (isDesktop(c.config)) {
c.config.launchArgs ||= [];
if (c.config.workspaceFolder) {
c.config.launchArgs.push(resolve(dirname(c.path), c.config.workspaceFolder));
}
env.VSCODE_TEST_OPTIONS = JSON.stringify({
mochaOpts: { ...args, ...c.config.mocha },
colorDefault: supportsColor.stdout || process.env.MOCHA_COLORS !== undefined,
preload: [
...(typeof c.config.mocha?.preload === 'string'
? [c.config.mocha.preload]
: c.config.mocha?.preload || []).map((f) => require.resolve(f, { paths: [c.path] })),
...(args.file?.map((f) => require.resolve(String(f), { paths: [process.cwd()] })) ||
[]),
],
files,
});
}
return {
...c,
files,
env,
extensionTestsPath: RUNNER_PATH,
extensionDevelopmentPath: c.config.extensionDevelopmentPath?.slice() || dirname(c.path),
};
}));
if (args.listConfiguration) {
console.log(JSON.stringify(resolvedConfigs, null, 2));
return 0;
}
let code = 0;
for (const { config, path, env, extensionTestsPath } of resolvedConfigs) {
if (isDesktop(config)) {
let electron;
try {
electron = await import('@vscode/test-electron');
}
catch (e) {
throw new CliExpectedError('@vscode/test-electron not found, you may need to install it ("npm install -D @vscode/test-electron")');
}
const nextCode = await electron.runTests({
...config,
version: args.codeVersion || config.version,
extensionDevelopmentPath: config.extensionDevelopmentPath?.slice() || dirname(path),
extensionTestsPath,
extensionTestsEnv: { ...config.env, ...env, ELECTRON_RUN_AS_NODE: undefined },
launchArgs: [...(config.launchArgs || [])],
platform: config.desktopPlatform,
reporter: config.download?.reporter,
timeout: config.download?.timeout,
reuseMachineInstall: config.useInstallation && 'fromMachine' in config.useInstallation
? config.useInstallation.fromMachine
: undefined,
vscodeExecutablePath: config.useInstallation && 'fromPath' in config.useInstallation
? config.useInstallation.fromPath
: undefined,
});
if (nextCode > 0 && args.bail) {
return nextCode;
}
code = Math.max(code, nextCode);
}
}
return code;
}
/** Gathers test files that match the config */
async function gatherFiles({ config, path }) {
const fileListsProms = [];
const cwd = dirname(path);
const ignoreGlobs = args.ignore?.map(String).filter((p) => !isAbsolute(p));
for (const file of config.files instanceof Array ? config.files : [config.files]) {
if (isAbsolute(file)) {
if (!ignoreGlobs?.some((i) => minimatch(file, i))) {
fileListsProms.push([file]);
}
}
else {
fileListsProms.push(glob(file, { cwd, ignore: ignoreGlobs }).then((l) => l.map((f) => join(cwd, f))));
}
}
const files = new Set((await Promise.all(fileListsProms)).flat());
args.ignore?.forEach((i) => files.delete(i));
return [...files];
}
/** Loads a specific config file by the path, throwing if loading fails. */
async function tryLoadConfigFile(path) {
const ext = path.split('.').pop();
if (!configFileRules.hasOwnProperty(ext)) {
throw new CliExpectedError(`I don't know how to load the extension '${ext}'. We can load: ${Object.keys(configFileRules).join(', ')}`);
}
try {
let loaded = await configFileRules[ext](path);
if ('default' in loaded) {
// handle default es module exports
loaded = loaded.default;
}
// allow returned promises to resolve:
loaded = await loaded;
return (Array.isArray(loaded) ? loaded : [loaded]).map((config) => ({ config, path }));
}
catch (e) {
throw new CliExpectedError(`Could not read config file ${path}: ${e.stack || e}`);
}
}
/** Loads the default config based on the process working directory. */
async function loadDefaultConfigFile() {
const base = '.vscode-test';
let dir = process.cwd();
while (true) {
for (const ext of Object.keys(configFileRules)) {
const candidate = join(dir, `${base}.${ext}`);
if (existsSync(candidate)) {
return tryLoadConfigFile(candidate);
}
}
const next = dirname(dir);
if (next === dir) {
break;
}
dir = next;
}
throw new CliExpectedError(`Could not find a ${base} file in this directory or any parent. You can specify one with the --config option.`);
}

View File

@@ -0,0 +1,5 @@
"use strict";
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,124 @@
/// <reference types="mocha" />
import { ProgressReporter } from '@vscode/test-electron';
export interface IBaseTestConfiguration {
/**
* A file or list of files in which to find tests. Non-absolute paths will
* be treated as glob expressions relative to the location of
* the `.vscode-test.js` file.
*/
files: string | readonly string[];
/**
* Version to download and install. This may be:
* - A quality, like `stable` or `insiders`
* - A version number, like `1.82.0`
* - A commit hash of a version to install
*
* Defaults to `stable`, which is latest stable version.
*/
version?: 'insiders' | 'stable' | string;
/**
* Defines extension directories to load during tests. Defaults to the directory
* of the `.vscode-test.js` file. Must include a `package.json` Extension Manifest.
*/
extensionDevelopmentPath?: string | readonly string[];
/**
* Path to a folder or workspace file that should be opened.
*/
workspaceFolder?: string;
/**
* Additional options to pass to the Mocha runner. Any options given on the
* command line will be merged into and override these defaults.
* @see https://mochajs.org/api/mocha
*/
mocha?: Mocha.MochaOptions & {
/**
* Specify file(s) to be loaded prior to root suite.
*/
preload?: string | string[];
};
/**
* Optional label for this configuration, which can be used to specify which
* configuration to run if multiple configurations are provided.
*/
label?: string;
}
export interface IDesktopTestConfiguration extends IBaseTestConfiguration {
/**
* Platform to use for running the tests.
*/
platform?: 'desktop';
/**
* The VS Code desktop platform to download. If not specified, it defaults
* to the current platform.
*
* Possible values are:
* - `win32-archive`
* - `win32-x64-archive`
* - `win32-arm64-archive `
* - `darwin`
* - `darwin-arm64`
* - `linux-x64`
* - `linux-arm64`
* - `linux-armhf`
*/
desktopPlatform?: string;
/**
* A list of launch arguments passed to VS Code executable, in addition to `--extensionDevelopmentPath`
* and `--extensionTestsPath` which are provided by `extensionDevelopmentPath` and `extensionTestsPath`
* options.
*
* If the first argument is a path to a file/folder/workspace, the launched VS Code instance
* will open it.
*
* See `code --help` for possible arguments.
*/
launchArgs?: string[];
/**
* Environment variables to set when running the test.
*/
env?: Record<string, string | undefined>;
/**
* Configures a specific VS Code installation to use instead of automatically
* downloading the {@link version}
*/
useInstallation?: {
/**
* Whether VS Code should be launched using default settings and extensions
* installed on this machine. If `false`, then separate directories will be
* used inside the `.vscode-test` folder within the project.
*
* Defaults to `false`.
*/
fromMachine: boolean;
} | {
/**
* The VS Code executable path used for testing.
*
* If not passed, will use `options.version` to download a copy of VS Code for testing.
* If `version` is not specified either, will download and use latest stable release.
*/
fromPath?: string;
};
download?: {
/**
* Progress reporter to use while VS Code is downloaded. Defaults to a
* console reporter. A {@link SilentReporter} is also available, and you
* may implement your own.
*/
reporter: ProgressReporter;
/**
* Number of milliseconds after which to time out if no data is received from
* the remote when downloading VS Code. Note that this is an 'idle' timeout
* and does not enforce the total time VS Code may take to download.
*/
timeout?: number;
};
}
/**
* Configuration that runs in browsers.
* @todo: this is incomplete, and does not yet function
*/
export interface IWebTestConfiguration extends IBaseTestConfiguration {
platform: 'firefox' | 'webkit' | 'chromium';
}
export type TestConfiguration = IDesktopTestConfiguration | IWebTestConfiguration;

View File

@@ -0,0 +1,80 @@
"use strict";
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
const Mocha = __importStar(require("mocha"));
const util_1 = require("util");
const fullJsonStreamReporterTypes_cjs_1 = require("./fullJsonStreamReporterTypes.cjs");
__exportStar(require("./fullJsonStreamReporterTypes.cjs"), exports);
/**
* Similar to the mocha JSON stream, but includes additional information
* on failure and when tests run. Specifically, the mocha json-stream does not
* include unmangled expected versus actual results.
*
* Writes a superset of the data that json-stream normally would.
*/
module.exports = class FullJsonStreamReporter {
constructor(runner) {
const total = runner.total;
runner.once(Mocha.Runner.constants.EVENT_RUN_BEGIN, () => writeEvent([fullJsonStreamReporterTypes_cjs_1.MochaEvent.Start, { total }]));
runner.once(Mocha.Runner.constants.EVENT_RUN_END, () => writeEvent([fullJsonStreamReporterTypes_cjs_1.MochaEvent.End, {}]));
runner.on(Mocha.Runner.constants.EVENT_SUITE_BEGIN, (suite) => writeEvent([fullJsonStreamReporterTypes_cjs_1.MochaEvent.SuiteStart, { path: suite.titlePath(), file: suite.file }]));
runner.on(Mocha.Runner.constants.EVENT_TEST_BEGIN, (test) => writeEvent([fullJsonStreamReporterTypes_cjs_1.MochaEvent.TestStart, clean(test)]));
runner.on(Mocha.Runner.constants.EVENT_TEST_PASS, (test) => writeEvent([fullJsonStreamReporterTypes_cjs_1.MochaEvent.Pass, clean(test)]));
runner.on(Mocha.Runner.constants.EVENT_TEST_FAIL, (test, err) => {
writeEvent([
fullJsonStreamReporterTypes_cjs_1.MochaEvent.Fail,
{
...clean(test),
actual: (0, util_1.inspect)(err.actual, { depth: 30 }),
expected: (0, util_1.inspect)(err.expected, { depth: 30 }),
err: err.message,
stack: err.stack || null,
},
]);
});
}
};
function writeEvent(event) {
process.stdout.write(JSON.stringify(event) + '\n');
}
const clean = (test) => {
return {
path: test.titlePath(),
duration: test.duration,
currentRetry: test.currentRetry(),
file: test.file,
speed: !test.duration || test.duration < test.slow() / 2
? 'fast'
: test.duration > test.slow()
? 'slow'
: 'medium',
};
};

View File

@@ -0,0 +1 @@
export * from './fullJsonStreamReporterTypes.cjs';

View File

@@ -0,0 +1,15 @@
"use strict";
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MochaEvent = void 0;
var MochaEvent;
(function (MochaEvent) {
MochaEvent["Start"] = "start";
MochaEvent["TestStart"] = "testStart";
MochaEvent["Pass"] = "pass";
MochaEvent["Fail"] = "fail";
MochaEvent["End"] = "end";
MochaEvent["SuiteStart"] = "suiteStart";
})(MochaEvent || (exports.MochaEvent = MochaEvent = {}));

View File

@@ -0,0 +1,33 @@
export declare enum MochaEvent {
Start = "start",
TestStart = "testStart",
Pass = "pass",
Fail = "fail",
End = "end",
SuiteStart = "suiteStart"
}
export interface IStartEvent {
total: number;
}
export interface ITestStartEvent {
path: string[];
currentRetry: number;
file?: string;
}
export interface IPassEvent extends ITestStartEvent {
duration?: number;
speed: 'fast' | 'medium' | 'slow';
}
export interface IFailEvent extends IPassEvent {
err: string;
stack: string | null;
expected?: string;
actual?: string;
}
export interface IEndEvent {
}
export interface ISuiteStartEvent {
path: string[];
file?: string;
}
export type MochaEventTuple = [MochaEvent.Start, IStartEvent] | [MochaEvent.TestStart, ITestStartEvent] | [MochaEvent.Pass, IPassEvent] | [MochaEvent.Fail, IFailEvent] | [MochaEvent.End, IEndEvent] | [MochaEvent.SuiteStart, ISuiteStartEvent];

View File

@@ -0,0 +1,25 @@
"use strict";
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.defineConfig = void 0;
__exportStar(require("./config.cjs"), exports);
// todo: can be removed if/when the extension uses Node16+ resolution
__exportStar(require("./fullJsonStreamReporterTypes.cjs"), exports);
const defineConfig = (config) => config;
exports.defineConfig = defineConfig;

View File

@@ -0,0 +1,6 @@
import { TestConfiguration } from './config.cjs';
export * from './config.cjs';
export * from './fullJsonStreamReporterTypes.cjs';
type AnyConfiguration = TestConfiguration | TestConfiguration[];
type AnyConfigurationOrPromise = AnyConfiguration | Promise<AnyConfiguration>;
export declare const defineConfig: (config: AnyConfigurationOrPromise | (() => AnyConfigurationOrPromise)) => AnyConfigurationOrPromise | (() => AnyConfigurationOrPromise);

View File

@@ -0,0 +1 @@
export * from './index.cjs';

View File

@@ -0,0 +1,4 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
export * from './index.cjs';

View File

@@ -0,0 +1,27 @@
"use strict";
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.run = void 0;
const mocha_1 = __importDefault(require("mocha"));
async function run() {
const { mochaOpts, files, preload, colorDefault, } = JSON.parse(process.env.VSCODE_TEST_OPTIONS);
// Create the mocha test
const mocha = new mocha_1.default({
ui: 'tdd',
color: colorDefault,
...mochaOpts,
});
for (const file of preload) {
require(file);
}
for (const file of files) {
mocha.addFile(file);
}
await new Promise((resolve, reject) => mocha.run((failures) => failures ? reject(failures > 1 ? `${failures} tests failed.` : `${failures} test failed.`) : resolve()));
}
exports.run = run;

View File

@@ -0,0 +1 @@
export declare function run(): Promise<void>;

View File

@@ -0,0 +1,63 @@
{
"name": "@vscode/test-cli",
"version": "0.0.4",
"description": "Command-line runner for VS Code extension tests",
"scripts": {
"prepack": "npm run build",
"build": "npm run clean && tsc",
"clean": "node -e \"fs.rmSync('out',{force:true,recursive:true})\"",
"test": "npm run clean && tsc --noEmit",
"watch": "tsc --watch",
"prettier": "prettier --write src"
},
"type": "module",
"main": "out/index.cjs",
"bin": {
"vscode-test": "./out/bin.mjs"
},
"exports": {
".": {
"import": "./out/index.mjs",
"require": "./out/index.cjs"
},
"./fullJsonStream": {
"require": "./out/fullJsonStreamReporter.cjs"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/Microsoft/vscode-test-cli.git"
},
"keywords": [
"vscode",
"test",
"cli"
],
"author": "Connor Peet <connor@peet.io>",
"license": "MIT",
"bugs": {
"url": "https://github.com/Microsoft/vscode-test-cli/issues"
},
"homepage": "https://github.com/Microsoft/vscode-test-cli#readme",
"devDependencies": {
"@types/node": "^18.18.4",
"@types/yargs": "^17.0.28",
"@vscode/test-electron": ">=2",
"@vscode/test-web": ">=0.0.46",
"prettier": "^3.0.3",
"typescript": "^5.2.2"
},
"prettier": {
"printWidth": 100,
"singleQuote": true
},
"dependencies": {
"@types/mocha": "^10.0.2",
"chokidar": "^3.5.3",
"glob": "^10.3.10",
"minimatch": "^9.0.3",
"mocha": "^10.2.0",
"supports-color": "^9.4.0",
"yargs": "^17.7.2"
}
}

View File

@@ -0,0 +1,15 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Tab indentation
[*]
indent_style = tab
trim_trailing_whitespace = true
# The indent size used in the `package.json` file cannot be changed
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
[{*.yml,*.yaml,package.json}]
indent_style = space
indent_size = 2

View File

@@ -0,0 +1,26 @@
module.exports = {
ignorePatterns: ['**/*.d.ts', '**/*.test.ts', '**/*.js', 'sample/**/*.*'],
parser: '@typescript-eslint/parser',
extends: ['plugin:@typescript-eslint/recommended'],
plugins: ['header'],
parserOptions: {
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
},
rules: {
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'header/header': [
'error',
'block',
`---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------`,
],
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
},
};

View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn lint-staged

View File

@@ -0,0 +1,5 @@
{
"semi": true,
"printWidth": 120,
"singleQuote": true
}

View File

@@ -0,0 +1,251 @@
# Changelog
### 2.3.7 | 2022-11-23
- Remove detection for unsupported win32 builds
- Add length and hash validation for downloaded builds
### 2.3.6 | 2022-10-24
- Fix windows sometimes failing on EPERM in download (again)
### 2.3.5 | 2022-10-04
- Fix windows sometimes failing on EPERM in download
### 2.3.4 | 2022-07-31
- Fix "insiders" string not matching correctly
### 2.3.3 | 2022-06-10
- Disable GPU sandbox by default, fixing failures in some CI's.
### 2.3.2 | 2022-05-11
- Fix download method not working for the vscode cli.
### 2.3.1 | 2022-04-04
- Gracefully kill VS Code if SIGINT is received
### 2.3.0 | 2022-02-27
- Automatically use the most recent version matching `engines.vscode` in extensions' package.json
- Allow insiders `version`s to be specified, such as `version: "1.76.0-insider"`
- Reduce the likelihood of 'broken' installations on interrupted downloads
- Remove dependency on outdated `unzipper` module
### 2.2.4 | 2022-02-19
- Use existing downloads if internet is inaccessible
### 2.2.3 | 2022-01-30
- Fix tests sometimes hanging on windows
### 2.2.2 | 2022-01-09
- Add default for platform in `resolveCliPathFromVSCodeExecutablePath` to match docs
### 2.2.1 | 2022-12-06
- Add an idle `timeout` for downloads
### 2.1.5 | 2022-06-27
- Automatically retry if VS Code download fails
### 2.1.4 | 2022-06-10
- Fix uncaught error when failing to connect to the extension service
### 2.1.3 | 2022-03-04
- Support arm64 builds on Linux
### 2.1.2 | 2022-02-04
- Fix executable path being returned incorrectly on cross-platform downloads
- Fix tests sometimes failing with EACCESS errors on OSX
### 2.1.1 | 2022-01-20
- Fix excessive logging when running in CI
### 2.1.0 | 2022-01-14
- Add a progress `reporter` option on the `TestOptions`, which can be used to see more detail or silence download progress.
### 2.0.3 | 2022-01-11
- Fix `@vscode/test-electron` auto updating
- Use arm64 version of VS Code on relevant platforms
### 2.0.2 | 2022-01-07
- Add `resolveCliArgsFromVSCodeExecutablePath`
### 2.0.1 | 2021-12-29
- Fix extra new lines added to test output
### 2.0.0 | 2021-12-14
- Run tests using a separate instance of VS Code by default. This can be disabled by setting `reuseMachineInstall: true`.
### 1.6.2 | 2021-07-15
- Add `--disable-workspace-trust` flag when running tests by default
### 1.6.1 | 2021-07-15
- Rename to `@vscode/test-electron`
### 1.6.0 | 2021-07-14
- Expose generic `download` API with support for `cachePath`
### 1.5.2 | 2021-03-29
- Don't write progress report when output is not connected to tty [#91](https://github.com/microsoft/vscode-test/pull/91)
### 1.5.1 | 2021-01-25
- Fix wrong http proxy agent used [#82](https://github.com/microsoft/vscode-test/issues/82)
### 1.5.0 | 2021-01-25
- Fix download failing on windows with long file paths
- Make installation platform aware [#78](https://github.com/microsoft/vscode-test/issues/78)
- Download and unzip directly for faster setup
- Add download progress indicator
- Show signal that caused vscode to quit if no exit code is present [#64](https://github.com/microsoft/vscode-test/issues/64)
### 1.4.1 | 2020-10-27
- Use "exit" event in runTest.ts. [#74](https://github.com/microsoft/vscode-test/issues/74).
### 1.4.0 | 2020-04-11
- Propagate signal when subprocess terminates. [#56](https://github.com/microsoft/vscode-test/pull/56).
### 1.3.0 | 2019-12-11
- Add `platform` option. By default, Windows/macOS/Linux defaults to use `win32-archive`, `darwin` and `linux-x64`.
On Windows, `win32-x64-archive` is also available for using 64 bit version of VS Code. #18.
- Allow running offline when `version` is specified and a matching version is found locally. #51.
- Show error when failing to unzip downloaded vscode archive. #50.
### 1.2.3 | 2019-10-31
- Add `--no-sandbox` option to default `launchArgs` for https://github.com/microsoft/vscode/issues/84238.
### 1.2.2 | 2019-10-31
- Reject `downloadAndUnzipVSCode` when `https.get` fails to parse the JSON sent back from VS Code update server. #44.
- Reject `downloadAndUnzipVSCode` promise when download fails due to network error. #49.
### 1.2.1 | 2019-10-31
- Update https-proxy-agent for https://www.npmjs.com/advisories/1184.
### 1.2.0 | 2019-08-06
- Remove downloaded Insiders at `.vscode-test/vscode-insiders` if it's outdated. [#25](https://github.com/microsoft/vscode-test/issues/25).
### 1.1.0 | 2019-08-02
- Add `resolveCliPathFromVSCodeExecutablePath` that would resolve `vscodeExecutablePath` to VS Code CLI path, which can be used
for extension management features such as `--install-extension` and `--uninstall-extension`. [#31](https://github.com/microsoft/vscode-test/issues/31).
### 1.0.2 | 2019-07-17
- Revert faulty fix for #29.
### 1.0.1 | 2019-07-16
- Use correct CLI path for launching VS Code on macOS / Linux. [#29](https://github.com/Microsoft/vscode-test/issues/29).
### 1.0.0 | 2019-07-03
- Stable release for changes introduced in the `next` tags.
### 1.0.0-next.1 | 2019-06-24
- Improve console message for downloading VS Code. [microsoft/vscode#76090](https://github.com/microsoft/vscode/issues/76090).
- Improve logging. No more prefix `Spawn Error` and direct `stdout` and `stderr` of launched process to `console.log` and `console.error`.
- `stable` added as a download version option.
### 1.0.0-next.0 | 2019-06-24
- Updated API:
- One single set of options.
- `extensionPath` => `extensionDevelopmentPath` to align with VS Code launch flags
- `testRunnerPath` => `extensionTestsPath` to align with VS Code launch flags
- `testRunnerEnv` => `extensionTestsEnv` to align with VS Code launch flags
- `additionalLaunchArgs` => `launchArgs`
- `testWorkspace` removed. Pass path to file/folder/workspace as first argument to `launchArgs` instead.
- `locale` removed. Pass `--locale` to `launchArgs` instead.
### 0.4.3 | 2019-05-30
- Improved API documentation.
### 0.4.2 | 2019-05-24
- `testWorkspace` is now optional.
### 0.4.1 | 2019-05-02
- Fix Linux crash because `testRunnerEnv` is not merged with `process.env` for spawning the
testing process. [#14](https://github.com/Microsoft/vscode-test/issues/14c).
### 0.4.0 | 2019-04-18
- Add `testRunnerEnv` option. [#13](https://github.com/Microsoft/vscode-test/issues/13).
### 0.3.5 | 2019-04-17
- Fix macOS Insiders incorrect url resolve.
### 0.3.4 | 2019-04-17
- One more fix for Insiders url resolver.
### 0.3.3 | 2019-04-17
- Correct Insiders download link.
### 0.3.2 | 2019-04-17
- Correctly resolve Insider exectuable. [#12](https://github.com/Microsoft/vscode-test/issues/12).
### 0.3.1 | 2019-04-16
- Log errors from stderr of the command to launch VS Code.
### 0.3.0 | 2019-04-13
- 🙌 Add TypeScript as dev dependency. [#9](https://github.com/Microsoft/vscode-test/pull/9).
- 🙌 Adding a simpler way of running tests with only `vscodeExecutablePath` and `launchArgs`. [#8](https://github.com/Microsoft/vscode-test/pull/8).
### 0.2.0 | 2019-04-12
- 🙌 Set `ExecutionPolicy` for Windows unzip command. [#6](https://github.com/Microsoft/vscode-test/pull/6).
- 🙌 Fix NPM http/https proxy handling. [#5](https://github.com/Microsoft/vscode-test/pull/5).
- Fix the option `vscodeLaunchArgs` so it's being used for launching VS Code. [#7](https://github.com/Microsoft/vscode-test/issues/7).
### 0.1.5 | 2019-03-21
- Log folder to download VS Code into.
### 0.1.4 | 2019-03-21
- Add `-NoProfile`, `-NonInteractive` and `-NoLogo` for using PowerShell to extract VS Code. [#2](https://github.com/Microsoft/vscode-test/issues/2).
- Use `Microsoft.PowerShell.Archive\Expand-Archive` to ensure using built-in `Expand-Archive`. [#2](https://github.com/Microsoft/vscode-test/issues/2).
### 0.1.3 | 2019-03-21
- Support specifying testing locale. [#1](https://github.com/Microsoft/vscode-test/pull/1).
- Fix zip extraction failure where `.vscode-test/vscode-<VERSION>` dir doesn't exist on Linux. [#3](https://github.com/Microsoft/vscode-test/issues/3).

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

View File

@@ -0,0 +1,151 @@
# vscode-test
![Test Status Badge](https://github.com/microsoft/vscode-test/workflows/Tests/badge.svg)
This module helps you test VS Code extensions.
Supported:
- Node >= 16.x
- Windows >= Windows Server 2012+ / Win10+ (anything with Powershell >= 5.0)
- macOS
- Linux
## Usage
See [./sample](./sample) for a runnable sample, with [Azure DevOps Pipelines](https://github.com/microsoft/vscode-test/blob/master/sample/azure-pipelines.yml) and [Travis CI](https://github.com/microsoft/vscode-test/blob/master/.travis.yml) configuration.
```ts
import { runTests } from '@vscode/test-electron';
async function go() {
try {
const extensionDevelopmentPath = path.resolve(__dirname, '../../../');
const extensionTestsPath = path.resolve(__dirname, './suite');
/**
* Basic usage
*/
await runTests({
extensionDevelopmentPath,
extensionTestsPath,
});
const extensionTestsPath2 = path.resolve(__dirname, './suite2');
const testWorkspace = path.resolve(__dirname, '../../../test-fixtures/fixture1');
/**
* Running another test suite on a specific workspace
*/
await runTests({
extensionDevelopmentPath,
extensionTestsPath: extensionTestsPath2,
launchArgs: [testWorkspace],
});
/**
* Use 1.36.1 release for testing
*/
await runTests({
version: '1.36.1',
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [testWorkspace],
});
/**
* Use Insiders release for testing
*/
await runTests({
version: 'insiders',
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [testWorkspace],
});
/**
* Noop, since 1.36.1 already downloaded to .vscode-test/vscode-1.36.1
*/
await downloadAndUnzipVSCode('1.36.1');
/**
* Manually download VS Code 1.35.0 release for testing.
*/
const vscodeExecutablePath = await downloadAndUnzipVSCode('1.35.0');
await runTests({
vscodeExecutablePath,
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [testWorkspace],
});
/**
* Install Python extension
*/
const [cli, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
cp.spawnSync(cli, [...args, '--install-extension', 'ms-python.python'], {
encoding: 'utf-8',
stdio: 'inherit',
});
/**
* - Add additional launch flags for VS Code
* - Pass custom environment variables to test runner
*/
await runTests({
vscodeExecutablePath,
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [
testWorkspace,
// This disables all extensions except the one being tested
'--disable-extensions',
],
// Custom environment variables for extension test script
extensionTestsEnv: { foo: 'bar' },
});
/**
* Use win64 instead of win32 for testing Windows
*/
if (process.platform === 'win32') {
await runTests({
extensionDevelopmentPath,
extensionTestsPath,
version: '1.40.0',
platform: 'win32-x64-archive',
});
}
} catch (err) {
console.error('Failed to run tests');
process.exit(1);
}
}
go();
```
## Development
- `yarn install`
- Make necessary changes in [`lib`](./lib)
- `yarn compile` (or `yarn watch`)
- In [`sample`](./sample), run `yarn install`, `yarn compile` and `yarn test` to make sure integration test can run successfully
## License
[MIT](LICENSE)
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

View File

@@ -0,0 +1,41 @@
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK -->
## Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
- Full paths of source file(s) related to the manifestation of the issue
- The location of the affected source code (tag/branch/commit or direct URL)
- Any special configuration required to reproduce the issue
- Step-by-step instructions to reproduce the issue
- Proof-of-concept or exploit code (if possible)
- Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
## Preferred Languages
We prefer all communications to be in English.
## Policy
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
<!-- END MICROSOFT SECURITY.MD BLOCK -->

View File

@@ -0,0 +1,13 @@
#!/bin/sh
ROOT_PID=$1
SIGNAL=$2
terminateTree() {
for cpid in $(/usr/bin/pgrep -P $1); do
terminateTree $cpid
done
kill -$SIGNAL $1 > /dev/null 2>&1
}
terminateTree $ROOT_PID

View File

@@ -0,0 +1,51 @@
import { ProgressReporter } from './progress';
interface IFetchStableOptions {
timeout: number;
cachePath: string;
platform: string;
}
interface IFetchInferredOptions extends IFetchStableOptions {
extensionsDevelopmentPath?: string | string[];
}
export declare const fetchStableVersions: (timeout: number) => Promise<string[]>;
export declare const fetchInsiderVersions: (timeout: number) => Promise<string[]>;
export declare function fetchTargetInferredVersion(options: IFetchInferredOptions): Promise<string>;
/**
* Adapted from https://github.com/microsoft/TypeScript/issues/29729
* Since `string | 'foo'` doesn't offer auto completion
*/
declare type StringLiteralUnion<T extends string> = T | (string & {});
export declare type DownloadVersion = StringLiteralUnion<'insiders' | 'stable'>;
export declare type DownloadPlatform = StringLiteralUnion<'darwin' | 'darwin-arm64' | 'win32-x64-archive' | 'win32-arm64-archive' | 'linux-x64' | 'linux-arm64' | 'linux-armhf'>;
export interface DownloadOptions {
readonly cachePath: string;
readonly version: DownloadVersion;
readonly platform: DownloadPlatform;
readonly extensionDevelopmentPath?: string | string[];
readonly reporter?: ProgressReporter;
readonly extractSync?: boolean;
readonly timeout?: number;
}
export declare const defaultCachePath: string;
/**
* Download and unzip a copy of VS Code.
* @returns Promise of `vscodeExecutablePath`.
*/
export declare function download(options?: Partial<DownloadOptions>): Promise<string>;
/**
* Download and unzip a copy of VS Code in `.vscode-test`. The paths are:
* - `.vscode-test/vscode-<PLATFORM>-<VERSION>`. For example, `./vscode-test/vscode-win32-1.32.0`
* - `.vscode-test/vscode-win32-insiders`.
*
* *If a local copy exists at `.vscode-test/vscode-<PLATFORM>-<VERSION>`, skip download.*
*
* @param version The version of VS Code to download such as `1.32.0`. You can also use
* `'stable'` for downloading latest stable release.
* `'insiders'` for downloading latest Insiders.
* When unspecified, download latest stable version.
*
* @returns Promise of `vscodeExecutablePath`.
*/
export declare function downloadAndUnzipVSCode(options: Partial<DownloadOptions>): Promise<string>;
export declare function downloadAndUnzipVSCode(version?: DownloadVersion, platform?: DownloadPlatform, reporter?: ProgressReporter, extractSync?: boolean): Promise<string>;
export {};

View File

@@ -0,0 +1,387 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.downloadAndUnzipVSCode = exports.download = exports.defaultCachePath = exports.fetchTargetInferredVersion = exports.fetchInsiderVersions = exports.fetchStableVersions = void 0;
const cp = require("child_process");
const fs = require("fs");
const os_1 = require("os");
const path = require("path");
const semver = require("semver");
const stream_1 = require("stream");
const util_1 = require("util");
const progress_1 = require("./progress");
const request = require("./request");
const util_2 = require("./util");
const extensionRoot = process.cwd();
const pipelineAsync = util_1.promisify(stream_1.pipeline);
const vscodeStableReleasesAPI = `https://update.code.visualstudio.com/api/releases/stable`;
const vscodeInsiderReleasesAPI = `https://update.code.visualstudio.com/api/releases/insider`;
const downloadDirNameFormat = /^vscode-(?<platform>[a-z]+)-(?<version>[0-9.]+)$/;
const makeDownloadDirName = (platform, version) => `vscode-${platform}-${version}`;
const DOWNLOAD_ATTEMPTS = 3;
exports.fetchStableVersions = util_2.onceWithoutRejections((timeout) => request.getJSON(vscodeStableReleasesAPI, timeout));
exports.fetchInsiderVersions = util_2.onceWithoutRejections((timeout) => request.getJSON(vscodeInsiderReleasesAPI, timeout));
/**
* Returns the stable version to run tests against. Attempts to get the latest
* version from the update sverice, but falls back to local installs if
* not available (e.g. if the machine is offline).
*/
async function fetchTargetStableVersion({ timeout, cachePath, platform }) {
try {
const versions = await exports.fetchStableVersions(timeout);
return versions[0];
}
catch (e) {
return fallbackToLocalEntries(cachePath, platform, e);
}
}
async function fetchTargetInferredVersion(options) {
if (!options.extensionsDevelopmentPath) {
return fetchTargetStableVersion(options);
}
// load all engines versions from all development paths. Then, get the latest
// stable version (or, latest Insiders version) that satisfies all
// `engines.vscode` constraints.
const extPaths = Array.isArray(options.extensionsDevelopmentPath)
? options.extensionsDevelopmentPath
: [options.extensionsDevelopmentPath];
const maybeExtVersions = await Promise.all(extPaths.map(getEngineVersionFromExtension));
const extVersions = maybeExtVersions.filter(util_2.isDefined);
const matches = (v) => !extVersions.some((range) => !semver.satisfies(v, range, { includePrerelease: true }));
try {
const stable = await exports.fetchStableVersions(options.timeout);
const found1 = stable.find(matches);
if (found1) {
return found1;
}
const insiders = await exports.fetchInsiderVersions(options.timeout);
const found2 = insiders.find(matches);
if (found2) {
return found2;
}
const v = extVersions.join(', ');
console.warn(`No version of VS Code satisfies all extension engine constraints (${v}). Falling back to stable.`);
return stable[0]; // 🤷
}
catch (e) {
return fallbackToLocalEntries(options.cachePath, options.platform, e);
}
}
exports.fetchTargetInferredVersion = fetchTargetInferredVersion;
async function getEngineVersionFromExtension(extensionPath) {
var _a;
try {
const packageContents = await fs.promises.readFile(path.join(extensionPath, 'package.json'), 'utf8');
const packageJson = JSON.parse(packageContents);
return (_a = packageJson === null || packageJson === void 0 ? void 0 : packageJson.engines) === null || _a === void 0 ? void 0 : _a.vscode;
}
catch {
return undefined;
}
}
async function fallbackToLocalEntries(cachePath, platform, fromError) {
const entries = await fs.promises.readdir(cachePath).catch(() => []);
const [fallbackTo] = entries
.map((e) => downloadDirNameFormat.exec(e))
.filter(util_2.isDefined)
.filter((e) => e.groups.platform === platform)
.map((e) => e.groups.version)
.sort((a, b) => Number(b) - Number(a));
if (fallbackTo) {
console.warn(`Error retrieving VS Code versions, using already-installed version ${fallbackTo}`, fromError);
return fallbackTo;
}
throw fromError;
}
async function isValidVersion(version, platform, timeout) {
if (version === 'insiders' || version === 'stable') {
return true;
}
if (util_2.isStableVersionIdentifier(version)) {
const stableVersionNumbers = await exports.fetchStableVersions(timeout);
if (stableVersionNumbers.includes(version)) {
return true;
}
}
if (util_2.isInsiderVersionIdentifier(version)) {
const insiderVersionNumbers = await exports.fetchInsiderVersions(timeout);
if (insiderVersionNumbers.includes(version)) {
return true;
}
}
if (/^[0-9a-f]{40}$/.test(version)) {
return true;
}
return false;
}
function getFilename(contentDisposition) {
const parts = contentDisposition.split(';').map((s) => s.trim());
for (const part of parts) {
const match = /^filename="?([^"]*)"?$/i.exec(part);
if (match) {
return match[1];
}
}
return undefined;
}
/**
* Download a copy of VS Code archive to `.vscode-test`.
*
* @param version The version of VS Code to download such as '1.32.0'. You can also use
* `'stable'` for downloading latest stable release.
* `'insiders'` for downloading latest Insiders.
*/
async function downloadVSCodeArchive(options) {
var _a, _b, _c;
if (!fs.existsSync(options.cachePath)) {
fs.mkdirSync(options.cachePath);
}
const timeout = options.timeout;
const downloadUrl = util_2.getVSCodeDownloadUrl(options.version, options.platform);
(_a = options.reporter) === null || _a === void 0 ? void 0 : _a.report({ stage: progress_1.ProgressReportStage.ResolvingCDNLocation, url: downloadUrl });
const res = await request.getStream(downloadUrl, timeout);
if (res.statusCode !== 302) {
throw 'Failed to get VS Code archive location';
}
const url = res.headers.location;
if (!url) {
throw 'Failed to get VS Code archive location';
}
const contentSHA256 = res.headers['x-sha256'];
res.destroy();
const download = await request.getStream(url, timeout);
const totalBytes = Number(download.headers['content-length']);
const contentDisposition = download.headers['content-disposition'];
const fileName = contentDisposition ? getFilename(contentDisposition) : undefined;
const isZip = (_b = fileName === null || fileName === void 0 ? void 0 : fileName.endsWith('zip')) !== null && _b !== void 0 ? _b : url.endsWith('.zip');
const timeoutCtrl = new request.TimeoutController(timeout);
(_c = options.reporter) === null || _c === void 0 ? void 0 : _c.report({
stage: progress_1.ProgressReportStage.Downloading,
url,
bytesSoFar: 0,
totalBytes,
});
let bytesSoFar = 0;
download.on('data', (chunk) => {
var _a;
bytesSoFar += chunk.length;
timeoutCtrl.touch();
(_a = options.reporter) === null || _a === void 0 ? void 0 : _a.report({
stage: progress_1.ProgressReportStage.Downloading,
url,
bytesSoFar,
totalBytes,
});
});
download.on('end', () => {
var _a;
timeoutCtrl.dispose();
(_a = options.reporter) === null || _a === void 0 ? void 0 : _a.report({
stage: progress_1.ProgressReportStage.Downloading,
url,
bytesSoFar: totalBytes,
totalBytes,
});
});
timeoutCtrl.signal.addEventListener('abort', () => {
download.emit('error', new request.TimeoutError(timeout));
download.destroy();
});
return {
stream: download,
format: isZip ? 'zip' : 'tgz',
sha256: contentSHA256,
length: totalBytes,
};
}
/**
* Unzip a .zip or .tar.gz VS Code archive stream.
*/
async function unzipVSCode(reporter, extractDir, platform, { format, stream, length, sha256 }) {
const stagingFile = path.join(os_1.tmpdir(), `vscode-test-${Date.now()}.zip`);
const checksum = util_2.validateStream(stream, length, sha256);
if (format === 'zip') {
try {
reporter.report({ stage: progress_1.ProgressReportStage.ExtractingSynchonrously });
// note: this used to use Expand-Archive, but this caused a failure
// on longer file paths on windows. And we used to use the streaming
// "unzipper", but the module was very outdated and a bit buggy.
// Instead, use jszip. It's well-used and actually 8x faster than
// Expand-Archive on my machine.
if (process.platform === 'win32') {
const [buffer, JSZip] = await Promise.all([util_2.streamToBuffer(stream), Promise.resolve().then(() => require('jszip'))]);
await checksum;
const content = await JSZip.loadAsync(buffer);
// extract file with jszip
for (const filename of Object.keys(content.files)) {
const file = content.files[filename];
const filepath = path.join(extractDir, filename);
if (file.dir) {
continue;
}
// vscode update zips are trusted, but check for zip slip anyway.
if (!util_2.isSubdirectory(extractDir, filepath)) {
throw new Error(`Invalid zip file: ${filename}`);
}
await fs.promises.mkdir(path.dirname(filepath), { recursive: true });
await pipelineAsync(file.nodeStream(), fs.createWriteStream(filepath));
}
}
else {
// darwin or *nix sync
await pipelineAsync(stream, fs.createWriteStream(stagingFile));
await checksum;
await spawnDecompressorChild('unzip', ['-q', stagingFile, '-d', extractDir]);
}
}
finally {
fs.unlink(stagingFile, () => undefined);
}
}
else {
// tar does not create extractDir by default
if (!fs.existsSync(extractDir)) {
fs.mkdirSync(extractDir);
}
// The CLI is a singular binary that doesn't have a wrapper component to remove
const s = platform.includes('cli-') ? 0 : 1;
await spawnDecompressorChild('tar', ['-xzf', '-', `--strip-components=${s}`, '-C', extractDir], stream);
await checksum;
}
}
function spawnDecompressorChild(command, args, input) {
return new Promise((resolve, reject) => {
const child = cp.spawn(command, args, { stdio: 'pipe' });
if (input) {
input.on('error', reject);
input.pipe(child.stdin);
}
child.stderr.pipe(process.stderr);
child.stdout.pipe(process.stdout);
child.on('error', reject);
child.on('exit', (code) => code === 0 ? resolve() : reject(new Error(`Failed to unzip archive, exited with ${code}`)));
});
}
exports.defaultCachePath = path.resolve(extensionRoot, '.vscode-test');
const COMPLETE_FILE_NAME = 'is-complete';
/**
* Download and unzip a copy of VS Code.
* @returns Promise of `vscodeExecutablePath`.
*/
async function download(options = {}) {
let version = options === null || options === void 0 ? void 0 : options.version;
const { platform = util_2.systemDefaultPlatform, cachePath = exports.defaultCachePath, reporter = new progress_1.ConsoleReporter(process.stdout.isTTY), timeout = 15000, } = options;
if (version === 'stable') {
version = await fetchTargetStableVersion({ timeout, cachePath, platform });
}
else if (version) {
/**
* Only validate version against server when no local download that matches version exists
*/
if (!fs.existsSync(path.resolve(cachePath, `vscode-${platform}-${version}`))) {
if (!(await isValidVersion(version, platform, timeout))) {
throw Error(`Invalid version ${version}`);
}
}
}
else {
version = await fetchTargetInferredVersion({
timeout,
cachePath,
platform,
extensionsDevelopmentPath: options.extensionDevelopmentPath,
});
}
if (platform === 'win32-archive' && semver.satisfies(version, '>= 1.85.0', { includePrerelease: true })) {
throw new Error('Windows 32-bit is no longer supported from v1.85 onwards');
}
reporter.report({ stage: progress_1.ProgressReportStage.ResolvedVersion, version });
const downloadedPath = path.resolve(cachePath, makeDownloadDirName(platform, version));
if (fs.existsSync(path.join(downloadedPath, COMPLETE_FILE_NAME))) {
if (util_2.isInsiderVersionIdentifier(version)) {
reporter.report({ stage: progress_1.ProgressReportStage.FetchingInsidersMetadata });
const { version: currentHash, date: currentDate } = util_2.insidersDownloadDirMetadata(downloadedPath, platform);
const { version: latestHash, timestamp: latestTimestamp } = version === 'insiders'
? await util_2.getLatestInsidersMetadata(util_2.systemDefaultPlatform)
: await util_2.getInsidersVersionMetadata(util_2.systemDefaultPlatform, version);
if (currentHash === latestHash) {
reporter.report({ stage: progress_1.ProgressReportStage.FoundMatchingInstall, downloadedPath });
return Promise.resolve(util_2.insidersDownloadDirToExecutablePath(downloadedPath, platform));
}
else {
try {
reporter.report({
stage: progress_1.ProgressReportStage.ReplacingOldInsiders,
downloadedPath,
oldDate: currentDate,
oldHash: currentHash,
newDate: new Date(latestTimestamp),
newHash: latestHash,
});
await fs.promises.rm(downloadedPath, { force: true, recursive: true });
}
catch (err) {
reporter.error(err);
throw Error(`Failed to remove outdated Insiders at ${downloadedPath}.`);
}
}
}
else if (util_2.isStableVersionIdentifier(version)) {
reporter.report({ stage: progress_1.ProgressReportStage.FoundMatchingInstall, downloadedPath });
return Promise.resolve(util_2.downloadDirToExecutablePath(downloadedPath, platform));
}
else {
reporter.report({ stage: progress_1.ProgressReportStage.FoundMatchingInstall, downloadedPath });
return Promise.resolve(util_2.insidersDownloadDirToExecutablePath(downloadedPath, platform));
}
}
for (let i = 0;; i++) {
try {
await fs.promises.rm(downloadedPath, { recursive: true, force: true });
const download = await downloadVSCodeArchive({
version,
platform,
cachePath,
reporter,
timeout,
});
// important! do not put anything async here, since unzipVSCode will need
// to start consuming the stream immediately.
await unzipVSCode(reporter, downloadedPath, platform, download);
await fs.promises.writeFile(path.join(downloadedPath, COMPLETE_FILE_NAME), '');
reporter.report({ stage: progress_1.ProgressReportStage.NewInstallComplete, downloadedPath });
break;
}
catch (error) {
if (i++ < DOWNLOAD_ATTEMPTS) {
reporter.report({
stage: progress_1.ProgressReportStage.Retrying,
attempt: i,
error: error,
totalAttempts: DOWNLOAD_ATTEMPTS,
});
}
else {
reporter.error(error);
throw Error(`Failed to download and unzip VS Code ${version}`);
}
}
}
reporter.report({ stage: progress_1.ProgressReportStage.NewInstallComplete, downloadedPath });
if (util_2.isStableVersionIdentifier(version)) {
return util_2.downloadDirToExecutablePath(downloadedPath, platform);
}
else {
return util_2.insidersDownloadDirToExecutablePath(downloadedPath, platform);
}
}
exports.download = download;
async function downloadAndUnzipVSCode(versionOrOptions, platform, reporter, extractSync) {
return await download(typeof versionOrOptions === 'object'
? versionOrOptions
: { version: versionOrOptions, platform, reporter, extractSync });
}
exports.downloadAndUnzipVSCode = downloadAndUnzipVSCode;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,119 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const child_process_1 = require("child_process");
const fs_1 = require("fs");
const os_1 = require("os");
const path_1 = require("path");
const vitest_1 = require("vitest");
const download_1 = require("./download");
const progress_1 = require("./progress");
const util_1 = require("./util");
const platforms = [
'darwin',
'darwin-arm64',
'win32-x64-archive',
'win32-arm64-archive',
'linux-x64',
'linux-arm64',
'linux-armhf',
];
vitest_1.describe('sane downloads', () => {
const testTempDir = path_1.join(os_1.tmpdir(), 'vscode-test-download');
vitest_1.beforeAll(async () => {
await fs_1.promises.mkdir(testTempDir, { recursive: true });
});
for (const quality of ['insiders', 'stable']) {
for (const platform of platforms) {
vitest_1.test.concurrent(`${quality}/${platform}`, async () => {
const location = await download_1.downloadAndUnzipVSCode({
platform,
version: quality,
cachePath: testTempDir,
reporter: new progress_1.SilentReporter(),
});
if (!fs_1.existsSync(location)) {
throw new Error(`expected ${location} to exist for ${platform}`);
}
const exePath = util_1.resolveCliPathFromVSCodeExecutablePath(location, platform);
if (!fs_1.existsSync(exePath)) {
throw new Error(`expected ${exePath} to from ${location}`);
}
if (platform === util_1.systemDefaultPlatform) {
const version = child_process_1.spawnSync(exePath, ['--version']);
vitest_1.expect(version.status).to.equal(0);
vitest_1.expect(version.stdout.toString().trim()).to.not.be.empty;
}
});
}
}
vitest_1.afterAll(async () => {
try {
await fs_1.promises.rmdir(testTempDir, { recursive: true });
}
catch {
// ignored
}
});
});
vitest_1.describe.skip('fetchTargetInferredVersion', () => {
let stable;
let insiders;
let extensionsDevelopmentPath = path_1.join(os_1.tmpdir(), 'vscode-test-tmp-workspace');
vitest_1.beforeAll(async () => {
[stable, insiders] = await Promise.all([download_1.fetchStableVersions(5000), download_1.fetchInsiderVersions(5000)]);
});
vitest_1.afterEach(async () => {
await fs_1.promises.rm(extensionsDevelopmentPath, { recursive: true, force: true });
});
const writeJSON = async (path, contents) => {
const target = path_1.join(extensionsDevelopmentPath, path);
await fs_1.promises.mkdir(path_1.dirname(target), { recursive: true });
await fs_1.promises.writeFile(target, JSON.stringify(contents));
};
const doFetch = (paths = ['./']) => download_1.fetchTargetInferredVersion({
cachePath: path_1.join(extensionsDevelopmentPath, '.cache'),
platform: 'win32-x64-archive',
timeout: 5000,
extensionsDevelopmentPath: paths.map((p) => path_1.join(extensionsDevelopmentPath, p)),
});
vitest_1.test('matches stable if no workspace', async () => {
const version = await doFetch();
vitest_1.expect(version).to.equal(stable[0]);
});
vitest_1.test('matches stable by default', async () => {
await writeJSON('package.json', {});
const version = await doFetch();
vitest_1.expect(version).to.equal(stable[0]);
});
vitest_1.test('matches if stable is defined', async () => {
await writeJSON('package.json', { engines: { vscode: '^1.50.0' } });
const version = await doFetch();
vitest_1.expect(version).to.equal(stable[0]);
});
vitest_1.test('matches best', async () => {
await writeJSON('package.json', { engines: { vscode: '<=1.60.5' } });
const version = await doFetch();
vitest_1.expect(version).to.equal('1.60.2');
});
vitest_1.test('matches multiple workspaces', async () => {
await writeJSON('a/package.json', { engines: { vscode: '<=1.60.5' } });
await writeJSON('b/package.json', { engines: { vscode: '<=1.55.5' } });
const version = await doFetch(['a', 'b']);
vitest_1.expect(version).to.equal('1.55.2');
});
vitest_1.test('matches insiders to better stable if there is one', async () => {
await writeJSON('package.json', { engines: { vscode: '^1.60.0-insider' } });
const version = await doFetch();
vitest_1.expect(version).to.equal(stable[0]);
});
vitest_1.test('matches current insiders', async () => {
await writeJSON('package.json', { engines: { vscode: `^${insiders[0]}` } });
const version = await doFetch();
vitest_1.expect(version).to.equal(insiders[0]);
});
vitest_1.test('matches insiders to exact', async () => {
await writeJSON('package.json', { engines: { vscode: '1.60.0-insider' } });
const version = await doFetch();
vitest_1.expect(version).to.equal('1.60.0-insider');
});
});

View File

@@ -0,0 +1,4 @@
export { download, downloadAndUnzipVSCode } from './download';
export { runTests } from './runTest';
export { resolveCliPathFromVSCodeExecutablePath, resolveCliArgsFromVSCodeExecutablePath } from './util';
export * from './progress';

View File

@@ -0,0 +1,26 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveCliArgsFromVSCodeExecutablePath = exports.resolveCliPathFromVSCodeExecutablePath = exports.runTests = exports.downloadAndUnzipVSCode = exports.download = void 0;
var download_1 = require("./download");
Object.defineProperty(exports, "download", { enumerable: true, get: function () { return download_1.download; } });
Object.defineProperty(exports, "downloadAndUnzipVSCode", { enumerable: true, get: function () { return download_1.downloadAndUnzipVSCode; } });
var runTest_1 = require("./runTest");
Object.defineProperty(exports, "runTests", { enumerable: true, get: function () { return runTest_1.runTests; } });
var util_1 = require("./util");
Object.defineProperty(exports, "resolveCliPathFromVSCodeExecutablePath", { enumerable: true, get: function () { return util_1.resolveCliPathFromVSCodeExecutablePath; } });
Object.defineProperty(exports, "resolveCliArgsFromVSCodeExecutablePath", { enumerable: true, get: function () { return util_1.resolveCliArgsFromVSCodeExecutablePath; } });
__exportStar(require("./progress"), exports);

View File

@@ -0,0 +1,79 @@
/** Stages of progress while downloading VS Code */
export declare enum ProgressReportStage {
/** Initial fetch of the latest version if not explicitly given */
FetchingVersion = "fetchingVersion",
/** Always fired when the version is determined. */
ResolvedVersion = "resolvedVersion",
/** Fired before fetching info about the latest Insiders version, when requesting insiders builds */
FetchingInsidersMetadata = "fetchingInsidersMetadata",
/** Fired if the current Insiders is out of date */
ReplacingOldInsiders = "replacingOldInsiders",
/** Fired when an existing install is found which does not require a download */
FoundMatchingInstall = "foundMatchingInstall",
/** Fired before the URL to the download zip or tarball is looked up */
ResolvingCDNLocation = "resolvingCDNLocation",
/** Fired continuously while a download happens */
Downloading = "downloading",
/** Fired when the command is issued to do a synchronous extraction. May not fire depending on the platform and options. */
ExtractingSynchonrously = "extractingSynchonrously",
/** Fired when the download fails and a retry will be attempted */
Retrying = "retrying",
/** Fired after folder is downloaded and unzipped */
NewInstallComplete = "newInstallComplete"
}
export declare type ProgressReport = {
stage: ProgressReportStage.FetchingVersion;
} | {
stage: ProgressReportStage.ResolvedVersion;
version: string;
} | {
stage: ProgressReportStage.FetchingInsidersMetadata;
} | {
stage: ProgressReportStage.ReplacingOldInsiders;
downloadedPath: string;
oldHash: string;
oldDate: Date;
newHash: string;
newDate: Date;
} | {
stage: ProgressReportStage.FoundMatchingInstall;
downloadedPath: string;
} | {
stage: ProgressReportStage.ResolvingCDNLocation;
url: string;
} | {
stage: ProgressReportStage.Downloading;
url: string;
totalBytes: number;
bytesSoFar: number;
} | {
stage: ProgressReportStage.Retrying;
error: Error;
attempt: number;
totalAttempts: number;
} | {
stage: ProgressReportStage.ExtractingSynchonrously;
} | {
stage: ProgressReportStage.NewInstallComplete;
downloadedPath: string;
};
export interface ProgressReporter {
report(report: ProgressReport): void;
error(err: unknown): void;
}
/** Silent progress reporter */
export declare class SilentReporter implements ProgressReporter {
report(): void;
error(): void;
}
/** Default progress reporter that logs VS Code download progress to console */
export declare class ConsoleReporter implements ProgressReporter {
private readonly showDownloadProgress;
private version?;
private downloadReport?;
constructor(showDownloadProgress: boolean);
report(report: ProgressReport): void;
error(err: unknown): void;
private flushDownloadReport;
private reportDownload;
}

View File

@@ -0,0 +1,106 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConsoleReporter = exports.SilentReporter = exports.ProgressReportStage = void 0;
/** Stages of progress while downloading VS Code */
var ProgressReportStage;
(function (ProgressReportStage) {
/** Initial fetch of the latest version if not explicitly given */
ProgressReportStage["FetchingVersion"] = "fetchingVersion";
/** Always fired when the version is determined. */
ProgressReportStage["ResolvedVersion"] = "resolvedVersion";
/** Fired before fetching info about the latest Insiders version, when requesting insiders builds */
ProgressReportStage["FetchingInsidersMetadata"] = "fetchingInsidersMetadata";
/** Fired if the current Insiders is out of date */
ProgressReportStage["ReplacingOldInsiders"] = "replacingOldInsiders";
/** Fired when an existing install is found which does not require a download */
ProgressReportStage["FoundMatchingInstall"] = "foundMatchingInstall";
/** Fired before the URL to the download zip or tarball is looked up */
ProgressReportStage["ResolvingCDNLocation"] = "resolvingCDNLocation";
/** Fired continuously while a download happens */
ProgressReportStage["Downloading"] = "downloading";
/** Fired when the command is issued to do a synchronous extraction. May not fire depending on the platform and options. */
ProgressReportStage["ExtractingSynchonrously"] = "extractingSynchonrously";
/** Fired when the download fails and a retry will be attempted */
ProgressReportStage["Retrying"] = "retrying";
/** Fired after folder is downloaded and unzipped */
ProgressReportStage["NewInstallComplete"] = "newInstallComplete";
})(ProgressReportStage = exports.ProgressReportStage || (exports.ProgressReportStage = {}));
/** Silent progress reporter */
class SilentReporter {
report() {
// no-op
}
error() {
// no-op
}
}
exports.SilentReporter = SilentReporter;
/** Default progress reporter that logs VS Code download progress to console */
class ConsoleReporter {
constructor(showDownloadProgress) {
this.showDownloadProgress = showDownloadProgress;
}
report(report) {
switch (report.stage) {
case ProgressReportStage.ResolvedVersion:
this.version = report.version;
break;
case ProgressReportStage.ReplacingOldInsiders:
console.log(`Removing outdated Insiders at ${report.downloadedPath} and re-downloading.`);
console.log(`Old: ${report.oldHash} | ${report.oldDate.toISOString()}`);
console.log(`New: ${report.newHash} | ${report.newDate.toISOString()}`);
break;
case ProgressReportStage.FoundMatchingInstall:
console.log(`Found existing install in ${report.downloadedPath}. Skipping download`);
break;
case ProgressReportStage.ResolvingCDNLocation:
console.log(`Downloading VS Code ${this.version} from ${report.url}`);
break;
case ProgressReportStage.Downloading:
if (!this.showDownloadProgress && report.bytesSoFar === 0) {
console.log(`Downloading VS Code (${report.totalBytes}B)`);
}
else if (!this.downloadReport) {
this.downloadReport = { timeout: setTimeout(() => this.reportDownload(), 100), report };
}
else {
this.downloadReport.report = report;
}
break;
case ProgressReportStage.Retrying:
this.flushDownloadReport();
console.log(`Error downloading, retrying (attempt ${report.attempt} of ${report.totalAttempts}): ${report.error.message}`);
break;
case ProgressReportStage.NewInstallComplete:
this.flushDownloadReport();
console.log(`Downloaded VS Code into ${report.downloadedPath}`);
break;
}
}
error(err) {
console.error(err);
}
flushDownloadReport() {
if (this.showDownloadProgress) {
this.reportDownload();
console.log('');
}
}
reportDownload() {
if (!this.downloadReport) {
return;
}
const { totalBytes, bytesSoFar } = this.downloadReport.report;
this.downloadReport = undefined;
const percent = Math.max(0, Math.min(1, bytesSoFar / totalBytes));
const progressBarSize = 30;
const barTicks = Math.floor(percent * progressBarSize);
const progressBar = '='.repeat(barTicks) + '-'.repeat(progressBarSize - barTicks);
process.stdout.write(`\x1b[G\x1b[0KDownloading VS Code [${progressBar}] ${(percent * 100).toFixed()}%`);
}
}
exports.ConsoleReporter = ConsoleReporter;

View File

@@ -0,0 +1,17 @@
/// <reference types="node" />
import { IncomingMessage } from 'http';
export declare function getStream(api: string, timeout: number): Promise<IncomingMessage>;
export declare function getJSON<T>(api: string, timeout: number): Promise<T>;
export declare class TimeoutController {
private readonly timeout;
private handle;
private readonly ctrl;
get signal(): AbortSignal;
constructor(timeout: number);
touch(): void;
dispose(): void;
private readonly reject;
}
export declare class TimeoutError extends Error {
constructor(duration: number);
}

View File

@@ -0,0 +1,81 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimeoutError = exports.TimeoutController = exports.getJSON = exports.getStream = void 0;
const https = require("https");
const util_1 = require("./util");
async function getStream(api, timeout) {
const ctrl = new TimeoutController(timeout);
return new Promise((resolve, reject) => {
ctrl.signal.addEventListener('abort', () => {
reject(new TimeoutError(timeout));
req.destroy();
});
const req = https.get(api, util_1.urlToOptions(api), (res) => resolve(res)).on('error', reject);
}).finally(() => ctrl.dispose());
}
exports.getStream = getStream;
async function getJSON(api, timeout) {
const ctrl = new TimeoutController(timeout);
return new Promise((resolve, reject) => {
ctrl.signal.addEventListener('abort', () => {
reject(new TimeoutError(timeout));
req.destroy();
});
const req = https
.get(api, util_1.urlToOptions(api), (res) => {
if (res.statusCode !== 200) {
reject('Failed to get JSON');
}
let data = '';
res.on('data', (chunk) => {
ctrl.touch();
data += chunk;
});
res.on('end', () => {
ctrl.dispose();
try {
const jsonData = JSON.parse(data);
resolve(jsonData);
}
catch (err) {
console.error(`Failed to parse response from ${api} as JSON`);
reject(err);
}
});
res.on('error', reject);
})
.on('error', reject);
}).finally(() => ctrl.dispose());
}
exports.getJSON = getJSON;
class TimeoutController {
constructor(timeout) {
this.timeout = timeout;
this.ctrl = new AbortController();
this.reject = () => {
this.ctrl.abort();
};
this.handle = setTimeout(this.reject, timeout);
}
get signal() {
return this.ctrl.signal;
}
touch() {
clearTimeout(this.handle);
this.handle = setTimeout(this.reject, this.timeout);
}
dispose() {
clearTimeout(this.handle);
}
}
exports.TimeoutController = TimeoutController;
class TimeoutError extends Error {
constructor(duration) {
super(`@vscode/test-electron request timeout out after ${duration}ms`);
}
}
exports.TimeoutError = TimeoutError;

View File

@@ -0,0 +1,107 @@
import { DownloadVersion, DownloadPlatform } from './download';
import { ProgressReporter } from './progress';
export interface TestOptions {
/**
* The VS Code executable path used for testing.
*
* If not passed, will use `options.version` to download a copy of VS Code for testing.
* If `version` is not specified either, will download and use latest stable release.
*/
vscodeExecutablePath?: string;
/**
* The VS Code version to download. Valid versions are:
* - `'stable'`
* - `'insiders'`
* - `'1.32.0'`, `'1.31.1'`, etc
*
* Defaults to `stable`, which is latest stable version.
*
* *If a local copy exists at `.vscode-test/vscode-<VERSION>`, skip download.*
*/
version?: DownloadVersion;
/**
* The VS Code platform to download. If not specified, it defaults to the
* current platform.
*
* Possible values are:
* - `win32-x64-archive`
* - `win32-arm64-archive `
* - `darwin`
* - `darwin-arm64`
* - `linux-x64`
* - `linux-arm64`
* - `linux-armhf`
*/
platform?: DownloadPlatform;
/**
* Whether VS Code should be launched using default settings and extensions
* installed on this machine. If `false`, then separate directories will be
* used inside the `.vscode-test` folder within the project.
*
* Defaults to `false`.
*/
reuseMachineInstall?: boolean;
/**
* Absolute path to the extension root. Passed to `--extensionDevelopmentPath`.
* Must include a `package.json` Extension Manifest.
*/
extensionDevelopmentPath: string | string[];
/**
* Absolute path to the extension tests runner. Passed to `--extensionTestsPath`.
* Can be either a file path or a directory path that contains an `index.js`.
* Must export a `run` function of the following signature:
*
* ```ts
* function run(): Promise<void>;
* ```
*
* When running the extension test, the Extension Development Host will call this function
* that runs the test suite. This function should throws an error if any test fails.
*
* The first argument is the path to the file specified in `extensionTestsPath`.
*
*/
extensionTestsPath: string;
/**
* Environment variables being passed to the extension test script.
*/
extensionTestsEnv?: {
[key: string]: string | undefined;
};
/**
* A list of launch arguments passed to VS Code executable, in addition to `--extensionDevelopmentPath`
* and `--extensionTestsPath` which are provided by `extensionDevelopmentPath` and `extensionTestsPath`
* options.
*
* If the first argument is a path to a file/folder/workspace, the launched VS Code instance
* will open it.
*
* See `code --help` for possible arguments.
*/
launchArgs?: string[];
/**
* Progress reporter to use while VS Code is downloaded. Defaults to a
* console reporter. A {@link SilentReporter} is also available, and you
* may implement your own.
*/
reporter?: ProgressReporter;
/**
* Whether the downloaded zip should be synchronously extracted. Should be
* omitted unless you're experiencing issues installing VS Code versions.
*/
extractSync?: boolean;
/**
* Number of milliseconds after which to time out if no data is received from
* the remote when downloading VS Code. Note that this is an 'idle' timeout
* and does not enforce the total time VS Code may take to download.
*/
timeout?: number;
}
/**
* Run VS Code extension test
*
* @returns The exit code of the command to launch VS Code extension test
*/
export declare function runTests(options: TestOptions): Promise<number>;
/** Adds the extensions and user data dir to the arguments for the VS Code CLI */
export declare function getProfileArguments(args: readonly string[]): string[];

View File

@@ -0,0 +1,128 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getProfileArguments = exports.runTests = void 0;
const cp = require("child_process");
const path = require("path");
const download_1 = require("./download");
const util_1 = require("./util");
/**
* Run VS Code extension test
*
* @returns The exit code of the command to launch VS Code extension test
*/
async function runTests(options) {
if (!options.vscodeExecutablePath) {
options.vscodeExecutablePath = await download_1.downloadAndUnzipVSCode(options);
}
let args = [
// https://github.com/microsoft/vscode/issues/84238
'--no-sandbox',
// https://github.com/microsoft/vscode-test/issues/221
'--disable-gpu-sandbox',
// https://github.com/microsoft/vscode-test/issues/120
'--disable-updates',
'--skip-welcome',
'--skip-release-notes',
'--disable-workspace-trust',
'--extensionTestsPath=' + options.extensionTestsPath,
];
if (Array.isArray(options.extensionDevelopmentPath)) {
args.push(...options.extensionDevelopmentPath.map((devPath) => `--extensionDevelopmentPath=${devPath}`));
}
else {
args.push(`--extensionDevelopmentPath=${options.extensionDevelopmentPath}`);
}
if (options.launchArgs) {
args = options.launchArgs.concat(args);
}
if (!options.reuseMachineInstall) {
args.push(...getProfileArguments(args));
}
return innerRunTests(options.vscodeExecutablePath, args, options.extensionTestsEnv);
}
exports.runTests = runTests;
/** Adds the extensions and user data dir to the arguments for the VS Code CLI */
function getProfileArguments(args) {
const out = [];
if (!hasArg('extensions-dir', args)) {
out.push(`--extensions-dir=${path.join(download_1.defaultCachePath, 'extensions')}`);
}
if (!hasArg('user-data-dir', args)) {
out.push(`--user-data-dir=${path.join(download_1.defaultCachePath, 'user-data')}`);
}
return out;
}
exports.getProfileArguments = getProfileArguments;
function hasArg(argName, argList) {
return argList.some((a) => a === `--${argName}` || a.startsWith(`--${argName}=`));
}
const SIGINT = 'SIGINT';
async function innerRunTests(executable, args, testRunnerEnv) {
const fullEnv = Object.assign({}, process.env, testRunnerEnv);
const cmd = cp.spawn(executable, args, { env: fullEnv });
let exitRequested = false;
const ctrlc1 = () => {
process.removeListener(SIGINT, ctrlc1);
process.on(SIGINT, ctrlc2);
console.log('Closing VS Code gracefully. Press Ctrl+C to force close.');
exitRequested = true;
cmd.kill(SIGINT); // this should cause the returned promise to resolve
};
const ctrlc2 = () => {
console.log('Closing VS Code forcefully.');
process.removeListener(SIGINT, ctrlc2);
exitRequested = true;
util_1.killTree(cmd.pid, true);
};
const prom = new Promise((resolve, reject) => {
if (cmd.pid) {
process.on(SIGINT, ctrlc1);
}
cmd.stdout.on('data', (d) => process.stdout.write(d));
cmd.stderr.on('data', (d) => process.stderr.write(d));
cmd.on('error', function (data) {
console.log('Test error: ' + data.toString());
});
let finished = false;
function onProcessClosed(code, signal) {
if (finished) {
return;
}
finished = true;
console.log(`Exit code: ${code !== null && code !== void 0 ? code : signal}`);
// fix: on windows, it seems like these descriptors can linger for an
// indeterminate amount of time, causing the process to hang.
cmd.stdout.destroy();
cmd.stderr.destroy();
if (code === null) {
reject(signal);
}
else if (code !== 0) {
reject('Failed');
}
else {
console.log('Done\n');
resolve(code !== null && code !== void 0 ? code : -1);
}
}
cmd.on('close', onProcessClosed);
cmd.on('exit', onProcessClosed);
});
let code;
try {
code = await prom;
}
finally {
process.removeListener(SIGINT, ctrlc1);
process.removeListener(SIGINT, ctrlc2);
}
// exit immediately if we handled a SIGINT and no one else did
if (exitRequested && process.listenerCount(SIGINT) === 0) {
process.exit(1);
}
return code;
}

View File

@@ -0,0 +1,70 @@
/// <reference types="node" />
import * as https from 'https';
import { DownloadPlatform } from './download';
import { TestOptions } from './runTest';
export declare let systemDefaultPlatform: DownloadPlatform;
export declare function isInsiderVersionIdentifier(version: string): boolean;
export declare function isStableVersionIdentifier(version: string): boolean;
export declare function getVSCodeDownloadUrl(version: string, platform?: DownloadPlatform): string;
export declare function urlToOptions(url: string): https.RequestOptions;
export declare function downloadDirToExecutablePath(dir: string, platform: DownloadPlatform): string;
export declare function insidersDownloadDirToExecutablePath(dir: string, platform: DownloadPlatform): string;
export declare function insidersDownloadDirMetadata(dir: string, platform: DownloadPlatform): {
version: any;
date: Date;
};
export interface IUpdateMetadata {
url: string;
name: string;
version: string;
productVersion: string;
hash: string;
timestamp: number;
sha256hash: string;
supportsFastUpdate: boolean;
}
export declare function getInsidersVersionMetadata(platform: string, version: string): Promise<IUpdateMetadata>;
export declare function getLatestInsidersMetadata(platform: string): Promise<IUpdateMetadata>;
/**
* Resolve the VS Code cli path from executable path returned from `downloadAndUnzipVSCode`.
* Usually you will want {@link resolveCliArgsFromVSCodeExecutablePath} instead.
*/
export declare function resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath: string, platform?: DownloadPlatform): string;
/**
* Resolve the VS Code cli arguments from executable path returned from `downloadAndUnzipVSCode`.
* You can use this path to spawn processes for extension management. For example:
*
* ```ts
* const cp = require('child_process');
* const { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } = require('@vscode/test-electron')
* const vscodeExecutablePath = await downloadAndUnzipVSCode('1.36.0');
* const [cli, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
*
* cp.spawnSync(cli, [...args, '--install-extension', '<EXTENSION-ID-OR-PATH-TO-VSIX>'], {
* encoding: 'utf-8',
* stdio: 'inherit'
* });
* ```
*
* @param vscodeExecutablePath The `vscodeExecutablePath` from `downloadAndUnzipVSCode`.
*/
export declare function resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath: string, options?: Pick<TestOptions, 'reuseMachineInstall' | 'platform'>): string[];
/** Predicates whether arg is undefined or null */
export declare function isDefined<T>(arg: T | undefined | null): arg is T;
/**
* Validates the stream data matches the given length and checksum, if any.
*
* Note: md5 is not ideal, but it's what we get from the CDN, and for the
* purposes of self-reported content verification is sufficient.
*/
export declare function validateStream(readable: NodeJS.ReadableStream, length: number, sha256?: string): Promise<void>;
/** Gets a Buffer from a Node.js stream */
export declare function streamToBuffer(readable: NodeJS.ReadableStream): Promise<Buffer>;
/** Gets whether child is a subdirectory of the parent */
export declare function isSubdirectory(parent: string, child: string): boolean;
/**
* Wraps a function so that it's called once, and never again, memoizing
* the result unless it rejects.
*/
export declare function onceWithoutRejections<T, Args extends unknown[]>(fn: (...args: Args) => Promise<T>): (...args: Args) => Promise<T>;
export declare function killTree(processId: number, force: boolean): Promise<void>;

View File

@@ -0,0 +1,269 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.killTree = exports.onceWithoutRejections = exports.isSubdirectory = exports.streamToBuffer = exports.validateStream = exports.isDefined = exports.resolveCliArgsFromVSCodeExecutablePath = exports.resolveCliPathFromVSCodeExecutablePath = exports.getLatestInsidersMetadata = exports.getInsidersVersionMetadata = exports.insidersDownloadDirMetadata = exports.insidersDownloadDirToExecutablePath = exports.downloadDirToExecutablePath = exports.urlToOptions = exports.getVSCodeDownloadUrl = exports.isStableVersionIdentifier = exports.isInsiderVersionIdentifier = exports.systemDefaultPlatform = void 0;
const child_process_1 = require("child_process");
const fs_1 = require("fs");
const createHttpProxyAgent = require("http-proxy-agent");
const createHttpsProxyAgent = require("https-proxy-agent");
const path = require("path");
const url_1 = require("url");
const request = require("./request");
const runTest_1 = require("./runTest");
const crypto_1 = require("crypto");
const windowsPlatforms = new Set(['win32-x64-archive', 'win32-arm64-archive']);
const darwinPlatforms = new Set(['darwin-arm64', 'darwin']);
switch (process.platform) {
case 'darwin':
exports.systemDefaultPlatform = process.arch === 'arm64' ? 'darwin-arm64' : 'darwin';
break;
case 'win32':
exports.systemDefaultPlatform = process.arch === 'arm64' ? 'win32-arm64-archive' : 'win32-x64-archive';
break;
default:
exports.systemDefaultPlatform =
process.arch === 'arm64' ? 'linux-arm64' : process.arch === 'arm' ? 'linux-armhf' : 'linux-x64';
}
function isInsiderVersionIdentifier(version) {
return version === 'insiders' || version.endsWith('-insider'); // insider or 1.2.3-insider version string
}
exports.isInsiderVersionIdentifier = isInsiderVersionIdentifier;
function isStableVersionIdentifier(version) {
return version === 'stable' || /^[0-9]+\.[0-9]+\.[0-9]$/.test(version); // stable or 1.2.3 version string
}
exports.isStableVersionIdentifier = isStableVersionIdentifier;
function getVSCodeDownloadUrl(version, platform = exports.systemDefaultPlatform) {
if (version === 'insiders') {
return `https://update.code.visualstudio.com/latest/${platform}/insider`;
}
else if (isInsiderVersionIdentifier(version)) {
return `https://update.code.visualstudio.com/${version}/${platform}/insider`;
}
else if (isStableVersionIdentifier(version)) {
return `https://update.code.visualstudio.com/${version}/${platform}/stable`;
}
else {
// insiders commit hash
return `https://update.code.visualstudio.com/commit:${version}/${platform}/insider`;
}
}
exports.getVSCodeDownloadUrl = getVSCodeDownloadUrl;
let PROXY_AGENT = undefined;
let HTTPS_PROXY_AGENT = undefined;
if (process.env.npm_config_proxy) {
PROXY_AGENT = createHttpProxyAgent(process.env.npm_config_proxy);
HTTPS_PROXY_AGENT = createHttpsProxyAgent(process.env.npm_config_proxy);
}
if (process.env.npm_config_https_proxy) {
HTTPS_PROXY_AGENT = createHttpsProxyAgent(process.env.npm_config_https_proxy);
}
function urlToOptions(url) {
const parsed = new url_1.URL(url);
const options = {};
if (PROXY_AGENT && parsed.protocol.startsWith('http:')) {
options.agent = PROXY_AGENT;
}
if (HTTPS_PROXY_AGENT && parsed.protocol.startsWith('https:')) {
options.agent = HTTPS_PROXY_AGENT;
}
return options;
}
exports.urlToOptions = urlToOptions;
function downloadDirToExecutablePath(dir, platform) {
if (windowsPlatforms.has(platform)) {
return path.resolve(dir, 'Code.exe');
}
else if (darwinPlatforms.has(platform)) {
return path.resolve(dir, 'Visual Studio Code.app/Contents/MacOS/Electron');
}
else {
return path.resolve(dir, 'code');
}
}
exports.downloadDirToExecutablePath = downloadDirToExecutablePath;
function insidersDownloadDirToExecutablePath(dir, platform) {
if (windowsPlatforms.has(platform)) {
return path.resolve(dir, 'Code - Insiders.exe');
}
else if (darwinPlatforms.has(platform)) {
return path.resolve(dir, 'Visual Studio Code - Insiders.app/Contents/MacOS/Electron');
}
else {
return path.resolve(dir, 'code-insiders');
}
}
exports.insidersDownloadDirToExecutablePath = insidersDownloadDirToExecutablePath;
function insidersDownloadDirMetadata(dir, platform) {
let productJsonPath;
if (windowsPlatforms.has(platform)) {
productJsonPath = path.resolve(dir, 'resources/app/product.json');
}
else if (darwinPlatforms.has(platform)) {
productJsonPath = path.resolve(dir, 'Visual Studio Code - Insiders.app/Contents/Resources/app/product.json');
}
else {
productJsonPath = path.resolve(dir, 'resources/app/product.json');
}
const productJson = JSON.parse(fs_1.readFileSync(productJsonPath, 'utf-8'));
return {
version: productJson.commit,
date: new Date(productJson.date),
};
}
exports.insidersDownloadDirMetadata = insidersDownloadDirMetadata;
async function getInsidersVersionMetadata(platform, version) {
const remoteUrl = `https://update.code.visualstudio.com/api/versions/${version}/${platform}/insider`;
return await request.getJSON(remoteUrl, 30000);
}
exports.getInsidersVersionMetadata = getInsidersVersionMetadata;
async function getLatestInsidersMetadata(platform) {
const remoteUrl = `https://update.code.visualstudio.com/api/update/${platform}/insider/latest`;
return await request.getJSON(remoteUrl, 30000);
}
exports.getLatestInsidersMetadata = getLatestInsidersMetadata;
/**
* Resolve the VS Code cli path from executable path returned from `downloadAndUnzipVSCode`.
* Usually you will want {@link resolveCliArgsFromVSCodeExecutablePath} instead.
*/
function resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath, platform = exports.systemDefaultPlatform) {
if (platform === 'win32-archive') {
throw new Error('Windows 32-bit is no longer supported');
}
if (windowsPlatforms.has(platform)) {
if (vscodeExecutablePath.endsWith('Code - Insiders.exe')) {
return path.resolve(vscodeExecutablePath, '../bin/code-insiders.cmd');
}
else {
return path.resolve(vscodeExecutablePath, '../bin/code.cmd');
}
}
else if (darwinPlatforms.has(platform)) {
return path.resolve(vscodeExecutablePath, '../../../Contents/Resources/app/bin/code');
}
else {
if (vscodeExecutablePath.endsWith('code-insiders')) {
return path.resolve(vscodeExecutablePath, '../bin/code-insiders');
}
else {
return path.resolve(vscodeExecutablePath, '../bin/code');
}
}
}
exports.resolveCliPathFromVSCodeExecutablePath = resolveCliPathFromVSCodeExecutablePath;
/**
* Resolve the VS Code cli arguments from executable path returned from `downloadAndUnzipVSCode`.
* You can use this path to spawn processes for extension management. For example:
*
* ```ts
* const cp = require('child_process');
* const { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } = require('@vscode/test-electron')
* const vscodeExecutablePath = await downloadAndUnzipVSCode('1.36.0');
* const [cli, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
*
* cp.spawnSync(cli, [...args, '--install-extension', '<EXTENSION-ID-OR-PATH-TO-VSIX>'], {
* encoding: 'utf-8',
* stdio: 'inherit'
* });
* ```
*
* @param vscodeExecutablePath The `vscodeExecutablePath` from `downloadAndUnzipVSCode`.
*/
function resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath, options) {
var _a;
const args = [
resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath, (_a = options === null || options === void 0 ? void 0 : options.platform) !== null && _a !== void 0 ? _a : exports.systemDefaultPlatform),
];
if (!(options === null || options === void 0 ? void 0 : options.reuseMachineInstall)) {
args.push(...runTest_1.getProfileArguments(args));
}
return args;
}
exports.resolveCliArgsFromVSCodeExecutablePath = resolveCliArgsFromVSCodeExecutablePath;
/** Predicates whether arg is undefined or null */
function isDefined(arg) {
return arg != null;
}
exports.isDefined = isDefined;
/**
* Validates the stream data matches the given length and checksum, if any.
*
* Note: md5 is not ideal, but it's what we get from the CDN, and for the
* purposes of self-reported content verification is sufficient.
*/
function validateStream(readable, length, sha256) {
let actualLen = 0;
const checksum = sha256 ? crypto_1.createHash('sha256') : undefined;
return new Promise((resolve, reject) => {
readable.on('data', (chunk) => {
checksum === null || checksum === void 0 ? void 0 : checksum.update(chunk);
actualLen += chunk.length;
});
readable.on('error', reject);
readable.on('end', () => {
if (actualLen !== length) {
return reject(new Error(`Downloaded stream length ${actualLen} does not match expected length ${length}`));
}
const digest = checksum === null || checksum === void 0 ? void 0 : checksum.digest('hex');
if (digest && digest !== sha256) {
return reject(new Error(`Downloaded file checksum ${digest} does not match expected checksum ${sha256}`));
}
resolve();
});
});
}
exports.validateStream = validateStream;
/** Gets a Buffer from a Node.js stream */
function streamToBuffer(readable) {
return new Promise((resolve, reject) => {
const chunks = [];
readable.on('data', (chunk) => chunks.push(chunk));
readable.on('error', reject);
readable.on('end', () => resolve(Buffer.concat(chunks)));
});
}
exports.streamToBuffer = streamToBuffer;
/** Gets whether child is a subdirectory of the parent */
function isSubdirectory(parent, child) {
const relative = path.relative(parent, child);
return !relative.startsWith('..') && !path.isAbsolute(relative);
}
exports.isSubdirectory = isSubdirectory;
/**
* Wraps a function so that it's called once, and never again, memoizing
* the result unless it rejects.
*/
function onceWithoutRejections(fn) {
let value;
return (...args) => {
if (!value) {
value = fn(...args).catch((err) => {
value = undefined;
throw err;
});
}
return value;
};
}
exports.onceWithoutRejections = onceWithoutRejections;
function killTree(processId, force) {
let cp;
if (process.platform === 'win32') {
const windir = process.env['WINDIR'] || 'C:\\Windows';
// when killing a process in Windows its child processes are *not* killed but become root processes.
// Therefore we use TASKKILL.EXE
cp = child_process_1.spawn(path.join(windir, 'System32', 'taskkill.exe'), [...(force ? ['/F'] : []), '/T', '/PID', processId.toString()], { stdio: 'inherit' });
}
else {
// on linux and OS X we kill all direct and indirect child processes as well
cp = child_process_1.spawn('sh', [path.resolve(__dirname, '../killTree.sh'), processId.toString(), force ? '9' : '15'], {
stdio: 'inherit',
});
}
return new Promise((resolve, reject) => {
cp.on('error', reject).on('exit', resolve);
});
}
exports.killTree = killTree;

View File

@@ -0,0 +1,54 @@
{
"name": "@vscode/test-electron",
"version": "2.3.8",
"scripts": {
"compile": "tsc -p ./",
"watch": "tsc -w -p ./",
"prepack": "tsc -p ./",
"fmt": "prettier --write \"lib/**/*.ts\" \"*.md\"",
"test": "eslint lib --ext ts && vitest && tsc --noEmit",
"prepare": "husky install"
},
"lint-staged": {
"*.ts": [
"eslint --fix",
"prettier --write"
],
"*.md": [
"prettier --write"
]
},
"main": "./out/index.js",
"engines": {
"node": ">=16"
},
"dependencies": {
"http-proxy-agent": "^4.0.1",
"https-proxy-agent": "^5.0.0",
"jszip": "^3.10.1",
"semver": "^7.5.2"
},
"devDependencies": {
"@types/node": "^18",
"@types/rimraf": "^3.0.0",
"@types/semver": "^7.3.13",
"@typescript-eslint/eslint-plugin": "^4.13.0",
"@typescript-eslint/parser": "^4.13.0",
"eslint": "^7.17.0",
"eslint-plugin-header": "^3.1.0",
"husky": "^8.0.3",
"lint-staged": "^13.1.2",
"prettier": "^2.8.4",
"typescript": "^4.3.5",
"vitest": "^0.10.2"
},
"license": "MIT",
"author": "Visual Studio Code Team",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/vscode-test.git"
},
"bugs": {
"url": "https://github.com/Microsoft/vscode-test/issues"
}
}

View File

@@ -0,0 +1,56 @@
name: $(Date:yyyyMMdd)$(Rev:.r)
trigger:
branches:
include:
- main
pr: none
resources:
repositories:
- repository: templates
type: github
name: microsoft/vscode-engineering
ref: main
endpoint: Monaco
parameters:
- name: publishPackage
displayName: 🚀 Publish test-electron
type: boolean
default: false
extends:
template: azure-pipelines/npm-package/pipeline.yml@templates
parameters:
npmPackages:
- name: test-electron
ghCreateTag: false
buildSteps:
- script: yarn --frozen-lockfile
displayName: Install dependencies
testPlatforms:
- name: Linux
nodeVersions:
- 16.x
testSteps:
- script: yarn --frozen-lockfile
displayName: Install dependencies
- script: yarn --cwd=sample --frozen-lockfile
displayName: Install dependencies (fs-provider)
- bash: |
/usr/bin/Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
echo ">>> Started xvfb"
displayName: Start xvfb
condition: eq(variables['Agent.OS'], 'Linux')
- script: yarn --cwd=sample test
displayName: Test package
env:
DISPLAY: ':99.0'
publishPackage: ${{ parameters.publishPackage }}

View File

@@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['lib/**/*.test.ts'],
testTimeout: 120_000,
},
});