Skip to content

Commit 9c6cec0

Browse files
author
Cody Mikol
committed
feat(*): first working release
now we can display tests in a given spec run tests in a given file display test resuts Fixes N/A
1 parent ccb751d commit 9c6cec0

16 files changed

+750
-164
lines changed

lua/kotest-treesitter-query.lua

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ TreesitterQuery.value = [[
88
99
;; --- DESCRIBE SPEC ---
1010
11-
; Matches describe("context") { /** body **/ }
11+
; Matches namespace describe("context") { /** body **/ }
1212
1313
(call_expression
1414
(call_expression
@@ -23,7 +23,7 @@ TreesitterQuery.value = [[
2323
)
2424
) @namespace.definition
2525
26-
; Matches it("context") { /** body **/ }
26+
; Matches test it("context") { /** body **/ }
2727
2828
(call_expression
2929
(call_expression
@@ -38,6 +38,10 @@ TreesitterQuery.value = [[
3838
)
3939
) @test.definition
4040
41+
; todo Mathes xdescribe("context") { /** body **/ }
42+
43+
; todo Mathes xit("context") { /** body **/ }
44+
4145
;; -- todo FUN SPEC --
4246
;; -- todo SHOULD SPEC --
4347
;; -- todo STRING SPEC --

lua/neotest-kotest.lua

Lines changed: 91 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,205 +1,134 @@
11
local lib = require("neotest.lib")
2+
local position_parser = require("src.position-parser")
3+
local package_query = require("src.treesitter.package-query")
4+
local class_query = require("src.treesitter.class-query")
25
local async = require("neotest.async")
3-
6+
local command = require("src.command")
7+
local filter = require("src.filter")
48
local treesitter_query = require("kotest-treesitter-query")
9+
local output_parser = require("src.output-parser")
510

611
local adapter = { name = "neotest-kotest" }
712

8-
---Find the project root directory given a current directory to work from.
9-
---Should no root be found, the adapter can still be used in a non-project context if a test file matches.
10-
---@async
11-
---@param dir string @Directory to treat as cwd
12-
---@return string | nil @Absolute root dir of test suite
1313
function adapter.root(dir)
14-
return lib.files.match_root_pattern("build.gradle.kts")(dir)
14+
return lib.files.match_root_pattern("build.gradle.kts")(dir)
1515
end
1616

17-
local ignored_directories =
18-
{ "docs", "build", "out", "generated", ".gradle", "main", ".idea", "buildSrc", "kapt", "taret" }
19-
20-
---Filter directories when searching for test files
21-
---@async
22-
---@param name string Name of directory
23-
---@param rel_path string Path to directory, relative to root
24-
---@param root string Root directory of project
25-
---@return boolean
2617
function adapter.filter_dir(name, rel_path, root)
27-
for _, v in ipairs(ignored_directories) do
28-
if v == name then
29-
return false
30-
end
31-
end
32-
return true
18+
return filter.test_directory(name)
3319
end
3420

35-
---@async
36-
---@param file_path string
37-
---@return boolean
3821
function adapter.is_test_file(file_path)
39-
if file_path == nil then
40-
return false
41-
end
42-
43-
if not vim.endswith(file_path, ".kt") then
44-
return false
45-
end
46-
47-
if string.find(file_path, "src/main") then
48-
return false
49-
end
50-
51-
return true
22+
return filter.is_test_file(file_path)
5223
end
5324

5425
local function get_match_type(captured_nodes)
55-
if captured_nodes["namespace.name"] then
56-
return "namespace"
57-
end
58-
if captured_nodes["test.name"] then
59-
return "test"
60-
end
26+
if captured_nodes["namespace.name"] then
27+
return "namespace"
28+
end
29+
if captured_nodes["test.name"] then
30+
return "test"
31+
end
6132
end
6233

6334
function adapter.build_position(file_path, source, captured_nodes)
64-
local match_type = get_match_type(captured_nodes)
65-
local definition = captured_nodes[match_type .. ".definition"]
35+
local match_type = get_match_type(captured_nodes)
36+
local definition = captured_nodes[match_type .. ".definition"]
6637

67-
local build_position = {
68-
type = match_type,
69-
path = file_path,
70-
range = { definition:range() },
71-
}
38+
local build_position = {
39+
type = match_type,
40+
path = file_path,
41+
range = { definition:range() },
42+
}
7243

73-
return build_position
44+
return build_position
7445
end
7546

76-
---Given a file path, parse all the tests within it.
47+
--- a file path, parse all the tests within it.
7748
---@async
7849
---@param file_path string Absolute file path
7950
---@return neotest.Tree | nil
8051
function adapter.discover_positions(path)
81-
local positions = lib.treesitter.parse_positions(path, treesitter_query.value, {
82-
nested_namespaces = true,
83-
nested_tests = false,
84-
-- build_position = 'require("neotest-kotest").build_position',
85-
})
86-
87-
return positions
88-
end
89-
90-
function get_package_name(file_path)
91-
local package_name_query = "(package_header (identifier) @package.name)"
92-
93-
local file = io.open(file_path)
94-
95-
if file == nil then
96-
return "*"
97-
end
98-
99-
local code = file:read("*all")
100-
101-
local new_buffer_number = vim.api.nvim_create_buf(false, true)
102-
vim.api.nvim_buf_set_lines(new_buffer_number, 0, -1, false, vim.split(code, "\n"))
103-
104-
file:close()
105-
106-
local language = "kotlin"
107-
108-
local parser = vim.treesitter.get_string_parser(code, language)
109-
local tree = parser:parse()
110-
local root = tree[1]:root()
111-
112-
local query = vim.treesitter.query.parse(language, package_name_query)
113-
114-
for _, match, _ in query:iter_matches(root, new_buffer_number, root:start(), root:end_()) do
115-
for _, node in pairs(match) do
116-
local start_row, start_col = node:start()
117-
local end_row, end_col = node:end_()
118-
119-
-- string:sub is 1 indexed, but the nodes apis return 0 indexed jawns...
120-
-- effectively making this a river of brain melting sadness
121-
local text = code:sub(start_row + 2, end_row - 1):sub(start_col, end_col - 1)
122-
123-
return text
124-
end
125-
end
126-
127-
-- local package_name = matches[0].captures["package.name"][1]
128-
129-
-- vim.inspect(package_name)
130-
131-
return nil
52+
local positions = lib.treesitter.parse_positions(path, treesitter_query.value, {
53+
nested_namespaces = true,
54+
nested_tests = false,
55+
})
56+
return positions
13257
end
13358

13459
---@param args neotest.run.RunArgs
13560
---@return nil | neotest.RunSpec | neotest.RunSpec[]
13661
function adapter.build_spec(args)
137-
local results_path = async.fn.tempname() .. ".json"
138-
139-
-- Write something so there is a place to stream to...
140-
lib.files.write(results_path, "")
141-
142-
local tree = args.tree
143-
144-
if not tree then
145-
return
146-
end
147-
148-
local pos = tree:data()
149-
150-
local root = adapter.root(pos.path)
151-
local spec = get_package_name(pos.path)
152-
local test = "*"
153-
154-
local command_three = "export kotest_filter_tests='"
155-
.. test
156-
.. "'; export kotest_filter_specs='"
157-
.. spec
158-
.. "'; ./gradlew clean test --info >> "
159-
.. results_path
160-
161-
local stream_data, stop_stream = lib.files.stream(results_path)
162-
163-
return {
164-
command = command_three,
165-
cwd = root,
166-
context = {
167-
results_path = results_path,
168-
file = pos.path,
169-
stop_stream = stop_stream,
170-
},
171-
stream = function()
172-
return function()
173-
print("streaming...")
174-
local new_results = stream_data()
175-
176-
local tests = {}
177-
178-
tests["foo"] = {
179-
status = "skipped",
180-
short = "something goofy",
181-
output = "comnsole out",
182-
location = "test",
183-
}
184-
185-
return tests
186-
end
187-
end,
188-
}
62+
local results_path = async.fn.tempname() .. ".json"
63+
64+
-- Write something so there is a place to stream to...
65+
lib.files.write(results_path, "")
66+
67+
local tree = args.tree
68+
69+
if not tree then
70+
return
71+
end
72+
73+
local pos = tree:data()
74+
75+
local root = adapter.root(pos.path)
76+
local pkg = position_parser.get_first_match_string(pos.path, package_query)
77+
print("pkg!!!: " .. vim.inspect(pkg))
78+
local className = position_parser.get_first_match_string(pos.path, class_query)
79+
print("class name!!!: " .. vim.inspect(className))
80+
local specPackage = pkg .. "." .. className
81+
print("specPackage: " .. specPackage)
82+
local tests = "*"
83+
84+
local gradle_command = command.parse(tests, specPackage, results_path)
85+
86+
local stream_data, stop_stream = lib.files.stream_lines(results_path)
87+
88+
print("command: " .. gradle_command)
89+
90+
local all_results = {}
91+
92+
return {
93+
command = gradle_command,
94+
cwd = root,
95+
context = {
96+
all_results = all_results,
97+
results_path = results_path,
98+
file = pos.path,
99+
stop_stream = stop_stream,
100+
},
101+
stream = function()
102+
return function()
103+
local new_results = stream_data()
104+
local success, parsed_result = pcall(output_parser.lines_to_results, new_results, pos.path, specPackage)
105+
if not success then
106+
print("An error ocurred while attempting to stream data to result: " ..
107+
vim.inspect(err) .. " new_results: " .. vim.inspect(new_results))
108+
return nil
109+
else
110+
for k, v in pairs(parsed_result) do all_results[k] = v end
111+
return parsed_result
112+
end
113+
end
114+
end,
115+
}
189116
end
190117

118+
---@class neotest.Result
119+
---@field status neotest.ResultStatus
120+
---@field output? string Path to file containing full output data
121+
---@field short? string Shortened output string
122+
---@field errors? neotest.Error[]
123+
191124
---@async
192125
---@param spec neotest.RunSpec
193126
---@param result neotest.StrategyResult
194127
---@param tree neotest.Tree
195128
---@return table<string, neotest.Result>
196129
function adapter.results(spec, result, tree)
197-
print("In results")
198-
199-
print(result)
200-
201-
spec.context.stop_stream()
202-
return { "test", {} }
130+
spec.context.stop_stream()
131+
return spec.context.all_results
203132
end
204133

205134
return adapter

lua/src/command.lua

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
local M = {}
2+
3+
-- tests is the name of the test block
4+
-- specs is the package name of the file you are interpreting
5+
-- outfile is where the test output will be written to.
6+
M.parse = function(tests, specs, outfile)
7+
return "export kotest_filter_tests='"
8+
.. tests
9+
.. "'; export kotest_filter_specs='"
10+
.. specs
11+
.. "'; ./gradlew cleanTest test --debug --console=plain | tee -a "
12+
.. outfile
13+
end
14+
15+
return M

lua/src/filter.lua

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
local M = {}
2+
3+
local ignored_directories =
4+
{ "docs", "build", "out", "generated", ".gradle", "main", ".idea", "buildSrc", "kapt", "taret" }
5+
6+
-- This filters out non-test directories that would bog down scnanning.
7+
---@param path string Name of directory
8+
M.is_test_directory = function(path)
9+
for _, v in ipairs(ignored_directories) do
10+
if v == path then
11+
return false
12+
end
13+
end
14+
return true
15+
end
16+
17+
M.is_test_file = function(file_path)
18+
if file_path == nil then
19+
return false
20+
end
21+
22+
if not vim.endswith(file_path, ".kt") then
23+
return false
24+
end
25+
26+
if string.find(file_path, "src/main") then
27+
return false
28+
end
29+
30+
return true
31+
end
32+
33+
return M

0 commit comments

Comments
 (0)