Skip to content

Commit 451ba91

Browse files
authored
fix: reduce locking (#14)
* fix: remove semaphores * error handling * fix * improve error handling * support disabling MTP with MSBuild property * improve caching * fix table equality bug * improve test discovery * fix tests * add backup client caching * add __eq to project info * isolate tests * bump test timeout * fix running empty file
1 parent f626075 commit 451ba91

File tree

15 files changed

+413
-367
lines changed

15 files changed

+413
-367
lines changed

lua/neotest-vstest/client.lua

Lines changed: 44 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -6,104 +6,70 @@ local dotnet_utils = require("neotest-vstest.dotnet_utils")
66

77
local client_discovery = {}
88

9-
local client_creation_semaphore = nio.control.semaphore(1)
109
local clients = {}
1110

1211
---@param project DotnetProjectInfo?
1312
---@param solution string? path to the solution file
1413
---@return neotest-vstest.Client?
1514
function client_discovery.get_client_for_project(project, solution)
1615
if not project then
16+
logger.debug("neotest-vstest: No project provided, returning nil client.")
1717
return nil
1818
end
1919

20-
---@type neotest-vstest.Client | boolean
21-
local client = false
20+
if clients[project.proj_file] ~= nil then
21+
return clients[project.proj_file] or nil
22+
end
2223

23-
client_creation_semaphore.with(function()
24-
if clients[project.proj_file] ~= nil then
25-
client = clients[project.proj_file]
26-
return
27-
end
24+
-- Check if the project is part of a solution.
25+
-- If not then do not create a client.
26+
local solution_projects = solution and dotnet_utils.get_solution_projects(solution)
27+
if solution_projects and #solution_projects.projects > 0 then
28+
local project_files = vim.tbl_map(function(proj)
29+
return proj.proj_file
30+
end, solution_projects.projects)
2831

29-
-- Check if the project is part of a solution.
30-
-- If not then do not create a client.
31-
local solution_projects = solution and dotnet_utils.get_solution_projects(solution)
32-
if solution_projects and #solution_projects.projects > 0 then
33-
if not vim.list_contains(solution_projects.projects, project) then
34-
logger.debug(
35-
"neotest-vstest: project is not part of the solution projects: "
36-
.. vim.inspect(solution_projects.projects)
37-
.. ", project: "
38-
.. vim.inspect(project)
39-
)
40-
clients[project.proj_file] = client
41-
return
42-
end
32+
if not vim.list_contains(project_files, project.proj_file) then
4333
logger.debug(
44-
"neotest-vstest: project is part of the solution projects: " .. vim.inspect(project)
34+
"neotest-vstest: project is not part of the solution projects: "
35+
.. vim.inspect(solution_projects.projects)
36+
.. ", project: "
37+
.. vim.inspect(project)
4538
)
46-
else
47-
logger.debug(
48-
"neotest-vstest: no solution projects found, using solution: " .. vim.inspect(solution)
49-
)
50-
end
51-
52-
-- Project is part of a solution or standalone, create a client.
53-
if project.is_mtp_project then
54-
logger.debug(
55-
"neotest-vstest: Creating mtp client for project "
56-
.. project.proj_file
57-
.. " and "
58-
.. project.dll_file
59-
)
60-
client = mtp_client:new(project)
61-
elseif project.is_test_project then
62-
client = vstest_client:new(project)
39+
clients[project.proj_file] = false
40+
return
6341
end
64-
clients[project.proj_file] = client
65-
end)
66-
67-
if client == false then
68-
return nil
6942
else
70-
return client
71-
end
72-
end
73-
74-
local solution_cache
75-
local solution_semaphore = nio.control.semaphore(1)
76-
77-
function client_discovery.discover_solution_tests(root)
78-
if solution_cache then
79-
return solution_cache
43+
logger.debug(
44+
"neotest-vstest: no solution projects found for solution: " .. vim.inspect(solution)
45+
)
8046
end
8147

82-
solution_semaphore.acquire()
83-
84-
local res = dotnet_utils.get_solution_projects(root)
85-
86-
dotnet_utils.build_path(root)
87-
88-
local project_clients = {}
89-
90-
for _, project in ipairs(res.projects) do
91-
if project.is_test_project or project.is_mtp_project then
92-
project_clients[project.proj_file] = client_discovery.get_client_for_project(project)
93-
end
94-
end
95-
96-
logger.debug("neotest-vstest: discovered projects:")
97-
logger.debug(res.projects)
98-
99-
for _, client in ipairs(project_clients) do
100-
local project_tests = client:discover_tests()
101-
vim.tbl_extend("force", solution_cache, project_tests)
48+
---@type neotest-vstest.Client
49+
local client
50+
51+
-- Project is part of a solution or standalone, create a client.
52+
if project.is_mtp_project then
53+
logger.debug(
54+
"neotest-vstest: Creating mtp client for project "
55+
.. project.proj_file
56+
.. " and "
57+
.. project.dll_file
58+
)
59+
client = mtp_client:new(project)
60+
elseif project.is_test_project then
61+
client = vstest_client:new(project)
62+
else
63+
logger.warn(
64+
"neotest-vstest: Project is neither test project nor mtp project, returning nil client for "
65+
.. project.proj_file
66+
)
67+
clients[project.proj_file] = false
68+
return
10269
end
10370

104-
solution_semaphore.release()
105-
106-
return solution_cache
71+
clients[project.proj_file] = client
72+
return client
10773
end
10874

10975
return client_discovery

lua/neotest-vstest/dotnet_utils.lua

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ function dotnet_utils.get_proj_info(path)
184184
"-getProperty:MSBuildProjectDirectory",
185185
"-getProperty:IsTestProject",
186186
"-getProperty:IsTestingPlatformApplication",
187+
"-getProperty:DisableTestingPlatformServerCapability",
187188
"-property:TargetFramework=" .. target_framework,
188189
}
189190

@@ -198,14 +199,23 @@ function dotnet_utils.get_proj_info(path)
198199
logger.debug("neotest-vstest: msbuild properties for " .. proj_file .. ":")
199200
logger.debug(properties)
200201

202+
local is_mtp_disabled = properties.DisableTestingPlatformServerCapability == "true"
203+
204+
---@class DotnetProjectInfo
201205
local proj_data = {
202206
proj_file = proj_file,
203207
dll_file = properties.TargetPath,
204208
proj_dir = properties.MSBuildProjectDirectory,
205209
is_test_project = properties.IsTestProject == "true",
206-
is_mtp_project = properties.IsTestingPlatformApplication == "true",
210+
is_mtp_project = not is_mtp_disabled and properties.IsTestingPlatformApplication == "true",
207211
}
208212

213+
setmetatable(proj_data, {
214+
__eq = function(a, b)
215+
return a.proj_file == b.proj_file
216+
end,
217+
})
218+
209219
if proj_data.dll_file == "" then
210220
logger.debug("neotest-vstest: failed to find dll file for " .. proj_file)
211221
logger.debug(path)
@@ -313,6 +323,7 @@ function dotnet_utils.build_path(path)
313323
nio.scheduler()
314324
logger.error("neotest-vstest: failed to build path " .. path)
315325
logger.error(out.stdout)
326+
logger.error(out.stderr)
316327
vim.notify_once("neotest-vstest: failed to build project " .. path, vim.log.levels.ERROR)
317328
return false
318329
end

lua/neotest-vstest/init.lua

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ function DotnetNeotestAdapter.root(path)
6969

7070
if solution_dir_future.wait() then
7171
logger.info(string.format("neotest-vstest: found solution file %s", solution))
72-
client_discovery.discover_solution_tests(solution)
7372
return solution_dir
7473
end
7574
end
@@ -79,24 +78,24 @@ function DotnetNeotestAdapter.root(path)
7978
end
8079

8180
function DotnetNeotestAdapter.is_test_file(file_path)
82-
local isTestFile = (vim.endswith(file_path, ".csproj") or vim.endswith(file_path, ".fsproj"))
81+
local isDotnetFile = (vim.endswith(file_path, ".csproj") or vim.endswith(file_path, ".fsproj"))
8382
or (vim.endswith(file_path, ".cs") or vim.endswith(file_path, ".fs"))
8483

85-
if not isTestFile then
84+
if not isDotnetFile then
8685
return false
8786
end
8887

8988
local project = dotnet_utils.get_proj_info(file_path)
9089
local client = client_discovery.get_client_for_project(project, solution)
9190

92-
local tests = (client and client:discover_tests_for_path(file_path)) or {}
93-
94-
local n = 0
95-
if tests then
96-
n = #vim.tbl_values(tests)
91+
if not client then
92+
logger.debug(
93+
"neotest-vstest: marking file as non-test file since no client was found: " .. file_path
94+
)
95+
return false
9796
end
9897

99-
return tests and n > 0
98+
return true
10099
end
101100

102101
function DotnetNeotestAdapter.filter_dir(name)
@@ -183,7 +182,6 @@ local function build_position(source, captured_nodes, tests_in_file, path)
183182
type = match_type,
184183
path = path,
185184
name = test.DisplayName,
186-
qualified_name = test.FullyQualifiedName,
187185
range = { definition:range() },
188186
})
189187
tests_in_file[id] = nil
@@ -224,15 +222,22 @@ local function get_top_level_tests(project)
224222

225223
local client = client_discovery.get_client_for_project(project, solution)
226224

227-
local tests_in_file = (client and client:discover_tests()) or {}
225+
if not client then
226+
logger.debug(
227+
"neotest-vstest: not discovering top-level tests due to no client for project: "
228+
.. vim.inspect(project)
229+
)
230+
end
228231

232+
local tests_in_file = (client and client:discover_tests()) or {}
233+
local tests_in_project = tests_in_file[project.proj_file]
229234
logger.debug(string.format("neotest-vstest: top-level tests in file: %s", project.dll_file))
230235

231-
if not tests_in_file or next(tests_in_file) == nil then
236+
if not tests_in_project or next(tests_in_project) == nil then
232237
return
233238
end
234239

235-
local n = #tests_in_file
240+
local n = #tests_in_project
236241

237242
local nodes = {
238243
{
@@ -246,18 +251,21 @@ local function get_top_level_tests(project)
246251
local i = 0
247252

248253
-- add tests which does not have a matching tree-sitter node.
249-
for id, test in pairs(tests_in_file) do
254+
for id, test in pairs(tests_in_project) do
250255
nodes[#nodes + 1] = {
251256
id = id,
252257
type = "test",
253-
path = project.proj_file,
258+
path = test.CodeFilePath,
254259
name = test.DisplayName,
255-
qualified_name = test.FullyQualifiedName,
256260
range = { i, 0, i + 1, -1 },
257261
}
258262
i = i + 1
259263
end
260264

265+
if #nodes <= 1 then
266+
return {}
267+
end
268+
261269
local structure = assert(build_structure(nodes, {}, {
262270
nested_tests = false,
263271
require_namespaces = false,
@@ -288,6 +296,9 @@ function DotnetNeotestAdapter.discover_positions(path)
288296
local client = client_discovery.get_client_for_project(project, solution)
289297

290298
if not client then
299+
logger.debug(
300+
"neotest-vstest: not discovering tests due to no client for file: " .. vim.inspect(path)
301+
)
291302
return
292303
end
293304

@@ -345,20 +356,24 @@ function DotnetNeotestAdapter.discover_positions(path)
345356

346357
-- add tests which does not have a matching tree-sitter node.
347358
for id, test in pairs(tests_in_file) do
359+
local line = test.LineNumber or 0
348360
nodes[#nodes + 1] = {
349361
id = id,
350362
type = "test",
351363
path = path,
352364
name = test.DisplayName,
353-
qualified_name = test.FullyQualifiedName,
354-
range = { test.LineNumber - 1, 0, test.LineNumber - 1, -1 },
365+
range = { line - 1, 0, line - 1, -1 },
355366
}
356367
end
357368

358369
for _, node in ipairs(nodes) do
359370
node.client = client
360371
end
361372

373+
if #nodes <= 1 then
374+
return {}
375+
end
376+
362377
local structure = assert(build_structure(nodes, {}, {
363378
nested_tests = false,
364379
require_namespaces = false,
@@ -430,7 +445,7 @@ function DotnetNeotestAdapter.build_spec(args)
430445
}
431446
end
432447

433-
function DotnetNeotestAdapter.results(spec, result, _tree)
448+
function DotnetNeotestAdapter.results(spec, result)
434449
logger.info("neotest-vstest: waiting for test results")
435450
logger.debug(spec)
436451
logger.debug(result)
@@ -440,7 +455,7 @@ function DotnetNeotestAdapter.results(spec, result, _tree)
440455
if not results then
441456
for _, id in ipairs(vim.tbl_values(spec.context.projects_id_map)) do
442457
results[id] = {
443-
status = types.ResultStatus.failed,
458+
status = types.ResultStatus.skipped,
444459
output = spec.context.result_path,
445460
errors = {
446461
{ message = result.output },

0 commit comments

Comments
 (0)