Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2 (#8911)

* Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2

* update distro

* fix layering

* update distro

* fix tests
This commit is contained in:
Anthony Dresser
2020-01-22 13:42:37 -08:00
committed by GitHub
parent 977111eb21
commit bd7aac8ee0
895 changed files with 24651 additions and 14520 deletions

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { replaceWhitespace, renderExpressionValue, renderVariable } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { replaceWhitespace, renderExpressionValue, renderVariable, renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import * as dom from 'vs/base/browser/dom';
import { Expression, Variable, Scope, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import { MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug';
@@ -32,6 +32,16 @@ suite('Debug - Base Debug View', () => {
assert.equal(replaceWhitespace('hey \r\t\n\t\t\n there'), 'hey \\r\\t\\n\\t\\t\\n there');
});
test('render view tree', () => {
const container = $('.container');
const treeContainer = renderViewTree(container);
assert.equal(treeContainer.className, 'debug-view-content');
assert.equal(container.childElementCount, 1);
assert.equal(container.firstChild, treeContainer);
assert.equal(treeContainer instanceof HTMLDivElement, true);
});
test.skip('render expression value', () => { // {{SQL CARBON EDIT}} skip test
let container = $('.container');
renderExpressionValue('render \n me', container, { showHover: true, preserveWhitespace: true });

View File

@@ -0,0 +1,349 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { URI as uri } from 'vs/base/common/uri';
import { DebugModel, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { NullOpenerService } from 'vs/platform/opener/common/opener';
import { getExpandedBodySize, getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/browser/breakpointsView';
import { dispose } from 'vs/base/common/lifecycle';
import { Range } from 'vs/editor/common/core/range';
import { IBreakpointData, IDebugSessionOptions, IBreakpointUpdateData, State } from 'vs/workbench/contrib/debug/common/debug';
import { TextModel } from 'vs/editor/common/model/textModel';
import { LanguageIdentifier, LanguageId } from 'vs/editor/common/modes';
import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution';
import { OverviewRulerLane } from 'vs/editor/common/model';
import { MarkdownString } from 'vs/base/common/htmlContent';
function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession {
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!);
}
function addBreakpointsAndCheckEvents(model: DebugModel, uri: uri, data: IBreakpointData[]): void {
let eventCount = 0;
const toDispose = model.onDidChangeBreakpoints(e => {
assert.equal(e?.sessionOnly, false);
assert.equal(e?.changed, undefined);
assert.equal(e?.removed, undefined);
const added = e?.added;
assert.notEqual(added, undefined);
assert.equal(added!.length, data.length);
eventCount++;
dispose(toDispose);
for (let i = 0; i < data.length; i++) {
assert.equal(e!.added![i] instanceof Breakpoint, true);
assert.equal((e!.added![i] as Breakpoint).lineNumber, data[i].lineNumber);
}
});
model.addBreakpoints(uri, data);
assert.equal(eventCount, 1);
}
suite('Debug - Breakpoints', () => {
let model: DebugModel;
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
});
// Breakpoints
test('simple', () => {
const modelUri = uri.file('/myfolder/myfile.js');
addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
assert.equal(model.areBreakpointsActivated(), true);
assert.equal(model.getBreakpoints().length, 2);
let eventCount = 0;
const toDispose = model.onDidChangeBreakpoints(e => {
eventCount++;
assert.equal(e?.added, undefined);
assert.equal(e?.sessionOnly, false);
assert.equal(e?.removed?.length, 2);
assert.equal(e?.changed, undefined);
dispose(toDispose);
});
model.removeBreakpoints(model.getBreakpoints());
assert.equal(eventCount, 1);
assert.equal(model.getBreakpoints().length, 0);
});
test('toggling', () => {
const modelUri = uri.file('/myfolder/myfile.js');
addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 12, enabled: true, condition: 'fake condition' }]);
assert.equal(model.getBreakpoints().length, 3);
const bp = model.getBreakpoints().pop();
if (bp) {
model.removeBreakpoints([bp]);
}
assert.equal(model.getBreakpoints().length, 2);
model.setBreakpointsActivated(false);
assert.equal(model.areBreakpointsActivated(), false);
model.setBreakpointsActivated(true);
assert.equal(model.areBreakpointsActivated(), true);
});
test('two files', () => {
const modelUri1 = uri.file('/myfolder/my file first.js');
const modelUri2 = uri.file('/secondfolder/second/second file.js');
addBreakpointsAndCheckEvents(model, modelUri1, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
assert.equal(getExpandedBodySize(model), 44);
addBreakpointsAndCheckEvents(model, modelUri2, [{ lineNumber: 1, enabled: true }, { lineNumber: 2, enabled: true }, { lineNumber: 3, enabled: false }]);
assert.equal(getExpandedBodySize(model), 110);
assert.equal(model.getBreakpoints().length, 5);
assert.equal(model.getBreakpoints({ uri: modelUri1 }).length, 2);
assert.equal(model.getBreakpoints({ uri: modelUri2 }).length, 3);
assert.equal(model.getBreakpoints({ lineNumber: 5 }).length, 1);
assert.equal(model.getBreakpoints({ column: 5 }).length, 0);
const bp = model.getBreakpoints()[0];
const update = new Map<string, IBreakpointUpdateData>();
update.set(bp.getId(), { lineNumber: 100 });
let eventFired = false;
const toDispose = model.onDidChangeBreakpoints(e => {
eventFired = true;
assert.equal(e?.added, undefined);
assert.equal(e?.removed, undefined);
assert.equal(e?.changed?.length, 1);
dispose(toDispose);
});
model.updateBreakpoints(update);
assert.equal(eventFired, true);
assert.equal(bp.lineNumber, 100);
assert.equal(model.getBreakpoints({ enabledOnly: true }).length, 3);
model.enableOrDisableAllBreakpoints(false);
model.getBreakpoints().forEach(bp => {
assert.equal(bp.enabled, false);
});
assert.equal(model.getBreakpoints({ enabledOnly: true }).length, 0);
model.setEnablement(bp, true);
assert.equal(bp.enabled, true);
model.removeBreakpoints(model.getBreakpoints({ uri: modelUri1 }));
assert.equal(getExpandedBodySize(model), 66);
assert.equal(model.getBreakpoints().length, 3);
});
test('conditions', () => {
const modelUri1 = uri.file('/myfolder/my file first.js');
addBreakpointsAndCheckEvents(model, modelUri1, [{ lineNumber: 5, condition: 'i < 5', hitCondition: '17' }, { lineNumber: 10, condition: 'j < 3' }]);
const breakpoints = model.getBreakpoints();
assert.equal(breakpoints[0].condition, 'i < 5');
assert.equal(breakpoints[0].hitCondition, '17');
assert.equal(breakpoints[1].condition, 'j < 3');
assert.equal(!!breakpoints[1].hitCondition, false);
assert.equal(model.getBreakpoints().length, 2);
model.removeBreakpoints(model.getBreakpoints());
assert.equal(model.getBreakpoints().length, 0);
});
test('function breakpoints', () => {
model.addFunctionBreakpoint('foo', '1');
model.addFunctionBreakpoint('bar', '2');
model.renameFunctionBreakpoint('1', 'fooUpdated');
model.renameFunctionBreakpoint('2', 'barUpdated');
const functionBps = model.getFunctionBreakpoints();
assert.equal(functionBps[0].name, 'fooUpdated');
assert.equal(functionBps[1].name, 'barUpdated');
model.removeFunctionBreakpoints();
assert.equal(model.getFunctionBreakpoints().length, 0);
});
test('multiple sessions', () => {
const modelUri = uri.file('/myfolder/myfile.js');
addBreakpointsAndCheckEvents(model, modelUri, [{ lineNumber: 5, enabled: true, condition: 'x > 5' }, { lineNumber: 10, enabled: false }]);
const breakpoints = model.getBreakpoints();
const session = createMockSession(model);
const data = new Map<string, DebugProtocol.Breakpoint>();
assert.equal(breakpoints[0].lineNumber, 5);
assert.equal(breakpoints[1].lineNumber, 10);
data.set(breakpoints[0].getId(), { verified: false, line: 10 });
data.set(breakpoints[1].getId(), { verified: true, line: 50 });
model.setBreakpointSessionData(session.getId(), {}, data);
assert.equal(breakpoints[0].lineNumber, 5);
assert.equal(breakpoints[1].lineNumber, 50);
const session2 = createMockSession(model);
const data2 = new Map<string, DebugProtocol.Breakpoint>();
data2.set(breakpoints[0].getId(), { verified: true, line: 100 });
data2.set(breakpoints[1].getId(), { verified: true, line: 500 });
model.setBreakpointSessionData(session2.getId(), {}, data2);
// Breakpoint is verified only once, show that line
assert.equal(breakpoints[0].lineNumber, 100);
// Breakpoint is verified two times, show the original line
assert.equal(breakpoints[1].lineNumber, 10);
model.setBreakpointSessionData(session.getId(), {}, undefined);
// No more double session verification
assert.equal(breakpoints[0].lineNumber, 100);
assert.equal(breakpoints[1].lineNumber, 500);
assert.equal(breakpoints[0].supported, false);
const data3 = new Map<string, DebugProtocol.Breakpoint>();
data3.set(breakpoints[0].getId(), { verified: true, line: 500 });
model.setBreakpointSessionData(session2.getId(), { supportsConditionalBreakpoints: true }, data2);
assert.equal(breakpoints[0].supported, true);
});
test('exception breakpoints', () => {
let eventCount = 0;
model.onDidChangeBreakpoints(() => eventCount++);
model.setExceptionBreakpoints([{ filter: 'uncaught', label: 'UNCAUGHT', default: true }]);
assert.equal(eventCount, 1);
let exceptionBreakpoints = model.getExceptionBreakpoints();
assert.equal(exceptionBreakpoints.length, 1);
assert.equal(exceptionBreakpoints[0].filter, 'uncaught');
assert.equal(exceptionBreakpoints[0].enabled, true);
model.setExceptionBreakpoints([{ filter: 'uncaught', label: 'UNCAUGHT' }, { filter: 'caught', label: 'CAUGHT' }]);
assert.equal(eventCount, 2);
exceptionBreakpoints = model.getExceptionBreakpoints();
assert.equal(exceptionBreakpoints.length, 2);
assert.equal(exceptionBreakpoints[0].filter, 'uncaught');
assert.equal(exceptionBreakpoints[0].enabled, true);
assert.equal(exceptionBreakpoints[1].filter, 'caught');
assert.equal(exceptionBreakpoints[1].label, 'CAUGHT');
assert.equal(exceptionBreakpoints[1].enabled, false);
});
test('data breakpoints', () => {
let eventCount = 0;
model.onDidChangeBreakpoints(() => eventCount++);
model.addDataBreakpoint('label', 'id', true, ['read']);
model.addDataBreakpoint('second', 'secondId', false, ['readWrite']);
const dataBreakpoints = model.getDataBreakpoints();
assert.equal(dataBreakpoints[0].canPersist, true);
assert.equal(dataBreakpoints[0].dataId, 'id');
assert.equal(dataBreakpoints[1].canPersist, false);
assert.equal(dataBreakpoints[1].description, 'second');
assert.equal(eventCount, 2);
model.removeDataBreakpoints(dataBreakpoints[0].getId());
assert.equal(eventCount, 3);
assert.equal(model.getDataBreakpoints().length, 1);
model.removeDataBreakpoints();
assert.equal(model.getDataBreakpoints().length, 0);
assert.equal(eventCount, 4);
});
test('message and class name', () => {
const modelUri = uri.file('/myfolder/my file first.js');
addBreakpointsAndCheckEvents(model, modelUri, [
{ lineNumber: 5, enabled: true, condition: 'x > 5' },
{ lineNumber: 10, enabled: false },
{ lineNumber: 12, enabled: true, logMessage: 'hello' },
{ lineNumber: 15, enabled: true, hitCondition: '12' },
{ lineNumber: 500, enabled: true },
]);
const breakpoints = model.getBreakpoints();
let result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[0]);
assert.equal(result.message, 'Expression: x > 5');
assert.equal(result.className, 'codicon-debug-breakpoint-conditional');
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[1]);
assert.equal(result.message, 'Disabled Breakpoint');
assert.equal(result.className, 'codicon-debug-breakpoint-disabled');
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[2]);
assert.equal(result.message, 'Log Message: hello');
assert.equal(result.className, 'codicon-debug-breakpoint-log');
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[3]);
assert.equal(result.message, 'Hit Count: 12');
assert.equal(result.className, 'codicon-debug-breakpoint-conditional');
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[4]);
assert.equal(result.message, 'Breakpoint');
assert.equal(result.className, 'codicon-debug-breakpoint');
result = getBreakpointMessageAndClassName(State.Stopped, false, breakpoints[2]);
assert.equal(result.message, 'Disabled Logpoint');
assert.equal(result.className, 'codicon-debug-breakpoint-log-disabled');
model.addDataBreakpoint('label', 'id', true, ['read']);
const dataBreakpoints = model.getDataBreakpoints();
result = getBreakpointMessageAndClassName(State.Stopped, true, dataBreakpoints[0]);
assert.equal(result.message, 'Data Breakpoint');
assert.equal(result.className, 'codicon-debug-breakpoint-data');
const functionBreakpoint = model.addFunctionBreakpoint('foo', '1');
result = getBreakpointMessageAndClassName(State.Stopped, true, functionBreakpoint);
assert.equal(result.message, 'Function Breakpoint');
assert.equal(result.className, 'codicon-debug-breakpoint-function');
const data = new Map<string, DebugProtocol.Breakpoint>();
data.set(breakpoints[0].getId(), { verified: false, line: 10 });
data.set(breakpoints[1].getId(), { verified: true, line: 50 });
data.set(breakpoints[2].getId(), { verified: true, line: 50, message: 'world' });
data.set(functionBreakpoint.getId(), { verified: true });
model.setBreakpointSessionData('mocksessionid', { supportsFunctionBreakpoints: false, supportsDataBreakpoints: true, supportsLogPoints: true }, data);
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[0]);
assert.equal(result.message, 'Unverified Breakpoint');
assert.equal(result.className, 'codicon-debug-breakpoint-unverified');
result = getBreakpointMessageAndClassName(State.Stopped, true, functionBreakpoint);
assert.equal(result.message, 'Function breakpoints not supported by this debug type');
assert.equal(result.className, 'codicon-debug-breakpoint-function-unverified');
result = getBreakpointMessageAndClassName(State.Stopped, true, breakpoints[2]);
assert.equal(result.message, 'Log Message: hello, world');
assert.equal(result.className, 'codicon-debug-breakpoint-log');
});
test('decorations', () => {
const modelUri = uri.file('/myfolder/my file first.js');
const languageIdentifier = new LanguageIdentifier('testMode', LanguageId.PlainText);
const textModel = new TextModel(
['this is line one', 'this is line two', ' this is line three it has whitespace at start', 'this is line four', 'this is line five'].join('\n'),
TextModel.DEFAULT_CREATION_OPTIONS,
languageIdentifier
);
addBreakpointsAndCheckEvents(model, modelUri, [
{ lineNumber: 1, enabled: true, condition: 'x > 5' },
{ lineNumber: 2, column: 4, enabled: false },
{ lineNumber: 3, enabled: true, logMessage: 'hello' },
{ lineNumber: 500, enabled: true },
]);
const breakpoints = model.getBreakpoints();
let decorations = createBreakpointDecorations(textModel, breakpoints, State.Running, true, true);
assert.equal(decorations.length, 3); // last breakpoint filtered out since it has a large line number
assert.deepEqual(decorations[0].range, new Range(1, 1, 1, 2));
assert.deepEqual(decorations[1].range, new Range(2, 4, 2, 5));
assert.deepEqual(decorations[2].range, new Range(3, 5, 3, 6));
assert.equal(decorations[0].options.beforeContentClassName, undefined);
assert.equal(decorations[1].options.beforeContentClassName, `debug-breakpoint-placeholder`);
assert.equal(decorations[0].options.overviewRuler?.position, OverviewRulerLane.Left);
const expected = new MarkdownString().appendCodeblock(languageIdentifier.language, 'Expression: x > 5');
assert.deepEqual(decorations[0].options.glyphMarginHoverMessage, expected);
decorations = createBreakpointDecorations(textModel, breakpoints, State.Running, true, false);
assert.equal(decorations[0].options.overviewRuler, null);
});
});

View File

@@ -0,0 +1,419 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import * as sinon from 'sinon';
import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { Range } from 'vs/editor/common/core/range';
import { IDebugSessionOptions, State } from 'vs/workbench/contrib/debug/common/debug';
import { NullOpenerService } from 'vs/platform/opener/common/opener';
import { createDecorationsForStackFrame } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution';
import { Constants } from 'vs/base/common/uint';
import { getContext, getContextForContributedActions } from 'vs/workbench/contrib/debug/browser/callStackView';
import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug/browser/debugService';
export function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession {
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!);
}
function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame, secondStackFrame: StackFrame } {
let firstStackFrame: StackFrame;
let secondStackFrame: StackFrame;
const thread = new class extends Thread {
public getCallStack(): StackFrame[] {
return [firstStackFrame, secondStackFrame];
}
}(session, 'mockthread', 1);
const firstSource = new Source({
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId');
const secondSource = new Source({
name: 'internalModule.js',
path: 'z/x/c/d/internalModule.js',
sourceReference: 11,
}, 'aDebugSessionId');
firstStackFrame = new StackFrame(thread, 1, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
secondStackFrame = new StackFrame(thread, 1, secondSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
return { firstStackFrame, secondStackFrame };
}
suite('Debug - CallStack', () => {
let model: DebugModel;
let rawSession: MockRawSession;
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
rawSession = new MockRawSession();
});
// Threads
test('threads simple', () => {
const threadId = 1;
const threadName = 'firstThread';
const session = createMockSession(model);
model.addSession(session);
assert.equal(model.getSessions(true).length, 1);
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId,
name: threadName
}]
});
assert.equal(session.getThread(threadId)!.name, threadName);
model.clearThreads(session.getId(), true);
assert.equal(session.getThread(threadId), undefined);
assert.equal(model.getSessions(true).length, 1);
});
test('threads multiple wtih allThreadsStopped', () => {
const threadId1 = 1;
const threadName1 = 'firstThread';
const threadId2 = 2;
const threadName2 = 'secondThread';
const stoppedReason = 'breakpoint';
// Add the threads
const session = createMockSession(model);
model.addSession(session);
session['raw'] = <any>rawSession;
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId1,
name: threadName1
}]
});
// Stopped event with all threads stopped
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId1,
name: threadName1
}, {
id: threadId2,
name: threadName2
}],
stoppedDetails: {
reason: stoppedReason,
threadId: 1,
allThreadsStopped: true
},
});
const thread1 = session.getThread(threadId1)!;
const thread2 = session.getThread(threadId2)!;
// at the beginning, callstacks are obtainable but not available
assert.equal(session.getAllThreads().length, 2);
assert.equal(thread1.name, threadName1);
assert.equal(thread1.stopped, true);
assert.equal(thread1.getCallStack().length, 0);
assert.equal(thread1.stoppedDetails!.reason, stoppedReason);
assert.equal(thread2.name, threadName2);
assert.equal(thread2.stopped, true);
assert.equal(thread2.getCallStack().length, 0);
assert.equal(thread2.stoppedDetails!.reason, undefined);
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
thread1.fetchCallStack().then(() => {
assert.notEqual(thread1.getCallStack().length, 0);
});
thread2.fetchCallStack().then(() => {
assert.notEqual(thread2.getCallStack().length, 0);
});
// calling multiple times getCallStack doesn't result in multiple calls
// to the debug adapter
thread1.fetchCallStack().then(() => {
return thread2.fetchCallStack();
});
// clearing the callstack results in the callstack not being available
thread1.clearCallStack();
assert.equal(thread1.stopped, true);
assert.equal(thread1.getCallStack().length, 0);
thread2.clearCallStack();
assert.equal(thread2.stopped, true);
assert.equal(thread2.getCallStack().length, 0);
model.clearThreads(session.getId(), true);
assert.equal(session.getThread(threadId1), undefined);
assert.equal(session.getThread(threadId2), undefined);
assert.equal(session.getAllThreads().length, 0);
});
test('threads mutltiple without allThreadsStopped', () => {
const sessionStub = sinon.spy(rawSession, 'stackTrace');
const stoppedThreadId = 1;
const stoppedThreadName = 'stoppedThread';
const runningThreadId = 2;
const runningThreadName = 'runningThread';
const stoppedReason = 'breakpoint';
const session = createMockSession(model);
model.addSession(session);
session['raw'] = <any>rawSession;
// Add the threads
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: stoppedThreadId,
name: stoppedThreadName
}]
});
// Stopped event with only one thread stopped
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: 1,
name: stoppedThreadName
}, {
id: runningThreadId,
name: runningThreadName
}],
stoppedDetails: {
reason: stoppedReason,
threadId: 1,
allThreadsStopped: false
}
});
const stoppedThread = session.getThread(stoppedThreadId)!;
const runningThread = session.getThread(runningThreadId)!;
// the callstack for the stopped thread is obtainable but not available
// the callstack for the running thread is not obtainable nor available
assert.equal(stoppedThread.name, stoppedThreadName);
assert.equal(stoppedThread.stopped, true);
assert.equal(session.getAllThreads().length, 2);
assert.equal(stoppedThread.getCallStack().length, 0);
assert.equal(stoppedThread.stoppedDetails!.reason, stoppedReason);
assert.equal(runningThread.name, runningThreadName);
assert.equal(runningThread.stopped, false);
assert.equal(runningThread.getCallStack().length, 0);
assert.equal(runningThread.stoppedDetails, undefined);
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
stoppedThread.fetchCallStack().then(() => {
assert.notEqual(stoppedThread.getCallStack().length, 0);
assert.equal(runningThread.getCallStack().length, 0);
assert.equal(sessionStub.callCount, 1);
});
// calling getCallStack on the running thread returns empty array
// and does not return in a request for the callstack in the debug
// adapter
runningThread.fetchCallStack().then(() => {
assert.equal(runningThread.getCallStack().length, 0);
assert.equal(sessionStub.callCount, 1);
});
// clearing the callstack results in the callstack not being available
stoppedThread.clearCallStack();
assert.equal(stoppedThread.stopped, true);
assert.equal(stoppedThread.getCallStack().length, 0);
model.clearThreads(session.getId(), true);
assert.equal(session.getThread(stoppedThreadId), undefined);
assert.equal(session.getThread(runningThreadId), undefined);
assert.equal(session.getAllThreads().length, 0);
});
test('stack frame get specific source name', () => {
const session = createMockSession(model);
model.addSession(session);
const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session);
assert.equal(firstStackFrame.getSpecificSourceName(), '.../b/c/d/internalModule.js');
assert.equal(secondStackFrame.getSpecificSourceName(), '.../x/c/d/internalModule.js');
});
test('stack frame toString()', () => {
const session = createMockSession(model);
const thread = new Thread(session, 'mockthread', 1);
const firstSource = new Source({
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId');
const stackFrame = new StackFrame(thread, 1, firstSource, 'app', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
assert.equal(stackFrame.toString(), 'app (internalModule.js:1)');
const secondSource = new Source(undefined, 'aDebugSessionId');
const stackFrame2 = new StackFrame(thread, 2, secondSource, 'module', 'normal', { startLineNumber: undefined!, startColumn: undefined!, endLineNumber: undefined!, endColumn: undefined! }, 2);
assert.equal(stackFrame2.toString(), 'module');
});
test('debug child sessions are added in correct order', () => {
const session = createMockSession(model);
model.addSession(session);
const secondSession = createMockSession(model, 'mockSession2');
model.addSession(secondSession);
const firstChild = createMockSession(model, 'firstChild', { parentSession: session });
model.addSession(firstChild);
const secondChild = createMockSession(model, 'secondChild', { parentSession: session });
model.addSession(secondChild);
const thirdSession = createMockSession(model, 'mockSession3');
model.addSession(thirdSession);
const anotherChild = createMockSession(model, 'secondChild', { parentSession: secondSession });
model.addSession(anotherChild);
const sessions = model.getSessions();
assert.equal(sessions[0].getId(), session.getId());
assert.equal(sessions[1].getId(), firstChild.getId());
assert.equal(sessions[2].getId(), secondChild.getId());
assert.equal(sessions[3].getId(), secondSession.getId());
assert.equal(sessions[4].getId(), anotherChild.getId());
assert.equal(sessions[5].getId(), thirdSession.getId());
});
test('decorations', () => {
const session = createMockSession(model);
model.addSession(session);
const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session);
let decorations = createDecorationsForStackFrame(firstStackFrame, firstStackFrame.range);
assert.equal(decorations.length, 2);
assert.deepEqual(decorations[0].range, new Range(1, 2, 1, 1));
assert.equal(decorations[0].options.glyphMarginClassName, 'codicon-debug-stackframe');
assert.deepEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1));
assert.equal(decorations[1].options.className, 'debug-top-stack-frame-line');
assert.equal(decorations[1].options.isWholeLine, true);
decorations = createDecorationsForStackFrame(secondStackFrame, firstStackFrame.range);
assert.equal(decorations.length, 2);
assert.deepEqual(decorations[0].range, new Range(1, 2, 1, 1));
assert.equal(decorations[0].options.glyphMarginClassName, 'codicon-debug-stackframe-focused');
assert.deepEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1));
assert.equal(decorations[1].options.className, 'debug-focused-stack-frame-line');
assert.equal(decorations[1].options.isWholeLine, true);
decorations = createDecorationsForStackFrame(firstStackFrame, new Range(1, 5, 1, 6));
assert.equal(decorations.length, 3);
assert.deepEqual(decorations[0].range, new Range(1, 2, 1, 1));
assert.equal(decorations[0].options.glyphMarginClassName, 'codicon-debug-stackframe');
assert.deepEqual(decorations[1].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1));
assert.equal(decorations[1].options.className, 'debug-top-stack-frame-line');
assert.equal(decorations[1].options.isWholeLine, true);
// Inline decoration gets rendered in this case
assert.equal(decorations[2].options.beforeContentClassName, 'debug-top-stack-frame-column');
assert.deepEqual(decorations[2].range, new Range(1, Constants.MAX_SAFE_SMALL_INTEGER, 1, 1));
});
test('contexts', () => {
const session = createMockSession(model);
model.addSession(session);
const { firstStackFrame, secondStackFrame } = createTwoStackFrames(session);
let context = getContext(firstStackFrame);
assert.equal(context.sessionId, firstStackFrame.thread.session.getId());
assert.equal(context.threadId, firstStackFrame.thread.getId());
assert.equal(context.frameId, firstStackFrame.getId());
context = getContext(secondStackFrame.thread);
assert.equal(context.sessionId, secondStackFrame.thread.session.getId());
assert.equal(context.threadId, secondStackFrame.thread.getId());
assert.equal(context.frameId, undefined);
context = getContext(session);
assert.equal(context.sessionId, session.getId());
assert.equal(context.threadId, undefined);
assert.equal(context.frameId, undefined);
let contributedContext = getContextForContributedActions(firstStackFrame);
assert.equal(contributedContext, firstStackFrame.source.raw.path);
contributedContext = getContextForContributedActions(firstStackFrame.thread);
assert.equal(contributedContext, firstStackFrame.thread.threadId);
contributedContext = getContextForContributedActions(session);
assert.equal(contributedContext, session.getId());
});
test('focusStackFrameThreadAndSesion', () => {
const threadId1 = 1;
const threadName1 = 'firstThread';
const threadId2 = 2;
const threadName2 = 'secondThread';
const stoppedReason = 'breakpoint';
// Add the threads
const session = new class extends DebugSession {
get state(): State {
return State.Stopped;
}
}({ resolved: { name: 'stoppedSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!);
const runningSession = createMockSession(model);
model.addSession(runningSession);
model.addSession(session);
session['raw'] = <any>rawSession;
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId1,
name: threadName1
}]
});
// Stopped event with all threads stopped
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId1,
name: threadName1
}, {
id: threadId2,
name: threadName2
}],
stoppedDetails: {
reason: stoppedReason,
threadId: 1,
allThreadsStopped: true
},
});
const thread = session.getThread(threadId1)!;
const runningThread = session.getThread(threadId2);
let toFocus = getStackFrameThreadAndSessionToFocus(model, undefined);
// Verify stopped session and stopped thread get focused
assert.deepEqual(toFocus, { stackFrame: undefined, thread: thread, session: session });
toFocus = getStackFrameThreadAndSessionToFocus(model, undefined, undefined, runningSession);
assert.deepEqual(toFocus, { stackFrame: undefined, thread: undefined, session: runningSession });
toFocus = getStackFrameThreadAndSessionToFocus(model, undefined, thread);
assert.deepEqual(toFocus, { stackFrame: undefined, thread: thread, session: session });
toFocus = getStackFrameThreadAndSessionToFocus(model, undefined, runningThread);
assert.deepEqual(toFocus, { stackFrame: undefined, thread: runningThread, session: session });
const stackFrame = new StackFrame(thread, 5, undefined!, 'stackframename2', undefined, undefined!, 1);
toFocus = getStackFrameThreadAndSessionToFocus(model, stackFrame);
assert.deepEqual(toFocus, { stackFrame: stackFrame, thread: thread, session: session });
});
});

View File

@@ -30,7 +30,7 @@ suite.skip('Debug - ANSI Handling', () => {
*/
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
session = new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService);
session = new DebugSession({ resolved: { name: 'test', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!);
const instantiationService: TestInstantiationService = <TestInstantiationService>workbenchInstantiationService();
linkDetector = instantiationService.createInstance(LinkDetector);

View File

@@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { findExpressionInStackFrame } from 'vs/workbench/contrib/debug/browser/debugHover';
import { createMockSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test';
import { StackFrame, Thread, DebugModel, Scope, Variable } from 'vs/workbench/contrib/debug/common/debugModel';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import type { IScope, IExpression } from 'vs/workbench/contrib/debug/common/debug';
suite('Debug - Hover', () => {
test('find expression in stack frame', async () => {
const model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
const session = createMockSession(model);
let stackFrame: StackFrame;
const thread = new class extends Thread {
public getCallStack(): StackFrame[] {
return [stackFrame];
}
}(session, 'mockthread', 1);
const firstSource = new Source({
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId');
let scope: Scope;
stackFrame = new class extends StackFrame {
getScopes(): Promise<IScope[]> {
return Promise.resolve([scope]);
}
}(thread, 1, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
let variableA: Variable;
let variableB: Variable;
scope = new class extends Scope {
getChildren(): Promise<IExpression[]> {
return Promise.resolve([variableA]);
}
}(stackFrame, 1, 'local', 1, false, 10, 10);
variableA = new class extends Variable {
getChildren(): Promise<IExpression[]> {
return Promise.resolve([variableB]);
}
}(session, 1, scope, 2, 'A', 'A', undefined!, 0, 0, {}, 'string');
variableB = new Variable(session, 1, scope, 2, 'B', 'A.B', undefined!, 0, 0, {}, 'string');
assert.equal(await findExpressionInStackFrame(stackFrame, []), undefined);
assert.equal(await findExpressionInStackFrame(stackFrame, ['A']), variableA);
assert.equal(await findExpressionInStackFrame(stackFrame, ['doesNotExist', 'no']), undefined);
assert.equal(await findExpressionInStackFrame(stackFrame, ['a']), undefined);
assert.equal(await findExpressionInStackFrame(stackFrame, ['B']), undefined);
assert.equal(await findExpressionInStackFrame(stackFrame, ['A', 'B']), variableB);
assert.equal(await findExpressionInStackFrame(stackFrame, ['A', 'C']), undefined);
// We do not search in expensive scopes
scope.expensive = true;
assert.equal(await findExpressionInStackFrame(stackFrame, ['A']), undefined);
});
});

View File

@@ -1,575 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { URI as uri } from 'vs/base/common/uri';
import severity from 'vs/base/common/severity';
import { DebugModel, Expression, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import * as sinon from 'sinon';
import { MockRawSession, MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
import { IBreakpointUpdateData, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
import { NullOpenerService } from 'vs/platform/opener/common/opener';
import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession';
import { timeout } from 'vs/base/common/async';
function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession {
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService);
}
suite('Debug - Model', () => {
let model: DebugModel;
let rawSession: MockRawSession;
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
rawSession = new MockRawSession();
});
// Breakpoints
test('breakpoints simple', () => {
const modelUri = uri.file('/myfolder/myfile.js');
model.addBreakpoints(modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
assert.equal(model.areBreakpointsActivated(), true);
assert.equal(model.getBreakpoints().length, 2);
model.removeBreakpoints(model.getBreakpoints());
assert.equal(model.getBreakpoints().length, 0);
});
test('breakpoints toggling', () => {
const modelUri = uri.file('/myfolder/myfile.js');
model.addBreakpoints(modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
model.addBreakpoints(modelUri, [{ lineNumber: 12, enabled: true, condition: 'fake condition' }]);
assert.equal(model.getBreakpoints().length, 3);
const bp = model.getBreakpoints().pop();
if (bp) {
model.removeBreakpoints([bp]);
}
assert.equal(model.getBreakpoints().length, 2);
model.setBreakpointsActivated(false);
assert.equal(model.areBreakpointsActivated(), false);
model.setBreakpointsActivated(true);
assert.equal(model.areBreakpointsActivated(), true);
});
test('breakpoints two files', () => {
const modelUri1 = uri.file('/myfolder/my file first.js');
const modelUri2 = uri.file('/secondfolder/second/second file.js');
model.addBreakpoints(modelUri1, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]);
model.addBreakpoints(modelUri2, [{ lineNumber: 1, enabled: true }, { lineNumber: 2, enabled: true }, { lineNumber: 3, enabled: false }]);
assert.equal(model.getBreakpoints().length, 5);
const bp = model.getBreakpoints()[0];
const update = new Map<string, IBreakpointUpdateData>();
update.set(bp.getId(), { lineNumber: 100 });
model.updateBreakpoints(update);
assert.equal(bp.lineNumber, 100);
model.enableOrDisableAllBreakpoints(false);
model.getBreakpoints().forEach(bp => {
assert.equal(bp.enabled, false);
});
model.setEnablement(bp, true);
assert.equal(bp.enabled, true);
model.removeBreakpoints(model.getBreakpoints({ uri: modelUri1 }));
assert.equal(model.getBreakpoints().length, 3);
});
test('breakpoints conditions', () => {
const modelUri1 = uri.file('/myfolder/my file first.js');
model.addBreakpoints(modelUri1, [{ lineNumber: 5, condition: 'i < 5', hitCondition: '17' }, { lineNumber: 10, condition: 'j < 3' }]);
const breakpoints = model.getBreakpoints();
assert.equal(breakpoints[0].condition, 'i < 5');
assert.equal(breakpoints[0].hitCondition, '17');
assert.equal(breakpoints[1].condition, 'j < 3');
assert.equal(!!breakpoints[1].hitCondition, false);
assert.equal(model.getBreakpoints().length, 2);
model.removeBreakpoints(model.getBreakpoints());
assert.equal(model.getBreakpoints().length, 0);
});
test('function breakpoints', () => {
model.addFunctionBreakpoint('foo', '1');
model.addFunctionBreakpoint('bar', '2');
model.renameFunctionBreakpoint('1', 'fooUpdated');
model.renameFunctionBreakpoint('2', 'barUpdated');
const functionBps = model.getFunctionBreakpoints();
assert.equal(functionBps[0].name, 'fooUpdated');
assert.equal(functionBps[1].name, 'barUpdated');
model.removeFunctionBreakpoints();
assert.equal(model.getFunctionBreakpoints().length, 0);
});
test('breakpoints multiple sessions', () => {
const modelUri = uri.file('/myfolder/myfile.js');
const breakpoints = model.addBreakpoints(modelUri, [{ lineNumber: 5, enabled: true, condition: 'x > 5' }, { lineNumber: 10, enabled: false }]);
const session = createMockSession(model);
const data = new Map<string, DebugProtocol.Breakpoint>();
assert.equal(breakpoints[0].lineNumber, 5);
assert.equal(breakpoints[1].lineNumber, 10);
data.set(breakpoints[0].getId(), { verified: false, line: 10 });
data.set(breakpoints[1].getId(), { verified: true, line: 50 });
model.setBreakpointSessionData(session.getId(), {}, data);
assert.equal(breakpoints[0].lineNumber, 5);
assert.equal(breakpoints[1].lineNumber, 50);
const session2 = createMockSession(model);
const data2 = new Map<string, DebugProtocol.Breakpoint>();
data2.set(breakpoints[0].getId(), { verified: true, line: 100 });
data2.set(breakpoints[1].getId(), { verified: true, line: 500 });
model.setBreakpointSessionData(session2.getId(), {}, data2);
// Breakpoint is verified only once, show that line
assert.equal(breakpoints[0].lineNumber, 100);
// Breakpoint is verified two times, show the original line
assert.equal(breakpoints[1].lineNumber, 10);
model.setBreakpointSessionData(session.getId(), {}, undefined);
// No more double session verification
assert.equal(breakpoints[0].lineNumber, 100);
assert.equal(breakpoints[1].lineNumber, 500);
assert.equal(breakpoints[0].supported, false);
const data3 = new Map<string, DebugProtocol.Breakpoint>();
data3.set(breakpoints[0].getId(), { verified: true, line: 500 });
model.setBreakpointSessionData(session2.getId(), { supportsConditionalBreakpoints: true }, data2);
assert.equal(breakpoints[0].supported, true);
});
// Threads
test('threads simple', () => {
const threadId = 1;
const threadName = 'firstThread';
const session = createMockSession(model);
model.addSession(session);
assert.equal(model.getSessions(true).length, 1);
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId,
name: threadName
}]
});
assert.equal(session.getThread(threadId)!.name, threadName);
model.clearThreads(session.getId(), true);
assert.equal(session.getThread(threadId), undefined);
assert.equal(model.getSessions(true).length, 1);
});
test('threads multiple wtih allThreadsStopped', () => {
const threadId1 = 1;
const threadName1 = 'firstThread';
const threadId2 = 2;
const threadName2 = 'secondThread';
const stoppedReason = 'breakpoint';
// Add the threads
const session = createMockSession(model);
model.addSession(session);
session['raw'] = <any>rawSession;
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId1,
name: threadName1
}]
});
// Stopped event with all threads stopped
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: threadId1,
name: threadName1
}, {
id: threadId2,
name: threadName2
}],
stoppedDetails: {
reason: stoppedReason,
threadId: 1,
allThreadsStopped: true
},
});
const thread1 = session.getThread(threadId1)!;
const thread2 = session.getThread(threadId2)!;
// at the beginning, callstacks are obtainable but not available
assert.equal(session.getAllThreads().length, 2);
assert.equal(thread1.name, threadName1);
assert.equal(thread1.stopped, true);
assert.equal(thread1.getCallStack().length, 0);
assert.equal(thread1.stoppedDetails!.reason, stoppedReason);
assert.equal(thread2.name, threadName2);
assert.equal(thread2.stopped, true);
assert.equal(thread2.getCallStack().length, 0);
assert.equal(thread2.stoppedDetails!.reason, undefined);
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
thread1.fetchCallStack().then(() => {
assert.notEqual(thread1.getCallStack().length, 0);
});
thread2.fetchCallStack().then(() => {
assert.notEqual(thread2.getCallStack().length, 0);
});
// calling multiple times getCallStack doesn't result in multiple calls
// to the debug adapter
thread1.fetchCallStack().then(() => {
return thread2.fetchCallStack();
});
// clearing the callstack results in the callstack not being available
thread1.clearCallStack();
assert.equal(thread1.stopped, true);
assert.equal(thread1.getCallStack().length, 0);
thread2.clearCallStack();
assert.equal(thread2.stopped, true);
assert.equal(thread2.getCallStack().length, 0);
model.clearThreads(session.getId(), true);
assert.equal(session.getThread(threadId1), undefined);
assert.equal(session.getThread(threadId2), undefined);
assert.equal(session.getAllThreads().length, 0);
});
test('threads mutltiple without allThreadsStopped', () => {
const sessionStub = sinon.spy(rawSession, 'stackTrace');
const stoppedThreadId = 1;
const stoppedThreadName = 'stoppedThread';
const runningThreadId = 2;
const runningThreadName = 'runningThread';
const stoppedReason = 'breakpoint';
const session = createMockSession(model);
model.addSession(session);
session['raw'] = <any>rawSession;
// Add the threads
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: stoppedThreadId,
name: stoppedThreadName
}]
});
// Stopped event with only one thread stopped
model.rawUpdate({
sessionId: session.getId(),
threads: [{
id: 1,
name: stoppedThreadName
}, {
id: runningThreadId,
name: runningThreadName
}],
stoppedDetails: {
reason: stoppedReason,
threadId: 1,
allThreadsStopped: false
}
});
const stoppedThread = session.getThread(stoppedThreadId)!;
const runningThread = session.getThread(runningThreadId)!;
// the callstack for the stopped thread is obtainable but not available
// the callstack for the running thread is not obtainable nor available
assert.equal(stoppedThread.name, stoppedThreadName);
assert.equal(stoppedThread.stopped, true);
assert.equal(session.getAllThreads().length, 2);
assert.equal(stoppedThread.getCallStack().length, 0);
assert.equal(stoppedThread.stoppedDetails!.reason, stoppedReason);
assert.equal(runningThread.name, runningThreadName);
assert.equal(runningThread.stopped, false);
assert.equal(runningThread.getCallStack().length, 0);
assert.equal(runningThread.stoppedDetails, undefined);
// after calling getCallStack, the callstack becomes available
// and results in a request for the callstack in the debug adapter
stoppedThread.fetchCallStack().then(() => {
assert.notEqual(stoppedThread.getCallStack().length, 0);
assert.equal(runningThread.getCallStack().length, 0);
assert.equal(sessionStub.callCount, 1);
});
// calling getCallStack on the running thread returns empty array
// and does not return in a request for the callstack in the debug
// adapter
runningThread.fetchCallStack().then(() => {
assert.equal(runningThread.getCallStack().length, 0);
assert.equal(sessionStub.callCount, 1);
});
// clearing the callstack results in the callstack not being available
stoppedThread.clearCallStack();
assert.equal(stoppedThread.stopped, true);
assert.equal(stoppedThread.getCallStack().length, 0);
model.clearThreads(session.getId(), true);
assert.equal(session.getThread(stoppedThreadId), undefined);
assert.equal(session.getThread(runningThreadId), undefined);
assert.equal(session.getAllThreads().length, 0);
});
// Expressions
function assertWatchExpressions(watchExpressions: Expression[], expectedName: string) {
assert.equal(watchExpressions.length, 2);
watchExpressions.forEach(we => {
assert.equal(we.available, false);
assert.equal(we.reference, 0);
assert.equal(we.name, expectedName);
});
}
test('watch expressions', () => {
assert.equal(model.getWatchExpressions().length, 0);
model.addWatchExpression('console');
model.addWatchExpression('console');
let watchExpressions = model.getWatchExpressions();
assertWatchExpressions(watchExpressions, 'console');
model.renameWatchExpression(watchExpressions[0].getId(), 'new_name');
model.renameWatchExpression(watchExpressions[1].getId(), 'new_name');
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
model.addWatchExpression('mockExpression');
model.moveWatchExpression(model.getWatchExpressions()[2].getId(), 1);
watchExpressions = model.getWatchExpressions();
assert.equal(watchExpressions[0].name, 'new_name');
assert.equal(watchExpressions[1].name, 'mockExpression');
assert.equal(watchExpressions[2].name, 'new_name');
model.removeWatchExpressions();
assert.equal(model.getWatchExpressions().length, 0);
});
test('repl expressions', () => {
const session = createMockSession(model);
assert.equal(session.getReplElements().length, 0);
model.addSession(session);
session['raw'] = <any>rawSession;
const thread = new Thread(session, 'mockthread', 1);
const stackFrame = new StackFrame(thread, 1, <any>undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
const replModel = new ReplModel();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
assert.equal(replModel.getReplElements().length, 3);
replModel.getReplElements().forEach(re => {
assert.equal((<ReplEvaluationInput>re).value, 'myVariable');
});
replModel.removeReplExpressions();
assert.equal(replModel.getReplElements().length, 0);
});
test('stack frame get specific source name', () => {
const session = createMockSession(model);
model.addSession(session);
let firstStackFrame: StackFrame;
let secondStackFrame: StackFrame;
const thread = new class extends Thread {
public getCallStack(): StackFrame[] {
return [firstStackFrame, secondStackFrame];
}
}(session, 'mockthread', 1);
const firstSource = new Source({
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId');
const secondSource = new Source({
name: 'internalModule.js',
path: 'z/x/c/d/internalModule.js',
sourceReference: 11,
}, 'aDebugSessionId');
firstStackFrame = new StackFrame(thread, 1, firstSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
secondStackFrame = new StackFrame(thread, 1, secondSource, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
assert.equal(firstStackFrame.getSpecificSourceName(), '.../b/c/d/internalModule.js');
assert.equal(secondStackFrame.getSpecificSourceName(), '.../x/c/d/internalModule.js');
});
test('stack frame toString()', () => {
const session = createMockSession(model);
const thread = new Thread(session, 'mockthread', 1);
const firstSource = new Source({
name: 'internalModule.js',
path: 'a/b/c/d/internalModule.js',
sourceReference: 10,
}, 'aDebugSessionId');
const stackFrame = new StackFrame(thread, 1, firstSource, 'app', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
assert.equal(stackFrame.toString(), 'app (internalModule.js:1)');
const secondSource = new Source(undefined, 'aDebugSessionId');
const stackFrame2 = new StackFrame(thread, 2, secondSource, 'module', 'normal', { startLineNumber: undefined!, startColumn: undefined!, endLineNumber: undefined!, endColumn: undefined! }, 2);
assert.equal(stackFrame2.toString(), 'module');
});
test('debug child sessions are added in correct order', () => {
const session = createMockSession(model);
model.addSession(session);
const secondSession = createMockSession(model, 'mockSession2');
model.addSession(secondSession);
const firstChild = createMockSession(model, 'firstChild', { parentSession: session });
model.addSession(firstChild);
const secondChild = createMockSession(model, 'secondChild', { parentSession: session });
model.addSession(secondChild);
const thirdSession = createMockSession(model, 'mockSession3');
model.addSession(thirdSession);
const anotherChild = createMockSession(model, 'secondChild', { parentSession: secondSession });
model.addSession(anotherChild);
const sessions = model.getSessions();
assert.equal(sessions[0].getId(), session.getId());
assert.equal(sessions[1].getId(), firstChild.getId());
assert.equal(sessions[2].getId(), secondChild.getId());
assert.equal(sessions[3].getId(), secondSession.getId());
assert.equal(sessions[4].getId(), anotherChild.getId());
assert.equal(sessions[5].getId(), thirdSession.getId());
});
// Repl output
test('repl output', () => {
const session = createMockSession(model);
const repl = new ReplModel();
repl.appendToRepl(session, 'first line\n', severity.Error);
repl.appendToRepl(session, 'second line ', severity.Error);
repl.appendToRepl(session, 'third line ', severity.Error);
repl.appendToRepl(session, 'fourth line', severity.Error);
let elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 2);
assert.equal(elements[0].value, 'first line\n');
assert.equal(elements[0].severity, severity.Error);
assert.equal(elements[1].value, 'second line third line fourth line');
assert.equal(elements[1].severity, severity.Error);
repl.appendToRepl(session, '1', severity.Warning);
elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 3);
assert.equal(elements[2].value, '1');
assert.equal(elements[2].severity, severity.Warning);
const keyValueObject = { 'key1': 2, 'key2': 'value' };
repl.appendToRepl(session, new RawObjectReplElement('fakeid', 'fake', keyValueObject), severity.Info);
const element = <RawObjectReplElement>repl.getReplElements()[3];
assert.equal(element.value, 'Object');
assert.deepEqual(element.valueObj, keyValueObject);
repl.removeReplExpressions();
assert.equal(repl.getReplElements().length, 0);
repl.appendToRepl(session, '1\n', severity.Info);
repl.appendToRepl(session, '2', severity.Info);
repl.appendToRepl(session, '3\n4', severity.Info);
repl.appendToRepl(session, '5\n', severity.Info);
repl.appendToRepl(session, '6', severity.Info);
elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 3);
assert.equal(elements[0], '1\n');
assert.equal(elements[1], '23\n45\n');
assert.equal(elements[2], '6');
});
test('repl merging', () => {
// 'mergeWithParent' should be ignored when there is no parent.
const parent = createMockSession(model, 'parent', { repl: 'mergeWithParent' });
const child1 = createMockSession(model, 'child1', { parentSession: parent, repl: 'separate' });
const child2 = createMockSession(model, 'child2', { parentSession: parent, repl: 'mergeWithParent' });
const grandChild = createMockSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' });
const child3 = createMockSession(model, 'child3', { parentSession: parent });
let parentChanges = 0;
parent.onDidChangeReplElements(() => ++parentChanges);
parent.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 1);
assert.equal(parent.getReplElements().length, 1);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 1);
assert.equal(grandChild.getReplElements().length, 1);
assert.equal(child3.getReplElements().length, 0);
grandChild.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 2);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 0);
child3.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 2);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 1);
child1.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 2);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 1);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 1);
});
test('repl ordering', async () => {
const session = createMockSession(model);
model.addSession(session);
const adapter = new MockDebugAdapter();
const raw = new RawDebugSession(adapter, undefined!, undefined!, undefined!, undefined!, undefined!);
session.initializeForTest(raw);
await session.addReplExpression(undefined, 'before.1');
assert.equal(session.getReplElements().length, 3);
assert.equal((<ReplEvaluationInput>session.getReplElements()[0]).value, 'before.1');
assert.equal((<SimpleReplElement>session.getReplElements()[1]).value, 'before.1');
assert.equal((<ReplEvaluationResult>session.getReplElements()[2]).value, '=before.1');
await session.addReplExpression(undefined, 'after.2');
await timeout(0);
assert.equal(session.getReplElements().length, 6);
assert.equal((<ReplEvaluationInput>session.getReplElements()[3]).value, 'after.2');
assert.equal((<ReplEvaluationResult>session.getReplElements()[4]).value, '=after.2');
assert.equal((<SimpleReplElement>session.getReplElements()[5]).value, 'after.2');
});
});

View File

@@ -0,0 +1,154 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import severity from 'vs/base/common/severity';
import { DebugModel, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import { MockRawSession, MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug';
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession';
import { timeout } from 'vs/base/common/async';
import { createMockSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test';
suite('Debug - REPL', () => {
let model: DebugModel;
let rawSession: MockRawSession;
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
rawSession = new MockRawSession();
});
test('repl output', () => {
const session = createMockSession(model);
const repl = new ReplModel();
repl.appendToRepl(session, 'first line\n', severity.Error);
repl.appendToRepl(session, 'second line ', severity.Error);
repl.appendToRepl(session, 'third line ', severity.Error);
repl.appendToRepl(session, 'fourth line', severity.Error);
let elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 2);
assert.equal(elements[0].value, 'first line\n');
assert.equal(elements[0].severity, severity.Error);
assert.equal(elements[1].value, 'second line third line fourth line');
assert.equal(elements[1].severity, severity.Error);
repl.appendToRepl(session, '1', severity.Warning);
elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 3);
assert.equal(elements[2].value, '1');
assert.equal(elements[2].severity, severity.Warning);
const keyValueObject = { 'key1': 2, 'key2': 'value' };
repl.appendToRepl(session, new RawObjectReplElement('fakeid', 'fake', keyValueObject), severity.Info);
const element = <RawObjectReplElement>repl.getReplElements()[3];
assert.equal(element.value, 'Object');
assert.deepEqual(element.valueObj, keyValueObject);
repl.removeReplExpressions();
assert.equal(repl.getReplElements().length, 0);
repl.appendToRepl(session, '1\n', severity.Info);
repl.appendToRepl(session, '2', severity.Info);
repl.appendToRepl(session, '3\n4', severity.Info);
repl.appendToRepl(session, '5\n', severity.Info);
repl.appendToRepl(session, '6', severity.Info);
elements = <SimpleReplElement[]>repl.getReplElements();
assert.equal(elements.length, 3);
assert.equal(elements[0], '1\n');
assert.equal(elements[1], '23\n45\n');
assert.equal(elements[2], '6');
});
test('repl merging', () => {
// 'mergeWithParent' should be ignored when there is no parent.
const parent = createMockSession(model, 'parent', { repl: 'mergeWithParent' });
const child1 = createMockSession(model, 'child1', { parentSession: parent, repl: 'separate' });
const child2 = createMockSession(model, 'child2', { parentSession: parent, repl: 'mergeWithParent' });
const grandChild = createMockSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' });
const child3 = createMockSession(model, 'child3', { parentSession: parent });
let parentChanges = 0;
parent.onDidChangeReplElements(() => ++parentChanges);
parent.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 1);
assert.equal(parent.getReplElements().length, 1);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 1);
assert.equal(grandChild.getReplElements().length, 1);
assert.equal(child3.getReplElements().length, 0);
grandChild.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 2);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 0);
child3.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 2);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 0);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 1);
child1.appendToRepl('1\n', severity.Info);
assert.equal(parentChanges, 2);
assert.equal(parent.getReplElements().length, 2);
assert.equal(child1.getReplElements().length, 1);
assert.equal(child2.getReplElements().length, 2);
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 1);
});
test('repl expressions', () => {
const session = createMockSession(model);
assert.equal(session.getReplElements().length, 0);
model.addSession(session);
session['raw'] = <any>rawSession;
const thread = new Thread(session, 'mockthread', 1);
const stackFrame = new StackFrame(thread, 1, <any>undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
const replModel = new ReplModel();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
assert.equal(replModel.getReplElements().length, 3);
replModel.getReplElements().forEach(re => {
assert.equal((<ReplEvaluationInput>re).value, 'myVariable');
});
replModel.removeReplExpressions();
assert.equal(replModel.getReplElements().length, 0);
});
test('repl ordering', async () => {
const session = createMockSession(model);
model.addSession(session);
const adapter = new MockDebugAdapter();
const raw = new RawDebugSession(adapter, undefined!, undefined!, undefined!, undefined!, undefined!);
session.initializeForTest(raw);
await session.addReplExpression(undefined, 'before.1');
assert.equal(session.getReplElements().length, 3);
assert.equal((<ReplEvaluationInput>session.getReplElements()[0]).value, 'before.1');
assert.equal((<SimpleReplElement>session.getReplElements()[1]).value, 'before.1');
assert.equal((<ReplEvaluationResult>session.getReplElements()[2]).value, '=before.1');
await session.addReplExpression(undefined, 'after.2');
await timeout(0);
assert.equal(session.getReplElements().length, 6);
assert.equal((<ReplEvaluationInput>session.getReplElements()[3]).value, 'after.2');
assert.equal((<ReplEvaluationResult>session.getReplElements()[4]).value, '=after.2');
assert.equal((<SimpleReplElement>session.getReplElements()[5]).value, 'after.2');
});
});

View File

@@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Expression, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel';
// Expressions
function assertWatchExpressions(watchExpressions: Expression[], expectedName: string) {
assert.equal(watchExpressions.length, 2);
watchExpressions.forEach(we => {
assert.equal(we.available, false);
assert.equal(we.reference, 0);
assert.equal(we.name, expectedName);
});
}
suite('Debug - Watch', () => {
let model: DebugModel;
setup(() => {
model = new DebugModel([], [], [], [], [], <any>{ isDirty: (e: any) => false });
});
test('watch expressions', () => {
assert.equal(model.getWatchExpressions().length, 0);
model.addWatchExpression('console');
model.addWatchExpression('console');
let watchExpressions = model.getWatchExpressions();
assertWatchExpressions(watchExpressions, 'console');
model.renameWatchExpression(watchExpressions[0].getId(), 'new_name');
model.renameWatchExpression(watchExpressions[1].getId(), 'new_name');
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
model.addWatchExpression('mockExpression');
model.moveWatchExpression(model.getWatchExpressions()[2].getId(), 1);
watchExpressions = model.getWatchExpressions();
assert.equal(watchExpressions[0].name, 'new_name');
assert.equal(watchExpressions[1].name, 'mockExpression');
assert.equal(watchExpressions[2].name, 'new_name');
model.removeWatchExpressions();
assert.equal(model.getWatchExpressions().length, 0);
});
});

View File

@@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { formatPII, getExactExpressionStartAndEnd } from 'vs/workbench/contrib/debug/common/debugUtils';
import { formatPII, getExactExpressionStartAndEnd, getVisibleAndSorted } from 'vs/workbench/contrib/debug/common/debugUtils';
import { IConfig } from 'vs/workbench/contrib/debug/common/debug';
suite('Debug - Utils', () => {
test('formatPII', () => {
@@ -37,4 +38,80 @@ suite('Debug - Utils', () => {
assert.deepEqual(getExactExpressionStartAndEnd('var t = a.b;c.d.name', 16, 20), { start: 13, end: 20 });
assert.deepEqual(getExactExpressionStartAndEnd('var t = a.b.c-d.name', 16, 20), { start: 15, end: 20 });
});
test('config presentation', () => {
const configs: IConfig[] = [];
configs.push({
type: 'node',
request: 'launch',
name: 'p'
});
configs.push({
type: 'node',
request: 'launch',
name: 'a'
});
configs.push({
type: 'node',
request: 'launch',
name: 'b',
presentation: {
hidden: false
}
});
configs.push({
type: 'node',
request: 'launch',
name: 'c',
presentation: {
hidden: true
}
});
configs.push({
type: 'node',
request: 'launch',
name: 'd',
presentation: {
group: '2_group',
order: 5
}
});
configs.push({
type: 'node',
request: 'launch',
name: 'e',
presentation: {
group: '2_group',
order: 52
}
});
configs.push({
type: 'node',
request: 'launch',
name: 'f',
presentation: {
group: '1_group',
order: 500
}
});
configs.push({
type: 'node',
request: 'launch',
name: 'g',
presentation: {
group: '5_group',
order: 500
}
});
const sorted = getVisibleAndSorted(configs);
assert.equal(sorted.length, 7);
assert.equal(sorted[0].name, 'f');
assert.equal(sorted[1].name, 'd');
assert.equal(sorted[2].name, 'e');
assert.equal(sorted[3].name, 'g');
assert.equal(sorted[4].name, 'b');
assert.equal(sorted[5].name, 'p');
assert.equal(sorted[6].name, 'a');
});
});

View File

@@ -9,7 +9,6 @@ import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { CompletionItem } from 'vs/editor/common/modes';
import Severity from 'vs/base/common/severity';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
@@ -237,8 +236,8 @@ export class MockSession implements IDebugSession {
return Promise.resolve([]);
}
completions(frameId: number, text: string, position: Position, overwriteBefore: number): Promise<CompletionItem[]> {
return Promise.resolve([]);
completions(frameId: number, text: string, position: Position, overwriteBefore: number): Promise<DebugProtocol.CompletionsResponse> {
throw new Error('not implemented');
}
clearThreads(removeThreads: boolean, reference?: number): void { }

View File

@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug';
import { timeout } from 'vs/base/common/async';
suite('Debug - AbstractDebugAdapter', () => {
suite('event ordering', () => {
let adapter: MockDebugAdapter;
let output: string[];
setup(() => {
adapter = new MockDebugAdapter();
output = [];
adapter.onEvent(ev => {
output.push((ev as DebugProtocol.OutputEvent).body.output);
Promise.resolve().then(() => output.push('--end microtask--'));
});
});
const evaluate = async (expression: string) => {
await new Promise(resolve => adapter.sendRequest('evaluate', { expression }, resolve));
output.push(`=${expression}`);
Promise.resolve().then(() => output.push('--end microtask--'));
};
test('inserts task boundary before response', async () => {
await evaluate('before.foo');
await timeout(0);
assert.deepStrictEqual(output, ['before.foo', '--end microtask--', '=before.foo', '--end microtask--']);
});
test('inserts task boundary after response', async () => {
await evaluate('after.foo');
await timeout(0);
assert.deepStrictEqual(output, ['=after.foo', '--end microtask--', 'after.foo', '--end microtask--']);
});
test('does not insert boundaries between events', async () => {
adapter.sendEventBody('output', { output: 'a' });
adapter.sendEventBody('output', { output: 'b' });
adapter.sendEventBody('output', { output: 'c' });
await timeout(0);
assert.deepStrictEqual(output, ['a', 'b', 'c', '--end microtask--', '--end microtask--', '--end microtask--']);
});
});
});