@@ -5,9 +5,6 @@ local files = require("neotest-vstest.files")
55
66local dotnet_utils = {}
77
8- --- @type { solution : string ?, projects : string[] }?
9- local project_cache
10-
118--- parses output of running `dotnet --info`
129--- @param input string ?
1310--- @return { sdk_path : string ? }
10198--- @field proj_file string
10299--- @field dll_file string
103100--- @field proj_dir string
101+ --- @field last_discovered integer
104102--- @field is_test_project boolean
105- --- @field is_mtp_project boolean is project compiler use Microsoft.Testting.Platform
103+ --- @field is_mtp_project boolean is project compiled using Microsoft.Testting.Platform
106104
107105--- @type table<string , DotnetProjectInfo>
108106local proj_info_cache = {}
109107
108+ --- @type table<string , string>
110109local file_to_project_map = {}
111110
112111local project_semaphore = {}
@@ -116,46 +115,25 @@ local project_semaphore = {}
116115--- @param path string
117116--- @return DotnetProjectInfo ?
118117function dotnet_utils .get_proj_info (path )
119- path = vim .fs .normalize (path )
118+ local normalized_path = vim .fs .normalize (path )
120119 logger .debug (" neotest-vstest: getting project info for " .. path )
121120
122121 local proj_file
123122
124- if file_to_project_map [path ] then
125- proj_file = file_to_project_map [path ]
123+ if file_to_project_map [normalized_path ] then
124+ proj_file = file_to_project_map [normalized_path ]
126125 elseif vim .endswith (path , " .csproj" ) or vim .endswith (path , " .fsproj" ) then
127126 proj_file = path
128127 else
129128 proj_file = vim .fs .find (function (name , _ )
130129 return name :match (" %.[cf]sproj$" )
131130 end , { upward = true , type = " file" , path = vim .fs .dirname (path ) })[1 ]
132-
133- if not project_cache then
134- file_to_project_map [path ] = proj_file
135- else
136- if
137- not vim .iter (project_cache .projects ):any (function (proj )
138- if proj == proj_file then
139- file_to_project_map [path ] = proj_file
140- return true
141- end
142- return false
143- end )
144- then
145- return nil
146- end
147- end
148131 end
149132
150133 if not proj_file then
151134 return nil
152135 end
153136
154- --- Simple check before acquiring the semaphore to avoid unnecessary waits.
155- if proj_info_cache [proj_file ] then
156- return proj_info_cache [proj_file ]
157- end
158-
159137 local semaphore
160138
161139 if project_semaphore [proj_file ] then
@@ -170,10 +148,17 @@ function dotnet_utils.get_proj_info(path)
170148 logger .debug (" neotest-vstest: found project file for " .. path .. " : " .. proj_file )
171149
172150 if proj_info_cache [proj_file ] then
173- semaphore .release ()
174- return proj_info_cache [proj_file ]
151+ local project = proj_info_cache [proj_file ]
152+ local last_modified = files .get_path_last_modified (project .proj_file )
153+ if last_modified and last_modified <= project .last_discovered then
154+ semaphore .release ()
155+ return project
156+ end
175157 end
176158
159+ logger .debug (" No cached project info found for " .. proj_file )
160+ logger .debug (proj_info_cache [proj_file ])
161+
177162 local target_framework = get_target_frameworks (proj_file )
178163
179164 if not target_framework then
@@ -212,6 +197,7 @@ function dotnet_utils.get_proj_info(path)
212197 proj_file = vim .fs .normalize (proj_file ),
213198 dll_file = properties .TargetPath ,
214199 proj_dir = properties .MSBuildProjectDirectory ,
200+ last_discovered = files .get_path_last_modified (proj_file ) or 0 ,
215201 is_test_project = properties .IsTestProject == " true" ,
216202 is_mtp_project = not is_mtp_disabled and properties .IsTestingPlatformApplication == " true" ,
217203 }
@@ -228,7 +214,7 @@ function dotnet_utils.get_proj_info(path)
228214 logger .debug (res .stdout )
229215 end
230216
231- proj_info_cache [proj_data .proj_file ] = proj_data
217+ proj_info_cache [vim . fs . normalize ( proj_data .proj_file ) ] = proj_data
232218
233219 for _ , item in ipairs (output .Items .Compile ) do
234220 file_to_project_map [vim .fs .normalize (item .FullPath )] = proj_data .proj_file
@@ -243,11 +229,9 @@ function dotnet_utils.get_proj_info(path)
243229 ) or nil
244230end
245231
246- local solution_discovery_semaphore = nio .control .semaphore (1 )
247-
248232--- @param solution_path string
249233--- @return string[]
250- function dotnet_utils . projects (solution_path )
234+ local function list_projects (solution_path )
251235 local _ , res = lib .process .run ({
252236 " dotnet" ,
253237 " sln" ,
@@ -274,17 +258,11 @@ end
274258--- Falls back to listing all project in directory.
275259--- @async
276260--- @param solution_path string
277- --- @return { solution : string ?, projects : DotnetProjectInfo[] }
278- function dotnet_utils .get_solution_projects (solution_path )
279- solution_discovery_semaphore .acquire ()
280- if project_cache then
281- solution_discovery_semaphore .release ()
282- return project_cache
283- end
284-
261+ --- @return DotnetProjectInfo[]
262+ local function get_solution_projects (solution_path )
285263 local solution_dir = vim .fs .dirname (solution_path )
286264
287- local projects = dotnet_utils . projects (solution_path )
265+ local projects = list_projects (solution_path )
288266
289267 local _test_projects = {}
290268
@@ -296,22 +274,78 @@ function dotnet_utils.get_solution_projects(solution_path)
296274
297275 local all_projects_info = nio .gather (_test_projects )
298276 local test_projects = {}
299- for _ , project in ipairs (all_projects_info ) do
277+ for _ , project in pairs (all_projects_info ) do
300278 if project and project .is_test_project then
301279 test_projects [# test_projects + 1 ] = project
302280 end
303281 end
304282
305283 logger .info (" found test projects in " .. solution_dir )
306- logger .info (test_projects )
284+ logger .debug (test_projects )
285+ logger .debug (all_projects_info )
307286
308- local res = { solution = solution_path , projects = test_projects }
287+ return test_projects
288+ end
309289
310- project_cache = res
290+ --- @class DotnetSolutionInfo
291+ --- @field solution_file string
292+ --- @field projects DotnetProjectInfo[]
293+ --- @field last_updated integer
311294
312- solution_discovery_semaphore .release ()
295+ --- @async
296+ --- @param solution_info DotnetSolutionInfo
297+ --- @return DotnetSolutionInfo
298+ local function get_updated_solution_info (solution_info )
299+ local last_modified = files .get_path_last_modified (solution_info .solution_file )
300+
301+ if solution_info .last_updated < last_modified then
302+ logger .debug (" neotest-vstest: updating solution info for " .. solution_info .solution_file )
303+ local projects = get_solution_projects (solution_info .solution_file )
304+ return {
305+ solution_file = solution_info .solution_file ,
306+ projects = projects ,
307+ last_updated = last_modified or 0 ,
308+ }
309+ end
310+
311+ return solution_info
312+ end
313+
314+ --- @type table<string , DotnetSolutionInfo>
315+ local solution_cache = {}
316+
317+ local solution_discovery_semaphore = nio .control .semaphore (1 )
318+
319+ --- @async
320+ --- @param solution_path string
321+ --- @return DotnetSolutionInfo ?
322+ function dotnet_utils .get_solution_info (solution_path )
323+ local normalized_path = vim .fs .normalize (solution_path )
324+ local cached_solution = normalized_path and solution_cache [normalized_path ]
325+
326+ solution_discovery_semaphore .acquire ()
313327
314- return res
328+ if cached_solution then
329+ local updated_solution_info
330+ get_updated_solution_info (cached_solution )
331+ solution_cache [normalized_path ] = updated_solution_info or cached_solution
332+ solution_discovery_semaphore .release ()
333+ return updated_solution_info
334+ end
335+
336+ local last_modified = files .get_path_last_modified (normalized_path )
337+
338+ local projects = get_solution_projects (solution_path )
339+ local solution_info = projects
340+ and {
341+ solution_file = solution_path ,
342+ projects = projects ,
343+ last_updated = last_modified or 0 ,
344+ }
345+ or nil
346+ solution_cache [normalized_path ] = solution_info
347+ solution_discovery_semaphore .release ()
348+ return solution_info
315349end
316350
317351--- return the unix timestamp of when the project dll file was last modified
0 commit comments