From e3c84fcf60b51275fa9eddedaad3a289c63f54dc Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Fri, 23 Jan 2026 14:04:24 +0100 Subject: [PATCH] Moved tests in test folder (and made methods public in server.go) --- internal/lsp/server.go | 98 +++++++++---------- .../lsp_completion_test.go | 96 +++++++++--------- .../server_test.go => test/lsp_server_test.go | 85 +++++++--------- 3 files changed, 134 insertions(+), 145 deletions(-) rename internal/lsp/completion_test.go => test/lsp_completion_test.go (65%) rename internal/lsp/server_test.go => test/lsp_server_test.go (62%) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index c548d52..fadf912 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -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, ¶ms); err == nil { - handleDidOpen(params) + HandleDidOpen(params) } case "textDocument/didChange": var params DidChangeTextDocumentParams if err := json.Unmarshal(msg.Params, ¶ms); err == nil { - handleDidChange(params) + HandleDidChange(params) } case "textDocument/hover": var params HoverParams if err := json.Unmarshal(msg.Params, ¶ms); 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, ¶ms); err == nil { - respond(msg.ID, handleDefinition(params)) + respond(msg.ID, HandleDefinition(params)) } case "textDocument/references": var params ReferenceParams if err := json.Unmarshal(msg.Params, ¶ms); err == nil { - respond(msg.ID, handleReferences(params)) + respond(msg.ID, HandleReferences(params)) } case "textDocument/completion": var params CompletionParams if err := json.Unmarshal(msg.Params, ¶ms); err == nil { - respond(msg.ID, handleCompletion(params)) + respond(msg.ID, HandleCompletion(params)) } case "textDocument/formatting": var params DocumentFormattingParams if err := json.Unmarshal(msg.Params, ¶ms); 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 := "" diff --git a/internal/lsp/completion_test.go b/test/lsp_completion_test.go similarity index 65% rename from internal/lsp/completion_test.go rename to test/lsp_completion_test.go index 145e5c6..0bfddf3 100644 --- a/internal/lsp/completion_test.go +++ b/test/lsp_completion_test.go @@ -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") } @@ -53,21 +53,21 @@ func TestHandleCompletion(t *testing.T) { content := ` +MyApp = { Class = RealTimeApplication - + } ` - 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") } @@ -141,21 +141,21 @@ $App = { +MyThread = { Class = RealTimeThread Functions = { } - + } ` - 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 { diff --git a/internal/lsp/server_test.go b/test/lsp_server_test.go similarity index 62% rename from internal/lsp/server_test.go rename to test/lsp_server_test.go index 22e9305..f68958d 100644 --- a/internal/lsp/server_test.go +++ b/test/lsp_server_test.go @@ -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))