@@ -11,6 +11,7 @@ import cp = require('child_process');
1111import path = require( 'path' ) ;
1212import util = require( 'util' ) ;
1313import vscode = require( 'vscode' ) ;
14+ import { promises as fs } from 'fs' ;
1415
1516import { applyCodeCoverageToAllEditors } from './goCover' ;
1617import { toolExecutionEnvironment } from './goEnv' ;
@@ -50,6 +51,7 @@ const testMethodRegex = /^\(([^)]+)\)\.(Test|Test\P{Ll}.*)$/u;
5051const benchmarkRegex = / ^ B e n c h m a r k $ | ^ B e n c h m a r k \P{ Ll} .* / u;
5152const fuzzFuncRegx = / ^ F u z z $ | ^ F u z z \P{ Ll} .* / u;
5253const testMainRegex = / T e s t M a i n \( .* \* t e s t i n g .M \) / ;
54+ const runTestSuiteRegex = / ^ \s * s u i t e \. R u n \( \w + , \s * (?: & ? (?< type1 > \w + ) \{ | n e w \( (?< type2 > \w + ) \) ) / mu;
5355
5456/**
5557 * Input to goTest.
@@ -164,7 +166,7 @@ export async function getTestFunctions(
164166 }
165167 const children = symbol . children ;
166168
167- // With gopls dymbol provider symbols , the symbols have the imports of all
169+ // With gopls symbol provider, the symbols have the imports of all
168170 // the package, so suite tests from all files will be found.
169171 const testify = importsTestify ( symbols ) ;
170172 return children . filter (
@@ -199,14 +201,15 @@ export function extractInstanceTestName(symbolName: string): string {
199201export function getTestFunctionDebugArgs (
200202 document : vscode . TextDocument ,
201203 testFunctionName : string ,
202- testFunctions : vscode . DocumentSymbol [ ]
204+ testFunctions : vscode . DocumentSymbol [ ] ,
205+ suiteToFunc : SuiteToTestMap
203206) : string [ ] {
204207 if ( benchmarkRegex . test ( testFunctionName ) ) {
205208 return [ '-test.bench' , '^' + testFunctionName + '$' , '-test.run' , 'a^' ] ;
206209 }
207210 const instanceMethod = extractInstanceTestName ( testFunctionName ) ;
208211 if ( instanceMethod ) {
209- const testFns = findAllTestSuiteRuns ( document , testFunctions ) ;
212+ const testFns = findAllTestSuiteRuns ( document , testFunctions , suiteToFunc ) ;
210213 const testSuiteRuns = [ '-test.run' , `^${ testFns . map ( ( t ) => t . name ) . join ( '|' ) } $` ] ;
211214 const testSuiteTests = [ '-testify.m' , `^${ instanceMethod } $` ] ;
212215 return [ ...testSuiteRuns , ...testSuiteTests ] ;
@@ -222,12 +225,22 @@ export function getTestFunctionDebugArgs(
222225 */
223226export function findAllTestSuiteRuns (
224227 doc : vscode . TextDocument ,
225- allTests : vscode . DocumentSymbol [ ]
228+ allTests : vscode . DocumentSymbol [ ] ,
229+ suiteToFunc : SuiteToTestMap
226230) : vscode . DocumentSymbol [ ] {
227- // get non-instance test functions
228- const testFunctions = allTests ?. filter ( ( t ) => ! testMethodRegex . test ( t . name ) ) ;
229- // filter further to ones containing suite.Run()
230- return testFunctions ?. filter ( ( t ) => doc . getText ( t . range ) . includes ( 'suite.Run(' ) ) ?? [ ] ;
231+ const suites = allTests
232+ // Find all tests with receivers.
233+ ?. map ( ( e ) => e . name . match ( testMethodRegex ) )
234+ . filter ( ( e ) => e ?. length === 3 )
235+ // Take out receiever, strip leading *.
236+ . map ( ( e ) => e && e [ 1 ] . replace ( / ^ \* / g, '' ) )
237+ // Map receiver name to test that runs "suite.Run".
238+ . map ( ( e ) => e && suiteToFunc [ e ] )
239+ // Filter out empty results.
240+ . filter ( ( e ) : e is vscode . DocumentSymbol => ! ! e ) ;
241+
242+ // Dedup.
243+ return [ ...new Set ( suites ) ] ;
231244}
232245
233246/**
@@ -254,6 +267,59 @@ export async function getBenchmarkFunctions(
254267 return children . filter ( ( sym ) => sym . kind === vscode . SymbolKind . Function && benchmarkRegex . test ( sym . name ) ) ;
255268}
256269
270+ export type SuiteToTestMap = Record < string , vscode . DocumentSymbol > ;
271+
272+ /**
273+ * Returns a mapping between a package's function receivers to
274+ * the test method that initiated them with "suite.Run".
275+ *
276+ * @param the URI of a Go source file.
277+ * @return function symbols from all source files of the package, mapped by target suite names.
278+ */
279+ export async function getSuiteToTestMap (
280+ goCtx : GoExtensionContext ,
281+ doc : vscode . TextDocument ,
282+ token ?: vscode . CancellationToken
283+ ) {
284+ // Get all the package documents.
285+ const packageDir = path . parse ( doc . fileName ) . dir ;
286+ const packageContent = await fs . readdir ( packageDir , { withFileTypes : true } ) ;
287+ const packageFilenames = packageContent
288+ // Only go files.
289+ . filter ( ( dirent ) => dirent . isFile ( ) )
290+ . map ( ( dirent ) => dirent . name )
291+ . filter ( ( name ) => name . endsWith ( '.go' ) ) ;
292+ const packageDocs = await Promise . all (
293+ packageFilenames . map ( ( e ) => path . join ( packageDir , e ) ) . map ( vscode . workspace . openTextDocument )
294+ ) ;
295+
296+ const suiteToTest : SuiteToTestMap = { } ;
297+ for ( const packageDoc of packageDocs ) {
298+ const funcs = await getTestFunctions ( goCtx , packageDoc , token ) ;
299+ if ( ! funcs ) {
300+ continue ;
301+ }
302+
303+ for ( const func of funcs ) {
304+ const funcText = packageDoc . getText ( func . range ) ;
305+
306+ // Matches run suites of the types:
307+ // type1: suite.Run(t, MySuite{
308+ // type1: suite.Run(t, &MySuite{
309+ // type2: suite.Run(t, new(MySuite)
310+ const matchRunSuite = funcText . match ( runTestSuiteRegex ) ;
311+ if ( ! matchRunSuite ) {
312+ continue ;
313+ }
314+
315+ const g = matchRunSuite . groups ;
316+ suiteToTest [ g ?. type1 || g ?. type2 || '' ] = func ;
317+ }
318+ }
319+
320+ return suiteToTest ;
321+ }
322+
257323/**
258324 * go test -json output format.
259325 * which is a subset of https://golang.org/cmd/test2json/#hdr-Output_Format
0 commit comments