mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -05:00
Merge from vscode 1ce89e2cb720d69c496c2815c4696ee4fd4429a6 (#6779)
* Merge from vscode 1ce89e2cb720d69c496c2815c4696ee4fd4429a6 * redisable accounts because of issues
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -34,4 +34,4 @@ export function setup() {
|
||||
await app.workbench.settingsEditor.clearUserSettings();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
191
test/smoke/src/vscode/puppeteerDriver.ts
Normal file
191
test/smoke/src/vscode/puppeteerDriver.ts
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user