diff --git a/pkg/query/metric.go b/pkg/query/metric.go index 2fcc78db..e337fc60 100644 --- a/pkg/query/metric.go +++ b/pkg/query/metric.go @@ -57,10 +57,21 @@ func (a *abstractResults) separateSourceScore(source string, score float64) floa return 0 } + sourceScore := a.repoOrderScore(source) + + // Exact matches always rank above everything else; among them, keep + // pacman.conf repo order by adding a bonus in [0, 1]. if score == 1.0 { - return 50 + return 50 + sourceScore/separateSourceMax } + return sourceScore +} + +// repoOrderScore scores sync repositories based on pacman.conf order (as reflected by dbExecutor.Repos()). +// First repo gets max, last repo gets min, evenly distributed across the range. +// AUR and unknown sources get 0. +func (a *abstractResults) repoOrderScore(source string) float64 { if v, ok := a.separateSourceCache[source]; ok { return v } @@ -70,8 +81,6 @@ func (a *abstractResults) separateSourceScore(source string, score float64) floa return 0 } - // Score sync repositories based on pacman.conf order (as reflected by dbExecutor.Repos()). - // First repo gets max, last repo gets min, evenly distributed across the range. for i, repo := range a.repoOrder { if repo != source { continue diff --git a/pkg/query/metric_test.go b/pkg/query/metric_test.go index 52206ee2..5328214a 100644 --- a/pkg/query/metric_test.go +++ b/pkg/query/metric_test.go @@ -43,3 +43,23 @@ func TestSeparateSourceScore_UsesRepoOrderEvenlyDistributed(t *testing.T) { assert.Equal(t, 0.0, a.separateSourceScore("aur", sim)) }) } + +func TestSeparateSourceScore_ExactMatchKeepsRepoOrder(t *testing.T) { + t.Parallel() + + a := &abstractResults{ + separateSources: true, + repoOrder: []string{"cachyos-extra-v3", "core", "extra"}, + separateSourceCache: map[string]float64{}, + } + + custom := a.separateSourceScore("cachyos-extra-v3", 1.0) + extra := a.separateSourceScore("extra", 1.0) + aurScore := a.separateSourceScore("aur", 1.0) + + assert.Greater(t, custom, extra) + assert.Greater(t, extra, aurScore) + assert.Equal(t, 50.0, aurScore) + // Exact matches must still outrank any non-exact match. + assert.Greater(t, extra+1.0, a.separateSourceScore("cachyos-extra-v3", 0.99)+0.99) +} diff --git a/pkg/query/query_builder_test.go b/pkg/query/query_builder_test.go index 509d53ae..7dcd5f2f 100644 --- a/pkg/query/query_builder_test.go +++ b/pkg/query/query_builder_test.go @@ -445,6 +445,83 @@ func TestSourceQueryBuilderTieSortsByRepoOrder(t *testing.T) { } } +func TestSourceQueryBuilderExactMatchSortsByRepoOrder(t *testing.T) { + t.Parallel() + + testCases := []struct { + desc string + bottomUp bool + repoOrder []string + dbOrder []string + wantSources []string + }{ + { + desc: "topdown custom repo first", + repoOrder: []string{"cachyos-extra-v3", "core", "extra"}, + dbOrder: []string{"extra", "cachyos-extra-v3"}, + wantSources: []string{"cachyos-extra-v3", "extra", "aur"}, + }, + { + desc: "topdown extra first", + repoOrder: []string{"core", "extra", "cachyos-extra-v3"}, + dbOrder: []string{"cachyos-extra-v3", "extra"}, + wantSources: []string{"extra", "cachyos-extra-v3", "aur"}, + }, + { + desc: "bottomup custom repo first", + bottomUp: true, + repoOrder: []string{"cachyos-extra-v3", "core", "extra"}, + dbOrder: []string{"extra", "cachyos-extra-v3"}, + wantSources: []string{"aur", "extra", "cachyos-extra-v3"}, + }, + } + + mockAUR := &mockaur.MockAUR{ + GetFn: func(ctx context.Context, query *aur.Query) ([]aur.Pkg, error) { + return []aur.Pkg{{Name: "chromium", PackageBase: "chromium", Version: "1-1"}}, nil + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + t.Parallel() + + mockDB := &mock.DBExecutor{ + ReposFn: func() []string { return tc.repoOrder }, + SyncPackagesFn: func(pkgs ...string) []mock.IPackage { + res := make([]mock.IPackage, 0, len(tc.dbOrder)) + for _, dbName := range tc.dbOrder { + res = append(res, &mock.Package{ + PBase: "chromium", + PName: "chromium", + PVersion: "153.0.8010.52-1", + PDescription: "A web browser built for speed, simplicity, and security", + PDB: mock.NewDB(dbName), + PArchitecture: "x86_64", + }) + } + return res + }, + LocalPackageFn: func(string) mock.IPackage { return nil }, + } + + queryBuilder := NewSourceQueryBuilder(mockAUR, + text.NewLogger(io.Discard, io.Discard, strings.NewReader(""), false, "test"), + "", parser.ModeAny, "", tc.bottomUp, + false, true) + + queryBuilder.Execute(t.Context(), mockDB, []string{"chromium"}) + + gotSources := make([]string, len(queryBuilder.results)) + for i, result := range queryBuilder.results { + gotSources[i] = result.source + } + + assert.Equal(t, tc.wantSources, gotSources) + }) + } +} + func TestSourceQueryBuilderTieDoesNotSeparateSources(t *testing.T) { t.Parallel()