Skip to content

Commit fd835e3

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

File tree

2 files changed

+229
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)