From c5c6e10963375ab799b39f334d75b5e91b70298d Mon Sep 17 00:00:00 2001 From: Nikolaj Sidorenco Date: Mon, 2 Dec 2024 19:19:55 +0100 Subject: [PATCH] Add testing * improve position detection * add tests * update workflow * add treesitter cli to workflow * bump lower bound nvim version * test on all os * set dotnet version --- .busted | 13 ++++ .github/workflows/main.yml | 59 ++++++++-------- .gitignore | 7 ++ .luacheckrc | 10 +++ Makefile | 12 +--- lua/neotest-dotnet/init.lua | 10 +-- lua/neotest-dotnet/vstest_wrapper.lua | 33 +++++---- neotest-dotnet-scm-1.rockspec | 27 ++++++++ run_tests.fsx => scripts/run_tests.fsx | 12 ++-- spec/installation_spec.lua | 12 ++++ spec/root_detection_spec.lua | 20 ++++++ spec/samples/test_project/project.fsproj | 0 spec/samples/test_solution/fsharp-test.sln | 34 ++++++++++ .../src/CSharpTest/CSharpTest.csproj | 23 +++++++ .../test_solution/src/CSharpTest/UnitTest1.cs | 10 +++ .../src/FsharpTest/FsharpTest.fsproj | 27 ++++++++ .../test_solution/src/FsharpTest/Program.fs | 1 + .../test_solution/src/FsharpTest/Tests.fs | 53 +++++++++++++++ .../src/FsharpTest/TestsNUnit.fs | 23 +++++++ spec/test_detection_spec.lua | 67 +++++++++++++++++++ 20 files changed, 388 insertions(+), 65 deletions(-) create mode 100644 .busted create mode 100644 .luacheckrc create mode 100644 neotest-dotnet-scm-1.rockspec rename run_tests.fsx => scripts/run_tests.fsx (96%) create mode 100644 spec/installation_spec.lua create mode 100644 spec/root_detection_spec.lua create mode 100644 spec/samples/test_project/project.fsproj create mode 100644 spec/samples/test_solution/fsharp-test.sln create mode 100644 spec/samples/test_solution/src/CSharpTest/CSharpTest.csproj create mode 100644 spec/samples/test_solution/src/CSharpTest/UnitTest1.cs create mode 100644 spec/samples/test_solution/src/FsharpTest/FsharpTest.fsproj create mode 100644 spec/samples/test_solution/src/FsharpTest/Program.fs create mode 100644 spec/samples/test_solution/src/FsharpTest/Tests.fs create mode 100644 spec/samples/test_solution/src/FsharpTest/TestsNUnit.fs create mode 100644 spec/test_detection_spec.lua diff --git a/.busted b/.busted new file mode 100644 index 0000000..ed81890 --- /dev/null +++ b/.busted @@ -0,0 +1,13 @@ +return { + _all = { + coverage = false, + lpath = "lua/?.lua;lua/?/init.lua", + lua = "nlua", + }, + default = { + verbose = true, + }, + tests = { + verbose = true, + }, +} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d69f201..091452d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,53 +23,48 @@ jobs: version: latest args: --check . - #documentation: - # runs-on: ubuntu-latest - # name: documentation - # steps: - # - uses: actions/checkout@v3 -# - # - name: setup neovim - # uses: rhysd/action-setup-vim@v1 - # with: - # neovim: true - # version: v0.8.2 - - # - name: generate documentation - # run: make documentation-ci - - # - name: check docs diff - # run: exit $(git diff --name-only origin/main -- doc | wc -l) - tests: - needs: + needs: - lint #- documentation runs-on: ubuntu-latest timeout-minutes: 2 strategy: matrix: - neovim_version: ['v0.9.1', 'v0.9.4', 'v0.10.0', 'nightly'] + os: [ubuntu-latest, macos-latest, windows-latest] + neovim_version: ["v0.10.0"] + include: + - os: ubuntu-latest + neovim_version: "nightly" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: "9.0.x" - - run: date +%F > todays-date + - name: Install C/C++ Compiler + uses: rlalik/setup-cpp-compiler@master + with: + compiler: clang-latest - - name: restore cache for today's nightly. - uses: actions/cache@v3 + - name: Install tree-sitter CLI + uses: baptiste0928/cargo-install@v3 with: - path: _neovim - key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }} + crate: tree-sitter-cli - - name: setup neovim - uses: rhysd/action-setup-vim@v1 + - name: Run tests + id: test + uses: nvim-neorocks/nvim-busted-action@v1 with: - neovim: true - version: ${{ matrix.neovim_version }} + nvim_version: ${{ matrix.neovim_version }} - - name: run tests - run: make test-ci + - name: Save neotest log + if: always() && steps.test.outcome == 'failure' + uses: actions/upload-artifact@v4 + with: + name: neotest-log-${{ matrix.neovim_version }}-${{ matrix.os }} + path: ~/.local/state/nvim/neotest.log release: name: release diff --git a/.gitignore b/.gitignore index ea910b6..8465e2a 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,10 @@ luac.out # Test dependencies deps/ **/obj/* +/luarocks +/lua +/lua_modules +/.luarocks + +obj/ +bin/ diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..8a1f518 --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,10 @@ +ignore = { + "631", -- max_line_length + "122", -- read-only field of global variable +} +read_globals = { + "vim", + "describe", + "it", + "assert", +} diff --git a/Makefile b/Makefile index 6a586d5..7bc3cba 100644 --- a/Makefile +++ b/Makefile @@ -5,17 +5,7 @@ all: # runs all the test files. test: - nvim --version | head -n 1 && echo '' - ./tests/test.sh - -# installs `mini.nvim`, used for both the tests and documentation. -deps: - @mkdir -p deps - git clone --depth 1 https://github.com/echasnovski/mini.doc.git deps/mini.doc.nvim - git clone --depth 1 https://github.com/nvim-neotest/neotest.git deps/neotest - git clone --depth 1 https://github.com/nvim-lua/plenary.nvim.git deps/plenary - git clone --depth 1 https://github.com/nvim-treesitter/nvim-treesitter.git deps/nvim-treesitter - git clone --depth 1 https://github.com/nvim-neotest/nvim-nio deps/nvim-nio + luarocks test --local # installs deps before running tests, useful for the CI. test-ci: deps test diff --git a/lua/neotest-dotnet/init.lua b/lua/neotest-dotnet/init.lua index 0b37dbb..2043562 100644 --- a/lua/neotest-dotnet/init.lua +++ b/lua/neotest-dotnet/init.lua @@ -75,9 +75,6 @@ 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") - local filetype = (vim.endswith(path, ".fs") and "fsharp") or "c_sharp" local tests_in_file = vstest.discover_tests(path) @@ -94,8 +91,11 @@ DotnetNeotestAdapter.discover_positions = function(path) local root = lib.treesitter.fast_parse(lang_tree):root() - local query = - lib.treesitter.normalise_query(lang, filetype == "fsharp" and fsharp_query or c_sharp_query) + local query = lib.treesitter.normalise_query( + lang, + filetype == "fsharp" and require("neotest-dotnet.queries.fsharp") + or require("neotest-dotnet.queries.c_sharp") + ) local sep = lib.files.sep local path_elems = vim.split(path, sep, { plain = true }) diff --git a/lua/neotest-dotnet/vstest_wrapper.lua b/lua/neotest-dotnet/vstest_wrapper.lua index 5b786a2..410f79f 100644 --- a/lua/neotest-dotnet/vstest_wrapper.lua +++ b/lua/neotest-dotnet/vstest_wrapper.lua @@ -34,6 +34,8 @@ end local function get_script(script_name) local script_paths = vim.api.nvim_get_runtime_file(script_name, true) + logger.debug("possible scripts:") + logger.debug(script_paths) for _, path in ipairs(script_paths) do if vim.endswith(path, ("neotest-dotnet%s" .. script_name):format(lib.files.sep)) then return path @@ -41,16 +43,10 @@ local function get_script(script_name) end end -local proj_file_path_map = {} - ---collects project information based on file ---@param path string ---@return { proj_file: string, dll_file: string, proj_dir: string } function M.get_proj_info(path) - if proj_file_path_map[path] then - return proj_file_path_map[path] - end - local proj_file = vim.fs.find(function(name, _) return name:match("%.[cf]sproj$") end, { upward = true, type = "file", path = vim.fs.dirname(path) })[1] @@ -70,7 +66,6 @@ function M.get_proj_info(path) proj_dir = dir_name, } - proj_file_path_map[path] = proj_data return proj_data end @@ -83,9 +78,12 @@ local function invoke_test_runner(command) return end - local test_discovery_script = get_script("run_tests.fsx") + local test_discovery_script = get_script("scripts/run_tests.fsx") local testhost_dll = get_vstest_path() + logger.debug("found discovery script: " .. test_discovery_script) + logger.debug("found testhost dll: " .. testhost_dll) + local vstest_command = { "dotnet", "fsi", test_discovery_script, testhost_dll } logger.info("starting vstest console with:") @@ -94,8 +92,12 @@ local function invoke_test_runner(command) local process = vim.system(vstest_command, { stdin = true, stdout = function(err, data) - logger.trace(data) - logger.trace(err) + if data then + logger.trace(data) + end + if err then + logger.trace(err) + end end, }, function(obj) logger.warn("vstest process died :(") @@ -165,7 +167,7 @@ function M.discover_tests(path) local json local proj_info = M.get_proj_info(path) - if not (proj_info.proj_file and proj_info.dll_file) then + if not proj_info.proj_file then logger.warn(string.format("failed to find project file for %s", path)) return {} end @@ -189,6 +191,13 @@ function M.discover_tests(path) logger.debug(stdout) end + proj_info = M.get_proj_info(path) + + if not proj_info.dll_file then + logger.warn(string.format("failed to find project dll for %s", path)) + return {} + end + local dll_open_err, dll_stats = nio.uv.fs_stat(proj_info.dll_file) assert(not dll_open_err, dll_open_err) @@ -286,7 +295,7 @@ function M.discover_tests(path) logger.debug("Waiting for result file to populated...") - local max_wait = 30 * 1000 -- 30 sec + local max_wait = 60 * 1000 -- 60 sec local done = M.spin_lock_wait_file(wait_file, max_wait) if done then diff --git a/neotest-dotnet-scm-1.rockspec b/neotest-dotnet-scm-1.rockspec new file mode 100644 index 0000000..b81ec30 --- /dev/null +++ b/neotest-dotnet-scm-1.rockspec @@ -0,0 +1,27 @@ +rockspec_format = "3.0" +package = "neotest-dotnet" +version = "scm-1" + +dependencies = { + "lua >= 5.1", + "neotest", + "tree-sitter-fsharp", + "tree-sitter-c_sharp", +} + +test_dependencies = { + "lua >= 5.1", + "busted", + "nlua", +} + +source = { + url = "git://github.com/issafalcon/neotest-dotnet", +} + +build = { + type = "builtin", + copy_directories = { + "scripts", + }, +} diff --git a/run_tests.fsx b/scripts/run_tests.fsx similarity index 96% rename from run_tests.fsx rename to scripts/run_tests.fsx index 738b03e..731a451 100644 --- a/run_tests.fsx +++ b/scripts/run_tests.fsx @@ -62,10 +62,11 @@ module TestDiscovery = ValueOption.None let logHandler (level: TestMessageLevel) (message: string) = - if level = TestMessageLevel.Error then - Console.Error.WriteLine(message) - else - Console.WriteLine(message) + if not <| String.IsNullOrWhiteSpace message then + if level = TestMessageLevel.Error then + Console.Error.WriteLine(message) + else + Console.WriteLine(message) type TestCaseDto = { CodeFilePath: string @@ -170,7 +171,7 @@ module TestDiscovery = member _.AttachDebuggerToProcess(pid: int, ct: CancellationToken) = use cts = CancellationTokenSource.CreateLinkedTokenSource(ct) - cts.CancelAfter(TimeSpan.FromSeconds(450)) + cts.CancelAfter(TimeSpan.FromSeconds(450.)) do Console.WriteLine($"spawned test process with pid: {pid}") @@ -234,6 +235,7 @@ module TestDiscovery = PlaygroundTestDiscoveryHandler() :> ITestDiscoveryEventsHandler2 for source in args.Sources do + Console.WriteLine($"Discovering tests for: {source}") r.DiscoverTests([| source |], sourceSettings, options, testSession, discoveryHandler) use testsWriter = new StreamWriter(args.OutputPath, append = false) diff --git a/spec/installation_spec.lua b/spec/installation_spec.lua new file mode 100644 index 0000000..01ce53b --- /dev/null +++ b/spec/installation_spec.lua @@ -0,0 +1,12 @@ +describe("Test environment", function() + it("Test can access vim namespace", function() + assert(vim, "Cannot access vim namespace") + assert.are.same(vim.trim(" a "), "a") + end) + it("Test can access neotest dependency", function() + assert(require("neotest"), "neotest") + end) + it("Test can access module in lua/neotest-dotnet", function() + assert(require("neotest-dotnet"), "Could not access main module") + end) +end) diff --git a/spec/root_detection_spec.lua b/spec/root_detection_spec.lua new file mode 100644 index 0000000..d64ecd4 --- /dev/null +++ b/spec/root_detection_spec.lua @@ -0,0 +1,20 @@ +describe("Test root detection", function() + it("Detect .sln file as root", function() + local plugin = require("neotest-dotnet") + local dir = vim.fn.getcwd() .. "/spec/samples/test_solution" + local root = plugin.root(dir) + assert.are_equal(dir, root) + end) + it("Detect .sln file as root from project dir", function() + local plugin = require("neotest-dotnet") + local dir = vim.fn.getcwd() .. "/spec/samples/test_solution" + local root = plugin.root(dir .. "/src/FsharpTest") + assert.are_equal(dir, root) + end) + it("Detect .fsproj file as root from project dir with no .sln file", function() + local plugin = require("neotest-dotnet") + local dir = vim.fn.getcwd() .. "/spec/samples/test_project" + local root = plugin.root(dir) + assert.are_equal(dir, root) + end) +end) diff --git a/spec/samples/test_project/project.fsproj b/spec/samples/test_project/project.fsproj new file mode 100644 index 0000000..e69de29 diff --git a/spec/samples/test_solution/fsharp-test.sln b/spec/samples/test_solution/fsharp-test.sln new file mode 100644 index 0000000..ddbc660 --- /dev/null +++ b/spec/samples/test_solution/fsharp-test.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{364BD0DC-1C6E-4811-BC58-D543DB1E67D2}" +EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FsharpTest", "src\FsharpTest\FsharpTest.fsproj", "{FFB89E81-0B57-4A30-9836-DC83EFD2ADA3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpTest", "src\CSharpTest\CSharpTest.csproj", "{D0B0861B-D9E5-4EA5-8CAE-0CDCF0054021}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FFB89E81-0B57-4A30-9836-DC83EFD2ADA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFB89E81-0B57-4A30-9836-DC83EFD2ADA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFB89E81-0B57-4A30-9836-DC83EFD2ADA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFB89E81-0B57-4A30-9836-DC83EFD2ADA3}.Release|Any CPU.Build.0 = Release|Any CPU + {D0B0861B-D9E5-4EA5-8CAE-0CDCF0054021}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0B0861B-D9E5-4EA5-8CAE-0CDCF0054021}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0B0861B-D9E5-4EA5-8CAE-0CDCF0054021}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0B0861B-D9E5-4EA5-8CAE-0CDCF0054021}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {FFB89E81-0B57-4A30-9836-DC83EFD2ADA3} = {364BD0DC-1C6E-4811-BC58-D543DB1E67D2} + {D0B0861B-D9E5-4EA5-8CAE-0CDCF0054021} = {364BD0DC-1C6E-4811-BC58-D543DB1E67D2} + EndGlobalSection +EndGlobal diff --git a/spec/samples/test_solution/src/CSharpTest/CSharpTest.csproj b/spec/samples/test_solution/src/CSharpTest/CSharpTest.csproj new file mode 100644 index 0000000..3aa9860 --- /dev/null +++ b/spec/samples/test_solution/src/CSharpTest/CSharpTest.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + diff --git a/spec/samples/test_solution/src/CSharpTest/UnitTest1.cs b/spec/samples/test_solution/src/CSharpTest/UnitTest1.cs new file mode 100644 index 0000000..5df4325 --- /dev/null +++ b/spec/samples/test_solution/src/CSharpTest/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace CSharpTest; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +} \ No newline at end of file diff --git a/spec/samples/test_solution/src/FsharpTest/FsharpTest.fsproj b/spec/samples/test_solution/src/FsharpTest/FsharpTest.fsproj new file mode 100644 index 0000000..7fc752d --- /dev/null +++ b/spec/samples/test_solution/src/FsharpTest/FsharpTest.fsproj @@ -0,0 +1,27 @@ + + + + net8.0 + + false + false + true + + + + + + + + + + + + + + + + + + + diff --git a/spec/samples/test_solution/src/FsharpTest/Program.fs b/spec/samples/test_solution/src/FsharpTest/Program.fs new file mode 100644 index 0000000..fdc31cd --- /dev/null +++ b/spec/samples/test_solution/src/FsharpTest/Program.fs @@ -0,0 +1 @@ +module Program = let [] main _ = 0 diff --git a/spec/samples/test_solution/src/FsharpTest/Tests.fs b/spec/samples/test_solution/src/FsharpTest/Tests.fs new file mode 100644 index 0000000..ab7a263 --- /dev/null +++ b/spec/samples/test_solution/src/FsharpTest/Tests.fs @@ -0,0 +1,53 @@ +namespace X.Tests + +open Xunit +open System.Threading.Tasks + +module A = + + [] + let ``My test`` () = + let fx x = + let x = 1 + Assert.True(false) + + fx () + + [] + let ``My test 2`` () = + let x = 1 + Assert.True(false) + + [] + let ``My test 3`` () = + let x = 1 + Assert.True(false) + + [] + let ``My slow test`` () = + task { + do! Task.Delay(10000) + Assert.True(true) + } + + [] + [] + [] + let ``Pass cool test parametrized function`` x _y _z = Assert.True(x > 0) + + + let notATest () = () + + +type ``X Should``() = + [] + member _.``Pass cool test``() = + do () + do () + do () + do () + Assert.True(true) + + [] + [] + member _.``Pass cool test parametrized``(x, _y, _z) = Assert.True(x > 0) diff --git a/spec/samples/test_solution/src/FsharpTest/TestsNUnit.fs b/spec/samples/test_solution/src/FsharpTest/TestsNUnit.fs new file mode 100644 index 0000000..1a83690 --- /dev/null +++ b/spec/samples/test_solution/src/FsharpTest/TestsNUnit.fs @@ -0,0 +1,23 @@ +namespace N.Tests + +open NUnit.Framework + +module A = + + [] + let ``My test`` () = + let x = 1 + let y = 2 + Assert.Pass() + + [] + [] + let ``Pass cool x parametrized function`` x _y _z = Assert.That(x > 0) + +[] +type ``X Should``() = + [] + member _.``Pass cool x``() = Assert.Pass() + + [] + member _.``Pass cool x parametrized``(x, _y, _z) = Assert.That(x > 0) diff --git a/spec/test_detection_spec.lua b/spec/test_detection_spec.lua new file mode 100644 index 0000000..aead8cf --- /dev/null +++ b/spec/test_detection_spec.lua @@ -0,0 +1,67 @@ +describe("Test test detection", function() + -- increase nio.test timeout + vim.env.PLENARY_TEST_TIMEOUT = 20000 + -- add test_discovery script and treesitter parsers installed with luarocks + vim.opt.runtimepath:append(vim.fn.getcwd()) + vim.opt.runtimepath:append(vim.fn.expand("~/.luarocks/lib/lua/5.1/")) + + local nio = require("nio") + + require("neotest").setup({ + adapters = { require("neotest-dotnet") }, + log_level = 0, + }) + + nio.tests.it("detect tests in fsharp file", function() + local plugin = require("neotest-dotnet") + local dir = vim.fn.getcwd() .. "/spec/samples/test_solution" + local test_file = dir .. "/src/FsharpTest/Tests.fs" + local positions = plugin.discover_positions(test_file) + + local tests = {} + + for _, position in positions:iter() do + if position.type == "test" then + tests[#tests + 1] = position.name + end + end + + local expected_tests = { + "X.Tests.A.My test", + "X.Tests.A.My test 2", + "X.Tests.A.My test 3", + "X.Tests.A.My slow test", + "X.Tests.A.Pass cool test parametrized function(x: 11, _y: 22, _z: 33)", + "X.Tests.A.Pass cool test parametrized function(x: 10, _y: 20, _z: 30)", + "X.Tests.X Should.Pass cool test", + "X.Tests.X Should.Pass cool test parametrized(x: 10, _y: 20, _z: 30)", + } + + table.sort(expected_tests) + table.sort(tests) + + assert.are_same(expected_tests, tests) + end) + + nio.tests.it("detect tests in c_sharp file", function() + local plugin = require("neotest-dotnet") + local dir = vim.fn.getcwd() .. "/spec/samples/test_solution" + local test_file = dir .. "/src/CSharpTest/UnitTest1.cs" + local positions = plugin.discover_positions(test_file) + + local tests = {} + + for _, position in positions:iter() do + if position.type == "test" then + tests[#tests + 1] = position.name + end + end + + local expected_tests = { "CSharpTest.UnitTest1.Test1" } + + table.sort(expected_tests) + table.sort(tests) + + assert.are_same(expected_tests, tests) + end) +end)