项目文件夹

文件
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

604 行
20 KiB
Go

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
package semantic
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"github.com/zzet/gortex/internal/graph"
)
// mockProvider is a test provider that records calls.
type mockProvider struct {
name string
languages []string
available bool
enrichFunc func(g graph.Store, root string) (*EnrichResult, error)
closed bool
}
func (m *mockProvider) Name() string { return m.name }
func (m *mockProvider) Languages() []string { return m.languages }
func (m *mockProvider) Available() bool { return m.available }
func (m *mockProvider) Close() error { m.closed = true; return nil }
func (m *mockProvider) Enrich(g graph.Store, repoRoot string) (*EnrichResult, error) {
if m.enrichFunc != nil {
return m.enrichFunc(g, repoRoot)
}
return &EnrichResult{
Provider: m.name,
Language: m.languages[0],
EdgesConfirmed: 5,
EdgesAdded: 2,
CoveragePercent: 95.0,
}, nil
}
func (m *mockProvider) EnrichFile(g graph.Store, repoRoot, filePath string) (*EnrichResult, error) {
return nil, nil
}
func TestManager_EnrichAll(t *testing.T) {
logger := zap.NewNop()
cfg := Config{
Enabled: true,
EagerLSP: true, // router-backed LSP dispatch is opt-in
Providers: []ProviderConfig{
{Name: "test-go", Languages: []string{"go"}, Priority: 1, Enabled: true},
},
}
mgr := NewManager(cfg, logger)
mgr.RegisterProvider(&mockProvider{
name: "test-go",
languages: []string{"go"},
available: true,
})
g := graph.New()
g.AddNode(&graph.Node{ID: "main.go::main", Kind: graph.KindFunction, Name: "main", FilePath: "main.go", Language: "go"})
roots := map[string]string{"default": "/tmp/test"}
results, _, err := mgr.EnrichAll(g, roots, EnrichOptions{})
require.NoError(t, err)
assert.Len(t, results, 1)
assert.Equal(t, "test-go", results[0].Provider)
assert.Equal(t, 5, results[0].EdgesConfirmed)
}
func TestManager_PrioritySelection(t *testing.T) {
logger := zap.NewNop()
cfg := Config{
Enabled: true,
EagerLSP: true, // router-backed LSP dispatch is opt-in
Providers: []ProviderConfig{
{Name: "high-priority", Languages: []string{"go"}, Priority: 1, Enabled: true},
{Name: "low-priority", Languages: []string{"go"}, Priority: 2, Enabled: true},
},
}
mgr := NewManager(cfg, logger)
highCalled := false
lowCalled := false
mgr.RegisterProvider(&mockProvider{
name: "high-priority",
languages: []string{"go"},
available: true,
enrichFunc: func(g graph.Store, root string) (*EnrichResult, error) {
highCalled = true
return &EnrichResult{Provider: "high-priority", Language: "go"}, nil
},
})
mgr.RegisterProvider(&mockProvider{
name: "low-priority",
languages: []string{"go"},
available: true,
enrichFunc: func(g graph.Store, root string) (*EnrichResult, error) {
lowCalled = true
return &EnrichResult{Provider: "low-priority", Language: "go"}, nil
},
})
g := graph.New()
roots := map[string]string{"default": "/tmp/test"}
_, _, err := mgr.EnrichAll(g, roots, EnrichOptions{})
require.NoError(t, err)
assert.True(t, highCalled, "high-priority provider should run")
assert.False(t, lowCalled, "low-priority provider should not run")
}
func TestManager_UnavailableProvider(t *testing.T) {
logger := zap.NewNop()
cfg := Config{
Enabled: true,
EagerLSP: true, // router-backed LSP dispatch is opt-in
Providers: []ProviderConfig{
{Name: "unavailable", Languages: []string{"go"}, Priority: 1, Enabled: true},
},
}
mgr := NewManager(cfg, logger)
mgr.RegisterProvider(&mockProvider{
name: "unavailable",
languages: []string{"go"},
available: false,
})
g := graph.New()
roots := map[string]string{"default": "/tmp/test"}
results, _, err := mgr.EnrichAll(g, roots, EnrichOptions{})
require.NoError(t, err)
assert.Len(t, results, 0)
}
func TestManager_Disabled(t *testing.T) {
logger := zap.NewNop()
cfg := Config{Enabled: false}
mgr := NewManager(cfg, logger)
mgr.RegisterProvider(&mockProvider{
name: "test",
languages: []string{"go"},
available: true,
})
g := graph.New()
roots := map[string]string{"default": "/tmp/test"}
results, _, err := mgr.EnrichAll(g, roots, EnrichOptions{})
require.NoError(t, err)
assert.Nil(t, results)
}
func TestManager_Close(t *testing.T) {
logger := zap.NewNop()
cfg := Config{Enabled: true}
mgr := NewManager(cfg, logger)
p := &mockProvider{name: "test", languages: []string{"go"}, available: true}
mgr.RegisterProvider(p)
err := mgr.Close()
require.NoError(t, err)
assert.True(t, p.closed)
}
// fakeRouter implements LSPRouter for tests so we can validate the
// Manager↔Router contract without spawning real LSP subprocesses.
type fakeRouter struct {
specs []string
available map[string]bool
languages map[string][]string // spec name → languages
priorities map[string]int // spec name → priority
providers map[string]Provider
closeCalls int
providerErrs map[string]error
maxAlive int
evictions uint64
calls []string // method-name trace for ordering assertions
}
func (f *fakeRouter) EnabledSpecNames() []string {
f.calls = append(f.calls, "EnabledSpecNames")
out := make([]string, len(f.specs))
copy(out, f.specs)
return out
}
func (f *fakeRouter) SpecAvailable(name string) bool {
f.calls = append(f.calls, "SpecAvailable:"+name)
if f.available == nil {
// Default-true so tests that don't set the map still get
// the spec exercised by the router code path.
return true
}
return f.available[name]
}
func (f *fakeRouter) SpecLanguages(name string) []string {
f.calls = append(f.calls, "SpecLanguages:"+name)
if langs, ok := f.languages[name]; ok {
out := make([]string, len(langs))
copy(out, langs)
return out
}
// Sensible fallback so tests that don't bother setting languages
// still get a non-empty list (matching mockProvider's "go" default).
return []string{"go"}
}
func (f *fakeRouter) SpecPriority(name string) int {
f.calls = append(f.calls, "SpecPriority:"+name)
if p, ok := f.priorities[name]; ok {
return p
}
return 99
}
func (f *fakeRouter) ProviderForSpec(name string) (Provider, error) {
f.calls = append(f.calls, "ProviderForSpec:"+name)
if err, ok := f.providerErrs[name]; ok {
return nil, err
}
p, ok := f.providers[name]
if !ok {
return nil, assertionError("no provider for spec " + name)
}
return p, nil
}
func (f *fakeRouter) ProviderForSpecWorkspace(name, workspace string) (Provider, error) {
f.calls = append(f.calls, "ProviderForSpecWorkspace:"+name)
if err, ok := f.providerErrs[name]; ok {
return nil, err
}
p, ok := f.providers[name]
if !ok {
return nil, assertionError("no provider for spec " + name)
}
return p, nil
}
func (f *fakeRouter) ReleaseSpecWorkspace(name, workspace string) {
f.calls = append(f.calls, "ReleaseSpecWorkspace:"+name)
}
func (f *fakeRouter) MaxAlive() int {
f.calls = append(f.calls, "MaxAlive")
return f.maxAlive
}
func (f *fakeRouter) SetMaxAlive(n int) {
f.calls = append(f.calls, "SetMaxAlive")
f.maxAlive = n
}
func (f *fakeRouter) EvictionCount() uint64 {
f.calls = append(f.calls, "EvictionCount")
return f.evictions
}
func (f *fakeRouter) Close() error {
f.closeCalls++
return nil
}
type assertionError string
func (e assertionError) Error() string { return string(e) }
// TestManager_LSPRouter_RoundTrip — SetLSPRouter / LSPRouter accessor
// pair returns the same instance.
func TestManager_LSPRouter_RoundTrip(t *testing.T) {
mgr := NewManager(Config{Enabled: true, EagerLSP: true}, zap.NewNop())
r := &fakeRouter{}
mgr.SetLSPRouter(r)
assert.Same(t, r, mgr.LSPRouter())
}
// TestManager_EnrichAll_RoutesThroughLSPRouter — when a router is
// installed, EnrichAll asks it for each enabled spec and runs Enrich
// against the returned provider exactly once per repo root.
func TestManager_EnrichAll_RoutesThroughLSPRouter(t *testing.T) {
logger := zap.NewNop()
// EagerLSP: this test exercises the synchronous router-backed LSP dispatch,
// which is opt-in (LSP is lazy by default).
cfg := Config{Enabled: true, EagerLSP: true}
mgr := NewManager(cfg, logger)
rsProvider := &mockProvider{
name: "lsp-rust-analyzer",
languages: []string{"rust"},
available: true,
}
r := &fakeRouter{
specs: []string{"rust-analyzer"},
providers: map[string]Provider{"rust-analyzer": rsProvider},
}
mgr.SetLSPRouter(r)
g := graph.New()
roots := map[string]string{"repo-a": "/tmp/a", "repo-b": "/tmp/b"}
results, _, err := mgr.EnrichAll(g, roots, EnrichOptions{})
require.NoError(t, err)
// Two repos × one router-backed spec = two enrichment results.
assert.Len(t, results, 2)
assert.Equal(t, "lsp-rust-analyzer", results[0].Provider)
}
// TestManager_EnrichAll_SkipsCoveredLanguages — when an eager
// provider already covers the same language a router-backed spec
// serves, the router-backed enrichment is skipped (priority semantics
// preserved).
func TestManager_EnrichAll_SkipsCoveredLanguages(t *testing.T) {
logger := zap.NewNop()
cfg := Config{
Enabled: true,
EagerLSP: true, // router-backed LSP dispatch is opt-in
Providers: []ProviderConfig{
{Name: "eager-go", Languages: []string{"go"}, Priority: 1, Enabled: true},
},
}
mgr := NewManager(cfg, logger)
mgr.RegisterProvider(&mockProvider{name: "eager-go", languages: []string{"go"}, available: true})
routerProvider := &mockProvider{name: "lsp-gopls", languages: []string{"go"}, available: true}
r := &fakeRouter{
specs: []string{"gopls"},
providers: map[string]Provider{"gopls": routerProvider},
}
mgr.SetLSPRouter(r)
g := graph.New()
roots := map[string]string{"default": "/tmp/test"}
results, _, err := mgr.EnrichAll(g, roots, EnrichOptions{})
require.NoError(t, err)
// Eager provider runs (1 repo), router-backed gopls is skipped
// because go is already covered.
assert.Len(t, results, 1)
assert.Equal(t, "eager-go", results[0].Provider)
}
// TestManager_EnrichAll_GatesAbsentLanguages — on a populated repo (positive
// presence evidence), a router-backed spec whose language the repo does NOT
// contain is never spawned (ProviderForSpec is not called for it), while a
// provider whose language IS present still runs.
func TestManager_EnrichAll_GatesAbsentLanguages(t *testing.T) {
logger := zap.NewNop()
cfg := Config{
Enabled: true,
EagerLSP: true, // router-backed LSP dispatch is opt-in
Providers: []ProviderConfig{
{Name: "eager-go", Languages: []string{"go"}, Priority: 1, Enabled: true},
},
}
mgr := NewManager(cfg, logger)
mgr.RegisterProvider(&mockProvider{name: "eager-go", languages: []string{"go"}, available: true})
// A router spec serving rust — a language this repo does not contain.
rustProvider := &mockProvider{name: "lsp-rust-analyzer", languages: []string{"rust"}, available: true}
r := &fakeRouter{
specs: []string{"rust-analyzer"},
available: map[string]bool{"rust-analyzer": true},
languages: map[string][]string{"rust-analyzer": {"rust"}},
providers: map[string]Provider{"rust-analyzer": rustProvider},
}
mgr.SetLSPRouter(r)
// Populated repo: one Go node tagged with the roots-key prefix, no Rust.
g := graph.New()
g.AddNode(&graph.Node{ID: "myrepo/main.go::main", Kind: graph.KindFunction, Name: "main", FilePath: "myrepo/main.go", Language: "go", RepoPrefix: "myrepo"})
roots := map[string]string{"myrepo": "/tmp/r"}
results, _, err := mgr.EnrichAll(g, roots, EnrichOptions{})
require.NoError(t, err)
// The eager Go provider runs; the Rust spec is gated out before any spawn.
require.Len(t, results, 1)
assert.Equal(t, "eager-go", results[0].Provider)
for _, c := range r.calls {
if c == "ProviderForSpec:rust-analyzer" {
t.Fatalf("rust spec should be gated out (repo has no rust nodes), but ProviderForSpec was called — calls: %v", r.calls)
}
}
}
// TestManager_HasProviders_RouterOnly — a router-only setup with at
// least one available spec returns true, and the check does NOT
// trigger ProviderForSpec (which would lazy-spawn a real LSP).
func TestManager_HasProviders_RouterOnly(t *testing.T) {
mgr := NewManager(Config{Enabled: true, EagerLSP: true}, zap.NewNop())
r := &fakeRouter{
specs: []string{"rust-analyzer"},
available: map[string]bool{"rust-analyzer": true},
}
mgr.SetLSPRouter(r)
assert.True(t, mgr.HasProviders())
for _, c := range r.calls {
if c == "ProviderForSpec:rust-analyzer" {
t.Fatalf("HasProviders should not lazy-spawn — calls: %v", r.calls)
}
}
}
// TestManager_HasProviders_NoneAvailable — router enabled but no spec
// available returns false (and again does not spawn).
func TestManager_HasProviders_NoneAvailable(t *testing.T) {
mgr := NewManager(Config{Enabled: true, EagerLSP: true}, zap.NewNop())
r := &fakeRouter{
specs: []string{"rust-analyzer"},
available: map[string]bool{"rust-analyzer": false},
}
mgr.SetLSPRouter(r)
assert.False(t, mgr.HasProviders())
}
// TestManager_Close_ShutsDownRouter — Manager.Close cascades into
// LSPRouter.Close exactly once.
func TestManager_Close_ShutsDownRouter(t *testing.T) {
mgr := NewManager(Config{Enabled: true, EagerLSP: true}, zap.NewNop())
r := &fakeRouter{}
mgr.SetLSPRouter(r)
require.NoError(t, mgr.Close())
assert.Equal(t, 1, r.closeCalls)
}
// TestManager_EnrichAll_ArbitratesRouterDupes — two router-backed
// specs serving the same language: only the lower-priority spec runs.
// The losing spec is never spawned (ProviderForSpec is not called for
// it).
func TestManager_EnrichAll_ArbitratesRouterDupes(t *testing.T) {
logger := zap.NewNop()
mgr := NewManager(Config{Enabled: true, EagerLSP: true}, logger)
pyrightProvider := &mockProvider{name: "lsp-pyright", languages: []string{"python"}, available: true}
jediProvider := &mockProvider{name: "lsp-jedi", languages: []string{"python"}, available: true}
r := &fakeRouter{
specs: []string{"pyright", "jedi-language-server"},
available: map[string]bool{"pyright": true, "jedi-language-server": true},
languages: map[string][]string{"pyright": {"python"}, "jedi-language-server": {"python"}},
priorities: map[string]int{"pyright": 5, "jedi-language-server": 6},
providers: map[string]Provider{"pyright": pyrightProvider, "jedi-language-server": jediProvider},
}
mgr.SetLSPRouter(r)
g := graph.New()
roots := map[string]string{"default": "/tmp/test"}
results, _, err := mgr.EnrichAll(g, roots, EnrichOptions{})
require.NoError(t, err)
require.Len(t, results, 1, "exactly one provider should run for python")
assert.Equal(t, "lsp-pyright", results[0].Provider, "pyright wins the lower-priority arbitration")
// Spawn must NOT have happened for the loser.
for _, c := range r.calls {
if c == "ProviderForSpec:jedi-language-server" {
t.Fatalf("loser spec was spawned — calls: %v", r.calls)
}
}
}
// TestManager_EnrichAll_RouterTieBreakerByName — equal priorities tie-
// break alphabetically by spec name (deterministic).
func TestManager_EnrichAll_RouterTieBreakerByName(t *testing.T) {
mgr := NewManager(Config{Enabled: true, EagerLSP: true}, zap.NewNop())
pa := &mockProvider{name: "lsp-aaa", languages: []string{"go"}, available: true}
pb := &mockProvider{name: "lsp-bbb", languages: []string{"go"}, available: true}
r := &fakeRouter{
specs: []string{"bbb", "aaa"},
available: map[string]bool{"aaa": true, "bbb": true},
languages: map[string][]string{"aaa": {"go"}, "bbb": {"go"}},
priorities: map[string]int{"aaa": 5, "bbb": 5},
providers: map[string]Provider{"aaa": pa, "bbb": pb},
}
mgr.SetLSPRouter(r)
results, _, err := mgr.EnrichAll(graph.New(), map[string]string{"default": "/tmp/x"}, EnrichOptions{})
require.NoError(t, err)
require.Len(t, results, 1)
assert.Equal(t, "lsp-aaa", results[0].Provider)
}
// TestManager_EnrichAll_RouterDedupAcrossLanguages — one spec serving
// two languages runs Enrich exactly once, not once per language.
func TestManager_EnrichAll_RouterDedupAcrossLanguages(t *testing.T) {
mgr := NewManager(Config{Enabled: true, EagerLSP: true}, zap.NewNop())
tsProvider := &mockProvider{
name: "lsp-typescript-language-server",
languages: []string{"typescript", "javascript"},
available: true,
}
r := &fakeRouter{
specs: []string{"typescript-language-server"},
available: map[string]bool{"typescript-language-server": true},
languages: map[string][]string{"typescript-language-server": {"typescript", "javascript"}},
priorities: map[string]int{"typescript-language-server": 5},
providers: map[string]Provider{"typescript-language-server": tsProvider},
}
mgr.SetLSPRouter(r)
results, _, err := mgr.EnrichAll(graph.New(), map[string]string{"default": "/tmp/x"}, EnrichOptions{})
require.NoError(t, err)
assert.Len(t, results, 1, "spec serving 2 languages should still run Enrich once")
}
// TestManager_Stats_IncludesRouterSpecs — Stats() reports
// router-enabled LSP specs alongside eagerly-registered providers.
// Router specs show up as "lsp-<spec>" with status driven by
// SpecAvailable, no spawn triggered.
func TestManager_Stats_IncludesRouterSpecs(t *testing.T) {
mgr := NewManager(Config{Enabled: true, EagerLSP: true}, zap.NewNop())
mgr.RegisterProvider(&mockProvider{
name: "scip-go",
languages: []string{"go"},
available: true,
})
r := &fakeRouter{
specs: []string{"rust-analyzer", "pyright"},
available: map[string]bool{"rust-analyzer": true, "pyright": false},
languages: map[string][]string{
"rust-analyzer": {"rust"},
"pyright": {"python"},
},
}
mgr.SetLSPRouter(r)
stats := mgr.Stats()
byName := make(map[string]string)
for _, s := range stats {
byName[s.Name+"/"+s.Language] = s.Status
}
assert.Equal(t, "ready", byName["scip-go/go"])
assert.Equal(t, "ready", byName["lsp-rust-analyzer/rust"])
assert.Equal(t, "unavailable", byName["lsp-pyright/python"])
// Router specs should not have triggered a spawn.
for _, c := range r.calls {
if c == "ProviderForSpec:rust-analyzer" || c == "ProviderForSpec:pyright" {
t.Fatalf("Stats spawned an LSP provider — calls: %v", r.calls)
}
}
}
// TestManager_ConfigPriority_Override — config Priority overrides the
// spec's built-in default in arbitration. User boosts jedi to win
// over pyright.
func TestManager_ConfigPriority_Override(t *testing.T) {
cfg := Config{
Enabled: true,
EagerLSP: true, // router-backed LSP dispatch is opt-in
Providers: []ProviderConfig{
{Name: "jedi-language-server", Priority: 1, Enabled: true},
{Name: "pyright", Priority: 5, Enabled: true},
},
}
mgr := NewManager(cfg, zap.NewNop())
pyrightProvider := &mockProvider{name: "lsp-pyright", languages: []string{"python"}, available: true}
jediProvider := &mockProvider{name: "lsp-jedi", languages: []string{"python"}, available: true}
r := &fakeRouter{
specs: []string{"pyright", "jedi-language-server"},
available: map[string]bool{"pyright": true, "jedi-language-server": true},
languages: map[string][]string{"pyright": {"python"}, "jedi-language-server": {"python"}},
priorities: map[string]int{"pyright": 5, "jedi-language-server": 6},
providers: map[string]Provider{"pyright": pyrightProvider, "jedi-language-server": jediProvider},
}
mgr.SetLSPRouter(r)
results, _, err := mgr.EnrichAll(graph.New(), map[string]string{"default": "/tmp/x"}, EnrichOptions{})
require.NoError(t, err)
require.Len(t, results, 1)
assert.Equal(t, "lsp-jedi", results[0].Provider, "config priority should beat spec default")
}
func TestManager_Stats(t *testing.T) {
logger := zap.NewNop()
cfg := Config{
Enabled: true,
EagerLSP: true, // router-backed LSP dispatch is opt-in
Providers: []ProviderConfig{
{Name: "test-go", Languages: []string{"go"}, Priority: 1, Enabled: true},
},
}
mgr := NewManager(cfg, logger)
mgr.RegisterProvider(&mockProvider{
name: "test-go",
languages: []string{"go"},
available: true,
})
stats := mgr.Stats()
require.Len(t, stats, 1)
assert.Equal(t, "test-go", stats[0].Name)
assert.Equal(t, "go", stats[0].Language)
assert.Equal(t, "ready", stats[0].Status)
}