Skip to content

Commit

Permalink
concurrent test discovery
Browse files Browse the repository at this point in the history
  • Loading branch information
Nsidorenco committed Nov 12, 2024
1 parent 6be1bb9 commit db99a2c
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 49 deletions.
19 changes: 15 additions & 4 deletions lua/neotest-dotnet/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@ DotnetNeotestAdapter.root = function(path)
end

DotnetNeotestAdapter.is_test_file = function(file_path)
return (vim.endswith(file_path, ".cs") or vim.endswith(file_path, ".fs"))
and vim.iter(vstest.discover_tests(file_path)):any(function(_, test)
return test.CodeFilePath == file_path
end)
if not (vim.endswith(file_path, ".cs") or vim.endswith(file_path, ".fs")) then
return false
else
for _, test in pairs(vstest.discover_tests(file_path)) do
if test.CodeFilePath == file_path then
return true
end
end
end

return false
end

DotnetNeotestAdapter.filter_dir = function(name)
Expand Down Expand Up @@ -70,6 +77,8 @@ local function build_position(source, captured_nodes, tests_in_file, path)
end

DotnetNeotestAdapter.discover_positions = function(path)
logger.info(string.format("scanning %s for tests...", path))

local fsharp_query = require("neotest-dotnet.queries.fsharp")
local c_sharp_query = require("neotest-dotnet.queries.c_sharp")

Expand Down Expand Up @@ -142,6 +151,8 @@ DotnetNeotestAdapter.discover_positions = function(path)
})
end

logger.info(string.format("done scanning %s for tests", path))

return tree
end

Expand Down
36 changes: 17 additions & 19 deletions lua/neotest-dotnet/vstest_wrapper.lua
Original file line number Diff line number Diff line change
Expand Up @@ -141,26 +141,37 @@ function M.spin_lock_wait_file(file_path, max_wait)
end
end

if not content then
logger.warn(string.format("timed out reading content of file %s", file_path))
end

return content
end

local discovery_cache = {}
local discovery_lock = nio.control.semaphore(1)
local build_semaphore = nio.control.semaphore(1)

---@class TestCase
---@field Id string
---@field CodeFilePath string
---@field DisplayName string
---@field FullyQualifiedName string
---@field Source string

---@param path string
---@return table test_cases
---@return table<string, TestCase> test_cases
function M.discover_tests(path)
local json = {}
local proj_info = M.get_proj_info(path)

discovery_lock.acquire()

if not proj_info.dll_file then
logger.warn(string.format("failed to find dll for file: %s", path))
return json
end

lib.process.run({ "dotnet", "build", proj_info.proj_file })
build_semaphore.with(function()
lib.process.run({ "dotnet", "build", proj_info.proj_file })
end)

local open_err, stats = nio.uv.fs_stat(proj_info.dll_file)
assert(not open_err, open_err)
Expand All @@ -174,20 +185,9 @@ function M.discover_tests(path)
and modified_time
and modified_time <= cached.last_modified
then
logger.debug("cache hit")
discovery_lock.release()
return cached.content
end

logger.debug(
string.format(
"cache not hit: %s %s %s",
proj_info.dll_file,
cached and cached.last_modified,
modified_time
)
)

local wait_file = nio.fn.tempname()
local output_file = nio.fn.tempname()

Expand All @@ -206,7 +206,7 @@ function M.discover_tests(path)

invoke_test_runner(command)

logger.debug("Waiting for result file to populate...")
logger.debug(string.format("Waiting for result file to populate for %s...", proj_info.proj_file))

local max_wait = 30 * 1000 -- 10 sec

Expand All @@ -224,8 +224,6 @@ function M.discover_tests(path)
}
end

discovery_lock.release()

return json
end

Expand Down
54 changes: 28 additions & 26 deletions run_tests.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ open Microsoft.VisualStudio.TestPlatform.ObjectModel.Client
open Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces

module TestDiscovery =
open System.Collections.Concurrent

[<return: Struct>]
let (|DiscoveryRequest|_|) (str: string) =
if str.StartsWith("discover") then
Expand Down Expand Up @@ -61,25 +63,33 @@ module TestDiscovery =
else
ValueOption.None

let discoveryCompleteEvent = new ManualResetEventSlim()

let discoveredTests = Dictionary<string, TestCase seq>()
let discoveredTests = ConcurrentDictionary<string, TestCase seq>()

type PlaygroundTestDiscoveryHandler() =
type PlaygroundTestDiscoveryHandler(resultFilePath, waitFilePath) =
interface ITestDiscoveryEventsHandler2 with
member _.HandleDiscoveredTests(discoveredTestCases: IEnumerable<TestCase>) =
discoveredTestCases
|> Seq.groupBy _.CodeFilePath
|> Seq.iter (fun (file, testCases) ->
if discoveredTests.ContainsKey file then
discoveredTests.Remove(file) |> ignore
discoveredTests.AddOrUpdate(file, testCases, (fun _ _ -> testCases)) |> ignore)

use testsWriter = new StreamWriter(resultFilePath, append = false)

discoveredTests
|> _.Values
|> Seq.collect (Seq.map (fun testCase -> testCase.Id, testCase))
|> Map
|> JsonConvert.SerializeObject
|> testsWriter.WriteLine

discoveredTests.Add(file, testCases))
use waitFileWriter = new StreamWriter(waitFilePath, append = false)
waitFileWriter.WriteLine("1")

member _.HandleDiscoveryComplete(_, _) = discoveryCompleteEvent.Set()
Console.WriteLine($"Wrote test results to {resultFilePath}")

member _.HandleLogMessage(_, _) = ()
member _.HandleRawMessage(_) = ()
member _.HandleDiscoveryComplete(_, _) = ()
member __.HandleLogMessage(_, _) = ()
member __.HandleRawMessage(_) = ()

type PlaygroundTestRunHandler(streamOutputPath, outputFilePath) =
interface ITestRunEventsHandler with
Expand Down Expand Up @@ -195,7 +205,6 @@ module TestDiscovery =
VsTestConsoleWrapper(console, ConsoleParameters(EnvironmentVariables = environmentVariables))

let testSession = TestSessionInfo()
let discoveryHandler = PlaygroundTestDiscoveryHandler()

r.StartSession()

Expand All @@ -204,23 +213,16 @@ module TestDiscovery =
while loop do
match Console.ReadLine() with
| DiscoveryRequest args ->
discoveryCompleteEvent.Reset()
r.DiscoverTests(args.Sources, sourceSettings, options, testSession, discoveryHandler)
let _ = discoveryCompleteEvent.Wait(TimeSpan.FromSeconds(30))

use testsWriter = new StreamWriter(args.OutputPath, append = false)

discoveredTests
|> _.Values
|> Seq.collect (Seq.map (fun testCase -> testCase.Id, testCase))
|> Map
|> JsonConvert.SerializeObject
|> testsWriter.WriteLine
// spawn as task to allow running discovery
task {
do! Task.Yield()

use waitFileWriter = new StreamWriter(args.WaitFile, append = false)
waitFileWriter.WriteLine("1")
let discoveryHandler =
PlaygroundTestDiscoveryHandler(args.OutputPath, args.WaitFile) :> ITestDiscoveryEventsHandler2

Console.WriteLine($"Wrote test results to {args.OutputPath}")
r.DiscoverTests(args.Sources, sourceSettings, options, testSession, discoveryHandler)
}
|> ignore
| RunTests args ->
let idMap =
discoveredTests
Expand Down

0 comments on commit db99a2c

Please sign in to comment.