Files
marte_dev_tools/test/lsp_completion_test.go
2026-02-02 16:09:50 +01:00

383 lines
9.1 KiB
Go

package integration
import (
"strings"
"testing"
"github.com/marte-community/marte-dev-tools/internal/index"
"github.com/marte-community/marte-dev-tools/internal/lsp"
"github.com/marte-community/marte-dev-tools/internal/parser"
"github.com/marte-community/marte-dev-tools/internal/schema"
)
func TestHandleCompletion(t *testing.T) {
setup := func() {
lsp.Tree = index.NewProjectTree()
lsp.Documents = make(map[string]string)
lsp.ProjectRoot = "."
lsp.GlobalSchema = schema.NewSchema()
}
uri := "file://test.marte"
path := "test.marte"
t.Run("Suggest Classes", func(t *testing.T) {
setup()
content := "+Obj = { Class = "
lsp.Documents[uri] = content
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 0, Character: len(content)},
}
list := lsp.HandleCompletion(params)
if list == nil || len(list.Items) == 0 {
t.Fatal("Expected class suggestions, got none")
}
found := false
for _, item := range list.Items {
if item.Label == "RealTimeApplication" {
found = true
break
}
}
if !found {
t.Error("Expected RealTimeApplication in class suggestions")
}
})
t.Run("Suggest Fields", func(t *testing.T) {
setup()
content := `
+MyApp = {
Class = RealTimeApplication
}
`
lsp.Documents[uri] = content
p := parser.NewParser(content)
cfg, _ := p.Parse()
lsp.Tree.AddFile(path, cfg)
// Position at line 3 (empty line inside MyApp)
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 3, Character: 4},
}
list := lsp.HandleCompletion(params)
if list == nil || len(list.Items) == 0 {
t.Fatal("Expected field suggestions, got none")
}
foundData := false
for _, item := range list.Items {
if item.Label == "Data" {
foundData = true
if item.Detail != "Mandatory" {
t.Errorf("Expected Data to be Mandatory, got %s", item.Detail)
}
}
}
if !foundData {
t.Error("Expected 'Data' in field suggestions for RealTimeApplication")
}
})
t.Run("Suggest References (DataSource)", func(t *testing.T) {
setup()
content := `
$App = {
$Data = {
+InDS = {
Class = FileReader
+Signals = {
Sig1 = { Type = uint32 }
}
}
}
}
+MyGAM = {
Class = IOGAM
+InputSignals = {
S1 = { DataSource = }
}
}
`
lsp.Documents[uri] = content
p := parser.NewParser(content)
cfg, _ := p.Parse()
lsp.Tree.AddFile(path, cfg)
lsp.Tree.ResolveReferences()
// Position at end of "DataSource = "
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 14, Character: 28},
}
list := lsp.HandleCompletion(params)
if list == nil || len(list.Items) == 0 {
t.Fatal("Expected DataSource suggestions, got none")
}
foundDS := false
for _, item := range list.Items {
if item.Label == "InDS" {
foundDS = true
break
}
}
if !foundDS {
t.Error("Expected 'InDS' in suggestions for DataSource field")
}
})
t.Run("Filter Existing Fields", func(t *testing.T) {
setup()
content := `
+MyThread = {
Class = RealTimeThread
Functions = { }
}
`
lsp.Documents[uri] = content
p := parser.NewParser(content)
cfg, _ := p.Parse()
lsp.Tree.AddFile(path, cfg)
// Position at line 4
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 4, Character: 4},
}
list := lsp.HandleCompletion(params)
for _, item := range list.Items {
if item.Label == "Functions" || item.Label == "Class" {
t.Errorf("Did not expect already defined field %s in suggestions", item.Label)
}
}
})
t.Run("Scope-aware suggestions", func(t *testing.T) {
setup()
// Define a project DataSource in one file
cfg1, _ := parser.NewParser("#package MYPROJ.Data\n+ProjectDS = { Class = FileReader +Signals = { S1 = { Type = int32 } } }").Parse()
lsp.Tree.AddFile("project_ds.marte", cfg1)
// Define an isolated file
contentIso := "+MyGAM = { Class = IOGAM +InputSignals = { S1 = { DataSource = } } }"
lsp.Documents["file://iso.marte"] = contentIso
cfg2, _ := parser.NewParser(contentIso).Parse()
lsp.Tree.AddFile("iso.marte", cfg2)
lsp.Tree.ResolveReferences()
// Completion in isolated file
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: "file://iso.marte"},
Position: lsp.Position{Line: 0, Character: strings.Index(contentIso, "DataSource = ") + len("DataSource = ") + 1},
}
list := lsp.HandleCompletion(params)
foundProjectDS := false
if list != nil {
for _, item := range list.Items {
if item.Label == "ProjectDS" {
foundProjectDS = true
break
}
}
}
if foundProjectDS {
t.Error("Did not expect ProjectDS in isolated file suggestions (isolation)")
}
// Completion in a project file
lineContent := "+MyGAM = { Class = IOGAM +InputSignals = { S1 = { DataSource = Dummy } } }"
contentPrj := "#package MYPROJ.App\n" + lineContent
lsp.Documents["file://prj.marte"] = contentPrj
pPrj := parser.NewParser(contentPrj)
cfg3, err := pPrj.Parse()
if err != nil {
t.Logf("Parser error in contentPrj: %v", err)
}
lsp.Tree.AddFile("prj.marte", cfg3)
lsp.Tree.ResolveReferences()
paramsPrj := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: "file://prj.marte"},
Position: lsp.Position{Line: 1, Character: strings.Index(lineContent, "Dummy")},
}
listPrj := lsp.HandleCompletion(paramsPrj)
foundProjectDS = false
if listPrj != nil {
for _, item := range listPrj.Items {
if item.Label == "ProjectDS" {
foundProjectDS = true
break
}
}
}
if !foundProjectDS {
t.Error("Expected ProjectDS in project file suggestions")
}
})
t.Run("Suggest Signal Types", func(t *testing.T) {
setup()
content := `
+DS = {
Class = FileReader
Signals = {
S1 = { Type = }
}
}
`
lsp.Documents[uri] = content
p := parser.NewParser(content)
cfg, _ := p.Parse()
lsp.Tree.AddFile(path, cfg)
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 4, Character: strings.Index(content, "Type = ") + len("Type = ") + 1},
}
list := lsp.HandleCompletion(params)
if list == nil {
t.Fatal("Expected signal type suggestions")
}
foundUint32 := false
for _, item := range list.Items {
if item.Label == "uint32" {
foundUint32 = true
break
}
}
if !foundUint32 {
t.Error("Expected uint32 in suggestions")
}
})
t.Run("Suggest CUE Enums", func(t *testing.T) {
setup()
// Inject custom schema with enum
custom := []byte(`
package schema
#Classes: {
TestEnumClass: {
Mode: "Auto" | "Manual"
}
}
`)
val := lsp.GlobalSchema.Context.CompileBytes(custom)
lsp.GlobalSchema.Value = lsp.GlobalSchema.Value.Unify(val)
content := `
+Obj = {
Class = TestEnumClass
Mode =
}
`
lsp.Documents[uri] = content
p := parser.NewParser(content)
cfg, _ := p.Parse()
lsp.Tree.AddFile(path, cfg)
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 3, Character: strings.Index(content, "Mode = ") + len("Mode = ") + 1},
}
list := lsp.HandleCompletion(params)
if list == nil {
t.Fatal("Expected enum suggestions")
}
foundAuto := false
for _, item := range list.Items {
if item.Label == "\"Auto\"" { // CUE string value includes quotes
foundAuto = true
break
}
}
if !foundAuto {
// Check if it returned without quotes?
// v.String() returns quoted for string.
t.Error("Expected \"Auto\" in suggestions")
for _, item := range list.Items {
t.Logf("Suggestion: %s", item.Label)
}
}
})
t.Run("Suggest Variables", func(t *testing.T) {
setup()
content := `
#var MyVar: uint = 10
+App = {
Field =
}
`
lsp.Documents[uri] = content
p := parser.NewParser(content)
cfg, _ := p.Parse()
lsp.Tree.AddFile(path, cfg)
// 1. Triggered by =
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 3, Character: 12}, // After "Field = "
}
list := lsp.HandleCompletion(params)
if list == nil {
t.Fatal("Expected suggestions")
}
found := false
for _, item := range list.Items {
if item.Label == "@MyVar" {
found = true
break
}
}
if !found {
t.Error("Expected @MyVar in suggestions for =")
}
// 2. Triggered by @
// "Field = @"
lsp.Documents[uri] = `
#var MyVar: uint = 10
+App = {
Field = @
}
`
params2 := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 3, Character: 13}, // After "Field = $"
}
list2 := lsp.HandleCompletion(params2)
if list2 == nil {
t.Fatal("Expected suggestions for @")
}
found = false
for _, item := range list2.Items {
if item.Label == "MyVar" { // suggestVariables returns "MyVar"
found = true
break
}
}
if !found {
t.Error("Expected MyVar in suggestions for @")
}
})
}