Skip to content

Commit 3aa143e

Browse files
authored
init (#27)
1 parent 0ae779f commit 3aa143e

File tree

3 files changed

+92
-55
lines changed

3 files changed

+92
-55
lines changed

lua/neotest-vstest/client.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ function client_discovery.get_client_for_project(project, solution)
9797

9898
-- Check if the project is part of a solution.
9999
-- If not then do not create a client.
100-
local solution_projects = solution and dotnet_utils.get_solution_projects(solution)
100+
local solution_projects = solution and dotnet_utils.get_solution_info(solution)
101101
if solution_projects and #solution_projects.projects > 0 then
102102
local exists_in_solution = vim.iter(solution_projects.projects):any(function(solution_project)
103103
return solution_project == project

lua/neotest-vstest/dotnet_utils.lua

Lines changed: 83 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ local files = require("neotest-vstest.files")
55

66
local 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? }
@@ -101,12 +98,14 @@ end
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>
108106
local proj_info_cache = {}
109107

108+
---@type table<string, string>
110109
local file_to_project_map = {}
111110

112111
local project_semaphore = {}
@@ -116,46 +115,25 @@ local project_semaphore = {}
116115
---@param path string
117116
---@return DotnetProjectInfo?
118117
function 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
244230
end
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
315349
end
316350

317351
---return the unix timestamp of when the project dll file was last modified

lua/neotest-vstest/init.lua

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ local function create_adapter(config)
2121

2222
local solution
2323
local solution_dir
24-
local solution_projects
2524

2625
---@package
2726
---@type neotest.Adapter
@@ -85,8 +84,8 @@ local function create_adapter(config)
8584

8685
if solution_dir_future.wait() and solution then
8786
logger.info(string.format("neotest-vstest: found solution file %s", solution))
88-
solution_projects = dotnet_utils.projects(solution)
8987
dotnet_utils.build_path(solution)
88+
dotnet_utils.get_solution_info(solution)
9089
return solution_dir
9190
end
9291
end
@@ -122,6 +121,8 @@ local function create_adapter(config)
122121
end
123122

124123
function DotnetNeotestAdapter.filter_dir(name, rel_path, root)
124+
local dotnet_utils = require("neotest-vstest.dotnet_utils")
125+
125126
if name == "bin" or name == "obj" then
126127
return false
127128
end
@@ -138,11 +139,13 @@ local function create_adapter(config)
138139
return true
139140
end
140141

141-
local found = vim.iter(solution_projects or {}):any(function(project)
142-
return vim.fs.dirname(project) == project_dir
142+
local solution_info = dotnet_utils.get_solution_info(solution)
143+
144+
local found = vim.iter(solution_info and solution_info.projects or {}):any(function(project)
145+
return vim.fs.normalize(project.proj_dir) == vim.fs.normalize(project_dir)
143146
end)
144147

145-
if solution_projects and not found then
148+
if solution_info and not found then
146149
return false
147150
end
148151

0 commit comments

Comments
 (0)