mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-13 11:38:36 -05:00
Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2 (#8911)
* Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2 * update distro * fix layering * update distro * fix tests
This commit is contained in:
@@ -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 });
|
||||
|
||||
349
src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts
Normal file
349
src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
419
src/vs/workbench/contrib/debug/test/browser/callStack.test.ts
Normal file
419
src/vs/workbench/contrib/debug/test/browser/callStack.test.ts
Normal 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 });
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
154
src/vs/workbench/contrib/debug/test/browser/repl.test.ts
Normal file
154
src/vs/workbench/contrib/debug/test/browser/repl.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
51
src/vs/workbench/contrib/debug/test/browser/watch.test.ts
Normal file
51
src/vs/workbench/contrib/debug/test/browser/watch.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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--']);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user