Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/TEST_OUTPUT/workspace/src/consumer.cpp in L1:C1 - L1:C1 (clangd returned null).
31 changes: 31 additions & 0 deletions integrationtests/snapshots/clangd/ast/consumer-class.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/TEST_OUTPUT/workspace/src/consumer.cpp in L7:C1 - L15:C2

CXXRecord [declaration] - TestClass (L7:C1 - L15:C2)
/TEST_OUTPUT/workspace/src/consumer.cpp:7:1, line:15:1> line:7:7 class TestClass definitionDefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
|-DefaultConstructor exists trivial constexpr needs_implicit defaulted_is_constexpr
|-CopyConstructor simple trivial has_const_param needs_implicit implicit_has_const_param
|-MoveConstructor exists simple trivial needs_implicit
|-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
|-MoveAssignment exists simple trivial needs_implicit
`-Destructor simple irrelevant trivial needs_implicit

AccessSpec [declaration] (L8:C2 - L8:C9)
/TEST_OUTPUT/workspace/src/consumer.cpp:8:2, col:8> col:2 public
CXXMethod [declaration] - method (L14:C3 - L14:C47)
/TEST_OUTPUT/workspace/src/consumer.cpp:14:3, col:46> col:8 method 'void (int)' implicit-inline
FunctionProto [type] (L14:C3 - L14:C25)
QualType 0xPOINTER 'void (int)'
Builtin [type] - void (L14:C3 - L14:C7)
QualType 0xPOINTER 'void'
ParmVar [declaration] - param (L14:C15 - L14:C24)
/TEST_OUTPUT/workspace/src/consumer.cpp:14:15, col:19> col:19 param 'int'
Builtin [type] - int (L14:C15 - L14:C18)
QualType 0xPOINTER 'int'
Compound [statement] (L14:C26 - L14:C47)
/TEST_OUTPUT/workspace/src/consumer.cpp:14:26, col:46>
Call [expression] (L14:C28 - L14:C44)
/TEST_OUTPUT/workspace/src/consumer.cpp:14:28, col:43> 'void'
ImplicitCast [expression] - FunctionToPointerDecay (L14:C28 - L14:C42)
/TEST_OUTPUT/workspace/src/consumer.cpp:14:28> 'void (*)()' <FunctionToPointerDecay>
DeclRef [expression] - helperFunction (L14:C28 - L14:C42)
/TEST_OUTPUT/workspace/src/consumer.cpp:14:28> 'void ()' lvalue Function 0xPOINTER 'helperFunction' 'void ()'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/TEST_OUTPUT/workspace/src/helper.cpp L7:C1
/TEST_OUTPUT/workspace/src/consumer.cpp L14:C8
/TEST_OUTPUT/workspace/src/main.cpp L10:C5
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/TEST_OUTPUT/workspace/src/consumer.cpp

consume (Function) L5:C1 - L5:C65
TestClass (Class) L7:C1 - L15:C2
method (Method) L14:C3 - L14:C47
2 changes: 1 addition & 1 deletion integrationtests/snapshots/clangd/hover/class-type.snap
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class TestClass

Size: 1 byte
Size: 1 byte, alignment 1 byte

class TestClass {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/TEST_OUTPUT/workspace/src/another_consumer.cpp
Inactive regions: 1
Region 1: L7:C1 - L8:C50
Line 7: // This block is intentionally inactive for testing clangd inactiveRegions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
---

/TEST_OUTPUT/workspace/src/main.cpp
References in File: 1
At: L14:C3

10|int main() {
11| helperFunction();
12| return 0;
13|
14| foo_bar();
15|
16| // Intentional error: unreachable code
17| std::cout << "This is unreachable" << std::endl;
18|}

---

/TEST_OUTPUT/workspace/clangd/src/main.cpp
References in File: 1
At: L14:C3

9|
10|int main() {
11| helperFunction();
12| return 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
---

/TEST_OUTPUT/workspace/src/main.cpp
References in File: 1
At: L11:C3

10|int main() {
11| helperFunction();
12| return 0;
13|
14| foo_bar();
15|
16| // Intentional error: unreachable code

---

/TEST_OUTPUT/workspace/clangd/src/consumer.cpp
References in File: 1
At: L14:C28
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
1. foo_bar (Function)
/TEST_OUTPUT/workspace/src/main.cpp L5:C6
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
1. helperFunction (Function)
/TEST_OUTPUT/workspace/clangd/src/helper.cpp L7:C6
81 changes: 81 additions & 0 deletions integrationtests/tests/clangd/ast/ast_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package ast_test

import (
"context"
"path/filepath"
"regexp"
"testing"
"time"

"github.com/isaacphi/mcp-language-server/integrationtests/tests/clangd/internal"
"github.com/isaacphi/mcp-language-server/integrationtests/tests/common"
"github.com/isaacphi/mcp-language-server/internal/tools"
)

// TestAST_Present verifies clangd's textDocument/ast extension for a range
// that covers the TestClass declaration in consumer.cpp.
func TestAST_Present(t *testing.T) {
suite := internal.GetTestSuite(t)

ctx, cancel := context.WithTimeout(suite.Context, 15*time.Second)
defer cancel()

// Open a file to ensure clangd loads compile commands and starts indexing.
indexFile := filepath.Join(suite.WorkspaceDir, "src/main.cpp")
if err := suite.Client.OpenFile(ctx, indexFile); err != nil {
t.Logf("Note: failed to open %s for indexing: %v", indexFile, err)
}

// Give clangd some time to index the workspace.
time.Sleep(5 * time.Second)

// Query AST for the TestClass declaration range in consumer.cpp.
targetFile := filepath.Join(suite.WorkspaceDir, "src/consumer.cpp")
result, err := tools.GetAST(ctx, suite.Client, targetFile, 7, 1, 15, 2)
if err != nil {
t.Fatalf("GetAST failed: %v", err)
}

// Normalize unstable details such as heap-based pointer IDs in QualType dumps,
// which change between runs but do not affect behavior.
result = normalizeASTOutput(result)

common.SnapshotTest(t, "clangd", "ast", "consumer-class", result)
}

// TestAST_Absent verifies behavior when querying an out-of-bounds range
// (no meaningful AST should be available).
func TestAST_Absent(t *testing.T) {
suite := internal.GetTestSuite(t)

ctx, cancel := context.WithTimeout(suite.Context, 15*time.Second)
defer cancel()

// Open a file to ensure clangd loads compile commands and starts indexing.
indexFile := filepath.Join(suite.WorkspaceDir, "src/main.cpp")
if err := suite.Client.OpenFile(ctx, indexFile); err != nil {
t.Logf("Note: failed to open %s for indexing: %v", indexFile, err)
}

time.Sleep(5 * time.Second)

targetFile := filepath.Join(suite.WorkspaceDir, "src/consumer.cpp")
// Use an empty range at the very start of the file; depending on clangd,
// this may yield no AST or a very small one, which we snapshot separately.
result, err := tools.GetAST(ctx, suite.Client, targetFile, 1, 1, 1, 1)
if err != nil {
t.Fatalf("GetAST failed: %v", err)
}

result = normalizeASTOutput(result)

common.SnapshotTest(t, "clangd", "ast", "consumer-class-out-of-range", result)
}

// normalizeASTOutput removes volatile pointer addresses (e.g. 0x7ffeabcd1234) from clangd AST dumps
// so that snapshots remain stable across runs and machines.
func normalizeASTOutput(input string) string {
pointerRe := regexp.MustCompile(`0x[0-9a-fA-F]+`)
return pointerRe.ReplaceAllString(input, "0xPOINTER")
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package call_hierarchy_test

import (
"context"
"path/filepath"
"strings"
"testing"
"time"

"github.com/isaacphi/mcp-language-server/integrationtests/tests/clangd/internal"
"github.com/isaacphi/mcp-language-server/integrationtests/tests/common"
"github.com/isaacphi/mcp-language-server/internal/tools"
)

// TestGetCallHierarchy tests the get_call_hierarchy tool: full incoming call tree
// with cycle detection. Uses position-based API. Tests helperFunction() which is called by main() and method().
func TestGetCallHierarchy(t *testing.T) {
openAllFilesAndWait := func(suite *common.TestSuite, ctx context.Context) {
for _, file := range []string{"src/main.cpp", "src/helper.cpp", "src/consumer.cpp"} {
filePath := filepath.Join(suite.WorkspaceDir, file)
_ = suite.Client.OpenFile(ctx, filePath)
}
time.Sleep(30 * time.Second)
}

suite := internal.GetTestSuite(t)
ctx, cancel := context.WithTimeout(suite.Context, 60*time.Second)
defer cancel()
openAllFilesAndWait(suite, ctx)

filePath := filepath.Join(suite.WorkspaceDir, "src/helper.cpp")
result, err := tools.GetFullCallHierarchy(ctx, suite.Client, filePath, 7, 6)
if err != nil {
t.Fatalf("GetFullCallHierarchy failed: %v", err)
}
if !strings.Contains(result, "main") {
t.Errorf("Result should contain main (caller of helperFunction). Result:\n%s", result)
}
common.SnapshotTest(t, "clangd", "call_hierarchy", "get-call-hierarchy-helper-incoming", result)
}
4 changes: 2 additions & 2 deletions integrationtests/tests/clangd/definition/definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func TestReadDefinition(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Call the ReadDefinition tool
result, err := tools.ReadDefinition(ctx, suite.Client, tc.symbolName)
result, err := tools.ReadDefinition(ctx, suite.Client, tc.symbolName, nil)
if err != nil {
t.Fatalf("Failed to read definition: %v", err)
}
Expand Down Expand Up @@ -154,7 +154,7 @@ func TestReadDefinitionInAnotherFile(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Call the ReadDefinition tool
result, err := tools.ReadDefinition(ctx, suite.Client, tc.symbolName)
result, err := tools.ReadDefinition(ctx, suite.Client, tc.symbolName, nil)
if err != nil {
t.Fatalf("Failed to read definition: %v", err)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package document_symbol_test

import (
"context"
"path/filepath"
"testing"
"time"

"github.com/isaacphi/mcp-language-server/integrationtests/tests/clangd/internal"
"github.com/isaacphi/mcp-language-server/integrationtests/tests/common"
"github.com/isaacphi/mcp-language-server/internal/tools"
)

// TestDocumentSymbol tests the document_symbol (outline) tool with clangd.
// Opens the file so clangd can index it, then requests document symbols and compares to snapshot.
func TestDocumentSymbol(t *testing.T) {
openFilesAndWait := func(suite *common.TestSuite, ctx context.Context) {
filesToOpen := []string{
"src/main.cpp",
"src/consumer.cpp",
}
for _, file := range filesToOpen {
filePath := filepath.Join(suite.WorkspaceDir, file)
err := suite.Client.OpenFile(ctx, filePath)
if err != nil {
t.Logf("Note: Failed to open %s: %v", file, err)
}
}
time.Sleep(10 * time.Second)
}

suite := internal.GetTestSuite(t)

ctx, cancel := context.WithTimeout(suite.Context, 30*time.Second)
defer cancel()

openFilesAndWait(suite, ctx)

filePath := filepath.Join(suite.WorkspaceDir, "src/consumer.cpp")
result, err := tools.DocumentSymbolOutline(ctx, suite.Client, filePath)
if err != nil {
t.Fatalf("DocumentSymbolOutline failed: %v", err)
}

common.SnapshotTest(t, "clangd", "document_symbol", "consumer_cpp", result)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package inactive_regions_test

import (
"context"
"path/filepath"
"testing"
"time"

"github.com/isaacphi/mcp-language-server/integrationtests/tests/clangd/internal"
"github.com/isaacphi/mcp-language-server/integrationtests/tests/common"
"github.com/isaacphi/mcp-language-server/internal/tools"
)

// TestInactiveRegions verifies clangd's textDocument/inactiveRegions extension via the tools helper.
func TestInactiveRegions(t *testing.T) {
suite := internal.GetTestSuite(t)

ctx, cancel := context.WithTimeout(suite.Context, 15*time.Second)
defer cancel()

// Open a file to ensure clangd loads compile commands and starts indexing.
indexFile := filepath.Join(suite.WorkspaceDir, "src/main.cpp")
if err := suite.Client.OpenFile(ctx, indexFile); err != nil {
t.Logf("Note: failed to open %s for indexing: %v", indexFile, err)
}

// Give clangd some time to index and compute inactive regions.
time.Sleep(5 * time.Second)

targetFile := filepath.Join(suite.WorkspaceDir, "src/another_consumer.cpp")

result, err := tools.GetInactiveRegions(ctx, suite.Client, targetFile)
if err != nil {
t.Fatalf("GetInactiveRegions failed: %v", err)
}

// Snapshot under clangd/inactive_regions.
common.SnapshotTest(t, "clangd", "inactive_regions", "another-consumer", result)
}

Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestFindReferences(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Call the FindReferences tool
result, err := tools.FindReferences(ctx, suite.Client, tc.symbolName)
result, err := tools.FindReferences(ctx, suite.Client, tc.symbolName, nil)
if err != nil {
t.Fatalf("Failed to find references for %s: %v. Result: %s", tc.symbolName, err, result)
}
Expand Down
Loading