add basic lsp tests

This commit is contained in:
Stone Tickle 2025-10-05 13:56:23 -04:00
parent b16c29ab20
commit 9a9ad1ff2d
31 changed files with 1053 additions and 0 deletions

View File

@ -21,6 +21,12 @@ precedence = "aggregate"
SPDX-FileCopyrightText = "Stone Tickle <lattis@mochiro.moe>"
SPDX-License-Identifier = "GPL-3.0-only"
[[annotations]]
path = "tests/lsp/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "Stone Tickle <lattis@mochiro.moe>"
SPDX-License-Identifier = "GPL-3.0-only"
[[annotations]]
path = "doc/book/**"
precedence = "aggregate"

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1,8 @@
[
{
"method": "muon/textDocument/didOpen",
"params": {
"relUri": "meson.build"
}
}
]

View File

@ -0,0 +1,3 @@
project('foobar')
unused_var = 1

View File

@ -0,0 +1,24 @@
[
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": [
{
"range": {
"start": {
"line": 2,
"character": 0
},
"end": {
"line": 2,
"character": 10
}
},
"severity": 2,
"message": "unused variable unused_var"
}
]
}
}
]

View File

@ -0,0 +1,14 @@
[
{
"method": "muon/textDocument/didOpen",
"params": {
"relUri": "meson.build"
}
},
{
"method": "muon/textDocument/didChange",
"params": {
"relUri": "meson.build"
}
}
]

View File

@ -0,0 +1,3 @@
project('foobar')
unused_var = 1

View File

@ -0,0 +1,4 @@
project('foobar')
unused_var = 1
message(unused_var)

View File

@ -0,0 +1,31 @@
[
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": [
{
"range": {
"start": {
"line": 2,
"character": 0
},
"end": {
"line": 2,
"character": 10
}
},
"severity": 2,
"message": "unused variable unused_var"
}
]
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": []
}
}
]

View File

@ -0,0 +1,23 @@
[
{
"method": "muon/textDocument/didOpen",
"params": {
"relUri": "meson.build"
}
},
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } },
{ "method": "muon/textDocument/didChange", "params": { "relUri": "meson.build" } }
]

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
executable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
xecutable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
executable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
xecutable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
executable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
xecutable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
executable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
xecutable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
executable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
xecutable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
executable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
xecutable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
executable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
xecutable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
executable('l', sources: f)

View File

@ -0,0 +1,31 @@
project('foobar')
add_languages('c')
f = files(
'a.c',
'b.c',
'c.c',
'd.c',
'e.c',
'f.c',
'g.c',
'h.c',
'i.c',
'j.c',
'k.c',
'l.c',
)
executable('a', sources: f)
executable('b', sources: f)
executable('c', sources: f)
executable('d', sources: f)
executable('e', sources: f)
executable('f', sources: f)
executable('g', sources: f)
executable('h', sources: f)
executable('i', sources: f)
executable('j', sources: f)
executable('k', sources: f)
xecutable('l', sources: f)

View File

@ -0,0 +1,227 @@
[
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": [
{
"range": {
"start": {
"line": 30,
"character": 0
},
"end": {
"line": 30,
"character": 9
}
},
"severity": 1,
"message": "undefined object xecutable"
}
]
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": []
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": [
{
"range": {
"start": {
"line": 30,
"character": 0
},
"end": {
"line": 30,
"character": 9
}
},
"severity": 1,
"message": "undefined object xecutable"
}
]
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": []
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": [
{
"range": {
"start": {
"line": 30,
"character": 0
},
"end": {
"line": 30,
"character": 9
}
},
"severity": 1,
"message": "undefined object xecutable"
}
]
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": []
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": [
{
"range": {
"start": {
"line": 30,
"character": 0
},
"end": {
"line": 30,
"character": 9
}
},
"severity": 1,
"message": "undefined object xecutable"
}
]
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": []
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": [
{
"range": {
"start": {
"line": 30,
"character": 0
},
"end": {
"line": 30,
"character": 9
}
},
"severity": 1,
"message": "undefined object xecutable"
}
]
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": []
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": [
{
"range": {
"start": {
"line": 30,
"character": 0
},
"end": {
"line": 30,
"character": 9
}
},
"severity": 1,
"message": "undefined object xecutable"
}
]
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": []
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": [
{
"range": {
"start": {
"line": 30,
"character": 0
},
"end": {
"line": 30,
"character": 9
}
},
"severity": 1,
"message": "undefined object xecutable"
}
]
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": []
}
},
{
"method": "muon/textDocument/publishDiagnostics",
"params": {
"relUri": "meson.build",
"diagnostics": [
{
"range": {
"start": {
"line": 30,
"character": 0
},
"end": {
"line": 30,
"character": 9
}
},
"severity": 1,
"message": "undefined object xecutable"
}
]
}
}
]

31
tests/lsp/meson.build Normal file
View File

@ -0,0 +1,31 @@
# SPDX-FileCopyrightText: Stone Tickle <lattis@mochiro.moe>
# SPDX-License-Identifier: GPL-3.0-only
lsp_tests = [
['basic'],
['diagnostics'],
['diagnostics_with_edit'],
['many_edits'],
]
foreach t : lsp_tests
name = t[0]
test_name = name
props = t.get(1, {})
test_args = [
'internal',
'eval',
meson.current_source_dir() / 'runner.meson',
muon,
meson.current_source_dir() / name,
meson.current_build_dir() / name,
]
test(
test_name,
muon,
args: test_args,
suite: ['lsp'],
)
endforeach

180
tests/lsp/runner.meson Normal file
View File

@ -0,0 +1,180 @@
# SPDX-FileCopyrightText: Stone Tickle <lattis@mochiro.moe>
# SPDX-License-Identifier: GPL-3.0-only
json = import('json')
fs = import('fs')
func uri(s str) -> str
return f'file://@s@'
endfunc
func initialize_msg(src_root str) -> dict[any]
return {
'method': 'initialize',
'params': {
'processId': 1,
'clientInfo': {'name': 'muon lsp tests runner'},
'locale': 'en',
'rootUri': uri(src_root),
'capabilities': {},
'initializationOptions': {},
'trace': 'verbose',
}
}
endfunc
func did_open_msg(path str) -> dict[any]
return {
'method': 'textDocument/didOpen',
'params': {
'textDocument': {
'uri': uri(path),
'languageId': 'meson',
'text': fs.read(path),
}
}
}
endfunc
func did_change_msg(path str, text_path str) -> dict[any]
return {
'method': 'textDocument/didChange',
'params': {
'textDocument': {
'uri': uri(path),
},
'contentChanges': [
{
'text': fs.read(text_path),
}
],
}
}
endfunc
func build_input(src_root str) -> str
input = [initialize_msg(src_root)] + json.parse(fs.read(src_root / 'input.json'))
file_rev = {}
tmp = []
msg_id = 1
foreach msg : input
# Replace custom messages
if msg.method == 'muon/textDocument/didOpen'
rel_uri = msg.params.relUri
msg = did_open_msg(src_root / rel_uri)
file_rev[rel_uri] = 0
elif msg.method == 'muon/textDocument/didChange'
rel_uri = msg.params.relUri
assert(rel_uri in file_rev)
rev = file_rev[rel_uri] += 1
msg = did_change_msg(src_root / rel_uri, src_root / f'@rel_uri@-@rev@')
endif
msg += {'jsonrpc': '2.0', 'id': msg_id}
msg = json.stringify(msg)
tmp += [
'Content-Length: @0@\r\n\r\n'.format(msg.length()),
msg
]
endforeach
return ''.join(tmp)
endfunc
func publish_diagnostics_msg(msg dict[any], path str) -> dict[any]
return {
'method': 'textDocument/publishDiagnostics',
'params': {
'uri': uri(path),
'diagnostics': msg.params.diagnostics,
}
}
endfunc
func parse_output(raw_output str) -> list[dict[any]]
tmp = []
foreach msg : raw_output.split('Content-Length').slice(1)
msg_parts = msg.split('\r\n\r\n')
msg = json.parse(msg_parts[1])
msg.delete('jsonrpc')
msg.delete('id')
tmp += msg
endforeach
return tmp
endfunc
func parse_expected_output(src_root str, output list[dict[any]]) -> list[dict[any]]
tmp = []
foreach msg : output
# Replace custom messages
if 'method' in msg and msg.method == 'muon/textDocument/publishDiagnostics'
msg = publish_diagnostics_msg(msg, src_root / msg.params.relUri)
endif
tmp += msg
endforeach
return tmp
endfunc
func main()
if argv.length() != 4
error('usage: @0@ <muon> <src_root> <build_root>'.format(argv[0]))
endif
muon = argv[1]
src_root = fs.make_absolute(argv[2])
build_root = fs.make_absolute(argv[3])
# build and write input
fs.mkdir(build_root)
tmp_json = build_root / 'input.json'
fs.write(tmp_json, build_input(src_root))
# run lsp
run_result = run_command(
[muon, '-v', '-C', src_root, 'analyze', 'lsp'],
feed: tmp_json,
check: true,
)
# print('got:\n')
# print('---\n')
# p(run_result.stdout())
# print('---\n')
# print(run_result.stderr())
# print('---\n')
# diff output with expected output
output = parse_output(run_result.stdout())
expected_output = parse_expected_output(src_root, json.parse(fs.read(src_root / 'output.json')))
if output.length() != expected_output.length() + 1
message('got @0@ messages but expected @1@'.format(output.length(), expected_output.length() + 1))
assert(false)
endif
foreach i : range(output.length())
if i == 0
assert(output[i].result.serverInfo.name == 'muon')
else
eq = output[i] == expected_output[i - 1]
if not eq
print('-----------------\nactual: ')
p(output[i], pretty: true)
print('!=\nexpected: ')
p(expected_output[i - 1], pretty: true)
print('-----------------\n')
endif
assert(eq)
endif
endforeach
endfunc
main()

View File

@ -7,6 +7,7 @@ add_test_setup('no_python', exclude_suites: 'requires_python')
subdir('fmt')
subdir('fuzz')
subdir('lang')
subdir('lsp')
subdir('project')
foreach script : embedded_scripts