Merge from vscode 1ce89e2cb720d69c496c2815c4696ee4fd4429a6 (#6779)

* Merge from vscode 1ce89e2cb720d69c496c2815c4696ee4fd4429a6

* redisable accounts because of issues
This commit is contained in:
Anthony Dresser
2019-08-15 23:56:46 -07:00
committed by GitHub
parent fb4e3c6a05
commit 6f297efb88
166 changed files with 1941 additions and 1279 deletions

View File

@@ -18,6 +18,7 @@
"@types/mocha": "2.2.41",
"@types/ncp": "2.0.1",
"@types/node": "^10.14.8",
"@types/puppeteer": "^1.19.0",
"@types/rimraf": "2.0.2",
"@types/webdriverio": "4.6.1",
"concurrently": "^3.5.1",

View File

@@ -131,7 +131,9 @@ export class Application {
verbose: this.options.verbose,
log: this.options.log,
extraArgs,
remote: this.options.remote
remote: this.options.remote,
web: this.options.web,
headless: this.options.headless
});
this._workbench = new Workbench(this._code, this.userDataPath);

View File

@@ -34,4 +34,4 @@ export function setup() {
await app.workbench.settingsEditor.clearUserSettings();
});
});
}
}

View File

@@ -30,7 +30,7 @@ export class SettingsEditor {
async clearUserSettings(): Promise<void> {
const settingsPath = path.join(this.userDataPath, 'User', 'settings.json');
await new Promise((c, e) => fs.writeFile(settingsPath, '{}', 'utf8', err => err ? e(err) : c()));
await new Promise((c, e) => fs.writeFile(settingsPath, '{\n}', 'utf8', err => err ? e(err) : c()));
await this.openSettings();
await this.editor.waitForEditorContents('settings.json', c => c === '{}');
@@ -39,4 +39,4 @@ export class SettingsEditor {
private async openSettings(): Promise<void> {
await this.quickopen.runCommand('Preferences: Open Settings (JSON)');
}
}
}

View File

@@ -59,7 +59,9 @@ const opts = minimist(args, {
],
boolean: [
'verbose',
'remote'
'remote',
'web',
'headless'
],
default: {
verbose: false
@@ -140,7 +142,7 @@ if (testCodePath) {
process.env.VSCODE_CLI = '1';
}
if (!fs.existsSync(electronPath || '')) {
if (!opts.web && !fs.existsSync(electronPath || '')) {
fail(`Can't find Code at ${electronPath}.`);
}
@@ -219,7 +221,9 @@ function createOptions(): ApplicationOptions {
verbose: opts.verbose,
log,
screenshotsPath,
remote: opts.remote
remote: opts.remote,
web: opts.web,
headless: opts.headless
};
}
@@ -241,11 +245,16 @@ after(async function () {
await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c()));
});
/*//{{SQL CARBON EDIT}}
if (!opts.web) {
setupDataMigrationTests(stableCodePath, testDataPath);
}*/
describe('Running Code', () => {
before(async function () {
const app = new Application(this.defaultOptions);
await app!.start();
await app!.start(opts.web ? false : undefined);
this.app = app;
//{{SQL CARBON EDIT}}
const testExtLoadedText = 'Test Extension Loaded';
const testSetupCompletedText = 'Test Setup Completed';
@@ -260,7 +269,6 @@ describe('Running Code', () => {
await app.workbench.quickopen.runCommand(waitForExtensionsCommand);
await app.workbench.statusbar.waitForStatusbarText(allExtensionsLoadedText, allExtensionsLoadedText);
//{{END}}
this.app = app;
});
after(async function () {
@@ -287,28 +295,27 @@ describe('Running Code', () => {
app.logger.log('*** Test start:', title);
});
}
//{{SQL CARBON EDIT}}
runProfilerTests();
runQueryEditorTests();
//Original
/*
setupDataLossTests();
if (!opts.web) { setupDataLossTests(); }
setupDataExplorerTests();
setupDataPreferencesTests();
if (!opts.web) { setupDataPreferencesTests(); }
setupDataSearchTests();
setupDataCSSTests();
setupDataEditorTests();
setupDataDebugTests();
if (!opts.web) { setupDataDebugTests(); }
setupDataGitTests();
setupDataStatusbarTests();
setupDataExtensionTests();
setupTerminalTests();
setupDataMultirootTests();
if (!opts.web) { setupDataMultirootTests(); }
setupDataLocalizationTests();
*/
//{{END}}
});
// {{SQL CARBON EDIT}}
// setupLaunchTests();
/*//{{SQL CARBON EDIT}}
if (!opts.web) {
setupLaunchTests();
}*/

View File

@@ -9,7 +9,8 @@ import * as os from 'os';
import * as fs from 'fs';
import * as mkdirp from 'mkdirp';
import { tmpName } from 'tmp';
import { IDriver, connect as connectDriver, IDisposable, IElement, Thenable } from './driver';
import { IDriver, connect as connectElectronDriver, IDisposable, IElement, Thenable } from './driver';
import { connect as connectPuppeteerDriver, launch } from './puppeteerDriver';
import { Logger } from '../logger';
import { ncp } from 'ncp';
import { URI } from 'vscode-uri';
@@ -62,7 +63,7 @@ function getBuildOutPath(root: string): string {
}
}
async function connect(child: cp.ChildProcess, outPath: string, handlePath: string, logger: Logger): Promise<Code> {
async function connect(connectDriver: typeof connectElectronDriver, child: cp.ChildProcess | undefined, outPath: string, handlePath: string, logger: Logger): Promise<Code> {
let errCount = 0;
while (true) {
@@ -71,7 +72,9 @@ async function connect(child: cp.ChildProcess, outPath: string, handlePath: stri
return new Code(client, driver, logger);
} catch (err) {
if (++errCount > 50) {
child.kill();
if (child) {
child.kill();
}
throw err;
}
@@ -94,7 +97,12 @@ export interface SpawnOptions {
verbose?: boolean;
extraArgs?: string[];
log?: string;
/** Run in the test resolver */
remote?: boolean;
/** Run in the web */
web?: boolean;
/** Run in headless mode (only applies when web is true) */
headless?: boolean;
}
async function createDriverHandle(): Promise<string> {
@@ -161,14 +169,20 @@ export async function spawn(options: SpawnOptions): Promise<Code> {
args.push(...options.extraArgs);
}
const spawnOptions: cp.SpawnOptions = { env };
let child: cp.ChildProcess | undefined;
let connectDriver: typeof connectElectronDriver;
const child = cp.spawn(electronPath, args, spawnOptions);
instances.add(child);
child.once('exit', () => instances.delete(child));
return connect(child, outPath, handle, options.logger);
if (options.web) {
await launch(args);
connectDriver = connectPuppeteerDriver.bind(connectPuppeteerDriver, !!options.headless);
} else {
const spawnOptions: cp.SpawnOptions = { env };
child = cp.spawn(electronPath, args, spawnOptions);
instances.add(child);
child.once('exit', () => instances.delete(child!));
connectDriver = connectElectronDriver;
}
return connect(connectDriver, child, outPath, handle, options.logger);
}
async function poll<T>(
@@ -366,4 +380,4 @@ export function findElements(element: IElement, fn: (element: IElement) => boole
}
return result;
}
}

View File

@@ -0,0 +1,191 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as puppeteer from 'puppeteer';
import { ChildProcess, spawn } from 'child_process';
import { join } from 'path';
import { mkdir } from 'fs';
import { promisify } from 'util';
const width = 1200;
const height = 800;
const vscodeToPuppeteerKey = {
cmd: 'Meta',
ctrl: 'Control',
shift: 'Shift',
enter: 'Enter',
escape: 'Escape',
right: 'ArrowRight',
up: 'ArrowUp',
down: 'ArrowDown',
left: 'ArrowLeft',
home: 'Home'
};
function buildDriver(browser: puppeteer.Browser, page: puppeteer.Page): IDriver {
const driver = {
_serviceBrand: undefined,
getWindowIds: () => {
return Promise.resolve([1]);
},
capturePage: () => Promise.resolve(''),
reloadWindow: (windowId) => Promise.resolve(),
exitApplication: () => browser.close(),
dispatchKeybinding: async (windowId, keybinding) => {
const chords = keybinding.split(' ');
for (let i = 0; i < chords.length; i++) {
const chord = chords[i];
if (i > 0) {
await timeout(100);
}
const keys = chord.split('+');
const keysDown: string[] = [];
for (let i = 0; i < keys.length; i++) {
if (keys[i] in vscodeToPuppeteerKey) {
keys[i] = vscodeToPuppeteerKey[keys[i]];
}
await page.keyboard.down(keys[i]);
keysDown.push(keys[i]);
}
while (keysDown.length > 0) {
await page.keyboard.up(keysDown.pop()!);
}
}
await timeout(100);
},
click: async (windowId, selector, xoffset, yoffset) => {
const { x, y } = await driver.getElementXY(windowId, selector, xoffset, yoffset);
await page.mouse.click(x + (xoffset ? xoffset : 0), y + (yoffset ? yoffset : 0));
},
doubleClick: async (windowId, selector) => {
await driver.click(windowId, selector, 0, 0);
await timeout(60);
await driver.click(windowId, selector, 0, 0);
await timeout(100);
},
setValue: async (windowId, selector, text) => page.evaluate(`window.driver.setValue('${selector}', '${text}')`),
getTitle: (windowId) => page.evaluate(`window.driver.getTitle()`),
isActiveElement: (windowId, selector) => page.evaluate(`window.driver.isActiveElement('${selector}')`),
getElements: (windowId, selector, recursive) => page.evaluate(`window.driver.getElements('${selector}', ${recursive})`),
getElementXY: (windowId, selector, xoffset?, yoffset?) => page.evaluate(`window.driver.getElementXY('${selector}', ${xoffset}, ${yoffset})`),
typeInEditor: (windowId, selector, text) => page.evaluate(`window.driver.typeInEditor('${selector}', '${text}')`),
getTerminalBuffer: (windowId, selector) => page.evaluate(`window.driver.getTerminalBuffer('${selector}')`),
writeInTerminal: (windowId, selector, text) => page.evaluate(`window.driver.writeInTerminal('${selector}', '${text}')`)
};
return driver;
}
function timeout(ms: number): Promise<void> {
return new Promise<void>(r => setTimeout(r, ms));
}
// function runInDriver(call: string, args: (string | boolean)[]): Promise<any> {}
let args: string[] | undefined;
let server: ChildProcess | undefined;
let endpoint: string | undefined;
export async function launch(_args: string[]): Promise<void> {
args = _args;
const webUserDataDir = args.filter(e => e.includes('--user-data-dir='))[0].replace('--user-data-dir=', '');
await promisify(mkdir)(webUserDataDir);
server = spawn(join(args[0], `resources/server/web.${process.platform === 'win32' ? 'bat' : 'sh'}`), ['--browser', 'none', '--driver', 'web', '--web-user-data-dir', webUserDataDir]);
server.stderr.on('data', e => console.log('Server stderr: ' + e));
process.on('exit', teardown);
process.on('SIGINT', teardown);
endpoint = await waitForEndpoint();
}
function teardown(): void {
if (server) {
server.kill();
server = undefined;
}
}
function waitForEndpoint(): Promise<string> {
return new Promise<string>(r => {
server!.stdout.on('data', d => {
const matches = d.toString('ascii').match(/Web UI available at (.+)/);
if (matches !== null) {
r(matches[1]);
}
});
});
}
export function connect(headless: boolean, outPath: string, handle: string): Promise<{ client: IDisposable, driver: IDriver }> {
return new Promise(async (c) => {
const browser = await puppeteer.launch({
// Run in Edge dev on macOS
// executablePath: '/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev',
headless,
slowMo: 80,
args: [`--window-size=${width},${height}`]
});
const page = (await browser.pages())[0];
await page.setViewport({ width, height });
const endpointSplit = endpoint!.split('#');
await page.goto(`${endpointSplit[0]}?folder=${args![1]}#${endpointSplit[1]}`);
const result = {
client: { dispose: () => teardown },
driver: buildDriver(browser, page)
};
c(result);
});
}
/**
* Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise,
* and others. This API makes no assumption about what promise library is being used which
* enables reusing existing code without migrating to a specific promise implementation. Still,
* we recommend the use of native promises which are available in this editor.
*/
export interface Thenable<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
}
export interface IElement {
tagName: string;
className: string;
textContent: string;
attributes: { [name: string]: string; };
children: IElement[];
top: number;
left: number;
}
export interface IDriver {
_serviceBrand: any;
getWindowIds(): Promise<number[]>;
capturePage(windowId: number): Promise<string>;
reloadWindow(windowId: number): Promise<void>;
exitApplication(): Promise<void>;
dispatchKeybinding(windowId: number, keybinding: string): Promise<void>;
click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void>;
doubleClick(windowId: number, selector: string): Promise<void>;
setValue(windowId: number, selector: string, text: string): Promise<void>;
getTitle(windowId: number): Promise<string>;
isActiveElement(windowId: number, selector: string): Promise<boolean>;
getElements(windowId: number, selector: string, recursive?: boolean): Promise<IElement[]>;
getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number; }>;
typeInEditor(windowId: number, selector: string, text: string): Promise<void>;
getTerminalBuffer(windowId: number, selector: string): Promise<string[]>;
writeInTerminal(windowId: number, selector: string, text: string): Promise<void>;
}
export interface IDisposable {
dispose(): void;
}

View File

@@ -7,7 +7,7 @@ const fs = require('fs');
const path = require('path');
const root = path.dirname(path.dirname(path.dirname(__dirname)));
const driverPath = path.join(root, 'src/vs/platform/driver/node/driver.ts');
const driverPath = path.join(root, 'src/vs/platform/driver/common/driver.ts');
let contents = fs.readFileSync(driverPath, 'utf8');
contents = /\/\/\*START([\s\S]*)\/\/\*END/mi.exec(contents)[1].trim();
@@ -47,4 +47,4 @@ export function connect(outPath: string, handle: string): Promise<{ client: IDis
const srcPath = path.join(path.dirname(__dirname), 'src/vscode');
const outDriverPath = path.join(srcPath, 'driver.d.ts');
fs.writeFileSync(outDriverPath, contents);
fs.writeFileSync(outDriverPath, contents);

View File

@@ -54,6 +54,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9"
integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw==
"@types/puppeteer@^1.19.0":
version "1.19.0"
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.19.0.tgz#59f0050bae019cee7c3af2bb840a25892a3078b6"
integrity sha512-Db9LWOuTm2bR/qgPE7PQCmnsCQ6flHdULuIDWTks8YdQ/SGHKg5WGWG54gl0734NDKCTF5MbqAp2qWuvBiyQ3Q==
dependencies:
"@types/node" "*"
"@types/rimraf@2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e"