Skip to content

Commit c554841

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

File tree

3 files changed

+195
-0
lines changed

3 files changed

+195
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "The logs emitted by the Agent during user command execution will be accepted and written to .amazonq/dev/run_command.log file in the user's local repository."
4+
}

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

Lines changed: 16 additions & 0 deletions
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+
const RunCommandLogFileName = '.amazonq/dev/run_command.log'
3233

3334
export interface BaseMessenger {
3435
sendAnswer(params: any): void
@@ -103,6 +104,21 @@ 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+
(zipFilePath: string, _fileContent: string) => zipFilePath === RunCommandLogFileName
110+
)
111+
if (logFileInfo) {
112+
const filePath = `${this.config.workspaceRoots[0]}/${RunCommandLogFileName}`
113+
const fileUri = vscode.Uri.file(filePath)
114+
115+
await fs.writeFile(fileUri, new TextEncoder().encode(logFileInfo.fileContent), {
116+
create: true,
117+
overwrite: true,
118+
})
119+
newFileContents.splice(newFileContents.indexOf(logFileInfo), 1)
120+
}
121+
106122
const newFileInfo = registerNewFiles(
107123
fs,
108124
newFileContents,

packages/core/src/test/amazonqDoc/session/sessionState.test.ts

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { DocPrepareCodeGenState } from '../../../amazonqDoc'
1010
import { createMockSessionStateAction } from '../../amazonq/utils'
1111

1212
import { createTestContext, setupTestHooks } from '../../amazonq/session/testSetup'
13+
const filesModule = require('../../../amazonq/util/files')
1314

1415
describe('sessionStateDoc', () => {
1516
const context = createTestContext()
@@ -27,3 +28,177 @@ describe('sessionStateDoc', () => {
2728
})
2829
})
2930
})
31+
32+
describe('CodeGenBase generateCode log file handling', () => {
33+
const RunCommandLogFileName = '.amazonq/dev/run_command.log'
34+
35+
const registerNewFilesStub = sinon.stub()
36+
registerNewFilesStub.callsFake((fs: any, newFileContents: any[]) => {
37+
return newFileContents
38+
})
39+
const getDeletedFileInfosStub = sinon.stub()
40+
getDeletedFileInfosStub.callsFake((fs: any, deletedFiles: any[]) => {
41+
return []
42+
})
43+
44+
class TestCodeGen extends (require('../../../amazonq/session/sessionState') as any).CodeGenBase {
45+
public generatedFiles: any[] = []
46+
constructor(config: any, tabID: string) {
47+
super(config, tabID)
48+
}
49+
protected handleProgress(messenger: any, action: any, detail?: string): void {
50+
// no-op
51+
}
52+
protected getScheme(): string {
53+
return 'file'
54+
}
55+
protected getTimeoutErrorCode(): string {
56+
return 'test_timeout'
57+
}
58+
protected handleGenerationComplete(messenger: any, newFileInfo: any[], action: any): void {
59+
this.generatedFiles = newFileInfo
60+
}
61+
protected handleError(messenger: any, codegenResult: any): Error {
62+
throw new Error('handleError called')
63+
}
64+
}
65+
66+
let testConfig: any
67+
let fakeProxyClient: any
68+
let fsMock: any
69+
let telemetryMock: any
70+
let messengerMock: any
71+
let testAction: any
72+
73+
beforeEach(() => {
74+
fakeProxyClient = {
75+
getCodeGeneration: sinon.stub().resolves({
76+
codeGenerationStatus: { status: 'Complete' },
77+
codeGenerationRemainingIterationCount: 0,
78+
codeGenerationTotalIterationCount: 1,
79+
}),
80+
exportResultArchive: sinon.stub(),
81+
}
82+
83+
testConfig = {
84+
conversationId: 'conv1',
85+
uploadId: 'upload1',
86+
workspaceRoots: ['/workspace'],
87+
proxyClient: fakeProxyClient,
88+
}
89+
90+
fsMock = {
91+
stat: sinon.stub(),
92+
readFile: sinon.stub(),
93+
writeFile: sinon.stub(),
94+
}
95+
96+
telemetryMock = {
97+
setCodeGenerationResult: sinon.spy(),
98+
setNumberOfFilesGenerated: sinon.spy(),
99+
setAmazonqNumberOfReferences: sinon.spy(),
100+
setGenerateCodeIteration: sinon.spy(),
101+
setGenerateCodeLastInvocationTime: sinon.spy(),
102+
recordUserCodeGenerationTelemetry: sinon.spy(),
103+
}
104+
105+
messengerMock = {
106+
sendAnswer: sinon.spy(),
107+
}
108+
109+
testAction = {
110+
telemetry: telemetryMock,
111+
fs: fsMock,
112+
messenger: messengerMock,
113+
uploadHistory: {},
114+
tokenSource: { token: { isCancellationRequested: false, onCancellationRequested: () => {} } },
115+
}
116+
})
117+
118+
afterEach(() => {
119+
sinon.restore()
120+
})
121+
122+
it('should append existing log file content to new log content and remove the log file entry when file exists', async () => {
123+
const logFileInfo = {
124+
zipFilePath: RunCommandLogFileName,
125+
fileContent: 'newLog',
126+
}
127+
const otherFile = { zipFilePath: 'other.ts', fileContent: 'other' }
128+
129+
fakeProxyClient.exportResultArchive.resolves({
130+
newFileContents: [logFileInfo, otherFile],
131+
deletedFiles: [],
132+
references: [],
133+
})
134+
135+
fsMock.stat.resolves({})
136+
137+
sinon.stub(filesModule, 'registerNewFiles').callsFake(registerNewFilesStub)
138+
sinon.stub(filesModule, 'getDeletedFileInfos').callsFake(getDeletedFileInfosStub)
139+
140+
const testCodeGen = new TestCodeGen(testConfig, 'tab1')
141+
142+
await testCodeGen.generateCode({
143+
messenger: messengerMock,
144+
fs: fsMock,
145+
codeGenerationId: 'codegen1',
146+
telemetry: telemetryMock,
147+
workspaceFolders: {},
148+
action: testAction,
149+
})
150+
151+
const expectedFilePath = `${testConfig.workspaceRoots[0]}/${RunCommandLogFileName}`
152+
const fileUri = vscode.Uri.file(expectedFilePath)
153+
sinon.assert.calledWith(fsMock.stat, fileUri)
154+
155+
assert.strictEqual(logFileInfo.fileContent, 'newLog')
156+
assert.deepStrictEqual(testCodeGen.generatedFiles, [otherFile])
157+
158+
sinon.restore()
159+
})
160+
161+
it('should create a new log file when log file does not exist', async () => {
162+
const logFileInfo = {
163+
zipFilePath: RunCommandLogFileName,
164+
content: 'newLog',
165+
}
166+
const otherFile = { zipFilePath: 'other.ts', fileContent: 'other' }
167+
168+
fakeProxyClient.exportResultArchive.resolves({
169+
newFileContents: [logFileInfo, otherFile],
170+
deletedFiles: [],
171+
references: [],
172+
})
173+
174+
fsMock.stat.rejects(new Error('Not found'))
175+
fsMock.writeFile.resolves()
176+
177+
sinon.stub(filesModule, 'registerNewFiles').callsFake(registerNewFilesStub)
178+
sinon.stub(filesModule, 'getDeletedFileInfos').callsFake(getDeletedFileInfosStub)
179+
180+
const testCodeGen = new TestCodeGen(testConfig, 'tab1')
181+
182+
await testCodeGen.generateCode({
183+
messenger: messengerMock,
184+
fs: fsMock,
185+
codeGenerationId: 'codegen2',
186+
telemetry: telemetryMock,
187+
workspaceFolders: {},
188+
action: testAction,
189+
})
190+
191+
const expectedFilePath = `${testConfig.workspaceRoots[0]}/${RunCommandLogFileName}`
192+
const fileUri = vscode.Uri.file(expectedFilePath)
193+
sinon.assert.calledWith(fsMock.stat, fileUri)
194+
sinon.assert.calledWith(fsMock.writeFile, fileUri, new TextEncoder().encode('newLog'), {
195+
create: true,
196+
overwrite: true,
197+
})
198+
sinon.assert.notCalled(fsMock.readFile)
199+
200+
assert.deepStrictEqual(testCodeGen.generatedFiles, [otherFile])
201+
202+
sinon.restore()
203+
})
204+
})

0 commit comments

Comments
 (0)