Moved tests in test folder (and made methods public in server.go)

This commit is contained in:
Martino Ferrari
2026-01-23 14:04:24 +01:00
parent 4a515fd6c3
commit e3c84fcf60
3 changed files with 134 additions and 145 deletions

View File

@@ -47,10 +47,10 @@ type CompletionList struct {
Items []CompletionItem `json:"items"`
}
var tree = index.NewProjectTree()
var documents = make(map[string]string)
var projectRoot string
var globalSchema *schema.Schema
var Tree = index.NewProjectTree()
var Documents = make(map[string]string)
var ProjectRoot string
var GlobalSchema *schema.Schema
type JsonRpcMessage struct {
Jsonrpc string `json:"jsonrpc"`
@@ -184,7 +184,7 @@ func RunServer() {
continue
}
handleMessage(msg)
HandleMessage(msg)
}
}
@@ -214,7 +214,7 @@ func readMessage(reader *bufio.Reader) (*JsonRpcMessage, error) {
return &msg, err
}
func handleMessage(msg *JsonRpcMessage) {
func HandleMessage(msg *JsonRpcMessage) {
switch msg.Method {
case "initialize":
var params InitializeParams
@@ -227,13 +227,13 @@ func handleMessage(msg *JsonRpcMessage) {
}
if root != "" {
projectRoot = root
ProjectRoot = root
logger.Printf("Scanning workspace: %s\n", root)
if err := tree.ScanDirectory(root); err != nil {
if err := Tree.ScanDirectory(root); err != nil {
logger.Printf("ScanDirectory failed: %v\n", err)
}
tree.ResolveReferences()
globalSchema = schema.LoadFullSchema(projectRoot)
Tree.ResolveReferences()
GlobalSchema = schema.LoadFullSchema(ProjectRoot)
}
}
@@ -258,18 +258,18 @@ func handleMessage(msg *JsonRpcMessage) {
case "textDocument/didOpen":
var params DidOpenTextDocumentParams
if err := json.Unmarshal(msg.Params, &params); err == nil {
handleDidOpen(params)
HandleDidOpen(params)
}
case "textDocument/didChange":
var params DidChangeTextDocumentParams
if err := json.Unmarshal(msg.Params, &params); err == nil {
handleDidChange(params)
HandleDidChange(params)
}
case "textDocument/hover":
var params HoverParams
if err := json.Unmarshal(msg.Params, &params); err == nil {
logger.Printf("Hover: %s:%d", params.TextDocument.URI, params.Position.Line)
res := handleHover(params)
res := HandleHover(params)
if res != nil {
logger.Printf("Res: %v", res.Contents)
} else {
@@ -283,22 +283,22 @@ func handleMessage(msg *JsonRpcMessage) {
case "textDocument/definition":
var params DefinitionParams
if err := json.Unmarshal(msg.Params, &params); err == nil {
respond(msg.ID, handleDefinition(params))
respond(msg.ID, HandleDefinition(params))
}
case "textDocument/references":
var params ReferenceParams
if err := json.Unmarshal(msg.Params, &params); err == nil {
respond(msg.ID, handleReferences(params))
respond(msg.ID, HandleReferences(params))
}
case "textDocument/completion":
var params CompletionParams
if err := json.Unmarshal(msg.Params, &params); err == nil {
respond(msg.ID, handleCompletion(params))
respond(msg.ID, HandleCompletion(params))
}
case "textDocument/formatting":
var params DocumentFormattingParams
if err := json.Unmarshal(msg.Params, &params); err == nil {
respond(msg.ID, handleFormatting(params))
respond(msg.ID, HandleFormatting(params))
}
}
}
@@ -307,9 +307,9 @@ func uriToPath(uri string) string {
return strings.TrimPrefix(uri, "file://")
}
func handleDidOpen(params DidOpenTextDocumentParams) {
func HandleDidOpen(params DidOpenTextDocumentParams) {
path := uriToPath(params.TextDocument.URI)
documents[params.TextDocument.URI] = params.TextDocument.Text
Documents[params.TextDocument.URI] = params.TextDocument.Text
p := parser.NewParser(params.TextDocument.Text)
config, err := p.Parse()
@@ -320,18 +320,18 @@ func handleDidOpen(params DidOpenTextDocumentParams) {
}
if config != nil {
tree.AddFile(path, config)
tree.ResolveReferences()
Tree.AddFile(path, config)
Tree.ResolveReferences()
runValidation(params.TextDocument.URI)
}
}
func handleDidChange(params DidChangeTextDocumentParams) {
func HandleDidChange(params DidChangeTextDocumentParams) {
if len(params.ContentChanges) == 0 {
return
}
text := params.ContentChanges[0].Text
documents[params.TextDocument.URI] = text
Documents[params.TextDocument.URI] = text
path := uriToPath(params.TextDocument.URI)
p := parser.NewParser(text)
config, err := p.Parse()
@@ -343,15 +343,15 @@ func handleDidChange(params DidChangeTextDocumentParams) {
}
if config != nil {
tree.AddFile(path, config)
tree.ResolveReferences()
Tree.AddFile(path, config)
Tree.ResolveReferences()
runValidation(params.TextDocument.URI)
}
}
func handleFormatting(params DocumentFormattingParams) []TextEdit {
func HandleFormatting(params DocumentFormattingParams) []TextEdit {
uri := params.TextDocument.URI
text, ok := documents[uri]
text, ok := Documents[uri]
if !ok {
return nil
}
@@ -383,7 +383,7 @@ func handleFormatting(params DocumentFormattingParams) []TextEdit {
}
func runValidation(uri string) {
v := validator.NewValidator(tree, projectRoot)
v := validator.NewValidator(Tree, ProjectRoot)
v.ValidateProject()
v.CheckUnused()
@@ -392,7 +392,7 @@ func runValidation(uri string) {
// Collect all known files to ensure we clear diagnostics for fixed files
knownFiles := make(map[string]bool)
collectFiles(tree.Root, knownFiles)
collectFiles(Tree.Root, knownFiles)
// Initialize all known files with empty diagnostics
for f := range knownFiles {
@@ -501,12 +501,12 @@ func mustMarshal(v any) json.RawMessage {
return b
}
func handleHover(params HoverParams) *Hover {
func HandleHover(params HoverParams) *Hover {
path := uriToPath(params.TextDocument.URI)
line := params.Position.Line + 1
col := params.Position.Character + 1
res := tree.Query(path, line, col)
res := Tree.Query(path, line, col)
if res == nil {
logger.Printf("No object/node/reference found")
return nil
@@ -553,10 +553,10 @@ func handleHover(params HoverParams) *Hover {
}
}
func handleCompletion(params CompletionParams) *CompletionList {
func HandleCompletion(params CompletionParams) *CompletionList {
uri := params.TextDocument.URI
path := uriToPath(uri)
text, ok := documents[uri]
text, ok := Documents[uri]
if !ok {
return nil
}
@@ -591,7 +591,7 @@ func handleCompletion(params CompletionParams) *CompletionList {
return suggestClasses()
}
container := tree.GetNodeContaining(path, parser.Position{Line: params.Position.Line + 1, Column: col + 1})
container := Tree.GetNodeContaining(path, parser.Position{Line: params.Position.Line + 1, Column: col + 1})
if container != nil {
return suggestFieldValues(container, key, path)
}
@@ -599,7 +599,7 @@ func handleCompletion(params CompletionParams) *CompletionList {
}
// Case 2: Typing a key inside an object
container := tree.GetNodeContaining(path, parser.Position{Line: params.Position.Line + 1, Column: col + 1})
container := Tree.GetNodeContaining(path, parser.Position{Line: params.Position.Line + 1, Column: col + 1})
if container != nil {
return suggestFields(container)
}
@@ -608,11 +608,11 @@ func handleCompletion(params CompletionParams) *CompletionList {
}
func suggestClasses() *CompletionList {
if globalSchema == nil {
if GlobalSchema == nil {
return nil
}
classesVal := globalSchema.Value.LookupPath(cue.ParsePath("#Classes"))
classesVal := GlobalSchema.Value.LookupPath(cue.ParsePath("#Classes"))
if classesVal.Err() != nil {
return nil
}
@@ -647,11 +647,11 @@ func suggestFields(container *index.ProjectNode) *CompletionList {
}}}
}
if globalSchema == nil {
if GlobalSchema == nil {
return nil
}
classPath := cue.ParsePath(fmt.Sprintf("#Classes.%s", cls))
classVal := globalSchema.Value.LookupPath(classPath)
classVal := GlobalSchema.Value.LookupPath(classPath)
if classVal.Err() != nil {
return nil
}
@@ -711,10 +711,10 @@ func suggestFields(container *index.ProjectNode) *CompletionList {
func suggestFieldValues(container *index.ProjectNode, field string, path string) *CompletionList {
var root *index.ProjectNode
if iso, ok := tree.IsolatedFiles[path]; ok {
if iso, ok := Tree.IsolatedFiles[path]; ok {
root = iso
} else {
root = tree.Root
root = Tree.Root
}
if field == "DataSource" {
@@ -779,12 +779,12 @@ func isDataSource(node *index.ProjectNode) bool {
return hasSignals
}
func handleDefinition(params DefinitionParams) any {
func HandleDefinition(params DefinitionParams) any {
path := uriToPath(params.TextDocument.URI)
line := params.Position.Line + 1
col := params.Position.Character + 1
res := tree.Query(path, line, col)
res := Tree.Query(path, line, col)
if res == nil {
return nil
}
@@ -819,12 +819,12 @@ func handleDefinition(params DefinitionParams) any {
return nil
}
func handleReferences(params ReferenceParams) []Location {
func HandleReferences(params ReferenceParams) []Location {
path := uriToPath(params.TextDocument.URI)
line := params.Position.Line + 1
col := params.Position.Character + 1
res := tree.Query(path, line, col)
res := Tree.Query(path, line, col)
if res == nil {
return nil
}
@@ -862,7 +862,7 @@ func handleReferences(params ReferenceParams) []Location {
}
// 1. References from index (Aliases)
for _, ref := range tree.References {
for _, ref := range Tree.References {
if ref.Target == canonical {
locations = append(locations, Location{
URI: "file://" + ref.File,
@@ -875,7 +875,7 @@ func handleReferences(params ReferenceParams) []Location {
}
// 2. References from Node Targets (Direct References)
tree.Walk(func(node *index.ProjectNode) {
Tree.Walk(func(node *index.ProjectNode) {
if node.Target == canonical {
for _, frag := range node.Fragments {
if frag.IsObject {
@@ -929,9 +929,9 @@ func formatNodeInfo(node *index.ProjectNode) string {
// Find references
var refs []string
for _, ref := range tree.References {
for _, ref := range Tree.References {
if ref.Target == node {
container := tree.GetNodeContaining(ref.File, ref.Position)
container := Tree.GetNodeContaining(ref.File, ref.Position)
if container != nil {
threadName := ""
stateName := ""

View File

@@ -1,21 +1,21 @@
package lsp
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() {
tree = index.NewProjectTree()
documents = make(map[string]string)
projectRoot = "."
globalSchema = schema.NewSchema()
lsp.Tree = index.NewProjectTree()
lsp.Documents = make(map[string]string)
lsp.ProjectRoot = "."
lsp.GlobalSchema = schema.NewSchema()
}
uri := "file://test.marte"
@@ -24,14 +24,14 @@ func TestHandleCompletion(t *testing.T) {
t.Run("Suggest Classes", func(t *testing.T) {
setup()
content := "+Obj = { Class = "
documents[uri] = content
lsp.Documents[uri] = content
params := CompletionParams{
TextDocument: TextDocumentIdentifier{URI: uri},
Position: Position{Line: 0, Character: len(content)},
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 0, Character: len(content)},
}
list := handleCompletion(params)
list := lsp.HandleCompletion(params)
if list == nil || len(list.Items) == 0 {
t.Fatal("Expected class suggestions, got none")
}
@@ -56,18 +56,18 @@ func TestHandleCompletion(t *testing.T) {
}
`
documents[uri] = content
lsp.Documents[uri] = content
p := parser.NewParser(content)
cfg, _ := p.Parse()
tree.AddFile(path, cfg)
lsp.Tree.AddFile(path, cfg)
// Position at line 3 (empty line inside MyApp)
params := CompletionParams{
TextDocument: TextDocumentIdentifier{URI: uri},
Position: Position{Line: 3, Character: 4},
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 3, Character: 4},
}
list := handleCompletion(params)
list := lsp.HandleCompletion(params)
if list == nil || len(list.Items) == 0 {
t.Fatal("Expected field suggestions, got none")
}
@@ -106,19 +106,19 @@ $App = {
}
}
`
documents[uri] = content
lsp.Documents[uri] = content
p := parser.NewParser(content)
cfg, _ := p.Parse()
tree.AddFile(path, cfg)
tree.ResolveReferences()
lsp.Tree.AddFile(path, cfg)
lsp.Tree.ResolveReferences()
// Position at end of "DataSource = "
params := CompletionParams{
TextDocument: TextDocumentIdentifier{URI: uri},
Position: Position{Line: 14, Character: 28},
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 14, Character: 28},
}
list := handleCompletion(params)
list := lsp.HandleCompletion(params)
if list == nil || len(list.Items) == 0 {
t.Fatal("Expected DataSource suggestions, got none")
}
@@ -144,18 +144,18 @@ $App = {
}
`
documents[uri] = content
lsp.Documents[uri] = content
p := parser.NewParser(content)
cfg, _ := p.Parse()
tree.AddFile(path, cfg)
lsp.Tree.AddFile(path, cfg)
// Position at line 4
params := CompletionParams{
TextDocument: TextDocumentIdentifier{URI: uri},
Position: Position{Line: 4, Character: 4},
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 4, Character: 4},
}
list := handleCompletion(params)
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)
@@ -163,27 +163,27 @@ $App = {
}
})
t.Run("Scope-aware suggestions", func(t *testing.T) {
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()
tree.AddFile("project_ds.marte", cfg1)
lsp.Tree.AddFile("project_ds.marte", cfg1)
// Define an isolated file
contentIso := "+MyGAM = { Class = IOGAM +InputSignals = { S1 = { DataSource = } } }"
documents["file://iso.marte"] = contentIso
lsp.Documents["file://iso.marte"] = contentIso
cfg2, _ := parser.NewParser(contentIso).Parse()
tree.AddFile("iso.marte", cfg2)
lsp.Tree.AddFile("iso.marte", cfg2)
tree.ResolveReferences()
lsp.Tree.ResolveReferences()
// Completion in isolated file
params := CompletionParams{
TextDocument: TextDocumentIdentifier{URI: "file://iso.marte"},
Position: Position{Line: 0, Character: strings.Index(contentIso, "DataSource = ") + len("DataSource = ") + 1},
params := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: "file://iso.marte"},
Position: lsp.Position{Line: 0, Character: strings.Index(contentIso, "DataSource = ") + len("DataSource = ") + 1},
}
list := handleCompletion(params)
list := lsp.HandleCompletion(params)
foundProjectDS := false
if list != nil {
for _, item := range list.Items {
@@ -200,21 +200,21 @@ $App = {
// Completion in a project file
lineContent := "+MyGAM = { Class = IOGAM +InputSignals = { S1 = { DataSource = Dummy } } }"
contentPrj := "#package MYPROJ.App\n" + lineContent
documents["file://prj.marte"] = contentPrj
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)
}
tree.AddFile("prj.marte", cfg3)
tree.ResolveReferences()
lsp.Tree.AddFile("prj.marte", cfg3)
lsp.Tree.ResolveReferences()
paramsPrj := CompletionParams{
TextDocument: TextDocumentIdentifier{URI: "file://prj.marte"},
Position: Position{Line: 1, Character: strings.Index(lineContent, "Dummy")},
paramsPrj := lsp.CompletionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: "file://prj.marte"},
Position: lsp.Position{Line: 1, Character: strings.Index(lineContent, "Dummy")},
}
listPrj := handleCompletion(paramsPrj)
listPrj := lsp.HandleCompletion(paramsPrj)
foundProjectDS = false
if listPrj != nil {
for _, item := range listPrj.Items {

View File

@@ -1,4 +1,4 @@
package lsp
package integration
import (
"encoding/json"
@@ -8,6 +8,7 @@ import (
"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"
)
@@ -24,50 +25,38 @@ func TestInitProjectScan(t *testing.T) {
t.Fatal(err)
}
// File 2: Reference
// +Source = { Class = C Link = Target }
// Link = Target starts at index ...
// #package Test.Common (21 chars including newline)
// +Source = { Class = C Link = Target }
// 012345678901234567890123456789012345
// Previous offset was 29.
// Now add 21?
// #package Test.Common\n
// +Source = ...
// So add 21 to Character? Or Line 1?
// It's on Line 1 (0-based 1).
if err := os.WriteFile(filepath.Join(tmpDir, "ref.marte"), []byte("#package Test.Common\n+Source = { Class = C Link = Target }"), 0644); err != nil {
t.Fatal(err)
}
// 2. Initialize
tree = index.NewProjectTree() // Reset global tree
lsp.Tree = index.NewProjectTree() // Reset global tree
initParams := InitializeParams{RootPath: tmpDir}
initParams := lsp.InitializeParams{RootPath: tmpDir}
paramsBytes, _ := json.Marshal(initParams)
msg := &JsonRpcMessage{
msg := &lsp.JsonRpcMessage{
Method: "initialize",
Params: paramsBytes,
ID: 1,
}
handleMessage(msg)
lsp.HandleMessage(msg)
// Query the reference in ref.marte at "Target"
// Target starts at index 29 (0-based) on Line 1
defParams := DefinitionParams{
TextDocument: TextDocumentIdentifier{URI: "file://" + filepath.Join(tmpDir, "ref.marte")},
Position: Position{Line: 1, Character: 29},
defParams := lsp.DefinitionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: "file://" + filepath.Join(tmpDir, "ref.marte")},
Position: lsp.Position{Line: 1, Character: 29},
}
res := handleDefinition(defParams)
res := lsp.HandleDefinition(defParams)
if res == nil {
t.Fatal("Definition not found via LSP after initialization")
}
locs, ok := res.([]Location)
locs, ok := res.([]lsp.Location)
if !ok {
t.Fatalf("Expected []Location, got %T", res)
t.Fatalf("Expected []lsp.Location, got %T", res)
}
if len(locs) == 0 {
@@ -83,7 +72,7 @@ func TestInitProjectScan(t *testing.T) {
func TestHandleDefinition(t *testing.T) {
// Reset tree for test
tree = index.NewProjectTree()
lsp.Tree = index.NewProjectTree()
content := `
+MyObject = {
@@ -100,28 +89,28 @@ func TestHandleDefinition(t *testing.T) {
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
tree.AddFile(path, config)
tree.ResolveReferences()
lsp.Tree.AddFile(path, config)
lsp.Tree.ResolveReferences()
t.Logf("Refs: %d", len(tree.References))
for _, r := range tree.References {
t.Logf("Refs: %d", len(lsp.Tree.References))
for _, r := range lsp.Tree.References {
t.Logf(" %s at %d:%d", r.Name, r.Position.Line, r.Position.Column)
}
// Test Go to Definition on MyObject reference
params := DefinitionParams{
TextDocument: TextDocumentIdentifier{URI: "file://" + path},
Position: Position{Line: 6, Character: 15}, // "MyObject" in RefField = MyObject
params := lsp.DefinitionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: "file://" + path},
Position: lsp.Position{Line: 6, Character: 15}, // "MyObject" in RefField = MyObject
}
result := handleDefinition(params)
result := lsp.HandleDefinition(params)
if result == nil {
t.Fatal("handleDefinition returned nil")
t.Fatal("HandleDefinition returned nil")
}
locations, ok := result.([]Location)
locations, ok := result.([]lsp.Location)
if !ok {
t.Fatalf("Expected []Location, got %T", result)
t.Fatalf("Expected []lsp.Location, got %T", result)
}
if len(locations) != 1 {
@@ -135,7 +124,7 @@ func TestHandleDefinition(t *testing.T) {
func TestHandleReferences(t *testing.T) {
// Reset tree for test
tree = index.NewProjectTree()
lsp.Tree = index.NewProjectTree()
content := `
+MyObject = {
@@ -155,17 +144,17 @@ func TestHandleReferences(t *testing.T) {
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
tree.AddFile(path, config)
tree.ResolveReferences()
lsp.Tree.AddFile(path, config)
lsp.Tree.ResolveReferences()
// Test Find References for MyObject (triggered from its definition)
params := ReferenceParams{
TextDocument: TextDocumentIdentifier{URI: "file://" + path},
Position: Position{Line: 1, Character: 1}, // "+MyObject"
Context: ReferenceContext{IncludeDeclaration: true},
params := lsp.ReferenceParams{
TextDocument: lsp.TextDocumentIdentifier{URI: "file://" + path},
Position: lsp.Position{Line: 1, Character: 1}, // "+MyObject"
Context: lsp.ReferenceContext{IncludeDeclaration: true},
}
locations := handleReferences(params)
locations := lsp.HandleReferences(params)
if len(locations) != 3 { // 1 declaration + 2 references
t.Fatalf("Expected 3 locations, got %d", len(locations))
}
@@ -181,15 +170,15 @@ Field=1
`
uri := "file:///test.marte"
// Open (populate documents map)
documents[uri] = content
// Open (populate Documents map)
lsp.Documents[uri] = content
// Format
params := DocumentFormattingParams{
TextDocument: TextDocumentIdentifier{URI: uri},
params := lsp.DocumentFormattingParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
}
edits := handleFormatting(params)
edits := lsp.HandleFormatting(params)
if len(edits) != 1 {
t.Fatalf("Expected 1 edit, got %d", len(edits))