Skip to content

Commit c4cde23

Browse files
author
Keegan Irby
committed
dispose listeners
1 parent a5fdc86 commit c4cde23

File tree

2 files changed

+86
-88
lines changed

2 files changed

+86
-88
lines changed

packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,25 +52,28 @@ export async function tailLogGroup(
5252
})
5353
return
5454
}
55-
56-
registry.set(uriToKey(session.uri), session)
57-
5855
const document = await prepareDocument(session)
5956

60-
hideShowStatusBarItemsOnActiveEditor(session, document)
61-
registerTabChangeCallback(session, registry, document, codeLensProvider)
57+
const disposables: vscode.Disposable[] = []
58+
disposables.push(hideShowStatusBarItemsOnActiveEditor(session, document))
59+
disposables.push(closeSessionWhenAllEditorsClosed(session, registry, document, codeLensProvider))
6260

63-
const stream = await session.startLiveTailSession()
64-
getLogger().info(`LiveTail session started: ${uriToKey(session.uri)}`)
65-
66-
span.record({
67-
source: source,
68-
result: 'Succeeded',
69-
sessionAlreadyStarted: false,
70-
hasLogEventFilterPattern: Boolean(wizardResponse.filterPattern),
71-
logStreamFilterType: wizardResponse.logStreamFilter.type,
72-
})
73-
await handleSessionStream(stream, document, session)
61+
try {
62+
const stream = await session.startLiveTailSession()
63+
registry.set(uriToKey(session.uri), session)
64+
codeLensProvider.refresh()
65+
getLogger().info(`LiveTail session started: ${uriToKey(session.uri)}`)
66+
span.record({
67+
source: source,
68+
result: 'Succeeded',
69+
sessionAlreadyStarted: false,
70+
hasLogEventFilterPattern: Boolean(wizardResponse.filterPattern),
71+
logStreamFilterType: wizardResponse.logStreamFilter.type,
72+
})
73+
await handleSessionStream(stream, document, session)
74+
} finally {
75+
disposables.forEach((disposable) => disposable.dispose())
76+
}
7477
})
7578
}
7679

@@ -224,8 +227,11 @@ function eventRate(event: LiveTailSessionUpdate): number {
224227
return event.sessionResults === undefined ? 0 : event.sessionResults.length
225228
}
226229

227-
function hideShowStatusBarItemsOnActiveEditor(session: LiveTailSession, document: vscode.TextDocument) {
228-
vscode.window.onDidChangeActiveTextEditor((editor) => {
230+
function hideShowStatusBarItemsOnActiveEditor(
231+
session: LiveTailSession,
232+
document: vscode.TextDocument
233+
): vscode.Disposable {
234+
return vscode.window.onDidChangeActiveTextEditor((editor) => {
229235
session.showStatusBarItem(editor?.document === document)
230236
})
231237
}
@@ -242,13 +248,13 @@ function hideShowStatusBarItemsOnActiveEditor(session: LiveTailSession, document
242248
* from view, will not be returned. Meaning a Tab that is created (shown in top bar), but not open, will not be returned. Even if
243249
* the tab isn't visible, we want to continue writing to the doc, and keep the session alive.
244250
*/
245-
function registerTabChangeCallback(
251+
function closeSessionWhenAllEditorsClosed(
246252
session: LiveTailSession,
247253
registry: LiveTailSessionRegistry,
248254
document: vscode.TextDocument,
249255
codeLensProvider: LiveTailCodeLensProvider
250-
) {
251-
vscode.window.tabGroups.onDidChangeTabs((tabEvent) => {
256+
): vscode.Disposable {
257+
return vscode.window.tabGroups.onDidChangeTabs((tabEvent) => {
252258
const isOpen = isLiveTailSessionOpenInAnyTab(session)
253259
if (!isOpen) {
254260
closeSession(session.uri, registry, 'ClosedEditors', codeLensProvider)

packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts

Lines changed: 59 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,20 @@
44
*/
55

66
import * as sinon from 'sinon'
7-
import * as FakeTimers from '@sinonjs/fake-timers'
87
import * as vscode from 'vscode'
98

109
import assert from 'assert'
1110
import { clearDocument, closeSession, tailLogGroup } from '../../../../awsService/cloudWatchLogs/commands/tailLogGroup'
12-
import { LiveTailSessionLogEvent, StartLiveTailResponseStream } from '@aws-sdk/client-cloudwatch-logs'
11+
import { StartLiveTailResponseStream } from '@aws-sdk/client-cloudwatch-logs'
1312
import { LiveTailSessionRegistry } from '../../../../awsService/cloudWatchLogs/registry/liveTailSessionRegistry'
1413
import { LiveTailSession } from '../../../../awsService/cloudWatchLogs/registry/liveTailSession'
15-
import { asyncGenerator } from '../../../../shared/utilities/collectionUtils'
1614
import {
1715
TailLogGroupWizard,
1816
TailLogGroupWizardResponse,
1917
} from '../../../../awsService/cloudWatchLogs/wizard/tailLogGroupWizard'
2018
import { getTestWindow } from '../../../shared/vscode/window'
2119
import { CloudWatchLogsSettings, uriToKey } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils'
22-
import { installFakeClock } from '../../../testUtil'
23-
import { DefaultAwsContext, ToolkitError } from '../../../../shared'
20+
import { DefaultAwsContext, ToolkitError, waitUntil } from '../../../../shared'
2421
import { LiveTailCodeLensProvider } from '../../../../awsService/cloudWatchLogs/document/liveTailCodeLensProvider'
2522

2623
describe('TailLogGroup', function () {
@@ -39,23 +36,12 @@ describe('TailLogGroup', function () {
3936
let cloudwatchSettingsSpy: sinon.SinonSpy
4037
let wizardSpy: sinon.SinonSpy
4138

42-
let clock: FakeTimers.InstalledClock
43-
44-
before(function () {
45-
clock = installFakeClock()
46-
})
47-
4839
beforeEach(function () {
49-
clock.reset()
5040
sandbox = sinon.createSandbox()
5141
registry = new LiveTailSessionRegistry()
5242
codeLensProvider = new LiveTailCodeLensProvider(registry)
5343
})
5444

55-
after(function () {
56-
clock.uninstall()
57-
})
58-
5945
afterEach(function () {
6046
sandbox.restore()
6147
})
@@ -64,47 +50,46 @@ describe('TailLogGroup', function () {
6450
sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns(testAwsAccountId)
6551
sandbox.stub(DefaultAwsContext.prototype, 'getCredentials').returns(Promise.resolve(testAwsCredentials))
6652

67-
wizardSpy = sandbox.stub(TailLogGroupWizard.prototype, 'run').callsFake(async function () {
68-
return getTestWizardResponse()
69-
})
70-
const testMessage2 = `${testMessage}-2`
71-
const testMessage3 = `${testMessage}-3`
53+
const startTimestamp = 1732276800000 // 11-22-2024 12:00:00PM GMT
54+
const updateFrames: StartLiveTailResponseStream[] = [
55+
getSessionUpdateFrame(false, `${testMessage}-1`, startTimestamp + 1000),
56+
getSessionUpdateFrame(false, `${testMessage}-2`, startTimestamp + 2000),
57+
getSessionUpdateFrame(false, `${testMessage}-3`, startTimestamp + 3000),
58+
]
59+
//Returns the configured update frames and then indefinitely blocks.
60+
//This keeps the stream 'open', simulating an open network stream waiting for new events.
61+
//If the stream were to close, the event listeners in the TailLogGroup command would dispose,
62+
//breaking the 'closes tab closes session' assertions this test makes.
63+
async function* generator(): AsyncIterable<StartLiveTailResponseStream> {
64+
for (const frame of updateFrames) {
65+
yield frame
66+
}
67+
await new Promise(() => {})
68+
}
69+
7270
startLiveTailSessionSpy = sandbox
7371
.stub(LiveTailSession.prototype, 'startLiveTailSession')
74-
.callsFake(async function () {
75-
return getTestResponseStream([
76-
{
77-
message: testMessage,
78-
timestamp: 876830400000,
79-
},
80-
{
81-
message: testMessage2,
82-
timestamp: 876830402000,
83-
},
84-
{
85-
message: testMessage3,
86-
timestamp: 876830403000,
87-
},
88-
])
89-
})
72+
.returns(Promise.resolve(generator()))
9073
stopLiveTailSessionSpy = sandbox
9174
.stub(LiveTailSession.prototype, 'stopLiveTailSession')
9275
.callsFake(async function () {
9376
return
9477
})
95-
78+
wizardSpy = sandbox.stub(TailLogGroupWizard.prototype, 'run').callsFake(async function () {
79+
return getTestWizardResponse()
80+
})
9681
//Set maxLines to 1.
9782
cloudwatchSettingsSpy = sandbox.stub(CloudWatchLogsSettings.prototype, 'get').callsFake(() => {
9883
return 1
9984
})
100-
await tailLogGroup(registry, testSource, codeLensProvider, {
85+
86+
//The mock stream doesn't 'close', causing tailLogGroup to not return. If we `await`, it will never resolve.
87+
//Run it in the background and use waitUntil to poll its state.
88+
void tailLogGroup(registry, testSource, codeLensProvider, {
10189
groupName: testLogGroup,
10290
regionName: testRegion,
10391
})
104-
assert.strictEqual(wizardSpy.calledOnce, true)
105-
assert.strictEqual(cloudwatchSettingsSpy.calledOnce, true)
106-
assert.strictEqual(startLiveTailSessionSpy.calledOnce, true)
107-
assert.strictEqual(registry.size, 1)
92+
await waitUntil(async () => registry.size !== 0, { interval: 100, timeout: 1000 })
10893

10994
//registry is asserted to have only one entry, so this is assumed to be the session that was
11095
//started in this test.
@@ -113,13 +98,24 @@ describe('TailLogGroup', function () {
11398
if (sessionUri === undefined) {
11499
throw Error
115100
}
116-
const document = getTestWindow().activeTextEditor?.document
101+
102+
assert.strictEqual(wizardSpy.calledOnce, true)
103+
assert.strictEqual(cloudwatchSettingsSpy.calledOnce, true)
104+
assert.strictEqual(startLiveTailSessionSpy.calledOnce, true)
105+
assert.strictEqual(registry.size, 1)
106+
107+
//Validate writing to the document.
108+
//MaxLines is set to 1, and "testMessage3" is the last event in the stream, its contents should be the only thing in the doc.
109+
const window = getTestWindow()
110+
const document = window.activeTextEditor?.document
117111
assert.strictEqual(sessionUri.toString(), document?.uri.toString())
118-
//Test responseStream has 3 events, maxLines is set to 1. Only 3rd event should be in doc.
119-
assert.strictEqual(document?.getText().trim(), `12:00:03\t${testMessage3}`)
112+
const doesDocumentContainExpectedContent = await waitUntil(
113+
async () => document?.getText().trim() === `12:00:03\t${testMessage}-3`,
114+
{ interval: 100, timeout: 1000 }
115+
)
116+
assert.strictEqual(doesDocumentContainExpectedContent, true)
120117

121118
//Test that closing all tabs the session's document is open in will cause the session to close
122-
const window = getTestWindow()
123119
let tabs: vscode.Tab[] = []
124120
window.tabGroups.all.forEach((tabGroup) => {
125121
tabs = tabs.concat(getLiveTailSessionTabsFromTabGroup(tabGroup, sessionUri!))
@@ -159,7 +155,6 @@ describe('TailLogGroup', function () {
159155
closeSession(session.uri, registry, testSource, codeLensProvider)
160156
assert.strictEqual(0, registry.size)
161157
assert.strictEqual(true, stopLiveTailSessionSpy.calledOnce)
162-
assert.strictEqual(0, clock.countTimers())
163158
})
164159

165160
it('clearDocument clears all text from document', async function () {
@@ -200,26 +195,23 @@ describe('TailLogGroup', function () {
200195
}
201196
}
202197

203-
//Creates a test response stream. Each log event provided will be its own "frame" of the input stream.
204-
function getTestResponseStream(logEvents: LiveTailSessionLogEvent[]): AsyncIterable<StartLiveTailResponseStream> {
205-
const sessionStartFrame: StartLiveTailResponseStream = {
206-
sessionStart: {
207-
logGroupIdentifiers: [testLogGroup],
198+
function getSessionUpdateFrame(
199+
isSampled: boolean,
200+
message: string,
201+
timestamp: number
202+
): StartLiveTailResponseStream {
203+
return {
204+
sessionUpdate: {
205+
sessionMetadata: {
206+
sampled: isSampled,
207+
},
208+
sessionResults: [
209+
{
210+
message: message,
211+
timestamp: timestamp,
212+
},
213+
],
208214
},
209-
sessionUpdate: undefined,
210215
}
211-
212-
const updateFrames: StartLiveTailResponseStream[] = logEvents.map((event) => {
213-
return {
214-
sessionUpdate: {
215-
sessionMetadata: {
216-
sampled: false,
217-
},
218-
sessionResults: [event],
219-
},
220-
}
221-
})
222-
223-
return asyncGenerator([sessionStartFrame, ...updateFrames])
224216
}
225217
})

0 commit comments

Comments
 (0)