@@ -11,6 +11,7 @@ import cp = require('child_process');
1111import path = require( 'path' ) ;
1212import util = require( 'util' ) ;
1313import vscode = require( 'vscode' ) ;
14+ import fs = require( 'fs' ) ;
1415
1516import { applyCodeCoverageToAllEditors } from './goCover' ;
1617import { toolExecutionEnvironment } from './goEnv' ;
@@ -45,6 +46,7 @@ const testMethodRegex = /^\(([^)]+)\)\.(Test|Test\P{Ll}.*)$/u;
4546const benchmarkRegex = / ^ B e n c h m a r k $ | ^ B e n c h m a r k \P{ Ll} .* / u;
4647const fuzzFuncRegx = / ^ F u z z $ | ^ F u z z \P{ Ll} .* / u;
4748const testMainRegex = / T e s t M a i n \( .* \* t e s t i n g .M \) / ;
49+ const runTestSuiteRegex = / ^ \s * s u i t e \. R u n \( \w + , \s * (?: & ? (?< type1 > \w + ) \{ | n e w \( (?< type2 > \w + ) \) ) / mu;
4850
4951/**
5052 * Input to goTest.
@@ -159,7 +161,7 @@ export async function getTestFunctions(
159161 }
160162 const children = symbol . children ;
161163
162- // With gopls dymbol provider symbols , the symbols have the imports of all
164+ // With gopls symbol provider, the symbols have the imports of all
163165 // the package, so suite tests from all files will be found.
164166 const testify = importsTestify ( symbols ) ;
165167 return children . filter (
@@ -194,14 +196,15 @@ export function extractInstanceTestName(symbolName: string): string {
194196export function getTestFunctionDebugArgs (
195197 document : vscode . TextDocument ,
196198 testFunctionName : string ,
197- testFunctions : vscode . DocumentSymbol [ ]
199+ testFunctions : vscode . DocumentSymbol [ ] ,
200+ suiteToFunc : SuiteToTestMap
198201) : string [ ] {
199202 if ( benchmarkRegex . test ( testFunctionName ) ) {
200203 return [ '-test.bench' , '^' + testFunctionName + '$' , '-test.run' , 'a^' ] ;
201204 }
202205 const instanceMethod = extractInstanceTestName ( testFunctionName ) ;
203206 if ( instanceMethod ) {
204- const testFns = findAllTestSuiteRuns ( document , testFunctions ) ;
207+ const testFns = findAllTestSuiteRuns ( document , testFunctions , suiteToFunc ) ;
205208 const testSuiteRuns = [ '-test.run' , `^${ testFns . map ( ( t ) => t . name ) . join ( '|' ) } $` ] ;
206209 const testSuiteTests = [ '-testify.m' , `^${ instanceMethod } $` ] ;
207210 return [ ...testSuiteRuns , ...testSuiteTests ] ;
@@ -217,12 +220,22 @@ export function getTestFunctionDebugArgs(
217220 */
218221export function findAllTestSuiteRuns (
219222 doc : vscode . TextDocument ,
220- allTests : vscode . DocumentSymbol [ ]
223+ allTests : vscode . DocumentSymbol [ ] ,
224+ suiteToFunc : SuiteToTestMap
221225) : vscode . DocumentSymbol [ ] {
222- // get non-instance test functions
223- const testFunctions = allTests ?. filter ( ( t ) => ! testMethodRegex . test ( t . name ) ) ;
224- // filter further to ones containing suite.Run()
225- return testFunctions ?. filter ( ( t ) => doc . getText ( t . range ) . includes ( 'suite.Run(' ) ) ?? [ ] ;
226+ const suites = allTests
227+ // Find all tests with receivers.
228+ ?. map ( ( e ) => e . name . match ( testMethodRegex ) )
229+ . filter ( ( e ) => e ?. length === 3 )
230+ // Take out receiever, strip leading *.
231+ . map ( ( e ) => e && e [ 1 ] . replace ( / ^ \* / g, '' ) )
232+ // Map receiver name to test that runs "suite.Run".
233+ . map ( ( e ) => e && suiteToFunc [ e ] )
234+ // Filter out empty results.
235+ . filter ( ( e ) : e is vscode . DocumentSymbol => ! ! e ) ;
236+
237+ // Dedup.
238+ return [ ...new Set ( suites ) ] ;
226239}
227240
228241/**
@@ -249,6 +262,56 @@ export async function getBenchmarkFunctions(
249262 return children . filter ( ( sym ) => sym . kind === vscode . SymbolKind . Function && benchmarkRegex . test ( sym . name ) ) ;
250263}
251264
265+ export type SuiteToTestMap = Record < string , vscode . DocumentSymbol > ;
266+
267+ /**
268+ * Returns a mapping between a package's function receivers to
269+ * the test method that initiated them with "suite.Run".
270+ *
271+ * @param the URI of a Go source file.
272+ * @return function symbols from all source files of the package, mapped by target suite names.
273+ */
274+ export async function getSuiteToTestMap (
275+ goCtx : GoExtensionContext ,
276+ doc : vscode . TextDocument ,
277+ token ?: vscode . CancellationToken
278+ ) {
279+ const fsReaddir = util . promisify ( fs . readdir ) ;
280+
281+ // Get all the package documents.
282+ const packageDir = path . parse ( doc . fileName ) . dir ;
283+ const packageFilenames = await fsReaddir ( packageDir ) ;
284+ const packageDocs = await Promise . all (
285+ packageFilenames . map ( ( e ) => path . join ( packageDir , e ) ) . map ( vscode . workspace . openTextDocument )
286+ ) ;
287+
288+ const suiteToTest : SuiteToTestMap = { } ;
289+ for ( const packageDoc of packageDocs ) {
290+ const funcs = await getTestFunctions ( goCtx , packageDoc , token ) ;
291+ if ( ! funcs ) {
292+ continue ;
293+ }
294+
295+ for ( const func of funcs ) {
296+ const funcText = packageDoc . getText ( func . range ) ;
297+
298+ // Matches run suites of the types:
299+ // - suite.Run(t, new(MySuite))
300+ // - suite.Run(t, MySuite{)
301+ // - suite.Run(t, &MySuite{)
302+ const matchRunSuite = funcText . match ( runTestSuiteRegex ) ;
303+ if ( ! matchRunSuite ) {
304+ continue ;
305+ }
306+
307+ const g = matchRunSuite . groups ;
308+ suiteToTest [ g ?. type1 || g ?. type2 || '' ] = func ;
309+ }
310+ }
311+
312+ return suiteToTest ;
313+ }
314+
252315/**
253316 * go test -json output format.
254317 * which is a subset of https://golang.org/cmd/test2json/#hdr-Output_Format
0 commit comments