Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

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)
}
}