Skip to content

Commit ceebd93

Browse files
committed
Improve documentation of the GNATtest integration code
1 parent 6ebce88 commit ceebd93

File tree

1 file changed

+120
-25
lines changed

1 file changed

+120
-25
lines changed

integration/vscode/ada/src/gnattest.ts

Lines changed: 120 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ export let testRunProfile: vscode.TestRunProfile;
1414

1515
/**
1616
* Types definition for the Gnattest XML file structure. The types match the XML
17-
* generated by src/test-mapping.adb in the libadalang-tools repository.
17+
* generated by src/test-mapping.adb in the libadalang-tools repository. However
18+
* these types do not describe the entire XML strutcture. Only the elements used
19+
* are described.
1820
*/
1921
export type Root = {
2022
tests_mapping: TestMapping;
@@ -34,7 +36,6 @@ type Unit = {
3436
type TestUnit = {
3537
'@_target_file': string;
3638
tested: Tested[];
37-
dangling: Dangling;
3839
};
3940

4041
type Tested = {
@@ -58,10 +59,6 @@ type Test = {
5859
'@_name': string;
5960
};
6061

61-
type Dangling = {
62-
test: Test[];
63-
};
64-
6562
/**
6663
* The XML paths in GNATtest XML files that should always be parsed as arrays.
6764
* This is based the code in src/test-mapping.adb in the libadalang-tools
@@ -72,7 +69,6 @@ const alwaysArray = [
7269
'tests_mapping.unit.test_unit',
7370
'tests_mapping.unit.test_unit.tested',
7471
'tests_mapping.unit.test_unit.tested.test_case',
75-
'tests_mapping.unit.test_unit.dangling.test',
7672
'tests_mapping.additional_tests',
7773
];
7874

@@ -133,24 +129,39 @@ async function refreshTestItemTree() {
133129
await addTestsRootLevel();
134130
}
135131

132+
/**
133+
* @returns the full path to the GNATtest XML file.
134+
*/
136135
async function getGnatTestXmlPath(): Promise<string> {
137136
const objDir = await getObjectDir();
138137
const gnatTestXmlPath = path.join(objDir, 'gnattest', 'harness', 'gnattest.xml');
139138
return gnatTestXmlPath;
140139
}
141140

141+
/**
142+
*
143+
* @returns the full path to the GNATtest test driver GPR project.
144+
*/
142145
async function getGnatTestDriverProjectPath(): Promise<string> {
143146
const objDir = await getObjectDir();
144147
const testDriverPath = path.join(objDir, 'gnattest', 'harness', 'test_driver.gpr');
145148
return testDriverPath;
146149
}
147150

151+
/**
152+
*
153+
* @returns the full path to the GNATtest test driver executable.
154+
*/
148155
async function getGnatTestDriverExecPath(): Promise<string> {
149156
const objDir = await getObjectDir();
150157
const testDriverPath = path.join(objDir, 'gnattest', 'harness', 'test_runner' + exe);
151158
return testDriverPath;
152159
}
153160

161+
/**
162+
* Parse the GNATtest XML file and create the top-level TestItems in the test
163+
* controller for later lazy resolution.
164+
*/
154165
export async function addTestsRootLevel() {
155166
if (fs.existsSync(await getGnatTestXmlPath())) {
156167
const xmlDoc: Root = await parseGnatTestXml();
@@ -161,7 +172,10 @@ export async function addTestsRootLevel() {
161172
}
162173
}
163174

164-
async function parseGnatTestXml() {
175+
/**
176+
* @returns the object resulting from parsing the GNATtest XML file.
177+
*/
178+
async function parseGnatTestXml(): Promise<Root> {
165179
const gnatTestXmlUri = vscode.Uri.file(await getGnatTestXmlPath());
166180
const fileContent = await vscode.workspace.fs.readFile(gnatTestXmlUri);
167181
const fileContentAsBuffer = Buffer.from(fileContent);
@@ -182,9 +196,11 @@ async function parseGnatTestXml() {
182196
return xmlDoc;
183197
}
184198

185-
/*
186-
Creating nested test items to visuliaze in the view
187-
*/
199+
/**
200+
* Create a TestItem for a GNATtest Unit node.
201+
*
202+
* @param unit - a Unit node from the GNATtest XML
203+
*/
188204
async function addUnitItem(unit: Unit) {
189205
const srcFile = unit['@_source_file'];
190206
const srcUri = await findFileInWorkspace(srcFile);
@@ -205,15 +221,25 @@ async function addUnitItem(unit: Unit) {
205221
}
206222
}
207223

224+
/**
225+
* Resolve a TestItem by computing its children node based on the GNATtest XML.
226+
*
227+
* @param testItem - the TestItem node
228+
* @param unit - the corresponding Unit node from the GNATtest XML
229+
*/
208230
function resolveUnitItem(testItem: TestItem, unit: Unit) {
209231
for (const t of unit.test_unit.flatMap((u) => u.tested)) {
210232
addTestedItem(testItem, t);
211233
}
212234
}
213235

214-
/*
215-
Adding Test Cases to a Test Unit Item
216-
*/
236+
/**
237+
* Create a TestItem corresponding to a node from the GNATtest XML. The new
238+
* TestItem is added as a child of parentTestItem.
239+
*
240+
* @param parentTestItem - parent TestItem
241+
* @param tested - the "Tested" node from the GNATtest XML
242+
*/
217243
function addTestedItem(parentTestItem: vscode.TestItem, tested: Tested) {
218244
const testedSubprogramName = tested['@_name'];
219245
const pos = new vscode.Position(
@@ -248,12 +274,25 @@ function addTestedItem(parentTestItem: vscode.TestItem, tested: Tested) {
248274
}
249275
}
250276

277+
/**
278+
* Resolve a TestItem by computing its children node based on the GNATtest XML.
279+
*
280+
* @param testItem - the TestItem to resolve
281+
* @param tested - the corresponding "Tested" node in the GNATtest XML
282+
*/
251283
async function resolveTestedItem(testItem: TestItem, tested: Tested) {
252284
for (const e of tested.test_case) {
253285
await addTestCaseItem(testItem, e);
254286
}
255287
}
256288

289+
/**
290+
* Create a TestItem corresponding to a node from the GNATtest XML. The new
291+
* TestItem is added as a child of parentTestItem.
292+
*
293+
* @param parentItem - the parent TestItem
294+
* @param testCase - the "TestCase" node from the GNATtest XML
295+
*/
257296
async function addTestCaseItem(parentItem: vscode.TestItem, testCase: TestCase) {
258297
const test: Test = testCase.test;
259298
const testFileBasename = test['@_file'];
@@ -321,12 +360,18 @@ function createTestCaseItemId(testCase: TestCase, sourceFileName: string): strin
321360
return `${sourceFileName}:${testCase['@_line']}`;
322361
}
323362

324-
async function findFileInWorkspace(name: string): Promise<vscode.Uri> {
325-
const matchingFiles = await vscode.workspace.findFiles(`**/${name}`, undefined, 1);
363+
/**
364+
*
365+
* @param basename - a basename
366+
* @returns - the Uri of the first file found in the workspace with the given basename.
367+
* @throws an Error in case no matching file is found in the workspace
368+
*/
369+
async function findFileInWorkspace(basename: string): Promise<vscode.Uri> {
370+
const matchingFiles = await vscode.workspace.findFiles(`**/${basename}`, undefined, 1);
326371
if (matchingFiles.length > 0) {
327372
return matchingFiles[0];
328373
} else {
329-
throw `Source file ${name} not found in workspace`;
374+
throw Error(`Source file ${basename} not found in workspace`);
330375
}
331376
}
332377

@@ -405,21 +450,26 @@ async function resolveHandler(
405450
}
406451
}
407452

453+
/**
454+
* Configure the handlers needed on the test controller to support test execution.
455+
*
456+
* @param controller - the TestController to configure
457+
*/
408458
function configureTestExecution(controller: vscode.TestController) {
409459
testRunProfile = controller.createRunProfile(
410460
'GNATtest',
411461
vscode.TestRunProfileKind.Run,
412462
runHandler
413463
);
414-
415-
// Tests Configuration Handler to Generates Tests for a Project.
416-
// testRunProfile.configureHandler = () => {
417-
// const terminal = vscode.window.createTerminal('Test Terminal');
418-
// terminal.sendText('gnattest -P ' + projectFileFullPath);
419-
// terminal.sendText('exit');
420-
// };
421464
}
422465

466+
/**
467+
* This handler is called when the User trigger an action in the UI intended to
468+
* run tests. The request might be about a subset of tests, or all tests.
469+
*
470+
* @param request - the request based on the User selections
471+
* @param token - a cancellation token
472+
*/
423473
async function runHandler(request: vscode.TestRunRequest, token: vscode.CancellationToken) {
424474
if ((request.include?.length ?? 0) === 0 && (request.exclude?.length ?? 0) === 0) {
425475
/**
@@ -595,6 +645,13 @@ async function handleRunAll(request: vscode.TestRunRequest, token: CancellationT
595645
}
596646
}
597647

648+
/**
649+
* Invoke gprbuild on the test driver project and pipe the output into the given
650+
* TestRun object.
651+
*
652+
* @param run - the TestRun object hosting this execution
653+
* @throws an Error if the process ends with an error code
654+
*/
598655
async function buildTestDriver(run: vscode.TestRun) {
599656
/**
600657
* TODO replace this with a task invocation to capture problems
@@ -620,6 +677,16 @@ async function buildTestDriver(run: vscode.TestRun) {
620677
}
621678
}
622679

680+
/**
681+
* Decide the outcome of the given test based on the test driver output, and
682+
* report it to the TestRun object.
683+
*
684+
* @param test - the test whose outcome must be decided
685+
* @param driverOutput - the test driver standard output
686+
* @param run - the TestRun object to report the outcome on
687+
* @param duration - the duration of execution of the test to be reported along
688+
* with the outcome, if the information is available.
689+
*/
623690
function determineTestOutcome(
624691
test: vscode.TestItem,
625692
driverOutput: string,
@@ -690,6 +757,12 @@ function determineTestOutcome(
690757
}
691758
}
692759

760+
/**
761+
*
762+
* @param items - a {@link vscode.TestItemCollection}
763+
* @param token - a cancellation token to stop the traversal
764+
* @returns the array of leaf TestItems reachable from the given collection.
765+
*/
693766
function collectLeafsFromCollection(
694767
items: vscode.TestItemCollection,
695768
token?: CancellationToken
@@ -704,6 +777,12 @@ function collectLeafsFromCollection(
704777
return res;
705778
}
706779

780+
/**
781+
*
782+
* @param item - a {@link vscode.TestItem}
783+
* @param token - a cancellation token to stop the traversal
784+
* @returns the array of leaf TestItems reachable from the given TestItem
785+
*/
707786
function collectLeafItems(item: TestItem, token?: CancellationToken): vscode.TestItem[] {
708787
if (item.children.size > 0) {
709788
const res: vscode.TestItem[] = [];
@@ -719,11 +798,27 @@ function collectLeafItems(item: TestItem, token?: CancellationToken): vscode.Tes
719798
}
720799
}
721800

801+
/**
802+
*
803+
* @param text - a string possibly containing special RegExp characters.
804+
* @returns a string where all RegExp special characters have been escaped. This
805+
* can be useful when searching for an exact string which may contain special
806+
* characters.
807+
*/
722808
function escapeRegExp(text: string) {
723809
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
724810
}
725811

726-
function logAndRun(run: vscode.TestRun, cmd: string[]) {
812+
/**
813+
*
814+
* Run a command line as a child process and pipe the output into the TestRun
815+
* object managing the test execution.
816+
*
817+
* @param run - the TestRun object hosting the execution
818+
* @param cmd - a command line to run
819+
* @returns the child process object returned by {@link cp.spawnSync}
820+
*/
821+
function logAndRun(run: vscode.TestRun, cmd: string[]): cp.SpawnSyncReturns<Buffer> {
727822
run.appendOutput(`$ ${cmd.map((arg) => `"${arg}"`).join(' ')}\r\n`);
728823
return cp.spawnSync(cmd[0], cmd.slice(1));
729824
}

0 commit comments

Comments
 (0)