@@ -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