Skip to content

Commit d9d7d80

Browse files
committed
feat(/dev): skip registering run command log file
1 parent b7ce8b4 commit d9d7d80

File tree

2 files changed

+222
-0
lines changed

2 files changed

+222
-0
lines changed

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

Lines changed: 26 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,31 @@ 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((file: any) => file.name === RunCommandLogFileName)
109+
if (logFileInfo) {
110+
const filePath = `${this.config.workspaceRoots[0]}/${RunCommandLogFileName}`
111+
const fileUri = vscode.Uri.file(filePath)
112+
113+
const fileExists = await fs
114+
.stat(fileUri)
115+
.then(() => true)
116+
.catch(() => false)
117+
118+
if (fileExists) {
119+
const fileContent = await fs.readFile(fileUri)
120+
const decodedContent = new TextDecoder().decode(fileContent)
121+
logFileInfo.content = decodedContent + logFileInfo.content
122+
} else {
123+
// Create a new log file if it does not exist by writing the logFileInfo content
124+
await fs.writeFile(fileUri, new TextEncoder().encode(logFileInfo.content), {
125+
create: true,
126+
overwrite: true,
127+
})
128+
}
129+
newFileContents.splice(newFileContents.indexOf(logFileInfo), 1)
130+
}
131+
106132
const newFileInfo = registerNewFiles(
107133
fs,
108134
newFileContents,

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

Lines changed: 196 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/files')
1314

1415
describe('sessionStateDoc', () => {
1516
const context = createTestContext()
@@ -26,4 +27,199 @@ describe('sessionStateDoc', () => {
2627
})
2728
})
2829
})
30+
31+
describe('CodeGenBase generateCode log file handling', () => {
32+
const RunCommandLogFileName = '.amazonq/dev/run_command.log'
33+
34+
// Minimal stub for registerNewFiles to return newFileContents without the log file entry
35+
const registerNewFilesStub = sinon.stub()
36+
registerNewFilesStub.callsFake((fs: any, newFileContents: any[]) => {
37+
// In our test scenario, assume registerNewFiles returns the files as is (log file already spliced out)
38+
return newFileContents
39+
})
40+
41+
// Fake CodeGenBase subclass for testing
42+
class TestCodeGen extends (require('../../../amazonq/session/sessionState') as any).CodeGenBase {
43+
public generatedFiles: any[] = []
44+
constructor(config: any, tabID: string) {
45+
super(config, tabID)
46+
}
47+
protected handleProgress(messenger: any, action: any, detail?: string): void {
48+
// no-op
49+
}
50+
protected getScheme(): string {
51+
return 'file'
52+
}
53+
protected getTimeoutErrorCode(): string {
54+
return 'test_timeout'
55+
}
56+
protected handleGenerationComplete(messenger: any, newFileInfo: any[], action: any): void {
57+
this.generatedFiles = newFileInfo
58+
}
59+
protected handleError(messenger: any, codegenResult: any): Error {
60+
throw new Error('handleError called')
61+
}
62+
}
63+
64+
// Common objects for testing
65+
let testConfig: any
66+
let fakeProxyClient: any
67+
let fsMock: any
68+
let telemetryMock: any
69+
let messengerMock: any
70+
let testAction: any
71+
72+
beforeEach(() => {
73+
fakeProxyClient = {
74+
getCodeGeneration: sinon.stub().resolves({
75+
codeGenerationStatus: { status: 'Complete' },
76+
codeGenerationRemainingIterationCount: 0,
77+
codeGenerationTotalIterationCount: 1,
78+
}),
79+
exportResultArchive: sinon.stub(),
80+
}
81+
82+
testConfig = {
83+
conversationId: 'conv1',
84+
uploadId: 'upload1',
85+
workspaceRoots: ['/workspace'],
86+
proxyClient: fakeProxyClient,
87+
}
88+
89+
fsMock = {
90+
stat: sinon.stub(),
91+
readFile: sinon.stub(),
92+
writeFile: sinon.stub(),
93+
}
94+
95+
telemetryMock = {
96+
setCodeGenerationResult: sinon.spy(),
97+
setNumberOfFilesGenerated: sinon.spy(),
98+
setAmazonqNumberOfReferences: sinon.spy(),
99+
setGenerateCodeIteration: sinon.spy(),
100+
setGenerateCodeLastInvocationTime: sinon.spy(),
101+
recordUserCodeGenerationTelemetry: sinon.spy(),
102+
}
103+
104+
messengerMock = {
105+
sendAnswer: sinon.spy(),
106+
}
107+
108+
testAction = {
109+
telemetry: telemetryMock,
110+
fs: fsMock,
111+
messenger: messengerMock,
112+
uploadHistory: {},
113+
tokenSource: { token: { isCancellationRequested: false, onCancellationRequested: () => {} } },
114+
}
115+
})
116+
117+
afterEach(() => {
118+
sinon.restore()
119+
})
120+
121+
it('should append existing log file content to new log content and remove the log file entry when file exists', async () => {
122+
// Prepare exportResultArchive to return a log file and another file
123+
const logFileInfo = {
124+
name: RunCommandLogFileName,
125+
content: 'newLog',
126+
}
127+
const otherFile = { name: 'other.ts', content: 'other' }
128+
129+
fakeProxyClient.exportResultArchive.resolves({
130+
newFileContents: [logFileInfo, otherFile],
131+
deletedFiles: [],
132+
references: [],
133+
})
134+
135+
// Stub fs.stat to simulate that file exists
136+
fsMock.stat.resolves({})
137+
// Stub fs.readFile to simulate existing file content
138+
fsMock.readFile.resolves(Buffer.from('existing'))
139+
140+
// Stub registerNewFiles function used inside generateCode
141+
// const registerNewFilesOriginal = filesModule.registerNewFiles
142+
// filesModule.registerNewFiles = registerNewFilesStub
143+
sinon.stub(filesModule, 'registerNewFiles').callsFake(registerNewFilesStub)
144+
145+
const testCodeGen = new TestCodeGen(testConfig, 'tab1')
146+
// Force a single polling iteration by simulating COMPLETE
147+
148+
// const result =
149+
await testCodeGen.generateCode({
150+
messenger: messengerMock,
151+
fs: fsMock,
152+
codeGenerationId: 'codegen1',
153+
telemetry: telemetryMock,
154+
workspaceFolders: {},
155+
action: testAction,
156+
})
157+
158+
// Check that fs.stat and fs.readFile were called with the expected fileUri
159+
const expectedFilePath = `${testConfig.workspaceRoots[0]}/${RunCommandLogFileName}`
160+
const fileUri = vscode.Uri.file(expectedFilePath)
161+
sinon.assert.calledWith(fsMock.stat, fileUri)
162+
sinon.assert.calledWith(fsMock.readFile, fileUri)
163+
164+
// Verify that the log file content was updated correctly: "existing" + "newLog"
165+
// And that the log file info was spliced out so that registerNewFiles gets only the other file.
166+
assert.strictEqual(logFileInfo.content, 'existingnewLog')
167+
assert.deepStrictEqual(testCodeGen.generatedFiles, [otherFile])
168+
169+
// Restore registerNewFiles to its original implementation
170+
sinon.restore()
171+
})
172+
173+
it('should create a new log file when log file does not exist', async () => {
174+
// Prepare exportResultArchive to return a log file and another file
175+
const logFileInfo = {
176+
name: RunCommandLogFileName,
177+
content: 'newLog',
178+
}
179+
const otherFile = { name: 'other.ts', content: 'other' }
180+
181+
fakeProxyClient.exportResultArchive.resolves({
182+
newFileContents: [logFileInfo, otherFile],
183+
deletedFiles: [],
184+
references: [],
185+
})
186+
187+
// Stub fs.stat to simulate that file does not exist
188+
fsMock.stat.rejects(new Error('Not found'))
189+
// fs.writeFile should be called in this case
190+
fsMock.writeFile.resolves()
191+
192+
// Stub registerNewFiles function
193+
// sinon.replace(filesModule, 'registerNewFiles', registerNewFilesStub)
194+
sinon.stub(filesModule, 'registerNewFiles').callsFake(registerNewFilesStub)
195+
196+
const testCodeGen = new TestCodeGen(testConfig, 'tab1')
197+
198+
// const result =
199+
await testCodeGen.generateCode({
200+
messenger: messengerMock,
201+
fs: fsMock,
202+
codeGenerationId: 'codegen2',
203+
telemetry: telemetryMock,
204+
workspaceFolders: {},
205+
action: testAction,
206+
})
207+
208+
// Verify that fs.stat was called and then fs.writeFile was invoked
209+
const expectedFilePath = `${testConfig.workspaceRoots[0]}/${RunCommandLogFileName}`
210+
const fileUri = vscode.Uri.file(expectedFilePath)
211+
sinon.assert.calledWith(fsMock.stat, fileUri)
212+
sinon.assert.calledWith(fsMock.writeFile, fileUri, new TextEncoder().encode('newLog'), {
213+
create: true,
214+
overwrite: true,
215+
})
216+
// fs.readFile should not be called
217+
sinon.assert.notCalled(fsMock.readFile)
218+
219+
// Verify the log file has been removed from new file contents
220+
assert.deepStrictEqual(testCodeGen.generatedFiles, [otherFile])
221+
222+
sinon.restore()
223+
})
224+
})
29225
})

0 commit comments

Comments
 (0)