Skip to content

Commit 235ca43

Browse files
committed
Support cancellation of test runs
1 parent 259976f commit 235ca43

File tree

2 files changed

+90
-77
lines changed

2 files changed

+90
-77
lines changed

integration/vscode/ada/src/gnattest.ts

Lines changed: 86 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as fs from 'fs';
55
import * as path from 'path';
66
import * as vscode from 'vscode';
77
import { TestItem } from 'vscode';
8-
import { integer } from 'vscode-languageclient';
8+
import { CancellationToken } from 'vscode-languageclient';
99
import { adaExtState } from './extension';
1010
import { exe, getObjectDir } from './helpers';
1111

@@ -197,8 +197,8 @@ export function parseResults(
197197
for (const e of tests) {
198198
// Check if the result line is for the test 'e'
199199
const test_src = getParentTestSourceName(e);
200-
const p: integer | undefined = e.parent?.range?.start.line;
201-
const test_line: integer = p ? p + 1 : 0;
200+
const p: number | undefined = e.parent?.range?.start.line;
201+
const test_line: number = p ? p + 1 : 0;
202202
const check_line = matchs[i].match(test_src.label + ':' + test_line.toString());
203203
// update the state of the test
204204
if (check_line != null && run != undefined) {
@@ -438,7 +438,11 @@ export function pathIsReadable(p: string): boolean {
438438
* @param item - the TestItem whose children must be computed, or `undefined` if
439439
* we should compute the root items of the tree.
440440
*/
441-
async function resolveHandler(item: TestItem | undefined, recursive = false) {
441+
async function resolveHandler(
442+
item: TestItem | undefined,
443+
recursive = false,
444+
token?: CancellationToken
445+
) {
442446
if (!item) {
443447
if (!watcher) {
444448
/**
@@ -474,7 +478,12 @@ async function resolveHandler(item: TestItem | undefined, recursive = false) {
474478

475479
if (recursive) {
476480
const promises: Promise<void>[] = [];
477-
item.children.forEach((i) => promises.push(resolveHandler(i, true)));
481+
item.children.forEach((i) => {
482+
if (token?.isCancellationRequested) {
483+
throw new vscode.CancellationError();
484+
}
485+
promises.push(resolveHandler(i, true, token));
486+
});
478487
await Promise.all(promises);
479488
}
480489
}
@@ -496,17 +505,17 @@ function configureTestExecution(controller: vscode.TestController) {
496505
}
497506

498507
async function runHandler(request: vscode.TestRunRequest, token: vscode.CancellationToken) {
499-
if (request.include == undefined && request.exclude == undefined) {
508+
if ((request.include?.length ?? 0) === 0 && (request.exclude?.length ?? 0) === 0) {
500509
/**
501510
* Run all tests. This ignores request.exclude which is why we only use
502511
* it in this branch.
503512
*/
504-
await handleRunAll(request);
513+
await handleRunAll(request, token);
505514
} else {
506515
/**
507516
* Run a specific set of tests
508517
*/
509-
await handleRunRequestedTests(request);
518+
await handleRunRequestedTests(request, token);
510519
}
511520
}
512521

@@ -515,36 +524,46 @@ async function runHandler(request: vscode.TestRunRequest, token: vscode.Cancella
515524
* controller.items) and request.exclude. It then runs the test driver for each
516525
* test, using the --routines argument at each run to select a specific test.
517526
*/
518-
async function handleRunRequestedTests(request: vscode.TestRunRequest) {
519-
const requestedRootTests = [];
527+
async function handleRunRequestedTests(request: vscode.TestRunRequest, token: CancellationToken) {
528+
const run = controller.createTestRun(request, undefined, false);
529+
try {
530+
const requestedRootTests = [];
531+
532+
if (request.include) {
533+
requestedRootTests.push(...request.include);
534+
} else {
535+
/**
536+
* Consider all tests as included
537+
*/
538+
controller.items.forEach((i) => requestedRootTests.push(i));
539+
}
520540

521-
if (request.include) {
522-
requestedRootTests.push(...request.include);
523-
} else {
524541
/**
525-
* Consider all tests as included
542+
* First resolve included tests as the API says that it is the
543+
* responsibility of the run handler.
526544
*/
527-
controller.items.forEach((i) => requestedRootTests.push(i));
528-
}
545+
await Promise.all(requestedRootTests.map((i) => resolveHandler(i, true, token)));
529546

530-
/**
531-
* First resolve included tests as the API says that it is the
532-
* responsibility of the run handler.
533-
*/
534-
await Promise.all(requestedRootTests.map((i) => resolveHandler(i, true)));
535-
536-
/**
537-
* Collect and filter tests to run.
538-
*/
539-
const requestedLeafTests = requestedRootTests.flatMap(collectLeafItems);
540-
const excludedLeafTests = request.exclude ? request.exclude.flatMap(collectLeafItems) : [];
541-
const testsToRun = requestedLeafTests.filter((t) => !excludedLeafTests?.includes(t));
542-
543-
const run = controller.createTestRun(request, undefined, false);
547+
/**
548+
* Collect and filter tests to run.
549+
*/
550+
const requestedLeafTests = requestedRootTests.flatMap((i) => collectLeafItems(i, token));
551+
const excludedLeafTests = request.exclude
552+
? request.exclude.flatMap((i) => collectLeafItems(i, token))
553+
: [];
554+
const testsToRun = requestedLeafTests.filter((t) => {
555+
if (token?.isCancellationRequested) {
556+
throw new vscode.CancellationError();
557+
}
558+
return !excludedLeafTests?.includes(t);
559+
});
544560

545-
try {
546561
await buildTestDriver(run);
547562

563+
if (token?.isCancellationRequested) {
564+
throw new vscode.CancellationError();
565+
}
566+
548567
/**
549568
* Mark tests as queued for execution
550569
*/
@@ -555,6 +574,9 @@ async function handleRunRequestedTests(request: vscode.TestRunRequest) {
555574
*/
556575
const execPath = await getGnatTestDriverExecPath();
557576
for (const test of testsToRun) {
577+
if (token?.isCancellationRequested) {
578+
throw new vscode.CancellationError();
579+
}
558580
const start = Date.now();
559581
run.started(test);
560582
const cmd = [execPath, '--passed-tests=show', `--routines=${test.id}`];
@@ -592,30 +614,43 @@ function prepareAndAppendOutput(run: vscode.TestRun, out: string) {
592614
* in {@link handleRunRequestedTests} fails because of GNATtest shortcomings, we
593615
* still have this approach of running all tests as a backup.
594616
*/
595-
async function handleRunAll(request: vscode.TestRunRequest) {
617+
async function handleRunAll(request: vscode.TestRunRequest, token: CancellationToken) {
596618
const run = controller.createTestRun(request, undefined, false);
597619
try {
598620
/**
599621
* First we need to build the test driver project.
600622
*/
601623
await buildTestDriver(run);
602624

625+
if (token?.isCancellationRequested) {
626+
throw new vscode.CancellationError();
627+
}
628+
603629
/**
604630
* Resolve all the test tree before collecting tests
605631
*/
606632
const promises: Promise<void>[] = [];
607-
controller.items.forEach((i) => promises.push(resolveHandler(i, true)));
633+
controller.items.forEach((i) => promises.push(resolveHandler(i, true, token)));
608634
await Promise.all(promises);
609635

636+
if (token?.isCancellationRequested) {
637+
throw new vscode.CancellationError();
638+
}
639+
610640
/**
611641
* Now let's collect all tests, i.e. all leafs of the TestItem tree.
612642
*/
613-
const allTests: TestItem[] = collectLeafsFromCollection(controller.items);
643+
const allTests: TestItem[] = collectLeafsFromCollection(controller.items, token);
614644

615645
/**
616646
* Mark all tests as started.
617647
*/
618-
allTests.forEach((t) => run.started(t));
648+
allTests.forEach((t) => {
649+
if (token?.isCancellationRequested) {
650+
throw new vscode.CancellationError();
651+
}
652+
run.started(t);
653+
});
619654

620655
/**
621656
* Invoke the test driver
@@ -628,6 +663,9 @@ async function handleRunAll(request: vscode.TestRunRequest) {
628663
prepareAndAppendOutput(run, driver.stderr.toLocaleString());
629664

630665
for (const test of allTests) {
666+
if (token?.isCancellationRequested) {
667+
throw new vscode.CancellationError();
668+
}
631669
determineTestOutcome(test, driverOutput, run);
632670
}
633671

@@ -736,19 +774,28 @@ function determineTestOutcome(
736774
}
737775
}
738776

739-
function collectLeafsFromCollection(items: vscode.TestItemCollection): vscode.TestItem[] {
777+
function collectLeafsFromCollection(
778+
items: vscode.TestItemCollection,
779+
token?: CancellationToken
780+
): vscode.TestItem[] {
740781
const res: vscode.TestItem[] = [];
741782
items.forEach((i) => {
742-
res.push(...collectLeafItems(i));
783+
if (token?.isCancellationRequested) {
784+
throw new vscode.CancellationError();
785+
}
786+
res.push(...collectLeafItems(i, token));
743787
});
744788
return res;
745789
}
746790

747-
function collectLeafItems(item: TestItem): vscode.TestItem[] {
791+
function collectLeafItems(item: TestItem, token?: CancellationToken): vscode.TestItem[] {
748792
if (item.children.size > 0) {
749793
const res: vscode.TestItem[] = [];
750794
item.children.forEach((i) => {
751-
res.push(...collectLeafItems(i));
795+
if (token?.isCancellationRequested) {
796+
throw new vscode.CancellationError();
797+
}
798+
res.push(...collectLeafItems(i, token));
752799
});
753800
return res;
754801
} else {
@@ -760,40 +807,6 @@ function escapeRegExp(text: string) {
760807
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
761808
}
762809

763-
/*
764-
test unit/case run handler
765-
*/
766-
function handleUnitRun(
767-
tests: vscode.TestItem[],
768-
run: vscode.TestRun,
769-
terminalName: string,
770-
gnattestPath: string
771-
) {
772-
const terminal = vscode.window.createTerminal(terminalName);
773-
const ext: string = process.platform == 'win32' ? '.exe' : '';
774-
// clean the previous results
775-
terminal.sendText('> ' + path.join(gnattestPath, 'result.txt'));
776-
// run every test case seperatly and append the results
777-
for (const test of tests) {
778-
run.appendOutput(`Running ${test.id}\r\n`);
779-
run.started(test);
780-
const parent = getParentTestSourceName(test);
781-
const p: integer | undefined = test.parent?.range?.start.line;
782-
const line: integer = p ? p + 1 : 0;
783-
terminal.sendText('gprbuild -P ' + path.join(gnattestPath, 'harness', 'test_driver.gpr'));
784-
terminal.sendText(
785-
path.join(gnattestPath, 'harness', 'test_runner' + ext) +
786-
' --routines=' +
787-
parent.id +
788-
':' +
789-
line.toString() +
790-
' >> ' +
791-
path.join(gnattestPath, 'result.txt')
792-
);
793-
}
794-
terminal.sendText('exit');
795-
}
796-
797810
function logAndRun(run: vscode.TestRun, cmd: string[]) {
798811
run.appendOutput(`$ ${cmd.map((arg) => `"${arg}"`).join(' ')}\r\n`);
799812
return cp.spawnSync(cmd[0], cmd.slice(1));

integration/vscode/ada/test/suite/gnattest/gnattest.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import * as vscode from 'vscode';
21
import * as assert from 'assert';
3-
import * as path from 'path';
42
import * as cp from 'child_process';
53
import { suite, test } from 'mocha';
6-
import * as gnattest from '../../../src/gnattest';
4+
import * as path from 'path';
5+
import * as vscode from 'vscode';
76
import { adaExtState } from '../../../src/extension';
8-
import { getObjectDir, getProjectFile } from '../../../src/helpers';
7+
import * as gnattest from '../../../src/gnattest';
8+
import { getProjectFile } from '../../../src/helpers';
99
import { activate, assertEqualToFileContent } from '../utils';
1010

1111
suite('GNATtest Integration Tests', function () {

0 commit comments

Comments
 (0)