Path: blob/main/src/vs/workbench/contrib/debug/test/browser/repl.test.ts
5240 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);137138// eslint-disable-next-line local/code-no-any-casts139session['raw'] = <any>rawSession;140const thread = new Thread(session, 'mockthread', 1);141// eslint-disable-next-line local/code-no-any-casts142const stackFrame = new StackFrame(thread, 1, <any>undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1, true);143const replModel = new ReplModel(configurationService);144replModel.addReplExpression(session, stackFrame, 'myVariable').then();145replModel.addReplExpression(session, stackFrame, 'myVariable').then();146replModel.addReplExpression(session, stackFrame, 'myVariable').then();147148assert.strictEqual(replModel.getReplElements().length, 3);149replModel.getReplElements().forEach(re => {150assert.strictEqual((<ReplEvaluationInput>re).value, 'myVariable');151});152153replModel.removeReplExpressions();154assert.strictEqual(replModel.getReplElements().length, 0);155});156157test('repl ordering', async () => {158const session = disposables.add(createTestSession(model));159model.addSession(session);160161const adapter = new MockDebugAdapter();162const raw = disposables.add(new RawDebugSession(adapter, undefined!, '', '', undefined!, undefined!, undefined!, undefined!,));163session.initializeForTest(raw);164165await session.addReplExpression(undefined, 'before.1');166assert.strictEqual(session.getReplElements().length, 3);167assert.strictEqual((<ReplEvaluationInput>session.getReplElements()[0]).value, 'before.1');168assert.strictEqual((<ReplOutputElement>session.getReplElements()[1]).value, 'before.1');169assert.strictEqual((<ReplEvaluationResult>session.getReplElements()[2]).value, '=before.1');170171await session.addReplExpression(undefined, 'after.2');172await timeout(0);173assert.strictEqual(session.getReplElements().length, 6);174assert.strictEqual((<ReplEvaluationInput>session.getReplElements()[3]).value, 'after.2');175assert.strictEqual((<ReplEvaluationResult>session.getReplElements()[4]).value, '=after.2');176assert.strictEqual((<ReplOutputElement>session.getReplElements()[5]).value, 'after.2');177});178179test('repl groups', async () => {180const session = disposables.add(createTestSession(model));181const repl = new ReplModel(configurationService);182183repl.appendToRepl(session, { output: 'first global line', sev: severity.Info });184repl.startGroup(session, 'group_1', true);185repl.appendToRepl(session, { output: 'first line in group', sev: severity.Info });186repl.appendToRepl(session, { output: 'second line in group', sev: severity.Info });187const elements = repl.getReplElements();188assert.strictEqual(elements.length, 2);189const group = elements[1] as ReplGroup;190assert.strictEqual(group.name, 'group_1');191assert.strictEqual(group.autoExpand, true);192assert.strictEqual(group.hasChildren, true);193assert.strictEqual(group.hasEnded, false);194195repl.startGroup(session, 'group_2', false);196repl.appendToRepl(session, { output: 'first line in subgroup', sev: severity.Info });197repl.appendToRepl(session, { output: 'second line in subgroup', sev: severity.Info });198const children = group.getChildren();199assert.strictEqual(children.length, 3);200assert.strictEqual((<ReplOutputElement>children[0]).value, 'first line in group');201assert.strictEqual((<ReplOutputElement>children[1]).value, 'second line in group');202assert.strictEqual((<ReplGroup>children[2]).name, 'group_2');203assert.strictEqual((<ReplGroup>children[2]).hasEnded, false);204assert.strictEqual((<ReplGroup>children[2]).getChildren().length, 2);205repl.endGroup();206assert.strictEqual((<ReplGroup>children[2]).hasEnded, true);207repl.appendToRepl(session, { output: 'third line in group', sev: severity.Info });208assert.strictEqual(group.getChildren().length, 4);209assert.strictEqual(group.hasEnded, false);210repl.endGroup();211assert.strictEqual(group.hasEnded, true);212repl.appendToRepl(session, { output: 'second global line', sev: severity.Info });213assert.strictEqual(repl.getReplElements().length, 3);214assert.strictEqual((<ReplOutputElement>repl.getReplElements()[2]).value, 'second global line');215});216217test('repl identical line collapsing - character by character', () => {218const session = disposables.add(createTestSession(model));219const repl = new ReplModel(configurationService);220221// Test case 1: Character-by-character output should NOT be collapsed222// These should print "111\n", not "(3)1"223repl.appendToRepl(session, { output: '1', sev: severity.Info });224repl.appendToRepl(session, { output: '1', sev: severity.Info });225repl.appendToRepl(session, { output: '1', sev: severity.Info });226repl.appendToRepl(session, { output: '\n', sev: severity.Info });227228let elements = <ReplOutputElement[]>repl.getReplElements();229// Should be one element with "111\n" value, not collapsed230assert.strictEqual(elements.length, 1);231assert.strictEqual(elements[0].value, '111\n');232assert.strictEqual(elements[0].count, 1);233234repl.removeReplExpressions();235236// Test case 2: Character-by-character with mixed output237repl.appendToRepl(session, { output: '5', sev: severity.Info });238repl.appendToRepl(session, { output: '5', sev: severity.Info });239repl.appendToRepl(session, { output: '\n', sev: severity.Info });240241elements = <ReplOutputElement[]>repl.getReplElements();242// Should be one element with "55\n" value, not "(2)5"243assert.strictEqual(elements.length, 1);244assert.strictEqual(elements[0].value, '55\n');245assert.strictEqual(elements[0].count, 1);246});247248test('repl identical line collapsing - single event multiple lines', () => {249const session = disposables.add(createTestSession(model));250const repl = new ReplModel(configurationService);251252// Test case: Single event with multiple identical lines should be collapsed253// This should be collapsed into "(2)hello"254repl.appendToRepl(session, { output: 'hello\nhello\n', sev: severity.Info });255256const elements = <ReplOutputElement[]>repl.getReplElements();257// Should be one collapsed element with count 2258assert.strictEqual(elements.length, 1);259assert.strictEqual(elements[0].value, 'hello\n');260assert.strictEqual(elements[0].count, 2);261});262263test('repl identical line collapsing - mixed scenarios', () => {264const session = disposables.add(createTestSession(model));265const repl = new ReplModel(configurationService);266267// Test case: Mix of single events and multi-line events268repl.appendToRepl(session, { output: 'test\n', sev: severity.Info });269repl.appendToRepl(session, { output: 'test\ntest\n', sev: severity.Info });270271const elements = <ReplOutputElement[]>repl.getReplElements();272// Should be one collapsed element with count 3273assert.strictEqual(elements.length, 1);274assert.strictEqual(elements[0].value, 'test\n');275assert.strictEqual(elements[0].count, 3);276});277278test('repl filter', async () => {279const session = disposables.add(createTestSession(model));280const repl = new ReplModel(configurationService);281const replFilter = new ReplFilter();282283const getFilteredElements = (): ReplOutputElement[] => {284const elements = repl.getReplElements();285return elements.filter((e): e is ReplOutputElement => {286const filterResult = replFilter.filter(e, TreeVisibility.Visible);287return filterResult === true || filterResult === TreeVisibility.Visible;288});289};290291repl.appendToRepl(session, { output: 'first line\n', sev: severity.Info });292repl.appendToRepl(session, { output: 'second line\n', sev: severity.Info });293repl.appendToRepl(session, { output: 'third line\n', sev: severity.Info });294repl.appendToRepl(session, { output: 'fourth line\n', sev: severity.Info });295296replFilter.filterQuery = 'first';297const r1 = getFilteredElements();298assert.strictEqual(r1.length, 1);299assert.strictEqual(r1[0].value, 'first line\n');300301replFilter.filterQuery = '!first';302const r2 = getFilteredElements();303assert.strictEqual(r1.length, 1);304assert.strictEqual(r2[0].value, 'second line\n');305assert.strictEqual(r2[1].value, 'third line\n');306assert.strictEqual(r2[2].value, 'fourth line\n');307308replFilter.filterQuery = 'first, line';309const r3 = getFilteredElements();310assert.strictEqual(r3.length, 4);311assert.strictEqual(r3[0].value, 'first line\n');312assert.strictEqual(r3[1].value, 'second line\n');313assert.strictEqual(r3[2].value, 'third line\n');314assert.strictEqual(r3[3].value, 'fourth line\n');315316replFilter.filterQuery = 'line, !second';317const r4 = getFilteredElements();318assert.strictEqual(r4.length, 3);319assert.strictEqual(r4[0].value, 'first line\n');320assert.strictEqual(r4[1].value, 'third line\n');321assert.strictEqual(r4[2].value, 'fourth line\n');322323replFilter.filterQuery = '!second, line';324const r4_same = getFilteredElements();325assert.strictEqual(r4.length, r4_same.length);326327replFilter.filterQuery = '!line';328const r5 = getFilteredElements();329assert.strictEqual(r5.length, 0);330331replFilter.filterQuery = 'smth';332const r6 = getFilteredElements();333assert.strictEqual(r6.length, 0);334});335});336337338