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
56 changes: 29 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
src="https://github.com/user-attachments/assets/7e297d7a-f06d-44a9-adef-92131185e8ca" />
</p>


# Neotest VSTest

Neotest adapter for dotnet
Expand Down Expand Up @@ -64,34 +63,37 @@ require("neotest").setup({
The adapter optionally supports extra settings:

```lua
-- NOTE: This should be set before calling require("neotest-vstest")
vim.g.neotest_vstest = {
-- Path to dotnet sdk path.
-- Used in cases where the sdk path cannot be auto discovered.
sdk_path = "/usr/local/dotnet/sdk/9.0.101/",
-- 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,
-- 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
}

require("neotest").setup({
adapters = {
require("neotest-vstest")({
-- Path to dotnet sdk path.
-- Used in cases where the sdk path cannot be auto discovered.
sdk_path = "/usr/local/dotnet/sdk/9.0.101/",
-- 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,
-- 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
})
require("neotest-vstest")
}
})
```
Expand Down
36 changes: 36 additions & 0 deletions lua/neotest-vstest/health.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
local M = {}
M.check = function()
vim.health.start("neotest-vstest healthcheck")

vim.health.info("checking for dependencies...")

local has_nio, nio = pcall(require, "nio")
if not has_nio then
vim.health.error("nio is not installed. Please install nio to use neotest-vstest.")
else
vim.health.ok("nio is installed.")
end

local has_neotest = pcall(require, "neotest")
if not has_neotest then
vim.health.error("neotest is not installed. Please install neotest to use neotest-vstest.")
else
vim.health.ok("neotest is installed.")
end

vim.health.info("Checking neotest-vstest configuration...")

vim.health.info("Checking for vstest.console.dll...")
local cli_wrapper = require("neotest-vstest.vstest.cli_wrapper")
local vstest_path = cli_wrapper.get_vstest_path()

-- make sure setup function parameters are ok
if not vstest_path then
vim.health.error(
"Could not determine location of vstest.console.dll. Please set vim.g.neotest_vstest.sdk_path to the dotnet sdk path."
)
else
vim.health.ok("Found vstest.console.dll at: " .. vstest_path)
end
end
return M
15 changes: 8 additions & 7 deletions lua/neotest-vstest/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
---@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

---@type neotest-vstest.Config
local default_config = {
timeout_ms = 5 * 30 * 1000,
}

vim.g.neotest_vstest = vim.tbl_deep_extend("force", default_config, vim.g.neotest_vstest or {})

---@param config? neotest-vstest.Config
---@return neotest.Adapter
local function create_adapter(config)
Expand Down Expand Up @@ -36,7 +43,6 @@ local function create_adapter(config)
local nio = require("nio")
local lib = require("neotest.lib")
local logger = require("neotest.logging")
local dotnet_utils = require("neotest-vstest.dotnet_utils")

if solution_dir then
return solution_dir
Expand Down Expand Up @@ -107,7 +113,6 @@ local function create_adapter(config)

function DotnetNeotestAdapter.is_test_file(file_path)
local logger = require("neotest.logging")
local dotnet_utils = require("neotest-vstest.dotnet_utils")
local client_discovery = require("neotest-vstest.client")

logger.trace("neotest-vstest: checking if file is test file: " .. file_path)
Expand Down Expand Up @@ -145,7 +150,6 @@ local function create_adapter(config)
end

function DotnetNeotestAdapter.filter_dir(name, rel_path, root)
local dotnet_utils = require("neotest-vstest.dotnet_utils")
local logger = require("neotest.logging")
logger.trace("neotest-vstest: filtering dir", name, rel_path, root)

Expand Down Expand Up @@ -378,7 +382,6 @@ local function create_adapter(config)
local lib = require("neotest.lib")
local types = require("neotest.types")
local logger = require("neotest.logging")
local dotnet_utils = require("neotest-vstest.dotnet_utils")
local client_discovery = require("neotest-vstest.client")

logger.info(string.format("neotest-vstest: scanning %s for tests...", path))
Expand Down Expand Up @@ -581,9 +584,7 @@ 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
vim.g.neotest_vstest = vim.tbl_deep_extend("force", vim.g.neotest_vstest or {}, opts or {})
return create_adapter(opts)
end

Expand Down
54 changes: 17 additions & 37 deletions lua/neotest-vstest/vstest/cli_wrapper.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,11 @@ local dotnet_utils = require("neotest-vstest.dotnet_utils")

local M = {}

local function get_vstest_path()
if not vim.g.neotest_vstest_sdk_path then
local process = nio.process.run({
cmd = "dotnet",
args = { "--info" },
})
function M.get_vstest_path()
local path_to_search = vim.g.neotest_vstest and vim.g.neotest_vstest.sdk_path

logger.debug(
"neotest-vstest: starting process to detect dotnet sdk path " .. tostring(process.pid)
)
if not path_to_search then
local process = vim.system({ "dotnet", "--info" })

local default_sdk_path
if vim.fn.has("win32") then
Expand All @@ -23,42 +18,26 @@ local function get_vstest_path()
default_sdk_path = "/usr/local/share/dotnet/sdk/"
end

if not process then
vim.g.neotest_vstest_sdk_path = default_sdk_path
local obj = process:wait()

local out = obj.stdout
local info = dotnet_utils.parse_dotnet_info(out or "")
if info.sdk_path then
path_to_search = info.sdk_path
logger.info(string.format("neotest-vstest: detected sdk path: %s", path_to_search))
else
path_to_search = default_sdk_path
local log_string = string.format(
"neotest-vstest: failed to detect sdk path. falling back to %s",
vim.g.neotest_vstest_sdk_path
path_to_search
)

logger.info(log_string)
nio.scheduler()
vim.notify_once(log_string)
else
local out = process.stdout.read()
local info = dotnet_utils.parse_dotnet_info(out or "")
if info.sdk_path then
vim.g.neotest_vstest_sdk_path = info.sdk_path
logger.info(
string.format("neotest-vstest: detected sdk path: %s", vim.g.neotest_vstest_sdk_path)
)
else
vim.g.neotest_vstest_sdk_path = default_sdk_path
local log_string = string.format(
"neotest-vstest: failed to detect sdk path. falling back to %s",
vim.g.neotest_vstest_sdk_path
)
logger.info(log_string)
nio.scheduler()
vim.notify_once(log_string)
end
process.close()
end
end

return vim.fs.find(
"vstest.console.dll",
{ upward = false, type = "file", path = vim.g.neotest_vstest_sdk_path }
)[1]
return vim.fs.find("vstest.console.dll", { upward = false, type = "file", path = path_to_search })[1]
end

local function get_script(script_name)
Expand All @@ -76,7 +55,8 @@ end
---@return { execute: fun(content: string), stop: fun() }
function M.create_test_runner(project)
local test_discovery_script = get_script("run_tests.fsx")
local testhost_dll = get_vstest_path()
nio.scheduler()
local testhost_dll = M.get_vstest_path()

logger.debug("neotest-vstest: found discovery script: " .. test_discovery_script)
logger.debug("neotest-vstest: found testhost dll: " .. testhost_dll)
Expand Down
8 changes: 4 additions & 4 deletions lua/neotest-vstest/vstest/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ 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)
if vim.g.neotest_vstest and vim.g.neotest_vstest.find_settings then
settings = vim.g.neotest_vstest.find_settings(project.proj_dir)
end
if settings ~= nil then
return settings
Expand Down Expand Up @@ -81,7 +81,7 @@ function Client:run_tests(ids)
end

nio.run(function()
cli_wrapper.spin_lock_wait_file(result_file, vim.g.neotest_vstest_timeout_ms)
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 Down Expand Up @@ -130,7 +130,7 @@ function Client:debug_tests(ids)
nio.run(function()
local parsed = {}
local file_exists =
cli_wrapper.spin_lock_wait_file(result_file, vim.g.neotest_vstest_timeout_ms)
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
Expand Down
Loading