Skip to content

Commit b01610c

Browse files
authored
feat(amazonq): inline unit test generation (#1406)
1 parent 71fc801 commit b01610c

File tree

7 files changed

+1228
-8
lines changed

7 files changed

+1228
-8
lines changed

server/aws-lsp-codewhisperer/src/language-server/inline-completion/codeWhispererServer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ export const CodewhispererServerFactory =
408408
}
409409

410410
const supplementalContext = await supplementalContextPromise
411+
// TODO: logging
411412
if (codeWhispererService instanceof CodeWhispererServiceToken) {
412413
requestContext.supplementalContexts = supplementalContext?.supplementalContextItems
413414
? supplementalContext.supplementalContextItems.map(v => ({

server/aws-lsp-codewhisperer/src/shared/models/model.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Port of implementation in AWS Toolkit for VSCode
22
// https://github.com/aws/aws-toolkit-vscode/blob/9d8ddbd85f4533e539a58e76f7c46883d8e50a79/packages/core/src/codewhisperer/models/model.ts
33

4-
export type UtgStrategy = 'ByName' | 'ByContent'
4+
// TODO: consolidate these strategy ids
5+
export type UtgStrategy = 'ByName' | 'ByContent' | 'NEW_UTG'
56

67
export type CrossFileStrategy = 'OpenTabs_BM25'
78

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import * as assert from 'assert'
7+
import * as fs from 'fs'
8+
import * as path from 'path'
9+
import * as os from 'os'
10+
import { FocalFileResolver } from './focalFileResolution'
11+
12+
describe('focalFileResolver', function () {
13+
let sut: FocalFileResolver
14+
let tmpProjectRoot: string
15+
16+
beforeEach(() => {
17+
sut = new FocalFileResolver()
18+
tmpProjectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'focalFileResolutionTest-'))
19+
})
20+
21+
afterEach(() => {
22+
fs.rmSync(tmpProjectRoot, { recursive: true, force: true })
23+
})
24+
25+
describe('inferFocalFilename', function () {
26+
describe('java', function () {
27+
const testCases = ['FooTest.java', 'FooTests.java', 'TestFoo.java', 'TestsFoo.java']
28+
29+
for (let i = 0; i < testCases.length; i++) {
30+
const testCase = testCases[i]
31+
it(`should infer and return correct source focal file name case ${i}`, () => {
32+
const result = sut.inferFocalFilename(testCase, 'java')
33+
assert.strictEqual(result, 'Foo.java')
34+
})
35+
}
36+
})
37+
38+
describe('python', function () {
39+
const testCases = ['test_py_class.py', 'py_class_test.py']
40+
41+
for (let i = 0; i < testCases.length; i++) {
42+
const testCase = testCases[i]
43+
it(`should infer and return correct source focal file name case ${i}`, () => {
44+
const result = sut.inferFocalFilename(testCase, 'python')
45+
assert.strictEqual(result, 'py_class.py')
46+
})
47+
}
48+
})
49+
50+
describe('js', function () {
51+
const testCases = ['foo.test.js', 'foo.spec.js']
52+
53+
for (let i = 0; i < testCases.length; i++) {
54+
const testCase = testCases[i]
55+
it(`should infer and return correct source focal file name case ${i}`, () => {
56+
const result = sut.inferFocalFilename(testCase, 'javascript')
57+
assert.strictEqual(result, 'foo.js')
58+
})
59+
}
60+
})
61+
62+
describe('ts', function () {
63+
const testCases = ['foo.test.ts', 'foo.spec.ts']
64+
65+
for (let i = 0; i < testCases.length; i++) {
66+
const testCase = testCases[i]
67+
it(`should infer and return correct source focal file name case ${i}`, () => {
68+
const result = sut.inferFocalFilename(testCase, 'typescript')
69+
assert.strictEqual(result, 'foo.ts')
70+
})
71+
}
72+
})
73+
})
74+
75+
describe('extractImportedPaths', function () {
76+
describe('java', function () {
77+
it('case1', function () {
78+
const p = path.join(tmpProjectRoot, 'FooTest.java')
79+
fs.writeFileSync(
80+
p,
81+
`
82+
package com.amazon.q.service;
83+
84+
import com.amazon.q.foo.FooClass;
85+
import com.amazon.q.bar.BarClass;
86+
import com.amazon.q.baz1.baz2.BazClass;
87+
88+
public class TestClass {}
89+
`
90+
)
91+
92+
const actual = sut.extractImportedPaths(p, 'java', tmpProjectRoot)
93+
assert.strictEqual(actual.size, 1)
94+
assert.ok(actual.has(path.join('com', 'amazon', 'q', 'service')))
95+
})
96+
})
97+
98+
describe('python', function () {
99+
it('case1', function () {
100+
const p = path.join(tmpProjectRoot, 'test_py_class.py')
101+
fs.writeFileSync(
102+
p,
103+
`
104+
import pytest
105+
import sys
106+
import os
107+
from py_class import PyClass
108+
from util import (foo,bar,baz)
109+
110+
def test_py_class():
111+
assert True
112+
`
113+
)
114+
115+
const actual = sut.extractImportedPaths(p, 'python', tmpProjectRoot)
116+
assert.strictEqual(actual.size, 5)
117+
assert.ok(actual.has('py_class'))
118+
assert.ok(actual.has('pytest'))
119+
assert.ok(actual.has('sys'))
120+
assert.ok(actual.has('os'))
121+
assert.ok(actual.has('util'))
122+
})
123+
})
124+
125+
describe('ts', function () {
126+
it('case1', function () {
127+
const p = path.join(tmpProjectRoot, 'src', 'test', 'foo.test.ts')
128+
fs.mkdirSync(path.join(tmpProjectRoot, 'src', 'test'), { recursive: true })
129+
fs.writeFileSync(
130+
p,
131+
`
132+
import { foo } from '../foo';
133+
import baz from '../baz';
134+
import * as util from '../utils/util';
135+
136+
test('foo', () => {
137+
expect(foo()).toBe('foo');
138+
});
139+
`
140+
)
141+
142+
const actual = sut.extractImportedPaths(p, 'typescript', tmpProjectRoot)
143+
assert.strictEqual(actual.size, 3)
144+
assert.ok(actual.has(path.join(tmpProjectRoot, 'src', 'foo')))
145+
assert.ok(actual.has(path.join(tmpProjectRoot, 'src', 'baz')))
146+
assert.ok(actual.has(path.join(tmpProjectRoot, 'src', 'utils', 'util')))
147+
})
148+
})
149+
150+
describe('js', function () {})
151+
})
152+
153+
describe('extractImportedSymbols', function () {
154+
it('case1', function () {
155+
const p = path.join(tmpProjectRoot, 'foo.js')
156+
fs.writeFileSync(
157+
p,
158+
`
159+
import { foo, bar } from '../src/sample';
160+
import baz from '../src/sample';`
161+
)
162+
163+
const actual = sut.extractImportedSymbols(p)
164+
assert.strictEqual(actual.size, 3)
165+
assert.ok(actual.has('foo'))
166+
assert.ok(actual.has('bar'))
167+
assert.ok(actual.has('baz'))
168+
})
169+
})
170+
171+
describe('extractExportedSymbolsFromFile', function () {
172+
it('', function () {
173+
fs.mkdirSync(path.join(tmpProjectRoot, 'src', 'test'), { recursive: true })
174+
const p = path.join(tmpProjectRoot, 'src', 'test', 'sample.js')
175+
fs.writeFileSync(
176+
p,
177+
`
178+
export function foo() {}
179+
export const bar = 1;
180+
export default baz;
181+
export { alpha, beta };`
182+
)
183+
184+
const actual = sut.extractExportedSymbolsFromFile(p)
185+
assert.strictEqual(actual.size, 5)
186+
assert.ok(actual.has('foo'))
187+
assert.ok(actual.has('bar'))
188+
assert.ok(actual.has('baz'))
189+
assert.ok(actual.has('alpha'))
190+
assert.ok(actual.has('beta'))
191+
})
192+
})
193+
194+
describe('resolveImportToAbsPath', function () {
195+
it('', function () {
196+
fs.mkdirSync(path.join(tmpProjectRoot, 'src', 'test'), { recursive: true })
197+
const p = path.join(tmpProjectRoot, 'src', 'test', 'foo.test.ts')
198+
const actual = sut.resolveImportToAbsPath(p, '../helper', tmpProjectRoot, 'typescript')
199+
assert.strictEqual(actual, path.join(tmpProjectRoot, 'src', 'helper'))
200+
})
201+
202+
it('alias', function () {
203+
fs.mkdirSync(path.join(tmpProjectRoot, 'src', 'test'), { recursive: true })
204+
const p = path.join(tmpProjectRoot, 'src', 'test', 'foo.test.ts')
205+
const actual = sut.resolveImportToAbsPath('foo.test.ts', '@src/utils', tmpProjectRoot, 'typescript')
206+
assert.strictEqual(actual, path.join(tmpProjectRoot, 'src', 'utils'))
207+
})
208+
})
209+
210+
describe('resolvePackageToPath', function () {
211+
it('dot', function () {
212+
const actual = sut.resolvePackageToPath('com.amazon.q.service', '.')
213+
assert.strictEqual(actual, path.join('com', 'amazon', 'q', 'service'))
214+
})
215+
216+
it('slash', function () {
217+
const actual = sut.resolvePackageToPath('com/amazon/q/service', '/')
218+
assert.strictEqual(actual, path.join('com', 'amazon', 'q', 'service'))
219+
})
220+
})
221+
222+
describe('walk should exclude hidden files and only include files with correct extensions', function () {
223+
/**
224+
* - root/
225+
* - src/
226+
* - foo.ts
227+
* - bar.ts
228+
* - ui/
229+
* - frontend.vue
230+
* - ui.html
231+
* - theme.css
232+
* - test/
233+
* - foo.test.ts
234+
* - bar.test.ts
235+
* - .github/
236+
* - workflows/
237+
* - foo.yml
238+
* - pull_request_template.md
239+
* - .idea
240+
* - aws.xml
241+
* - package.json
242+
* - package-lock.json
243+
* - webpack.config
244+
*/
245+
it('case 1', async function () {
246+
fs.mkdirSync(path.join(tmpProjectRoot, 'src'), { recursive: true })
247+
fs.writeFileSync(path.join(tmpProjectRoot, 'src', 'foo.ts'), 'class Foo')
248+
fs.writeFileSync(path.join(tmpProjectRoot, 'src', 'bar.ts'), 'class Bar')
249+
250+
fs.mkdirSync(path.join(tmpProjectRoot, 'src', 'ui'), { recursive: true })
251+
fs.writeFileSync(path.join(tmpProjectRoot, 'src', 'ui', 'frontend.vue'), '')
252+
fs.writeFileSync(path.join(tmpProjectRoot, 'src', 'ui', 'ui.html'), '')
253+
fs.writeFileSync(path.join(tmpProjectRoot, 'src', 'ui', 'theme.css'), '')
254+
255+
fs.mkdirSync(path.join(tmpProjectRoot, 'src', 'test'), { recursive: true })
256+
fs.writeFileSync(path.join(tmpProjectRoot, 'src', 'test', 'foo.test.ts'), 'class FooTest')
257+
fs.writeFileSync(path.join(tmpProjectRoot, 'src', 'test', 'bar.test.ts'), 'class BarTest')
258+
259+
fs.mkdirSync(path.join(tmpProjectRoot, '.github'), { recursive: true })
260+
fs.mkdirSync(path.join(tmpProjectRoot, '.github', 'workflows'), { recursive: true })
261+
fs.writeFileSync(path.join(tmpProjectRoot, '.github', 'workflows', 'foo.yml'), '')
262+
fs.writeFileSync(path.join(tmpProjectRoot, '.github', 'pull_request_template.md'), '')
263+
264+
fs.mkdirSync(path.join(tmpProjectRoot, '.idea'), { recursive: true })
265+
fs.writeFileSync(path.join(tmpProjectRoot, '.idea', 'aws.xml'), '')
266+
267+
fs.writeFileSync(path.join(tmpProjectRoot, 'package.json'), '')
268+
fs.writeFileSync(path.join(tmpProjectRoot, 'package-lock.json'), '')
269+
fs.writeFileSync(path.join(tmpProjectRoot, 'webpack.config'), '')
270+
271+
const files = await sut.walk(tmpProjectRoot, 'typescript')
272+
const basenames = files.map(it => path.basename(it))
273+
274+
assert.ok(files.length === 4)
275+
assert.ok(basenames.includes('foo.ts'))
276+
assert.ok(basenames.includes('bar.ts'))
277+
assert.ok(basenames.includes('foo.test.ts'))
278+
assert.ok(basenames.includes('bar.test.ts'))
279+
})
280+
})
281+
})

0 commit comments

Comments
 (0)