Merge pull request #103210 from TylerLeonhardt/add-debug-adapter-named-pipe-server

Add DebugAdapterNamedPipeServer
This commit is contained in:
Andre Weinand
2020-08-19 12:07:54 +02:00
committed by GitHub
8 changed files with 166 additions and 15 deletions
+16 -1
View File
@@ -10899,6 +10899,21 @@ declare module 'vscode' {
constructor(port: number, host?: string);
}
/**
* Represents a debug adapter running as a Named Pipe (on Windows)/UNIX Domain Socket (on non-Windows) based server.
*/
export class DebugAdapterNamedPipeServer {
/**
* The path to the NamedPipe/UNIX Domain Socket.
*/
readonly path: string;
/**
* Create a description for a debug adapter running as a socket based server.
*/
constructor(path: string);
}
/**
* A debug adapter that implements the Debug Adapter Protocol can be registered with VS Code if it implements the DebugAdapter interface.
*/
@@ -10937,7 +10952,7 @@ declare module 'vscode' {
constructor(implementation: DebugAdapter);
}
export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterInlineImplementation;
export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterNamedPipeServer | DebugAdapterInlineImplementation;
export interface DebugAdapterDescriptorFactory {
/**
@@ -1019,6 +1019,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
ConfigurationTarget: extHostTypes.ConfigurationTarget,
DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable,
DebugAdapterServer: extHostTypes.DebugAdapterServer,
DebugAdapterNamedPipeServer: extHostTypes.DebugAdapterNamedPipeServer,
DebugAdapterInlineImplementation: extHostTypes.DebugAdapterInlineImplementation,
DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior,
Diagnostic: extHostTypes.Diagnostic,
@@ -11,12 +11,12 @@ import {
MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID,
IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto
} from 'vs/workbench/api/common/extHost.protocol';
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode, DebugAdapterInlineImplementation } from 'vs/workbench/api/common/extHostTypes';
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer } from 'vs/workbench/api/common/extHostTypes';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor, IDebugAdapterImpl } from 'vs/workbench/contrib/debug/common/debug';
import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor, IDebugAdapterImpl, IDebugAdapterNamedPipeServer } from 'vs/workbench/contrib/debug/common/debug';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver';
import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration';
@@ -737,6 +737,11 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E
port: x.port,
host: x.host
};
} else if (x instanceof DebugAdapterNamedPipeServer) {
return <IDebugAdapterNamedPipeServer>{
type: 'pipeServer',
path: x.path
};
} else if (x instanceof DebugAdapterInlineImplementation) {
return <IDebugAdapterImpl>{
type: 'implementation',
@@ -2294,6 +2294,12 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer {
}
}
@es5ClassCompat
export class DebugAdapterNamedPipeServer implements vscode.DebugAdapterNamedPipeServer {
constructor(public readonly path: string) {
}
}
@es5ClassCompat
export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInlineImplementation {
readonly implementation: vscode.DebugAdapter;
@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import type * as vscode from 'vscode';
import * as env from 'vs/base/common/platform';
import { DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes';
import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
import { ExecutableDebugAdapter, SocketDebugAdapter, NamedPipeDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
@@ -49,6 +49,8 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
switch (adapter.type) {
case 'server':
return new SocketDebugAdapter(adapter);
case 'pipeServer':
return new NamedPipeDebugAdapter(adapter);
case 'executable':
return new ExecutableDebugAdapter(adapter, session.type);
}
@@ -573,6 +573,11 @@ export interface IDebugAdapterServer {
readonly host?: string;
}
export interface IDebugAdapterNamedPipeServer {
readonly type: 'pipeServer';
readonly path: string;
}
export interface IDebugAdapterInlineImpl extends IDisposable {
readonly onDidSendMessage: Event<DebugProtocol.Message>;
handleMessage(message: DebugProtocol.Message): void;
@@ -583,7 +588,7 @@ export interface IDebugAdapterImpl {
readonly implementation: IDebugAdapterInlineImpl;
}
export type IAdapterDescriptor = IDebugAdapterExecutable | IDebugAdapterServer | IDebugAdapterImpl;
export type IAdapterDescriptor = IDebugAdapterExecutable | IDebugAdapterServer | IDebugAdapterNamedPipeServer | IDebugAdapterImpl;
export interface IPlatformSpecificAdapterContribution {
program?: string;
@@ -14,7 +14,7 @@ import * as objects from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
import { IDebugAdapterExecutable, IDebuggerContribution, IPlatformSpecificAdapterContribution, IDebugAdapterServer } from 'vs/workbench/contrib/debug/common/debug';
import { IDebugAdapterExecutable, IDebuggerContribution, IPlatformSpecificAdapterContribution, IDebugAdapterServer, IDebugAdapterNamedPipeServer } from 'vs/workbench/contrib/debug/common/debug';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { AbstractDebugAdapter } from '../common/abstractDebugAdapter';
@@ -91,25 +91,22 @@ export abstract class StreamDebugAdapter extends AbstractDebugAdapter {
}
}
/**
* An implementation that connects to a debug adapter via a socket.
*/
export class SocketDebugAdapter extends StreamDebugAdapter {
export abstract class NetworkDebugAdapter extends StreamDebugAdapter {
private socket?: net.Socket;
protected socket?: net.Socket;
constructor(private adapterServer: IDebugAdapterServer) {
super();
}
protected abstract createConnection(connectionListener: () => void): net.Socket;
startSession(): Promise<void> {
return new Promise<void>((resolve, reject) => {
let connected = false;
this.socket = net.createConnection(this.adapterServer.port, this.adapterServer.host || '127.0.0.1', () => {
this.socket = this.createConnection(() => {
this.connect(this.socket!, this.socket!);
resolve();
connected = true;
});
this.socket.on('close', () => {
if (connected) {
this._onError.fire(new Error('connection closed'));
@@ -117,6 +114,7 @@ export class SocketDebugAdapter extends StreamDebugAdapter {
reject(new Error('connection closed'));
}
});
this.socket.on('error', error => {
if (connected) {
this._onError.fire(error);
@@ -136,6 +134,34 @@ export class SocketDebugAdapter extends StreamDebugAdapter {
}
}
/**
* An implementation that connects to a debug adapter via a socket.
*/
export class SocketDebugAdapter extends NetworkDebugAdapter {
constructor(private adapterServer: IDebugAdapterServer) {
super();
}
protected createConnection(connectionListener: () => void): net.Socket {
return net.createConnection(this.adapterServer.port, this.adapterServer.host || '127.0.0.1', connectionListener);
}
}
/**
* An implementation that connects to a debug adapter via a NamedPipe (on Windows)/UNIX Domain Socket (on non-Windows).
*/
export class NamedPipeDebugAdapter extends NetworkDebugAdapter {
constructor(private adapterServer: IDebugAdapterNamedPipeServer) {
super();
}
protected createConnection(connectionListener: () => void): net.Socket {
return net.createConnection(this.adapterServer.path, connectionListener);
}
}
/**
* An implementation that launches the debug adapter as a separate process and communicates via stdin/stdout.
*/
@@ -0,0 +1,91 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as crypto from 'crypto';
import * as net from 'net';
import * as platform from 'vs/base/common/platform';
import { tmpdir } from 'os';
import { join } from 'vs/base/common/path';
import { SocketDebugAdapter, NamedPipeDebugAdapter, StreamDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
function rndPort(): number {
const min = 8000;
const max = 9000;
return Math.floor(Math.random() * (max - min) + min);
}
function sendInitializeRequest(debugAdapter: StreamDebugAdapter): Promise<DebugProtocol.Response> {
return new Promise((resolve, reject) => {
debugAdapter.sendRequest('initialize', { adapterID: 'test' }, (result) => {
resolve(result);
});
});
}
function serverConnection(socket: net.Socket) {
socket.on('data', (data: Buffer) => {
const str = data.toString().split('\r\n')[2];
const request = JSON.parse(str);
const response: any = {
seq: request.seq,
request_seq: request.seq,
type: 'response',
command: request.command
};
if (request.arguments.adapterID === 'test') {
response.success = true;
} else {
response.success = false;
response.message = 'failed';
}
const responsePayload = JSON.stringify(response);
socket.write(`Content-Length: ${responsePayload.length}\r\n\r\n${responsePayload}`);
});
}
suite('Debug - StreamDebugAdapter', () => {
const port = rndPort();
const pipeName = crypto.randomBytes(10).toString('utf8');
const pipePath = platform.isWindows ? join('\\\\.\\pipe\\', pipeName) : join(tmpdir(), pipeName);
const testCases: { testName: string, debugAdapter: StreamDebugAdapter, connectionDetail: string | number }[] = [
{
testName: 'NamedPipeDebugAdapter',
debugAdapter: new NamedPipeDebugAdapter({
type: 'pipeServer',
path: pipePath
}),
connectionDetail: pipePath
},
{
testName: 'SocketDebugAdapter',
debugAdapter: new SocketDebugAdapter({
type: 'server',
port
}),
connectionDetail: port
}
];
for (const testCase of testCases) {
test(`StreamDebugAdapter (${testCase.testName}) can initialize a connection`, async () => {
const server = net.createServer(serverConnection).listen(testCase.connectionDetail);
const debugAdapter = testCase.debugAdapter;
try {
await debugAdapter.startSession();
const response: DebugProtocol.Response = await sendInitializeRequest(debugAdapter);
assert.strictEqual(response.command, 'initialize');
assert.strictEqual(response.request_seq, 1);
assert.strictEqual(response.success, true, response.message);
} finally {
await debugAdapter.stopSession();
server.close();
debugAdapter.dispose();
}
});
}
});