a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
107 lines
3.5 KiB
Go
107 lines
3.5 KiB
Go
package lsp
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/zzet/gortex/internal/semantic"
|
|
)
|
|
|
|
// shrinkReadinessTimers speeds the readiness poll loop for tests and restores
|
|
// the production values on cleanup.
|
|
func shrinkReadinessTimers(t *testing.T) {
|
|
t.Helper()
|
|
pi, lt := csharpReadinessProbeInterval, csharpSolutionLoadTimeout
|
|
csharpReadinessProbeInterval = 5 * time.Millisecond
|
|
csharpSolutionLoadTimeout = 2 * time.Second
|
|
t.Cleanup(func() {
|
|
csharpReadinessProbeInterval = pi
|
|
csharpSolutionLoadTimeout = lt
|
|
})
|
|
}
|
|
|
|
// TestPollWorkspaceReady_ReadyAfterLoad models a Roslyn / MSBuild server whose
|
|
// workspace/symbol answers empty while the solution loads and returns a match
|
|
// once it is live. pollWorkspaceReady must block across the empty phase and
|
|
// return nil only once the probe is non-empty, so the enrichment sweep starts
|
|
// against a server that can actually resolve.
|
|
func TestPollWorkspaceReady_ReadyAfterLoad(t *testing.T) {
|
|
shrinkReadinessTimers(t)
|
|
|
|
var calls int64
|
|
srv := newFakeLSPServer()
|
|
srv.handle("workspace/symbol", func(json.RawMessage) (any, *jsonRPCError) {
|
|
if atomic.AddInt64(&calls, 1) < 3 {
|
|
return []any{}, nil // still loading — empty
|
|
}
|
|
return []map[string]any{{"name": "Foo", "kind": 5}}, nil
|
|
})
|
|
p, cleanup := providerWithFakeServer(t, srv, []string{"csharp"})
|
|
defer cleanup()
|
|
|
|
require.NoError(t, p.pollWorkspaceReady(context.Background(), "Foo"))
|
|
assert.GreaterOrEqual(t, atomic.LoadInt64(&calls), int64(3),
|
|
"probe must poll across the empty solution-load phase before reporting ready")
|
|
}
|
|
|
|
// TestPollWorkspaceReady_NeverReady: a server that never loads (always empty)
|
|
// must surface semantic.ErrWorkspaceNotReady so the Manager records an honest
|
|
// state and skips the futile sweep.
|
|
func TestPollWorkspaceReady_NeverReady(t *testing.T) {
|
|
shrinkReadinessTimers(t)
|
|
|
|
srv := newFakeLSPServer()
|
|
srv.handle("workspace/symbol", func(json.RawMessage) (any, *jsonRPCError) {
|
|
return []any{}, nil
|
|
})
|
|
p, cleanup := providerWithFakeServer(t, srv, []string{"csharp"})
|
|
defer cleanup()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
|
defer cancel()
|
|
err := p.pollWorkspaceReady(ctx, "Foo")
|
|
require.Error(t, err)
|
|
assert.True(t, errors.Is(err, semantic.ErrWorkspaceNotReady),
|
|
"a workspace that never loads must report ErrWorkspaceNotReady, got %v", err)
|
|
}
|
|
|
|
// TestCSharpProbeQuery derives a workspace/symbol query from the first C#
|
|
// declaration under the repo root, and reports none for a tree with no C#.
|
|
func TestCSharpProbeQuery(t *testing.T) {
|
|
dir := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "Widget.cs"),
|
|
[]byte("namespace Demo {\n public interface IWidget { void Do(); }\n}\n"), 0o644))
|
|
q, ok := csharpProbeQuery(dir)
|
|
require.True(t, ok)
|
|
assert.Equal(t, "IWidget", q)
|
|
|
|
empty := t.TempDir()
|
|
require.NoError(t, os.WriteFile(filepath.Join(empty, "README.md"), []byte("hi"), 0o644))
|
|
_, ok = csharpProbeQuery(empty)
|
|
assert.False(t, ok)
|
|
}
|
|
|
|
// TestCSharpDeclName covers the identifier extraction the probe query relies on.
|
|
func TestCSharpDeclName(t *testing.T) {
|
|
cases := []struct{ src, want string }{
|
|
{"public class Foo {}", "Foo"},
|
|
{"internal interface IBar { }", "IBar"},
|
|
{"public record class Money(decimal V);", "Money"},
|
|
{"public record Point(int X, int Y);", "Point"},
|
|
{"namespace A.B.Baz;", "Baz"},
|
|
{"// just a comment\n", ""},
|
|
}
|
|
for _, c := range cases {
|
|
assert.Equal(t, c.want, csharpDeclName([]byte(c.src)), "src=%q", c.src)
|
|
}
|
|
}
|