Path: blob/main/src/vs/workbench/contrib/debug/test/browser/repl.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/456import assert from 'assert';7import { TreeVisibility } from '../../../../../base/browser/ui/tree/tree.js';8import { timeout } from '../../../../../base/common/async.js';9import severity from '../../../../../base/common/severity.js';10import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';11import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';12import { RawDebugSession } from '../../browser/rawDebugSession.js';13import { ReplFilter } from '../../browser/replFilter.js';14import { DebugModel, StackFrame, Thread } from '../../common/debugModel.js';15import { RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult, ReplGroup, ReplModel, ReplOutputElement, ReplVariableElement } from '../../common/replModel.js';16import { createTestSession } from './callStack.test.js';17import { createMockDebugModel } from './mockDebugModel.js';18import { MockDebugAdapter, MockRawSession } from '../common/mockDebug.js';1920suite('Debug - REPL', () => {21let model: DebugModel;22let rawSession: MockRawSession;23const disposables = ensureNoDisposablesAreLeakedInTestSuite();24const configurationService = new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } });2526setup(() => {27model = createMockDebugModel(disposables);28rawSession = new MockRawSession();29});3031test('repl output', () => {32const session = disposables.add(createTestSession(model));33const repl = new ReplModel(configurationService);34repl.appendToRepl(session, { output: 'first line\n', sev: severity.Error });35repl.appendToRepl(session, { output: 'second line ', sev: severity.Error });36repl.appendToRepl(session, { output: 'third line ', sev: severity.Error });37repl.appendToRepl(session, { output: 'fourth line', sev: severity.Error });3839let elements = <ReplOutputElement[]>repl.getReplElements();40assert.strictEqual(elements.length, 2);41assert.strictEqual(elements[0].value, 'first line\n');42assert.strictEqual(elements[0].severity, severity.Error);43assert.strictEqual(elements[1].value, 'second line third line fourth line');44assert.strictEqual(elements[1].severity, severity.Error);4546repl.appendToRepl(session, { output: '1', sev: severity.Warning });47elements = <ReplOutputElement[]>repl.getReplElements();48assert.strictEqual(elements.length, 3);49assert.strictEqual(elements[2].value, '1');50assert.strictEqual(elements[2].severity, severity.Warning);5152const keyValueObject = { 'key1': 2, 'key2': 'value' };53repl.appendToRepl(session, { output: '', expression: new RawObjectReplElement('fakeid', 'fake', keyValueObject), sev: severity.Info });54const element = <ReplVariableElement>repl.getReplElements()[3];55assert.strictEqual(element.expression.value, 'Object');56assert.deepStrictEqual((element.expression as RawObjectReplElement).valueObj, keyValueObject);5758repl.removeReplExpressions();59assert.strictEqual(repl.getReplElements().length, 0);6061repl.appendToRepl(session, { output: '1\n', sev: severity.Info });62repl.appendToRepl(session, { output: '2', sev: severity.Info });63repl.appendToRepl(session, { output: '3\n4', sev: severity.Info });64repl.appendToRepl(session, { output: '5\n', sev: severity.Info });65repl.appendToRepl(session, { output: '6', sev: severity.Info });66elements = <ReplOutputElement[]>repl.getReplElements();67assert.deepStrictEqual(elements.map(e => e.toString()), ['1\n', '23\n', '45\n', '6']);6869repl.removeReplExpressions();70});7172test('repl output count', () => {73const session = disposables.add(createTestSession(model));74const repl = new ReplModel(configurationService);75repl.appendToRepl(session, { output: 'first line\n', sev: severity.Info });76repl.appendToRepl(session, { output: 'first line\n', sev: severity.Info });77repl.appendToRepl(session, { output: 'first line\n', sev: severity.Info });78repl.appendToRepl(session, { output: 'second line\n', sev: severity.Info });79repl.appendToRepl(session, { output: 'second line\n', sev: severity.Info });80repl.appendToRepl(session, { output: 'third line\n', sev: severity.Info });81const elements = <ReplOutputElement[]>repl.getReplElements();82assert.deepStrictEqual(elements.map(e => ({ value: e.value, count: e.count })), [83{ value: 'first line\n', count: 3 },84{ value: 'second line\n', count: 2 },85{ value: 'third line\n', count: 1 }86]);87});8889test('repl merging', () => {90// 'mergeWithParent' should be ignored when there is no parent.91const parent = disposables.add(createTestSession(model, 'parent', { repl: 'mergeWithParent' }));92const child1 = disposables.add(createTestSession(model, 'child1', { parentSession: parent, repl: 'separate' }));93const child2 = disposables.add(createTestSession(model, 'child2', { parentSession: parent, repl: 'mergeWithParent' }));94const grandChild = disposables.add(createTestSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' }));95const child3 = disposables.add(createTestSession(model, 'child3', { parentSession: parent }));9697let parentChanges = 0;98disposables.add(parent.onDidChangeReplElements(() => ++parentChanges));99100parent.appendToRepl({ output: '1\n', sev: severity.Info });101assert.strictEqual(parentChanges, 1);102assert.strictEqual(parent.getReplElements().length, 1);103assert.strictEqual(child1.getReplElements().length, 0);104assert.strictEqual(child2.getReplElements().length, 1);105assert.strictEqual(grandChild.getReplElements().length, 1);106assert.strictEqual(child3.getReplElements().length, 0);107108grandChild.appendToRepl({ output: '2\n', sev: severity.Info });109assert.strictEqual(parentChanges, 2);110assert.strictEqual(parent.getReplElements().length, 2);111assert.strictEqual(child1.getReplElements().length, 0);112assert.strictEqual(child2.getReplElements().length, 2);113assert.strictEqual(grandChild.getReplElements().length, 2);114assert.strictEqual(child3.getReplElements().length, 0);115116child3.appendToRepl({ output: '3\n', sev: severity.Info });117assert.strictEqual(parentChanges, 2);118assert.strictEqual(parent.getReplElements().length, 2);119assert.strictEqual(child1.getReplElements().length, 0);120assert.strictEqual(child2.getReplElements().length, 2);121assert.strictEqual(grandChild.getReplElements().length, 2);122assert.strictEqual(child3.getReplElements().length, 1);123124child1.appendToRepl({ output: '4\n', sev: severity.Info });125assert.strictEqual(parentChanges, 2);126assert.strictEqual(parent.getReplElements().length, 2);127assert.strictEqual(child1.getReplElements().length, 1);128assert.strictEqual(child2.getReplElements().length, 2);129assert.strictEqual(grandChild.getReplElements().length, 2);130assert.strictEqual(child3.getReplElements().length, 1);131});132133test('repl expressions', () => {134const session = disposables.add(createTestSession(model));135assert.strictEqual(session.getReplElements().length, 0);136model.addSession(session);137138session['raw'] = <any>rawSession;139const thread = new Thread(session, 'mockthread', 1);140const stackFrame = new StackFrame(thread, 1, <any>undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1, true);141const replModel = new ReplModel(configurationService);142replModel.addReplExpression(session, stackFrame, 'myVariable').then();143replModel.addReplExpression(session, stackFrame, 'myVariable').then();144replModel.addReplExpression(session, stackFrame, 'myVariable').then();145146assert.strictEqual(replModel.getReplElements().length, 3);147replModel.getReplElements().forEach(re => {148assert.strictEqual((<ReplEvaluationInput>re).value, 'myVariable');149});150151replModel.removeReplExpressions();152assert.strictEqual(replModel.getReplElements().length, 0);153});154155test('repl ordering', async () => {156const session = disposables.add(createTestSession(model));157model.addSession(session);158159const adapter = new MockDebugAdapter();160const raw = disposables.add(new RawDebugSession(adapter, undefined!, '', '', undefined!, undefined!, undefined!, undefined!,));161session.initializeForTest(raw);162163await session.addReplExpression(undefined, 'before.1');164assert.strictEqual(session.getReplElements().length, 3);165assert.strictEqual((<ReplEvaluationInput>session.getReplElements()[0]).value, 'before.1');166assert.strictEqual((<ReplOutputElement>session.getReplElements()[1]).value, 'before.1');167assert.strictEqual((<ReplEvaluationResult>session.getReplElements()[2]).value, '=before.1');168169await session.addReplExpression(undefined, 'after.2');170await timeout(0);171assert.strictEqual(session.getReplElements().length, 6);172assert.strictEqual((<ReplEvaluationInput>session.getReplElements()[3]).value, 'after.2');173assert.strictEqual((<ReplEvaluationResult>session.getReplElements()[4]).value, '=after.2');174assert.strictEqual((<ReplOutputElement>session.getReplElements()[5]).value, 'after.2');175});176177test('repl groups', async () => {178const session = disposables.add(createTestSession(model));179const repl = new ReplModel(configurationService);180181repl.appendToRepl(session, { output: 'first global line', sev: severity.Info });182repl.startGroup(session, 'group_1', true);183repl.appendToRepl(session, { output: 'first line in group', sev: severity.Info });184repl.appendToRepl(session, { output: 'second line in group', sev: severity.Info });185const elements = repl.getReplElements();186assert.strictEqual(elements.length, 2);187const group = elements[1] as ReplGroup;188assert.strictEqual(group.name, 'group_1');189assert.strictEqual(group.autoExpand, true);190assert.strictEqual(group.hasChildren, true);191assert.strictEqual(group.hasEnded, false);192193repl.startGroup(session, 'group_2', false);194repl.appendToRepl(session, { output: 'first line in subgroup', sev: severity.Info });195repl.appendToRepl(session, { output: 'second line in subgroup', sev: severity.Info });196const children = group.getChildren();197assert.strictEqual(children.length, 3);198assert.strictEqual((<ReplOutputElement>children[0]).value, 'first line in group');199assert.strictEqual((<ReplOutputElement>children[1]).value, 'second line in group');200assert.strictEqual((<ReplGroup>children[2]).name, 'group_2');201assert.strictEqual((<ReplGroup>children[2]).hasEnded, false);202assert.strictEqual((<ReplGroup>children[2]).getChildren().length, 2);203repl.endGroup();204assert.strictEqual((<ReplGroup>children[2]).hasEnded, true);205repl.appendToRepl(session, { output: 'third line in group', sev: severity.Info });206assert.strictEqual(group.getChildren().length, 4);207assert.strictEqual(group.hasEnded, false);208repl.endGroup();209assert.strictEqual(group.hasEnded, true);210repl.appendToRepl(session, { output: 'second global line', sev: severity.Info });211assert.strictEqual(repl.getReplElements().length, 3);212assert.strictEqual((<ReplOutputElement>repl.getReplElements()[2]).value, 'second global line');213});214215test('repl identical line collapsing - character by character', () => {216const session = disposables.add(createTestSession(model));217const repl = new ReplModel(configurationService);218219// Test case 1: Character-by-character output should NOT be collapsed220// These should print "111\n", not "(3)1"221repl.appendToRepl(session, { output: '1', sev: severity.Info });222repl.appendToRepl(session, { output: '1', sev: severity.Info });223repl.appendToRepl(session, { output: '1', sev: severity.Info });224repl.appendToRepl(session, { output: '\n', sev: severity.Info });225226let elements = <ReplOutputElement[]>repl.getReplElements();227// Should be one element with "111\n" value, not collapsed228assert.strictEqual(elements.length, 1);229assert.strictEqual(elements[0].value, '111\n');230assert.strictEqual(elements[0].count, 1);231232repl.removeReplExpressions();233234// Test case 2: Character-by-character with mixed output235repl.appendToRepl(session, { output: '5', sev: severity.Info });236repl.appendToRepl(session, { output: '5', sev: severity.Info });237repl.appendToRepl(session, { output: '\n', sev: severity.Info });238239elements = <ReplOutputElement[]>repl.getReplElements();240// Should be one element with "55\n" value, not "(2)5"241assert.strictEqual(elements.length, 1);242assert.strictEqual(elements[0].value, '55\n');243assert.strictEqual(elements[0].count, 1);244});245246test('repl identical line collapsing - single event multiple lines', () => {247const session = disposables.add(createTestSession(model));248const repl = new ReplModel(configurationService);249250// Test case: Single event with multiple identical lines should be collapsed251// This should be collapsed into "(2)hello"252repl.appendToRepl(session, { output: 'hello\nhello\n', sev: severity.Info });253254const elements = <ReplOutputElement[]>repl.getReplElements();255// Should be one collapsed element with count 2256assert.strictEqual(elements.length, 1);257assert.strictEqual(elements[0].value, 'hello\n');258assert.strictEqual(elements[0].count, 2);259});260261test('repl identical line collapsing - mixed scenarios', () => {262const session = disposables.add(createTestSession(model));263const repl = new ReplModel(configurationService);264265// Test case: Mix of single events and multi-line events266repl.appendToRepl(session, { output: 'test\n', sev: severity.Info });267repl.appendToRepl(session, { output: 'test\ntest\n', sev: severity.Info });268269const elements = <ReplOutputElement[]>repl.getReplElements();270// Should be one collapsed element with count 3271assert.strictEqual(elements.length, 1);272assert.strictEqual(elements[0].value, 'test\n');273assert.strictEqual(elements[0].count, 3);274});275276test('repl filter', async () => {277const session = disposables.add(createTestSession(model));278const repl = new ReplModel(configurationService);279const replFilter = new ReplFilter();280281const getFilteredElements = (): ReplOutputElement[] => {282const elements = repl.getReplElements();283return elements.filter((e): e is ReplOutputElement => {284const filterResult = replFilter.filter(e, TreeVisibility.Visible);285return filterResult === true || filterResult === TreeVisibility.Visible;286});287};288289repl.appendToRepl(session, { output: 'first line\n', sev: severity.Info });290repl.appendToRepl(session, { output: 'second line\n', sev: severity.Info });291repl.appendToRepl(session, { output: 'third line\n', sev: severity.Info });292repl.appendToRepl(session, { output: 'fourth line\n', sev: severity.Info });293294replFilter.filterQuery = 'first';295const r1 = getFilteredElements();296assert.strictEqual(r1.length, 1);297assert.strictEqual(r1[0].value, 'first line\n');298299replFilter.filterQuery = '!first';300const r2 = getFilteredElements();301assert.strictEqual(r1.length, 1);302assert.strictEqual(r2[0].value, 'second line\n');303assert.strictEqual(r2[1].value, 'third line\n');304assert.strictEqual(r2[2].value, 'fourth line\n');305306replFilter.filterQuery = 'first, line';307const r3 = getFilteredElements();308assert.strictEqual(r3.length, 4);309assert.strictEqual(r3[0].value, 'first line\n');310assert.strictEqual(r3[1].value, 'second line\n');311assert.strictEqual(r3[2].value, 'third line\n');312assert.strictEqual(r3[3].value, 'fourth line\n');313314replFilter.filterQuery = 'line, !second';315const r4 = getFilteredElements();316assert.strictEqual(r4.length, 3);317assert.strictEqual(r4[0].value, 'first line\n');318assert.strictEqual(r4[1].value, 'third line\n');319assert.strictEqual(r4[2].value, 'fourth line\n');320321replFilter.filterQuery = '!second, line';322const r4_same = getFilteredElements();323assert.strictEqual(r4.length, r4_same.length);324325replFilter.filterQuery = '!line';326const r5 = getFilteredElements();327assert.strictEqual(r5.length, 0);328329replFilter.filterQuery = 'smth';330const r6 = getFilteredElements();331assert.strictEqual(r6.length, 0);332});333});334335336