Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ root = true
charset = utf-8
indent_style = space
indent_size = 2
continuation_indent = 2
trim_trailing_whitespace = true
insert_final_newline = true
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,24 @@ require("neotest").setup({
-- table is passed directly to DAP when debugging tests.
dap_settings = {
type = "netcoredbg",
}
},
-- If multiple solutions exists the adapter will ask you to choose one.
-- If you have a different heuristic for choosing a solution you can provide a function here.
solution_selector = function(solutions)
return nil -- return the solution you want to use or nil to let the adapter choose.
end
end,
-- If multiple .runsettings/testconfig.json files are present in the test project directory
-- you will be given the choice of file to use when setting up the adapter.
-- Or you can provide a function here
-- default nil to select from all files in project directory
settings_selector = function(project_dir)
return nil -- return the .runsettings/testconfig.json file you want to use or let the adapter choose
end,
build_opts = {
-- Arguments that will be added to all `dotnet build` and `dotnet msbuild` commands
additional_args = {}
}
},
timeout_ms = 30 * 5 * 1000 -- number of milliseconds to wait before timeout while communicating with adapter client
})
}
})
Expand Down
5 changes: 5 additions & 0 deletions lua/neotest-vstest/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
---@field build_opts? BuildOpts
---@field dap_settings? dap.Configuration dap settings for debugging
---@field solution_selector? fun(solutions: string[]): string|nil
---@field settings_selector? fun(project_dir: string): string|nil function to find the .runsettings/testconfig.json in the project dir
---@field timeout_ms? number milliseconds to wait before timing out connection with test runner

---@param config? neotest-vstest.Config
---@return neotest.Adapter
Expand Down Expand Up @@ -559,6 +561,7 @@ local function create_adapter(config)

return results
end

return DotnetNeotestAdapter
end

Expand All @@ -567,6 +570,8 @@ local DotnetNeotestAdapter = create_adapter()
---@param opts neotest-vstest.Config
local function apply_user_settings(_, opts)
vim.g.neotest_vstest_sdk_path = opts and opts.sdk_path or nil
vim.g.neotest_vstest_find_settings = opts and opts.settings_selector or nil
vim.g.neotest_vstest_timeout_ms = opts and opts.timeout_ms or 5 * 30 * 1000
return create_adapter(opts)
end

Expand Down
59 changes: 56 additions & 3 deletions lua/neotest-vstest/vstest/client.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,60 @@ local cli_wrapper = require("neotest-vstest.vstest.cli_wrapper")

local M = {}

---@param project_dir string
---@return string? path to .runsettings file to use or nil
function M.find_runsettings_for_project(project_dir)
local settings = vim.fs.find(function(name, _)
return name:match("%.runsettings$")
end, {
upward = false,
type = "file",
path = project_dir,
limit = math.huge,
})

for _, set in pairs(settings) do
logger.debug(string.format("neotest-vstest: Found .runsettings: %s", set))
end

local setting
if #settings > 0 then
local settings_future = nio.control.future()

if #settings == 1 then
setting = settings[1]
settings_future.set(setting)
logger.info(string.format("neotest-vstest: selected .runsetting file: %s", setting))
else
vim.schedule(function()
nio.run(function()
vim.ui.select(settings, {
prompt = "Multiple .runsettings exists. Select a .runsettings file: ",
}, function(selected)
if selected then
setting = selected
logger.info(string.format("neotest-vstest: selected .runsetting file: %s", setting))
settings_future.set(setting)
else
settings_future.set("nil")
end
end)
end)
end)
end

if settings_future.wait() and setting then
return setting
end
end
logger.info(string.format("neotest-vstest: Found no .runsettings files"))
return nil
end

---@param runner function
---@param project DotnetProjectInfo
---@return table?
function M.discover_tests_in_project(runner, project)
function M.discover_tests_in_project(runner, settings, project)
local tests_in_files = {}

local wait_file = nio.fn.tempname()
Expand All @@ -19,6 +69,7 @@ function M.discover_tests_in_project(runner, project)
"discover",
output_file,
wait_file,
settings or "nil",
{ project.dll_file },
})
:flatten()
Expand Down Expand Up @@ -85,7 +136,7 @@ end
---@param runner function
---@param ids string|string[]
---@return string process_output_path, string result_stream_file_path, string result_file_path
function M.run_tests(runner, ids)
function M.run_tests(runner, settings, ids)
local process_output_path = nio.fn.tempname()
lib.files.write(process_output_path, "")

Expand All @@ -105,6 +156,7 @@ function M.run_tests(runner, ids)
result_path,
process_output_path,
output_dir_path,
settings or "nil",
ids,
})
:flatten()
Expand All @@ -119,7 +171,7 @@ end
---@param runner function
---@param ids string|string[]
---@return string? pid, async fun() on_attach, string process_output_path, string result_stream_file_path, string result_file_path
function M.debug_tests(runner, ids)
function M.debug_tests(runner, settings, ids)
local process_output_path = nio.fn.tempname()
lib.files.write(process_output_path, "")

Expand Down Expand Up @@ -150,6 +202,7 @@ function M.debug_tests(runner, ids)
result_path,
process_output_path,
output_dir_path,
settings or "nil",
ids,
})
:flatten()
Expand Down
33 changes: 27 additions & 6 deletions lua/neotest-vstest/vstest/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ local vstest_client = require("neotest-vstest.vstest.client")

--- @class neotest-vstest.vstest-client: neotest-vstest.Client
--- @field project DotnetProjectInfo
--- @field settings string? path to .runsettings file or nil
--- @field private test_runner { execute: function, stop: function }
local Client = {}
Client.__index = Client
Expand All @@ -19,20 +20,35 @@ end
---@param project DotnetProjectInfo
function Client:new(project)
logger.info("neotest-vstest: Creating new (vstest) client for: " .. vim.inspect(project))
local findSettings = function()
local settings = nil
if vim.g.neotest_vstest_find_settings then
settings = vim.g.neotest_vstest_find_settings(project.proj_dir)
end
if settings ~= nil then
return settings
else
return vstest_client.find_runsettings_for_project(project.proj_dir)
end
end
local client = {
project = project,
test_cases = {},
last_discovered = 0,
test_runner = cli_wrapper.create_test_runner(project),
settings = findSettings(),
}
setmetatable(client, self)

return client
end

function Client:discover_tests()
self.test_cases = vstest_client.discover_tests_in_project(self.test_runner.execute, self.project)
or {}
self.test_cases = vstest_client.discover_tests_in_project(
self.test_runner.execute,
self.settings,
self.project
) or {}

return self.test_cases
end
Expand All @@ -43,7 +59,7 @@ end
function Client:run_tests(ids)
local result_future = nio.control.future()
local process_output_file, stream_file, result_file =
vstest_client.run_tests(self.test_runner.execute, ids)
vstest_client.run_tests(self.test_runner.execute, self.settings, ids)

local result_stream_data, result_stop_stream = lib.files.stream_lines(stream_file)
local output_stream_data, output_stop_stream = lib.files.stream_lines(process_output_file)
Expand All @@ -65,7 +81,7 @@ function Client:run_tests(ids)
end

nio.run(function()
cli_wrapper.spin_lock_wait_file(result_file, 5 * 30 * 1000)
cli_wrapper.spin_lock_wait_file(result_file, vim.g.neotest_vstest_timeout_ms)
local parsed = {}
local results = lib.files.read_lines(result_file)
for _, line in ipairs(results) do
Expand All @@ -90,7 +106,7 @@ end
function Client:debug_tests(ids)
local result_future = nio.control.future()
local pid, on_attach, process_output_file, stream_file, result_file =
vstest_client.debug_tests(self.test_runner.execute, ids)
vstest_client.debug_tests(self.test_runner.execute, self.settings, ids)

local result_stream_data, result_stop_stream = lib.files.stream_lines(stream_file)
local output_stream_data, output_stop_stream = lib.files.stream_lines(process_output_file)
Expand All @@ -112,8 +128,13 @@ function Client:debug_tests(ids)
end

nio.run(function()
cli_wrapper.spin_lock_wait_file(result_file, 5 * 30 * 1000)
local parsed = {}
local file_exists =
cli_wrapper.spin_lock_wait_file(result_file, vim.g.neotest_vstest_timeout_ms)
assert(
file_exists,
"neotest-vstest: (possible timeout, check logs) result file does not exist: " .. result_file
)
local results = lib.files.read_lines(result_file)
for _, line in ipairs(results) do
local success, result = pcall(vim.json.decode, line)
Expand Down
36 changes: 29 additions & 7 deletions scripts/run_tests.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ module TestDiscovery =

{| OutputPath = args[0]
WaitFile = args[1]
Sources = args[2..] |}
RunSettings = args[2]
Sources = args[3..] |}
|> ValueOption.Some
else
ValueOption.None
Expand All @@ -63,7 +64,8 @@ module TestDiscovery =
OutputPath = args[1]
ProcessOutput = args[2]
OutputDirPath = args[3]
Ids = args[4..] |> Array.map Guid.Parse |}
RunSettings = args[4]
Ids = args[5..] |> Array.map Guid.Parse |}
|> ValueOption.Some
else
ValueOption.None
Expand All @@ -79,7 +81,8 @@ module TestDiscovery =
OutputPath = args[3]
ProcessOutput = args[4]
OutputDirPath = args[5]
Ids = args[6..] |> Array.map Guid.Parse |}
RunSettings = args[6]
Ids = args[7..] |> Array.map Guid.Parse |}
|> ValueOption.Some
else
ValueOption.None
Expand Down Expand Up @@ -256,7 +259,7 @@ module TestDiscovery =

let console = argv[0]

let sourceSettings =
let nullSettings =
"""
<RunSettings>
</RunSettings>
Expand Down Expand Up @@ -294,8 +297,14 @@ module TestDiscovery =
let discoveryHandler =
PlaygroundTestDiscoveryHandler(args.WaitFile, args.OutputPath) :> ITestDiscoveryEventsHandler2

let settings =
if (args.RunSettings.CompareTo "nil" <> 0) then
System.IO.File.ReadAllText(args.RunSettings)
else
nullSettings

Console.WriteLine($"Discovering tests for: {sourcesStr}")
r.DiscoverTests(args.Sources, sourceSettings, options, testSession, discoveryHandler)
r.DiscoverTests(args.Sources, settings, options, testSession, discoveryHandler)
Console.WriteLine($"Discovering tests for: {sourcesStr}")
with e ->
Console.WriteLine($"failed to discovery tests for {sourcesStr}. Exception: {e}")
Expand All @@ -311,8 +320,15 @@ module TestDiscovery =
args.ProcessOutput,
args.OutputDirPath
)

let settings =
if (args.RunSettings.CompareTo "nil" <> 0) then
System.IO.File.ReadAllText(args.RunSettings)
else
nullSettings

// spawn as task to allow running concurrent tests
do! r.RunTestsAsync(testCases, sourceSettings, testHandler)
do! r.RunTestsAsync(testCases, settings, testHandler)
Console.WriteLine($"Done running tests for ids: ")

for id in args.Ids do
Expand All @@ -333,11 +349,17 @@ module TestDiscovery =
args.OutputDirPath
)

let settings=
if (args.RunSettings.CompareTo "nil" <> 0) then
System.IO.File.ReadAllText(args.RunSettings)
else
nullSettings

let debugLauncher = DebugLauncher(args.PidPath, args.AttachedPath)
Console.WriteLine($"Starting {Seq.length testCases} tests in debug-mode")

do! Task.Yield()
r.RunTestsWithCustomTestHost(testCases, sourceSettings, testHandler, debugLauncher)
r.RunTestsWithCustomTestHost(testCases, settings, testHandler, debugLauncher)
}
|> ignore
| input ->
Expand Down
Loading