Skip to content

Commit dabd1b9

Browse files
committed
feat(amazonq): skip registering run command log file
1 parent ce01fa8 commit dabd1b9

File tree

3 files changed

+204
-0
lines changed

3 files changed

+204
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "Save user command execution logs to plugin output."
4+
}

packages/core/src/amazonq/session/sessionState.ts

+11
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { prepareRepoData, getDeletedFileInfos, registerNewFiles, PrepareRepoData
2929
import { uploadCode } from '../util/upload'
3030

3131
export const EmptyCodeGenID = 'EMPTY_CURRENT_CODE_GENERATION_ID'
32+
export const RunCommandLogFileName = '.amazonq/dev/run_command.log'
3233

3334
export interface BaseMessenger {
3435
sendAnswer(params: any): void
@@ -103,6 +104,16 @@ export abstract class CodeGenBase {
103104
case CodeGenerationStatus.COMPLETE: {
104105
const { newFileContents, deletedFiles, references } =
105106
await this.config.proxyClient.exportResultArchive(this.conversationId)
107+
108+
const logFileInfo = newFileContents.find(
109+
(file: { zipFilePath: string; fileContent: string }) =>
110+
file.zipFilePath === RunCommandLogFileName
111+
)
112+
if (logFileInfo) {
113+
getLogger().info(`sessionState: Run Command logs, ${logFileInfo.fileContent}`)
114+
newFileContents.splice(newFileContents.indexOf(logFileInfo), 1)
115+
}
116+
106117
const newFileInfo = registerNewFiles(
107118
fs,
108119
newFileContents,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import * as vscode from 'vscode'
7+
import sinon from 'sinon'
8+
import { CodeGenBase } from '../../../amazonq/session/sessionState'
9+
import { RunCommandLogFileName } from '../../../amazonq/session/sessionState'
10+
import assert from 'assert'
11+
import * as workspaceUtils from '../../../shared/utilities/workspaceUtils'
12+
import * as loggerModule from '../../../shared/logger/logger'
13+
import { TelemetryHelper } from '../../../amazonq/util/telemetryHelper'
14+
15+
describe('CodeGenBase generateCode log file handling', () => {
16+
class TestCodeGen extends CodeGenBase {
17+
public generatedFiles: any[] = []
18+
constructor(config: any, tabID: string) {
19+
super(config, tabID)
20+
}
21+
protected handleProgress(_messenger: any): void {
22+
// No-op for test.
23+
}
24+
protected getScheme(): string {
25+
return 'file'
26+
}
27+
protected getTimeoutErrorCode(): string {
28+
return 'test_timeout'
29+
}
30+
protected handleGenerationComplete(_messenger: any, newFileInfo: any[]): void {
31+
this.generatedFiles = newFileInfo
32+
}
33+
protected handleError(_messenger: any, _codegenResult: any): Error {
34+
throw new Error('handleError called')
35+
}
36+
}
37+
38+
let fakeProxyClient: any
39+
let testConfig: any
40+
let fsMock: any
41+
let messengerMock: any
42+
let loggerMock: any
43+
let testAction: any
44+
45+
beforeEach(async () => {
46+
const ret = {
47+
testworkspacefolder: {
48+
uri: vscode.Uri.file('/path/to/testworkspacefolder'),
49+
name: 'testworkspacefolder',
50+
index: 0,
51+
},
52+
}
53+
sinon.stub(workspaceUtils, 'getWorkspaceFoldersByPrefixes').returns(ret)
54+
55+
fakeProxyClient = {
56+
getCodeGeneration: sinon.stub().resolves({
57+
codeGenerationStatus: { status: 'Complete' },
58+
codeGenerationRemainingIterationCount: 0,
59+
codeGenerationTotalIterationCount: 1,
60+
}),
61+
exportResultArchive: sinon.stub(),
62+
}
63+
64+
testConfig = {
65+
conversationId: 'conv_test',
66+
uploadId: 'upload_test',
67+
workspaceRoots: ['/path/to/testworkspacefolder'],
68+
proxyClient: fakeProxyClient,
69+
}
70+
71+
fsMock = {
72+
writeFile: sinon.stub().resolves(),
73+
registerProvider: sinon.stub().resolves(),
74+
}
75+
76+
messengerMock = { sendAnswer: sinon.spy() }
77+
78+
loggerMock = {
79+
info: sinon.spy(),
80+
}
81+
82+
testAction = {
83+
fs: fsMock,
84+
messenger: messengerMock,
85+
logger: loggerMock,
86+
tokenSource: {
87+
token: {
88+
isCancellationRequested: false,
89+
onCancellationRequested: () => {},
90+
},
91+
},
92+
}
93+
})
94+
95+
afterEach(() => {
96+
sinon.restore()
97+
})
98+
99+
it('adds the log content to logger if present and excludes it from new files', async () => {
100+
const logFileInfo = {
101+
zipFilePath: RunCommandLogFileName,
102+
fileContent: 'Log content',
103+
}
104+
const otherFile = { zipFilePath: 'other.ts', fileContent: 'other content' }
105+
fakeProxyClient.exportResultArchive.resolves({
106+
newFileContents: [logFileInfo, otherFile],
107+
deletedFiles: [],
108+
references: [],
109+
})
110+
const controlledLogger = {
111+
info: sinon.spy(),
112+
debug: sinon.spy(),
113+
verbose: sinon.spy(),
114+
warn: sinon.spy(),
115+
error: sinon.spy(),
116+
log: sinon.spy(),
117+
setLogLevel: sinon.spy(),
118+
setLogLevelToDefault: sinon.spy(),
119+
logLevelEnabled: sinon.spy(),
120+
getLogById: sinon.spy(),
121+
sendToLog: sinon.spy(),
122+
}
123+
sinon.stub(loggerModule, 'getLogger').returns(controlledLogger)
124+
const testCodeGen = new TestCodeGen(testConfig, 'tab1')
125+
126+
const result = await testCodeGen.generateCode({
127+
messenger: messengerMock,
128+
fs: fsMock,
129+
codeGenerationId: 'codegen1',
130+
telemetry: new TelemetryHelper(),
131+
workspaceFolders: [testConfig.workspaceRoots[0]],
132+
action: testAction,
133+
})
134+
135+
sinon.assert.calledWith(controlledLogger.info, sinon.match(`sessionState: Run Command logs, Log content`))
136+
137+
const expectedNewFile = {
138+
zipFilePath: 'other.ts',
139+
fileContent: 'other content',
140+
changeApplied: false,
141+
rejected: false,
142+
relativePath: 'other.ts',
143+
virtualMemoryUri: vscode.Uri.file(`/upload_test/other.ts`),
144+
workspaceFolder: {
145+
index: 0,
146+
name: 'testworkspacefolder',
147+
uri: vscode.Uri.file('/path/to/testworkspacefolder'),
148+
},
149+
}
150+
assert.deepStrictEqual(result.newFiles[0].fileContent, expectedNewFile.fileContent)
151+
})
152+
153+
it('skips log file handling if log file is not present', async () => {
154+
const file1 = { zipFilePath: 'file1.ts', fileContent: 'content1' }
155+
fakeProxyClient.exportResultArchive.resolves({
156+
newFileContents: [file1],
157+
deletedFiles: [],
158+
references: [],
159+
})
160+
161+
const testCodeGen = new TestCodeGen(testConfig, 'tab1')
162+
163+
const result = await testCodeGen.generateCode({
164+
messenger: messengerMock,
165+
fs: fsMock,
166+
codeGenerationId: 'codegen2',
167+
telemetry: new TelemetryHelper(),
168+
workspaceFolders: [testConfig.workspaceRoots[0]],
169+
action: testAction,
170+
})
171+
172+
sinon.assert.notCalled(loggerMock.info)
173+
174+
const expectedNewFile = {
175+
zipFilePath: 'file1.ts',
176+
fileContent: 'content1',
177+
changeApplied: false,
178+
rejected: false,
179+
relativePath: 'file1.ts',
180+
virtualMemoryUri: vscode.Uri.file(`/upload_test/file1.ts`),
181+
workspaceFolder: {
182+
index: 0,
183+
name: 'testworkspacefolder',
184+
uri: vscode.Uri.file('/path/to/testworkspacefolder'),
185+
},
186+
}
187+
assert.deepStrictEqual(result.newFiles[0].fileContent, expectedNewFile.fileContent)
188+
})
189+
})

0 commit comments

Comments
 (0)