280 строки
13 KiB
TypeScript
280 строки
13 KiB
TypeScript
/*---------------------------------------------------------
|
|
* Copyright (C) Microsoft Corporation. All rights reserved.
|
|
*--------------------------------------------------------*/
|
|
|
|
import * as assert from 'assert';
|
|
import { EventEmitter } from 'events';
|
|
import * as mockery from 'mockery';
|
|
import { IMock, It, Mock, MockBehavior, Times } from 'typemoq';
|
|
import { chromeConnection, ISourceMapPathOverrides, telemetry, TargetVersions, Version } from 'vscode-chrome-debug-core';
|
|
import { DebugProtocol } from 'vscode-debugprotocol';
|
|
import { ChromeDebugAdapter as _ChromeDebugAdapter } from '../src/chromeDebugAdapter';
|
|
import { getMockChromeConnectionApi, IMockChromeConnectionAPI } from './debugProtocolMocks';
|
|
import * as testUtils from './testUtils';
|
|
import { ChromeProvidedPortConnection } from '../src/chromeProvidedPortConnection';
|
|
|
|
class MockChromeDebugSession {
|
|
public sendEvent(event: DebugProtocol.Event): void {
|
|
}
|
|
|
|
public sendRequest(command: string, args: any, timeout: number, cb: (response: DebugProtocol.Response) => void): void {
|
|
}
|
|
}
|
|
|
|
const MODULE_UNDER_TEST = '../src/chromeDebugAdapter';
|
|
suite('ChromeDebugAdapter', () => {
|
|
let mockChromeConnection: IMock<ChromeProvidedPortConnection>;
|
|
let mockEventEmitter: EventEmitter;
|
|
let mockChrome: IMockChromeConnectionAPI;
|
|
|
|
let mockChromeDebugSession: IMock<MockChromeDebugSession>;
|
|
let chromeDebugAdapter: _ChromeDebugAdapter;
|
|
|
|
setup(() => {
|
|
testUtils.setupUnhandledRejectionListener();
|
|
testUtils.registerLocMocks();
|
|
mockery.enable({ useCleanCache: true, warnOnReplace: false, warnOnUnregistered: false });
|
|
|
|
// Create a ChromeConnection mock with .on and .attach. Tests can fire events via mockEventEmitter
|
|
mockChromeConnection = Mock.ofType(ChromeProvidedPortConnection, MockBehavior.Strict);
|
|
mockChrome = getMockChromeConnectionApi();
|
|
mockEventEmitter = mockChrome.mockEventEmitter;
|
|
mockChromeDebugSession = Mock.ofType(MockChromeDebugSession, MockBehavior.Strict);
|
|
mockChromeDebugSession
|
|
.setup(x => x.sendEvent(It.isAny()))
|
|
.verifiable(Times.atLeast(0));
|
|
mockChromeDebugSession
|
|
.setup(x => x.sendRequest(It.isAnyString(), It.isAny(), It.isAnyNumber(), It.isAny()))
|
|
.verifiable(Times.atLeast(0));
|
|
|
|
mockChromeConnection
|
|
.setup(x => x.setTargetFilter(It.isAny()))
|
|
.verifiable(Times.atLeast(0));
|
|
mockChromeConnection
|
|
.setup(x => x.api)
|
|
.returns(() => mockChrome.apiObjects)
|
|
.verifiable(Times.atLeast(0));
|
|
mockChromeConnection
|
|
.setup(x => x.attach(It.isValue(undefined), It.isAnyNumber(), It.isValue(undefined)))
|
|
.returns(() => Promise.resolve())
|
|
.verifiable(Times.atLeast(0));
|
|
mockChromeConnection
|
|
.setup(x => x.isAttached)
|
|
.returns(() => false)
|
|
.verifiable(Times.atLeast(0));
|
|
mockChromeConnection
|
|
.setup(x => x.run())
|
|
.returns(() => Promise.resolve())
|
|
.verifiable(Times.atLeast(0));
|
|
mockChromeConnection
|
|
.setup(x => x.onClose(It.isAny()))
|
|
.verifiable(Times.atLeast(0));
|
|
mockChromeConnection
|
|
.setup(x => x.events)
|
|
.returns(x => null)
|
|
.verifiable(Times.atLeast(0));
|
|
mockChromeConnection
|
|
.setup(x => x.version)
|
|
.returns(() => Promise.resolve(new TargetVersions(Version.unknownVersion(), Version.unknownVersion())))
|
|
.verifiable(Times.atLeast(0));
|
|
mockChromeConnection
|
|
.setup(x => x.setUserDataDir(It.isAnyString()))
|
|
.verifiable(Times.atLeast(0));
|
|
|
|
// Instantiate the ChromeDebugAdapter, injecting the mock ChromeConnection
|
|
const cDAClass: typeof _ChromeDebugAdapter = require(MODULE_UNDER_TEST).ChromeDebugAdapter;
|
|
chromeDebugAdapter = new cDAClass({ chromeConnection: function() { return mockChromeConnection.object; } } as any, mockChromeDebugSession.object as any);
|
|
});
|
|
|
|
teardown(() => {
|
|
testUtils.removeUnhandledRejectionListener();
|
|
mockery.deregisterAll();
|
|
mockery.disable();
|
|
mockChromeConnection.verifyAll();
|
|
});
|
|
|
|
suite('launch()', () => {
|
|
let originalFork: any;
|
|
let originalSpawn: any;
|
|
let originalStatSync: any;
|
|
|
|
setup(() => {
|
|
mockChromeConnection
|
|
.setup(x => x.attach(It.isValue(undefined), It.isAnyNumber(), It.isAnyString(), It.isValue(undefined), It.isValue(undefined)))
|
|
.returns(() => Promise.resolve())
|
|
.verifiable();
|
|
|
|
mockChrome.Runtime
|
|
.setup(x => x.evaluate(It.isAny()))
|
|
.returns(() => Promise.resolve<any>({ result: { type: 'string', value: '123' }}));
|
|
|
|
mockChrome.Network
|
|
.setup(x => x.setCacheDisabled(It.isAny()))
|
|
.returns(() => Promise.resolve());
|
|
});
|
|
|
|
teardown(() => {
|
|
// Hacky mock cleanup
|
|
require('child_process').fork = originalFork;
|
|
require('fs').statSync = originalStatSync;
|
|
});
|
|
|
|
test('launches with minimal correct args', () => {
|
|
let spawnCalled = false;
|
|
function fork(chromeSpawnHelperPath: string, [chromePath, ...args]: string[]): any {
|
|
// Just assert that the chrome path is some string with 'chrome' in the path, and there are >0 args
|
|
assert(chromeSpawnHelperPath.indexOf('chromeSpawnHelper.js') >= 0);
|
|
return spawn(chromePath, args);
|
|
}
|
|
|
|
function spawn(chromePath: string, args: string[]): any {
|
|
assert(chromePath.toLowerCase().indexOf('chrome') >= 0);
|
|
assert(args.indexOf('--remote-debugging-port=9222') >= 0);
|
|
assert(args.indexOf('about:blank') >= 0); // Now we use the landing page for all scenarios
|
|
assert(args.indexOf('abc') >= 0);
|
|
assert(args.indexOf('def') >= 0);
|
|
spawnCalled = true;
|
|
|
|
const stdio = { on: () => { } };
|
|
return { on: () => { }, unref: () => { }, stdout: stdio, stderr: stdio };
|
|
}
|
|
|
|
// Mock fork/spawn for chrome process, and 'fs' for finding chrome.exe.
|
|
// These are mocked as empty above - note that it's too late for mockery here.
|
|
originalFork = require('child_process').fork;
|
|
originalSpawn = require('child_process').spawn;
|
|
require('child_process').fork = fork;
|
|
require('child_process').spawn = spawn;
|
|
originalStatSync = require('fs').statSync;
|
|
require('fs').statSync = () => true;
|
|
|
|
return chromeDebugAdapter.launch({ file: 'c:\\path with space\\index.html', runtimeArgs: ['abc', 'def'] },
|
|
new telemetry.TelemetryPropertyCollector())
|
|
.then(() => assert(spawnCalled));
|
|
});
|
|
|
|
test('launches unelevated with client', async () => {
|
|
let telemetryPropertyCollector = new telemetry.TelemetryPropertyCollector();
|
|
chromeDebugAdapter.initialize({
|
|
adapterID: 'test debug adapter',
|
|
pathFormat: 'path',
|
|
supportsLaunchUnelevatedProcessRequest: true
|
|
});
|
|
|
|
const originalGetPlatform = require('os').platform;
|
|
require('os').platform = () => { return 'win32'; };
|
|
|
|
const originalGetBrowser = require('../src/utils').getBrowserPath;
|
|
require('../src/utils').getBrowserPath = () => { return 'c:\\someplace\\chrome.exe'; };
|
|
|
|
const expectedProcessId = 325;
|
|
let collectedLaunchParams: any;
|
|
mockChromeDebugSession
|
|
.setup(x => x.sendRequest('launchUnelevated',
|
|
It.is((param: any) => {
|
|
collectedLaunchParams = param;
|
|
return true;
|
|
}),
|
|
10000,
|
|
It.is(
|
|
(callback: (response: DebugProtocol.Response) => void) => {
|
|
callback({
|
|
seq: null,
|
|
type: 'command',
|
|
request_seq: 100,
|
|
command: 'launchUnelevated',
|
|
success: true,
|
|
body: {
|
|
processId: expectedProcessId
|
|
}
|
|
});
|
|
return true;
|
|
})))
|
|
.verifiable(Times.atLeast(1));
|
|
|
|
await chromeDebugAdapter.launch({
|
|
file: 'c:\\path with space\\index.html',
|
|
runtimeArgs: ['abc', 'def'],
|
|
shouldLaunchChromeUnelevated: true
|
|
}, telemetryPropertyCollector);
|
|
|
|
assert.equal(expectedProcessId, (<any>chromeDebugAdapter)._chromePID, 'Debug Adapter should receive the Chrome process id');
|
|
assert(collectedLaunchParams.process != null);
|
|
assert(collectedLaunchParams.process.match(/chrome/i));
|
|
assert(collectedLaunchParams.args != null);
|
|
|
|
assert(collectedLaunchParams.args.filter(arg => arg === '--no-default-browser-check').length !== 0,
|
|
'Should have seen the --no-default-browser-check parameter');
|
|
assert(collectedLaunchParams.args.filter(arg => arg === '--no-first-run').length !== 0,
|
|
'Should have seen the --no-first-run parameter');
|
|
assert(collectedLaunchParams.args.filter(arg => arg === 'abc').length !== 0,
|
|
'Should have seen the abc parameter');
|
|
assert(collectedLaunchParams.args.filter(arg => arg === 'def').length !== 0,
|
|
'Should have seen the def parameter');
|
|
assert(collectedLaunchParams.args.filter(arg => arg === 'about:blank').length !== 0,
|
|
'Should have seen the about:blank parameter');
|
|
assert(collectedLaunchParams.args.filter(arg => arg.match(/remote-debugging-port/)).length !== 0,
|
|
'Should have seen a parameter like remote-debugging-port');
|
|
assert(collectedLaunchParams.args.filter(arg => arg.match(/user-data-dir/)).length !== 0,
|
|
'Should have seen a parameter like user-data-dir');
|
|
|
|
const telemetryProperties = telemetryPropertyCollector.getProperties();
|
|
assert.equal(telemetryProperties.shouldLaunchChromeUnelevated, 'true', "Should send telemetry that Chrome is requested to be launched unelevated.'");
|
|
assert.equal(telemetryProperties.doesHostSupportLaunchUnelevated, 'true', "Should send telemetry that host supports launcheing Chrome unelevated.'");
|
|
|
|
require('os').platform = originalGetPlatform;
|
|
require('../src/utils').getBrowserPath = originalGetBrowser;
|
|
});
|
|
});
|
|
|
|
suite('resolveWebRootPattern', () => {
|
|
const WEBROOT = testUtils.pathResolve('/project/webroot');
|
|
const resolveWebRootPattern = require(MODULE_UNDER_TEST).resolveWebRootPattern;
|
|
|
|
test('does nothing when no ${webRoot} present', () => {
|
|
const overrides: ISourceMapPathOverrides = { '/src': '/project' };
|
|
assert.deepEqual(
|
|
resolveWebRootPattern(WEBROOT, overrides),
|
|
overrides);
|
|
});
|
|
|
|
test('resolves the webRoot pattern', () => {
|
|
assert.deepEqual(
|
|
resolveWebRootPattern(WEBROOT, <ISourceMapPathOverrides>{ '/src': '${webRoot}/app/src'}),
|
|
{ '/src': WEBROOT + '/app/src' });
|
|
assert.deepEqual(
|
|
resolveWebRootPattern(WEBROOT, <ISourceMapPathOverrides>{ '${webRoot}/src': '${webRoot}/app/src'}),
|
|
{ [WEBROOT + '/src']: WEBROOT + '/app/src'});
|
|
});
|
|
|
|
test(`ignores the webRoot pattern when it's not at the beginning of the string`, () => {
|
|
const overrides: ISourceMapPathOverrides = { '/another/${webRoot}/src': '/app/${webRoot}/src'};
|
|
assert.deepEqual(
|
|
resolveWebRootPattern(WEBROOT, overrides),
|
|
overrides);
|
|
});
|
|
|
|
test('works on a set of overrides', () => {
|
|
const overrides: ISourceMapPathOverrides = {
|
|
'/src*': '${webRoot}/app',
|
|
'*/app.js': '*/app.js',
|
|
'/src/app.js': '/src/${webRoot}',
|
|
'/app.js': '${webRoot}/app.js',
|
|
'${webRoot}/app1.js': '${webRoot}/app.js'
|
|
};
|
|
|
|
const expOverrides: ISourceMapPathOverrides = {
|
|
'/src*': WEBROOT + '/app',
|
|
'*/app.js': '*/app.js',
|
|
'/src/app.js': '/src/${webRoot}',
|
|
'/app.js': WEBROOT + '/app.js',
|
|
[WEBROOT + '/app1.js']: WEBROOT + '/app.js'
|
|
};
|
|
|
|
assert.deepEqual(
|
|
resolveWebRootPattern(WEBROOT, overrides),
|
|
expOverrides);
|
|
});
|
|
});
|
|
});
|