fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

Scoring: test against local scip-ctags (#677)

This change refactors our end-to-end scoring tests and enables local testing
using the scip-ctags binary:
* Split scoring tests out of `e2e_test` and into their own file `scoring_test`
* Split huge test methods into targeted ones like `TestFileNameMatch`,
`TestJava`, `TestGo`, etc.
* For languages that scip-ctags supports, rerun the same cases using the
scip-ctags binary

To run scip-ctags tests locally, you can set the env variable
```
SCIP_CTAGS_COMMAND=<sourcegraph-repo>/dev/scip-ctags-dev
```

This doesn't yet update Zoekt CI to run scip-ctags tests. That will be tackled
in a follow-up.

+741 -627
-626
build/e2e_test.go
··· 20 20 "encoding/json" 21 21 "fmt" 22 22 "log" 23 - "math" 24 23 "os" 25 24 "path/filepath" 26 25 "reflect" ··· 33 32 "github.com/google/go-cmp/cmp" 34 33 "github.com/google/go-cmp/cmp/cmpopts" 35 34 "github.com/grafana/regexp" 36 - 37 35 "github.com/sourcegraph/zoekt" 38 36 "github.com/sourcegraph/zoekt/query" 39 37 "github.com/sourcegraph/zoekt/shards" ··· 783 781 }) 784 782 } 785 783 } 786 - 787 - // With this test we want to capture regressions in the names returned by our 788 - // language detection and the scores assigned to file matches. We rely on the 789 - // detected language and its spelling, for example, in scoring (see scoreKind). 790 - func TestScoring(t *testing.T) { 791 - if os.Getenv("CI") == "" && checkCTags() == "" { 792 - t.Skip("ctags not available") 793 - } 794 - dir := t.TempDir() 795 - 796 - opts := Options{ 797 - IndexDir: dir, 798 - RepositoryDescription: zoekt.Repository{ 799 - Name: "repo", 800 - }, 801 - } 802 - 803 - exampleJava, err := os.ReadFile("./testdata/example.java") 804 - if err != nil { 805 - t.Fatal(err) 806 - } 807 - 808 - exampleKotlin, err := os.ReadFile("./testdata/example.kt") 809 - if err != nil { 810 - t.Fatal(err) 811 - } 812 - 813 - exampleCpp, err := os.ReadFile("./testdata/example.cc") 814 - if err != nil { 815 - t.Fatal(err) 816 - } 817 - 818 - examplePython, err := os.ReadFile("./testdata/example.py") 819 - if err != nil { 820 - t.Fatal(err) 821 - } 822 - 823 - exampleRuby, err := os.ReadFile("./testdata/example.rb") 824 - if err != nil { 825 - t.Fatal(err) 826 - } 827 - 828 - exampleScala, err := os.ReadFile("./testdata/example.scala") 829 - if err != nil { 830 - t.Fatal(err) 831 - } 832 - 833 - cases := []struct { 834 - fileName string 835 - content []byte 836 - query query.Q 837 - wantLanguage string 838 - wantScore float64 839 - }{ 840 - // 841 - // Kotlin 842 - // 843 - { 844 - fileName: "example.kt", 845 - content: exampleKotlin, 846 - query: &query.Substring{Content: true, Pattern: "oxyPreloader"}, 847 - wantLanguage: "Kotlin", 848 - // 5500 (partial symbol at boundary) + 1000 (Kotlin class) + 50 (partial word) + 10 (file order) 849 - wantScore: 6560, 850 - }, 851 - { 852 - fileName: "example.kt", 853 - content: exampleKotlin, 854 - query: &query.Substring{Content: true, Pattern: "ViewMetadata"}, 855 - wantLanguage: "Kotlin", 856 - // 7000 (symbol) + 900 (Kotlin interface) + 500 (word) + 10 (file order) 857 - wantScore: 8410, 858 - }, 859 - { 860 - fileName: "example.kt", 861 - content: exampleKotlin, 862 - query: &query.Substring{Content: true, Pattern: "onScrolled"}, 863 - wantLanguage: "Kotlin", 864 - // 7000 (symbol) + 800 (Kotlin method) + 500 (word) + 10 (file order) 865 - wantScore: 8310, 866 - }, 867 - { 868 - fileName: "example.kt", 869 - content: exampleKotlin, 870 - query: &query.Substring{Content: true, Pattern: "PreloadErrorHandler"}, 871 - wantLanguage: "Kotlin", 872 - // 7000 (symbol) + 700 (Kotlin typealias) + 500 (word) + 10 (file order) 873 - wantScore: 8210, 874 - }, 875 - { 876 - fileName: "example.kt", 877 - content: exampleKotlin, 878 - query: &query.Substring{Content: true, Pattern: "FLING_THRESHOLD_PX"}, 879 - wantLanguage: "Kotlin", 880 - // 7000 (symbol) + 600 (Kotlin constant) + 500 (word) + 10 (file order) 881 - wantScore: 8110, 882 - }, 883 - { 884 - fileName: "example.kt", 885 - content: exampleKotlin, 886 - query: &query.Substring{Content: true, Pattern: "scrollState"}, 887 - wantLanguage: "Kotlin", 888 - // 7000 (symbol) + 500 (Kotlin variable) + 500 (word) + 10 (file order) 889 - wantScore: 8010, 890 - }, 891 - // 892 - // Java 893 - // 894 - { 895 - fileName: "example.java", 896 - content: exampleJava, 897 - query: &query.Substring{Content: true, Pattern: "nerClass"}, 898 - wantLanguage: "Java", 899 - // 5500 (partial symbol at boundary) + 1000 (Java class) + 50 (partial word) + 10 (file order) 900 - wantScore: 6560, 901 - }, 902 - { 903 - fileName: "example.java", 904 - content: exampleJava, 905 - query: &query.Substring{Content: true, Pattern: "StaticClass"}, 906 - wantLanguage: "Java", 907 - // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word) + 10 (file order) 908 - wantScore: 7010, 909 - }, 910 - { 911 - fileName: "example.java", 912 - content: exampleJava, 913 - query: &query.Substring{Content: true, Pattern: "innerEnum"}, 914 - wantLanguage: "Java", 915 - // 7000 (symbol) + 900 (Java enum) + 500 (word) + 10 (file order) 916 - wantScore: 8410, 917 - }, 918 - { 919 - fileName: "example.java", 920 - content: exampleJava, 921 - query: &query.Substring{Content: true, Pattern: "innerInterface"}, 922 - wantLanguage: "Java", 923 - // 7000 (symbol) + 800 (Java interface) + 500 (word) + 10 (file order) 924 - wantScore: 8310, 925 - }, 926 - { 927 - fileName: "example.java", 928 - content: exampleJava, 929 - query: &query.Substring{Content: true, Pattern: "innerMethod"}, 930 - wantLanguage: "Java", 931 - // 7000 (symbol) + 700 (Java method) + 500 (word) + 10 (file order) 932 - wantScore: 8210, 933 - }, 934 - { 935 - fileName: "example.java", 936 - content: exampleJava, 937 - query: &query.Substring{Content: true, Pattern: "field"}, 938 - wantLanguage: "Java", 939 - // 7000 (symbol) + 600 (Java field) + 500 (word) + 10 (file order) 940 - wantScore: 8110, 941 - }, 942 - { 943 - fileName: "example.java", 944 - content: exampleJava, 945 - query: &query.Substring{Content: true, Pattern: "B"}, 946 - wantLanguage: "Java", 947 - // 7000 (symbol) + 500 (Java enum constant) + 500 (word) + 10 (file order) 948 - wantScore: 8010, 949 - }, 950 - // 2 Atoms (1x content and 1x filename) 951 - { 952 - fileName: "example.java", 953 - content: exampleJava, 954 - query: &query.Substring{Pattern: "example"}, // matches filename and a Java field 955 - wantLanguage: "Java", 956 - // 5500 (edge symbol) + 600 (Java field) + 500 (word) + 200 (atom) + 10 (file order) 957 - wantScore: 6810, 958 - }, 959 - // 3 Atoms (2x content, 1x filename) 960 - { 961 - fileName: "example.java", 962 - content: exampleJava, 963 - query: &query.Or{Children: []query.Q{ 964 - &query.Substring{Pattern: "example"}, // matches filename and Java field 965 - &query.Substring{Content: true, Pattern: "runInnerInterface"}, // matches a Java method 966 - }}, 967 - wantLanguage: "Java", 968 - // 7000 (symbol) + 700 (Java method) + 500 (word) + 266.67 (atom) + 10 (file order) 969 - wantScore: 8476.667, 970 - }, 971 - // 4 Atoms (4x content) 972 - { 973 - fileName: "example.java", 974 - content: exampleJava, 975 - query: &query.Or{Children: []query.Q{ 976 - &query.Substring{Content: true, Pattern: "testAnon"}, 977 - &query.Substring{Content: true, Pattern: "Override"}, 978 - &query.Substring{Content: true, Pattern: "InnerEnum"}, 979 - &query.Substring{Content: true, Pattern: "app"}, 980 - }}, 981 - wantLanguage: "Java", 982 - // 7000 (symbol) + 900 (Java enum) + 500 (word) + 300 (atom) + 10 (file order) 983 - wantScore: 8710, 984 - }, 985 - // 986 - // Go 987 - // 988 - { 989 - fileName: "a/b/c/config.go", 990 - query: &query.Substring{FileName: true, Pattern: "config"}, 991 - wantLanguage: "Go", 992 - // 5500 (partial base at boundary) + 500 (word) + 10 (file order) 993 - wantScore: 6010, 994 - }, 995 - { 996 - fileName: "a/b/c/config.go", 997 - query: &query.Substring{FileName: true, Pattern: "config.go"}, 998 - wantLanguage: "Go", 999 - // 7000 (full base match) + 500 (word) + 10 (file order) 1000 - wantScore: 7510, 1001 - }, 1002 - { 1003 - fileName: "a/config/c/d.go", 1004 - query: &query.Substring{FileName: true, Pattern: "config"}, 1005 - wantLanguage: "Go", 1006 - // 500 (word) + 10 (file order) 1007 - wantScore: 510, 1008 - }, 1009 - { 1010 - fileName: "src/net/http/client.go", 1011 - content: []byte(` 1012 - package http 1013 - type aInterface interface {} 1014 - `), 1015 - query: &query.Substring{Content: true, Pattern: "aInterface"}, 1016 - wantLanguage: "Go", 1017 - // 7000 (full base match) + 1000 (Go interface) + 500 (word) + 10 (file order) 1018 - wantScore: 8510, 1019 - }, 1020 - { 1021 - fileName: "src/net/http/client.go", 1022 - content: []byte(` 1023 - package http 1024 - type aStruct struct {} 1025 - `), 1026 - query: &query.Substring{Content: true, Pattern: "aStruct"}, 1027 - wantLanguage: "Go", 1028 - // 7000 (full base match) + 900 (Go struct) + 500 (word) + 10 (file order) 1029 - wantScore: 8410, 1030 - }, 1031 - { 1032 - fileName: "src/net/http/client.go", 1033 - content: []byte(` 1034 - package http 1035 - func aFunc() bool {} 1036 - `), 1037 - query: &query.Substring{Content: true, Pattern: "aFunc"}, 1038 - wantLanguage: "Go", 1039 - // 7000 (full base match) + 800 (Go function) + 500 (word) + 10 (file order) 1040 - wantScore: 8310, 1041 - }, 1042 - { 1043 - fileName: "src/net/http/client.go", 1044 - content: []byte(` 1045 - package http 1046 - func Get() { 1047 - panic("") 1048 - } 1049 - `), 1050 - query: &query.And{Children: []query.Q{ 1051 - &query.Symbol{Expr: &query.Substring{Pattern: "http", Content: true}}, 1052 - &query.Symbol{Expr: &query.Substring{Pattern: "Get", Content: true}}}}, 1053 - wantLanguage: "Go", 1054 - // 7000 (full base match) + 800 (Go func) + 50 (Exported Go) + 500 (word) + 200 (atom) + 10 (file order) 1055 - wantScore: 8560, 1056 - }, 1057 - // 1058 - // C++ 1059 - // 1060 - { 1061 - fileName: "example.cc", 1062 - content: exampleCpp, 1063 - query: &query.Substring{Content: true, Pattern: "FooClass"}, 1064 - wantLanguage: "C++", 1065 - // 7000 (Symbol) + 1000 (C++ class) + 500 (full word) + 10 (file order) 1066 - wantScore: 8510, 1067 - }, 1068 - { 1069 - fileName: "example.cc", 1070 - content: exampleCpp, 1071 - query: &query.Substring{Content: true, Pattern: "NestedEnum"}, 1072 - wantLanguage: "C++", 1073 - // 7000 (Symbol) + 900 (C++ enum) + 500 (full word) + 10 (file order) 1074 - wantScore: 8410, 1075 - }, 1076 - { 1077 - fileName: "example.cc", 1078 - content: exampleCpp, 1079 - query: &query.Substring{Content: true, Pattern: "main"}, 1080 - wantLanguage: "C++", 1081 - // 7000 (Symbol) + 800 (C++ function) + 500 (full word) + 10 (file order) 1082 - wantScore: 8310, 1083 - }, 1084 - { 1085 - fileName: "example.cc", 1086 - content: exampleCpp, 1087 - query: &query.Substring{Content: true, Pattern: "FooStruct"}, 1088 - wantLanguage: "C++", 1089 - // 7000 (Symbol) + 700 (C++ struct) + 500 (full word) + 10 (file order) 1090 - wantScore: 8210, 1091 - }, 1092 - { 1093 - fileName: "example.cc", 1094 - content: exampleCpp, 1095 - query: &query.Substring{Content: true, Pattern: "TheUnion"}, 1096 - wantLanguage: "C++", 1097 - // 7000 (Symbol) + 600 (C++ union) + 500 (full word) + 10 (file order) 1098 - wantScore: 8110, 1099 - }, 1100 - // 1101 - // Python 1102 - // 1103 - { 1104 - fileName: "example.py", 1105 - content: examplePython, 1106 - query: &query.Substring{Content: true, Pattern: "C1"}, 1107 - wantLanguage: "Python", 1108 - // 7000 (symbol) + 1000 (Python class) + 500 (word) + 10 (file order) 1109 - wantScore: 8510, 1110 - }, 1111 - { 1112 - fileName: "example.py", 1113 - content: examplePython, 1114 - query: &query.Substring{Content: true, Pattern: "g"}, 1115 - wantLanguage: "Python", 1116 - // 7000 (symbol) + 800 (Python function) + 500 (word) + 10 (file order) 1117 - wantScore: 8310, 1118 - }, 1119 - { 1120 - fileName: "example.py", 1121 - content: examplePython, 1122 - query: &query.Substring{Content: true, Pattern: "__init__"}, 1123 - wantLanguage: "Python", 1124 - // 7000 (symbol) + 400 (Python member) + 50 (partial word) + 10 (file order) 1125 - wantScore: 7460, 1126 - }, 1127 - // 1128 - // Ruby 1129 - // 1130 - { 1131 - fileName: "example.rb", 1132 - content: exampleRuby, 1133 - query: &query.Substring{Content: true, Pattern: "Parental"}, 1134 - wantLanguage: "Ruby", 1135 - // 7000 (symbol) + 1000 (Ruby class) + 500 (word) + 10 (file order) 1136 - wantScore: 8510, 1137 - }, 1138 - { 1139 - fileName: "example.rb", 1140 - content: exampleRuby, 1141 - query: &query.Substring{Content: true, Pattern: "parental_func"}, 1142 - wantLanguage: "Ruby", 1143 - // 7000 (symbol) + 900 (Ruby method) + 500 (word) + 10 (file order) 1144 - wantScore: 8410, 1145 - }, 1146 - { 1147 - fileName: "example.rb", 1148 - content: exampleRuby, 1149 - query: &query.Substring{Content: true, Pattern: "MyModule"}, 1150 - wantLanguage: "Ruby", 1151 - // 7000 (symbol) + 500 (Ruby module) + 500 (word) + 10 (file order) 1152 - wantScore: 8210, 1153 - }, 1154 - // 1155 - // Scala 1156 - // 1157 - { 1158 - fileName: "example.scala", 1159 - content: exampleScala, 1160 - query: &query.Substring{Content: true, Pattern: "SymbolIndexBucket"}, 1161 - wantLanguage: "Scala", 1162 - // 7000 (symbol) + 1000 (Scala class) + 500 (word) + 10 (file order) 1163 - wantScore: 8510, 1164 - }, 1165 - { 1166 - fileName: "example.scala", 1167 - content: exampleScala, 1168 - query: &query.Substring{Content: true, Pattern: "stdLibPatches"}, 1169 - wantLanguage: "Scala", 1170 - // 7000 (symbol) + 800 (Scala object) + 500 (word) + 10 (file order) 1171 - wantScore: 8310, 1172 - }, 1173 - { 1174 - fileName: "example.scala", 1175 - content: exampleScala, 1176 - query: &query.Substring{Content: true, Pattern: "close"}, 1177 - wantLanguage: "Scala", 1178 - // 7000 (symbol) + 700 (Scala method) + 500 (word) + 10 (file order) 1179 - wantScore: 8210, 1180 - }, 1181 - { 1182 - fileName: "example.scala", 1183 - content: exampleScala, 1184 - query: &query.Substring{Content: true, Pattern: "javaSymbol"}, 1185 - wantLanguage: "Scala", 1186 - // 7000 (symbol) + 500 (Scala method) + 500 (word) + 10 (file order) 1187 - wantScore: 8010, 1188 - }, 1189 - } 1190 - 1191 - epsilon := 0.01 1192 - for _, c := range cases { 1193 - t.Run(c.wantLanguage, func(t *testing.T) { 1194 - b, err := NewBuilder(opts) 1195 - if err != nil { 1196 - t.Fatalf("NewBuilder: %v", err) 1197 - } 1198 - if err := b.AddFile(c.fileName, c.content); err != nil { 1199 - t.Fatal(err) 1200 - } 1201 - if err := b.Finish(); err != nil { 1202 - t.Fatalf("Finish: %v", err) 1203 - } 1204 - 1205 - ss, err := shards.NewDirectorySearcher(dir) 1206 - if err != nil { 1207 - t.Fatalf("NewDirectorySearcher(%s): %v", dir, err) 1208 - } 1209 - defer ss.Close() 1210 - 1211 - srs, err := ss.Search(context.Background(), c.query, &zoekt.SearchOptions{DebugScore: true}) 1212 - if err != nil { 1213 - t.Fatal(err) 1214 - } 1215 - 1216 - if got, want := len(srs.Files), 1; got != want { 1217 - t.Fatalf("file matches: want %d, got %d", want, got) 1218 - } 1219 - 1220 - if got := srs.Files[0].Score; math.Abs(got-c.wantScore) > epsilon { 1221 - t.Fatalf("score: want %f, got %f\ndebug: %s\ndebugscore: %s", c.wantScore, got, srs.Files[0].Debug, srs.Files[0].LineMatches[0].DebugScore) 1222 - } 1223 - 1224 - if got := srs.Files[0].Language; got != c.wantLanguage { 1225 - t.Fatalf("want %s, got %s", c.wantLanguage, got) 1226 - } 1227 - }) 1228 - } 1229 - } 1230 - 1231 - func TestScoringWithDocumentRanks(t *testing.T) { 1232 - if os.Getenv("CI") == "" && checkCTags() == "" { 1233 - t.Skip("ctags not available") 1234 - } 1235 - dir := t.TempDir() 1236 - 1237 - opts := Options{ 1238 - IndexDir: dir, 1239 - RepositoryDescription: zoekt.Repository{ 1240 - Name: "repo", 1241 - }, 1242 - DocumentRanksVersion: "ranking", 1243 - } 1244 - 1245 - searchQuery := &query.Substring{Content: true, Pattern: "Inner"} 1246 - exampleJava, err := os.ReadFile("./testdata/example.java") 1247 - if err != nil { 1248 - t.Fatal(err) 1249 - } 1250 - 1251 - cases := []struct { 1252 - name string 1253 - documentRank float64 1254 - documentRanksWeight float64 1255 - wantScore float64 1256 - }{ 1257 - { 1258 - name: "score with no document ranks", 1259 - // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 10 (file order) 1260 - wantScore: 7010.00, 1261 - }, 1262 - { 1263 - name: "score with document ranks", 1264 - documentRank: 0.8, 1265 - // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 225 (file rank) + 10 (file order) 1266 - wantScore: 7235.00, 1267 - }, 1268 - { 1269 - name: "score with custom document ranks weight", 1270 - documentRank: 0.8, 1271 - documentRanksWeight: 1000.0, 1272 - // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 25.00 (file rank) + 10 (file order) 1273 - wantScore: 7035.00, 1274 - }, 1275 - } 1276 - 1277 - for _, c := range cases { 1278 - t.Run(c.name, func(t *testing.T) { 1279 - b, err := NewBuilder(opts) 1280 - if err != nil { 1281 - t.Fatalf("NewBuilder: %v", err) 1282 - } 1283 - 1284 - err = b.Add(zoekt.Document{Name: "example.java", Content: exampleJava, Ranks: []float64{c.documentRank}}) 1285 - if err != nil { 1286 - t.Fatal(err) 1287 - } 1288 - 1289 - if err := b.Finish(); err != nil { 1290 - t.Fatalf("Finish: %v", err) 1291 - } 1292 - 1293 - ss, err := shards.NewDirectorySearcher(dir) 1294 - if err != nil { 1295 - t.Fatalf("NewDirectorySearcher(%s): %v", dir, err) 1296 - } 1297 - defer ss.Close() 1298 - 1299 - srs, err := ss.Search(context.Background(), searchQuery, &zoekt.SearchOptions{ 1300 - UseDocumentRanks: true, 1301 - DocumentRanksWeight: c.documentRanksWeight, 1302 - DebugScore: true, 1303 - }) 1304 - 1305 - if err != nil { 1306 - t.Fatal(err) 1307 - } 1308 - 1309 - if got, want := len(srs.Files), 1; got != want { 1310 - t.Fatalf("file matches: want %d, got %d", want, got) 1311 - } 1312 - 1313 - if got := srs.Files[0].Score; got != c.wantScore { 1314 - t.Fatalf("score: want %f, got %f\ndebug: %s\ndebugscore: %s", c.wantScore, got, srs.Files[0].Debug, srs.Files[0].LineMatches[0].DebugScore) 1315 - } 1316 - }) 1317 - } 1318 - } 1319 - 1320 - func TestRepoRanks(t *testing.T) { 1321 - if os.Getenv("CI") == "" && checkCTags() == "" { 1322 - t.Skip("ctags not available") 1323 - } 1324 - dir := t.TempDir() 1325 - 1326 - opts := Options{ 1327 - IndexDir: dir, 1328 - RepositoryDescription: zoekt.Repository{ 1329 - Name: "repo", 1330 - }, 1331 - DocumentRanksVersion: "ranking", 1332 - } 1333 - 1334 - searchQuery := &query.Substring{Content: true, Pattern: "Inner"} 1335 - exampleJava, err := os.ReadFile("./testdata/example.java") 1336 - if err != nil { 1337 - t.Fatal(err) 1338 - } 1339 - 1340 - cases := []struct { 1341 - name string 1342 - repoRank uint16 1343 - wantScore float64 1344 - }{ 1345 - { 1346 - name: "no shard rank", 1347 - // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 10 (file order) 1348 - wantScore: 7010.00, 1349 - }, 1350 - { 1351 - name: "medium shard rank", 1352 - repoRank: 30000, 1353 - // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 10 (file order) + 9.16 (repo rank) 1354 - wantScore: 7019.16, 1355 - }, 1356 - { 1357 - name: "high shard rank", 1358 - repoRank: 60000, 1359 - // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 10 (file order) + 18.31 (repo rank) 1360 - wantScore: 7028.31, 1361 - }, 1362 - } 1363 - 1364 - for _, c := range cases { 1365 - t.Run(c.name, func(t *testing.T) { 1366 - opts.RepositoryDescription = zoekt.Repository{ 1367 - Name: "repo", 1368 - Rank: c.repoRank, 1369 - } 1370 - 1371 - b, err := NewBuilder(opts) 1372 - if err != nil { 1373 - t.Fatalf("NewBuilder: %v", err) 1374 - } 1375 - 1376 - err = b.Add(zoekt.Document{Name: "example.java", Content: exampleJava}) 1377 - if err != nil { 1378 - t.Fatal(err) 1379 - } 1380 - 1381 - if err := b.Finish(); err != nil { 1382 - t.Fatalf("Finish: %v", err) 1383 - } 1384 - 1385 - ss, err := shards.NewDirectorySearcher(dir) 1386 - if err != nil { 1387 - t.Fatalf("NewDirectorySearcher(%s): %v", dir, err) 1388 - } 1389 - defer ss.Close() 1390 - 1391 - srs, err := ss.Search(context.Background(), searchQuery, &zoekt.SearchOptions{ 1392 - UseDocumentRanks: true, 1393 - DebugScore: true, 1394 - }) 1395 - 1396 - if err != nil { 1397 - t.Fatal(err) 1398 - } 1399 - 1400 - if got, want := len(srs.Files), 1; got != want { 1401 - t.Fatalf("file matches: want %d, got %d", want, got) 1402 - } 1403 - 1404 - if got := srs.Files[0].Score; math.Abs(got-c.wantScore) >= 0.01 { 1405 - t.Fatalf("score: want %f, got %f\ndebug: %s\ndebugscore: %s", c.wantScore, got, srs.Files[0].Debug, srs.Files[0].LineMatches[0].DebugScore) 1406 - } 1407 - }) 1408 - } 1409 - }
+740
build/scoring_test.go
··· 1 + // Copyright 2016 Google Inc. All rights reserved. 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package build 16 + 17 + import ( 18 + "context" 19 + "math" 20 + "os" 21 + "testing" 22 + 23 + "github.com/sourcegraph/zoekt" 24 + "github.com/sourcegraph/zoekt/ctags" 25 + "github.com/sourcegraph/zoekt/query" 26 + "github.com/sourcegraph/zoekt/shards" 27 + ) 28 + 29 + type scoreCase struct { 30 + fileName string 31 + content []byte 32 + query query.Q 33 + language string 34 + wantScore float64 35 + } 36 + 37 + func TestFileNameMatch(t *testing.T) { 38 + cases := []scoreCase{ 39 + { 40 + fileName: "a/b/c/config.go", 41 + query: &query.Substring{FileName: true, Pattern: "config"}, 42 + language: "Go", 43 + // 5500 (partial base at boundary) + 500 (word) + 10 (file order) 44 + wantScore: 6010, 45 + }, 46 + { 47 + fileName: "a/b/c/config.go", 48 + query: &query.Substring{FileName: true, Pattern: "config.go"}, 49 + language: "Go", 50 + // 7000 (full base match) + 500 (word) + 10 (file order) 51 + wantScore: 7510, 52 + }, 53 + { 54 + fileName: "a/config/c/d.go", 55 + query: &query.Substring{FileName: true, Pattern: "config"}, 56 + language: "Go", 57 + // 500 (word) + 10 (file order) 58 + wantScore: 510, 59 + }, 60 + } 61 + 62 + for _, c := range cases { 63 + checkScoring(t, c, ctags.UniversalCTags) 64 + } 65 + } 66 + 67 + func TestJava(t *testing.T) { 68 + exampleJava, err := os.ReadFile("./testdata/example.java") 69 + if err != nil { 70 + t.Fatal(err) 71 + } 72 + 73 + cases := []scoreCase{ 74 + { 75 + fileName: "example.java", 76 + content: exampleJava, 77 + query: &query.Substring{Content: true, Pattern: "nerClass"}, 78 + language: "Java", 79 + // 5500 (partial symbol at boundary) + 1000 (Java class) + 50 (partial word) + 10 (file order) 80 + wantScore: 6560, 81 + }, 82 + { 83 + fileName: "example.java", 84 + content: exampleJava, 85 + query: &query.Substring{Content: true, Pattern: "StaticClass"}, 86 + language: "Java", 87 + // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word) + 10 (file order) 88 + wantScore: 7010, 89 + }, 90 + { 91 + fileName: "example.java", 92 + content: exampleJava, 93 + query: &query.Substring{Content: true, Pattern: "innerEnum"}, 94 + language: "Java", 95 + // 7000 (symbol) + 900 (Java enum) + 500 (word) + 10 (file order) 96 + wantScore: 8410, 97 + }, 98 + { 99 + fileName: "example.java", 100 + content: exampleJava, 101 + query: &query.Substring{Content: true, Pattern: "innerInterface"}, 102 + language: "Java", 103 + // 7000 (symbol) + 800 (Java interface) + 500 (word) + 10 (file order) 104 + wantScore: 8310, 105 + }, 106 + { 107 + fileName: "example.java", 108 + content: exampleJava, 109 + query: &query.Substring{Content: true, Pattern: "innerMethod"}, 110 + language: "Java", 111 + // 7000 (symbol) + 700 (Java method) + 500 (word) + 10 (file order) 112 + wantScore: 8210, 113 + }, 114 + { 115 + fileName: "example.java", 116 + content: exampleJava, 117 + query: &query.Substring{Content: true, Pattern: "field"}, 118 + language: "Java", 119 + // 7000 (symbol) + 600 (Java field) + 500 (word) + 10 (file order) 120 + wantScore: 8110, 121 + }, 122 + { 123 + fileName: "example.java", 124 + content: exampleJava, 125 + query: &query.Substring{Content: true, Pattern: "B"}, 126 + language: "Java", 127 + // 7000 (symbol) + 500 (Java enum constant) + 500 (word) + 10 (file order) 128 + wantScore: 8010, 129 + }, 130 + // 2 Atoms (1x content and 1x filename) 131 + { 132 + fileName: "example.java", 133 + content: exampleJava, 134 + query: &query.Substring{Pattern: "example"}, // matches filename and a Java field 135 + language: "Java", 136 + // 5500 (edge symbol) + 600 (Java field) + 500 (word) + 200 (atom) + 10 (file order) 137 + wantScore: 6810, 138 + }, 139 + // 3 Atoms (2x content, 1x filename) 140 + { 141 + fileName: "example.java", 142 + content: exampleJava, 143 + query: &query.Or{Children: []query.Q{ 144 + &query.Substring{Pattern: "example"}, // matches filename and Java field 145 + &query.Substring{Content: true, Pattern: "runInnerInterface"}, // matches a Java method 146 + }}, 147 + language: "Java", 148 + // 7000 (symbol) + 700 (Java method) + 500 (word) + 266.67 (atom) + 10 (file order) 149 + wantScore: 8476.667, 150 + }, 151 + // 4 Atoms (4x content) 152 + { 153 + fileName: "example.java", 154 + content: exampleJava, 155 + query: &query.Or{Children: []query.Q{ 156 + &query.Substring{Content: true, Pattern: "testAnon"}, 157 + &query.Substring{Content: true, Pattern: "Override"}, 158 + &query.Substring{Content: true, Pattern: "InnerEnum"}, 159 + &query.Substring{Content: true, Pattern: "app"}, 160 + }}, 161 + language: "Java", 162 + // 7000 (symbol) + 900 (Java enum) + 500 (word) + 300 (atom) + 10 (file order) 163 + wantScore: 8710, 164 + }, 165 + } 166 + 167 + for _, c := range cases { 168 + checkScoring(t, c, ctags.UniversalCTags) 169 + } 170 + } 171 + 172 + func TestKotlin(t *testing.T) { 173 + exampleKotlin, err := os.ReadFile("./testdata/example.kt") 174 + if err != nil { 175 + t.Fatal(err) 176 + } 177 + 178 + cases := []scoreCase{ 179 + { 180 + fileName: "example.kt", 181 + content: exampleKotlin, 182 + query: &query.Substring{Content: true, Pattern: "oxyPreloader"}, 183 + language: "Kotlin", 184 + // 5500 (partial symbol at boundary) + 1000 (Kotlin class) + 50 (partial word) + 10 (file order) 185 + wantScore: 6560, 186 + }, 187 + { 188 + fileName: "example.kt", 189 + content: exampleKotlin, 190 + query: &query.Substring{Content: true, Pattern: "ViewMetadata"}, 191 + language: "Kotlin", 192 + // 7000 (symbol) + 900 (Kotlin interface) + 500 (word) + 10 (file order) 193 + wantScore: 8410, 194 + }, 195 + { 196 + fileName: "example.kt", 197 + content: exampleKotlin, 198 + query: &query.Substring{Content: true, Pattern: "onScrolled"}, 199 + language: "Kotlin", 200 + // 7000 (symbol) + 800 (Kotlin method) + 500 (word) + 10 (file order) 201 + wantScore: 8310, 202 + }, 203 + { 204 + fileName: "example.kt", 205 + content: exampleKotlin, 206 + query: &query.Substring{Content: true, Pattern: "PreloadErrorHandler"}, 207 + language: "Kotlin", 208 + // 7000 (symbol) + 700 (Kotlin typealias) + 500 (word) + 10 (file order) 209 + wantScore: 8210, 210 + }, 211 + { 212 + fileName: "example.kt", 213 + content: exampleKotlin, 214 + query: &query.Substring{Content: true, Pattern: "FLING_THRESHOLD_PX"}, 215 + language: "Kotlin", 216 + // 7000 (symbol) + 600 (Kotlin constant) + 500 (word) + 10 (file order) 217 + wantScore: 8110, 218 + }, 219 + { 220 + fileName: "example.kt", 221 + content: exampleKotlin, 222 + query: &query.Substring{Content: true, Pattern: "scrollState"}, 223 + language: "Kotlin", 224 + // 7000 (symbol) + 500 (Kotlin variable) + 500 (word) + 10 (file order) 225 + wantScore: 8010, 226 + }, 227 + } 228 + 229 + parserType := ctags.UniversalCTags 230 + for _, c := range cases { 231 + t.Run(c.language, func(t *testing.T) { 232 + checkScoring(t, c, parserType) 233 + }) 234 + } 235 + } 236 + 237 + func TestCpp(t *testing.T) { 238 + exampleCpp, err := os.ReadFile("./testdata/example.cc") 239 + if err != nil { 240 + t.Fatal(err) 241 + } 242 + 243 + cases := []scoreCase{ 244 + { 245 + fileName: "example.cc", 246 + content: exampleCpp, 247 + query: &query.Substring{Content: true, Pattern: "FooClass"}, 248 + language: "C++", 249 + // 7000 (Symbol) + 1000 (C++ class) + 500 (full word) + 10 (file order) 250 + wantScore: 8510, 251 + }, 252 + { 253 + fileName: "example.cc", 254 + content: exampleCpp, 255 + query: &query.Substring{Content: true, Pattern: "NestedEnum"}, 256 + language: "C++", 257 + // 7000 (Symbol) + 900 (C++ enum) + 500 (full word) + 10 (file order) 258 + wantScore: 8410, 259 + }, 260 + { 261 + fileName: "example.cc", 262 + content: exampleCpp, 263 + query: &query.Substring{Content: true, Pattern: "main"}, 264 + language: "C++", 265 + // 7000 (Symbol) + 800 (C++ function) + 500 (full word) + 10 (file order) 266 + wantScore: 8310, 267 + }, 268 + { 269 + fileName: "example.cc", 270 + content: exampleCpp, 271 + query: &query.Substring{Content: true, Pattern: "FooStruct"}, 272 + language: "C++", 273 + // 7000 (Symbol) + 700 (C++ struct) + 500 (full word) + 10 (file order) 274 + wantScore: 8210, 275 + }, 276 + { 277 + fileName: "example.cc", 278 + content: exampleCpp, 279 + query: &query.Substring{Content: true, Pattern: "TheUnion"}, 280 + language: "C++", 281 + // 7000 (Symbol) + 600 (C++ union) + 500 (full word) + 10 (file order) 282 + wantScore: 8110, 283 + }, 284 + } 285 + 286 + parserType := ctags.UniversalCTags 287 + for _, c := range cases { 288 + t.Run(c.language, func(t *testing.T) { 289 + checkScoring(t, c, parserType) 290 + }) 291 + } 292 + } 293 + 294 + func TestPython(t *testing.T) { 295 + examplePython, err := os.ReadFile("./testdata/example.py") 296 + if err != nil { 297 + t.Fatal(err) 298 + } 299 + 300 + cases := []scoreCase{ 301 + { 302 + fileName: "example.py", 303 + content: examplePython, 304 + query: &query.Substring{Content: true, Pattern: "C1"}, 305 + language: "Python", 306 + // 7000 (symbol) + 1000 (Python class) + 500 (word) + 10 (file order) 307 + wantScore: 8510, 308 + }, 309 + { 310 + fileName: "example.py", 311 + content: examplePython, 312 + query: &query.Substring{Content: true, Pattern: "g"}, 313 + language: "Python", 314 + // 7000 (symbol) + 800 (Python function) + 500 (word) + 10 (file order) 315 + wantScore: 8310, 316 + }, 317 + } 318 + 319 + for _, parserType := range []ctags.CTagsParserType{ctags.UniversalCTags, ctags.ScipCTags} { 320 + for _, c := range cases { 321 + checkScoring(t, c, parserType) 322 + } 323 + } 324 + 325 + // Only test SCIP, as universal-ctags doesn't correctly recognize this as a method 326 + scipOnlyCase := scoreCase{ 327 + fileName: "example.py", 328 + content: examplePython, 329 + query: &query.Substring{Content: true, Pattern: "__init__"}, 330 + language: "Python", 331 + // 7000 (symbol) + 800 (Python method) + 50 (partial word) + 10 (file order) 332 + wantScore: 7860, 333 + } 334 + 335 + checkScoring(t, scipOnlyCase, ctags.ScipCTags) 336 + } 337 + 338 + func TestRuby(t *testing.T) { 339 + exampleRuby, err := os.ReadFile("./testdata/example.rb") 340 + if err != nil { 341 + t.Fatal(err) 342 + } 343 + 344 + cases := []scoreCase{ 345 + { 346 + fileName: "example.rb", 347 + content: exampleRuby, 348 + query: &query.Substring{Content: true, Pattern: "Parental"}, 349 + language: "Ruby", 350 + // 7000 (symbol) + 1000 (Ruby class) + 500 (word) + 10 (file order) 351 + wantScore: 8510, 352 + }, 353 + { 354 + fileName: "example.rb", 355 + content: exampleRuby, 356 + query: &query.Substring{Content: true, Pattern: "parental_func"}, 357 + language: "Ruby", 358 + // 7000 (symbol) + 900 (Ruby method) + 500 (word) + 10 (file order) 359 + wantScore: 8410, 360 + }, 361 + { 362 + fileName: "example.rb", 363 + content: exampleRuby, 364 + query: &query.Substring{Content: true, Pattern: "MyModule"}, 365 + language: "Ruby", 366 + // 7000 (symbol) + 500 (Ruby module) + 500 (word) + 10 (file order) 367 + wantScore: 8210, 368 + }, 369 + } 370 + 371 + for _, parserType := range []ctags.CTagsParserType{ctags.UniversalCTags, ctags.ScipCTags} { 372 + for _, c := range cases { 373 + checkScoring(t, c, parserType) 374 + } 375 + } 376 + } 377 + 378 + func TestScala(t *testing.T) { 379 + exampleScala, err := os.ReadFile("./testdata/example.scala") 380 + if err != nil { 381 + t.Fatal(err) 382 + } 383 + 384 + cases := []scoreCase{ 385 + { 386 + fileName: "example.scala", 387 + content: exampleScala, 388 + query: &query.Substring{Content: true, Pattern: "SymbolIndexBucket"}, 389 + language: "Scala", 390 + // 7000 (symbol) + 1000 (Scala class) + 500 (word) + 10 (file order) 391 + wantScore: 8510, 392 + }, 393 + { 394 + fileName: "example.scala", 395 + content: exampleScala, 396 + query: &query.Substring{Content: true, Pattern: "stdLibPatches"}, 397 + language: "Scala", 398 + // 7000 (symbol) + 800 (Scala object) + 500 (word) + 10 (file order) 399 + wantScore: 8310, 400 + }, 401 + { 402 + fileName: "example.scala", 403 + content: exampleScala, 404 + query: &query.Substring{Content: true, Pattern: "close"}, 405 + language: "Scala", 406 + // 7000 (symbol) + 700 (Scala method) + 500 (word) + 10 (file order) 407 + wantScore: 8210, 408 + }, 409 + { 410 + fileName: "example.scala", 411 + content: exampleScala, 412 + query: &query.Substring{Content: true, Pattern: "javaSymbol"}, 413 + language: "Scala", 414 + // 7000 (symbol) + 500 (Scala method) + 500 (word) + 10 (file order) 415 + wantScore: 8010, 416 + }, 417 + } 418 + 419 + parserType := ctags.UniversalCTags 420 + for _, c := range cases { 421 + checkScoring(t, c, parserType) 422 + } 423 + } 424 + 425 + func TestGo(t *testing.T) { 426 + cases := []scoreCase{ 427 + { 428 + fileName: "src/net/http/client.go", 429 + content: []byte(` 430 + package http 431 + type aInterface interface {} 432 + `), 433 + query: &query.Substring{Content: true, Pattern: "aInterface"}, 434 + language: "Go", 435 + // 7000 (full base match) + 1000 (Go interface) + 500 (word) + 10 (file order) 436 + wantScore: 8510, 437 + }, 438 + { 439 + fileName: "src/net/http/client.go", 440 + content: []byte(` 441 + package http 442 + type aStruct struct {} 443 + `), 444 + query: &query.Substring{Content: true, Pattern: "aStruct"}, 445 + language: "Go", 446 + // 7000 (full base match) + 900 (Go struct) + 500 (word) + 10 (file order) 447 + wantScore: 8410, 448 + }, 449 + { 450 + fileName: "src/net/http/client.go", 451 + content: []byte(` 452 + package http 453 + func aFunc() bool {} 454 + `), 455 + query: &query.Substring{Content: true, Pattern: "aFunc"}, 456 + language: "Go", 457 + // 7000 (full base match) + 800 (Go function) + 500 (word) + 10 (file order) 458 + wantScore: 8310, 459 + }, 460 + { 461 + fileName: "src/net/http/client.go", 462 + content: []byte(` 463 + package http 464 + func Get() { 465 + panic("") 466 + } 467 + `), 468 + query: &query.And{Children: []query.Q{ 469 + &query.Symbol{Expr: &query.Substring{Pattern: "http", Content: true}}, 470 + &query.Symbol{Expr: &query.Substring{Pattern: "Get", Content: true}}}}, 471 + language: "Go", 472 + // 7000 (full base match) + 800 (Go func) + 50 (Exported Go) + 500 (word) + 200 (atom) + 10 (file order) 473 + wantScore: 8560, 474 + }, 475 + } 476 + 477 + for _, parserType := range []ctags.CTagsParserType{ctags.UniversalCTags, ctags.ScipCTags} { 478 + for _, c := range cases { 479 + checkScoring(t, c, parserType) 480 + } 481 + } 482 + } 483 + 484 + func skipIfCTagsUnavailable(t *testing.T, parserType ctags.CTagsParserType) { 485 + // Never skip universal-ctags tests in CI 486 + if os.Getenv("CI") != "" && parserType == ctags.UniversalCTags { 487 + return 488 + } 489 + 490 + switch parserType { 491 + case ctags.UniversalCTags: 492 + if checkCTags() == "" { 493 + t.Skip("ctags not available") 494 + } 495 + case ctags.ScipCTags: 496 + if checkScipCTags() == "" { 497 + t.Skip("scip-ctags not available") 498 + } 499 + default: 500 + t.Fatalf("unexpected parser type") 501 + } 502 + } 503 + 504 + func checkScoring(t *testing.T, c scoreCase, parserType ctags.CTagsParserType) { 505 + skipIfCTagsUnavailable(t, parserType) 506 + 507 + name := c.language 508 + if parserType == ctags.ScipCTags { 509 + name += "-scip" 510 + } 511 + 512 + t.Run(name, func(t *testing.T) { 513 + dir := t.TempDir() 514 + 515 + opts := Options{ 516 + IndexDir: dir, 517 + RepositoryDescription: zoekt.Repository{ 518 + Name: "repo", 519 + }, 520 + LanguageMap: ctags.LanguageMap{ 521 + normalizeLanguage(c.language): parserType}, 522 + } 523 + 524 + epsilon := 0.01 525 + 526 + b, err := NewBuilder(opts) 527 + if err != nil { 528 + t.Fatalf("NewBuilder: %v", err) 529 + } 530 + if err := b.AddFile(c.fileName, c.content); err != nil { 531 + t.Fatal(err) 532 + } 533 + if err := b.Finish(); err != nil { 534 + t.Fatalf("Finish: %v", err) 535 + } 536 + 537 + ss, err := shards.NewDirectorySearcher(dir) 538 + if err != nil { 539 + t.Fatalf("NewDirectorySearcher(%s): %v", dir, err) 540 + } 541 + defer ss.Close() 542 + 543 + srs, err := ss.Search(context.Background(), c.query, &zoekt.SearchOptions{DebugScore: true}) 544 + if err != nil { 545 + t.Fatal(err) 546 + } 547 + 548 + if got, want := len(srs.Files), 1; got != want { 549 + t.Fatalf("file matches: want %d, got %d", want, got) 550 + } 551 + 552 + if got := srs.Files[0].Score; math.Abs(got-c.wantScore) > epsilon { 553 + t.Fatalf("score: want %f, got %f\ndebug: %s\ndebugscore: %s", c.wantScore, got, srs.Files[0].Debug, srs.Files[0].LineMatches[0].DebugScore) 554 + } 555 + 556 + if got := srs.Files[0].Language; got != c.language { 557 + t.Fatalf("want %s, got %s", c.language, got) 558 + } 559 + }) 560 + } 561 + 562 + func TestDocumentRanks(t *testing.T) { 563 + if os.Getenv("CI") == "" && checkCTags() == "" { 564 + t.Skip("ctags not available") 565 + } 566 + dir := t.TempDir() 567 + 568 + opts := Options{ 569 + IndexDir: dir, 570 + RepositoryDescription: zoekt.Repository{ 571 + Name: "repo", 572 + }, 573 + DocumentRanksVersion: "ranking", 574 + } 575 + 576 + searchQuery := &query.Substring{Content: true, Pattern: "Inner"} 577 + exampleJava, err := os.ReadFile("./testdata/example.java") 578 + if err != nil { 579 + t.Fatal(err) 580 + } 581 + 582 + cases := []struct { 583 + name string 584 + documentRank float64 585 + documentRanksWeight float64 586 + wantScore float64 587 + }{ 588 + { 589 + name: "score with no document ranks", 590 + // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 10 (file order) 591 + wantScore: 7010.00, 592 + }, 593 + { 594 + name: "score with document ranks", 595 + documentRank: 0.8, 596 + // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 225 (file rank) + 10 (file order) 597 + wantScore: 7235.00, 598 + }, 599 + { 600 + name: "score with custom document ranks weight", 601 + documentRank: 0.8, 602 + documentRanksWeight: 1000.0, 603 + // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 25.00 (file rank) + 10 (file order) 604 + wantScore: 7035.00, 605 + }, 606 + } 607 + 608 + for _, c := range cases { 609 + t.Run(c.name, func(t *testing.T) { 610 + b, err := NewBuilder(opts) 611 + if err != nil { 612 + t.Fatalf("NewBuilder: %v", err) 613 + } 614 + 615 + err = b.Add(zoekt.Document{Name: "example.java", Content: exampleJava, Ranks: []float64{c.documentRank}}) 616 + if err != nil { 617 + t.Fatal(err) 618 + } 619 + 620 + if err := b.Finish(); err != nil { 621 + t.Fatalf("Finish: %v", err) 622 + } 623 + 624 + ss, err := shards.NewDirectorySearcher(dir) 625 + if err != nil { 626 + t.Fatalf("NewDirectorySearcher(%s): %v", dir, err) 627 + } 628 + defer ss.Close() 629 + 630 + srs, err := ss.Search(context.Background(), searchQuery, &zoekt.SearchOptions{ 631 + UseDocumentRanks: true, 632 + DocumentRanksWeight: c.documentRanksWeight, 633 + DebugScore: true, 634 + }) 635 + 636 + if err != nil { 637 + t.Fatal(err) 638 + } 639 + 640 + if got, want := len(srs.Files), 1; got != want { 641 + t.Fatalf("file matches: want %d, got %d", want, got) 642 + } 643 + 644 + if got := srs.Files[0].Score; got != c.wantScore { 645 + t.Fatalf("score: want %f, got %f\ndebug: %s\ndebugscore: %s", c.wantScore, got, srs.Files[0].Debug, srs.Files[0].LineMatches[0].DebugScore) 646 + } 647 + }) 648 + } 649 + } 650 + 651 + func TestRepoRanks(t *testing.T) { 652 + if os.Getenv("CI") == "" && checkCTags() == "" { 653 + t.Skip("ctags not available") 654 + } 655 + dir := t.TempDir() 656 + 657 + opts := Options{ 658 + IndexDir: dir, 659 + RepositoryDescription: zoekt.Repository{ 660 + Name: "repo", 661 + }, 662 + DocumentRanksVersion: "ranking", 663 + } 664 + 665 + searchQuery := &query.Substring{Content: true, Pattern: "Inner"} 666 + exampleJava, err := os.ReadFile("./testdata/example.java") 667 + if err != nil { 668 + t.Fatal(err) 669 + } 670 + 671 + cases := []struct { 672 + name string 673 + repoRank uint16 674 + wantScore float64 675 + }{ 676 + { 677 + name: "no shard rank", 678 + // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 10 (file order) 679 + wantScore: 7010.00, 680 + }, 681 + { 682 + name: "medium shard rank", 683 + repoRank: 30000, 684 + // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 10 (file order) + 9.16 (repo rank) 685 + wantScore: 7019.16, 686 + }, 687 + { 688 + name: "high shard rank", 689 + repoRank: 60000, 690 + // 5500 (partial symbol at boundary) + 1000 (Java class) + 500 (word match) + 10 (file order) + 18.31 (repo rank) 691 + wantScore: 7028.31, 692 + }, 693 + } 694 + 695 + for _, c := range cases { 696 + t.Run(c.name, func(t *testing.T) { 697 + opts.RepositoryDescription = zoekt.Repository{ 698 + Name: "repo", 699 + Rank: c.repoRank, 700 + } 701 + 702 + b, err := NewBuilder(opts) 703 + if err != nil { 704 + t.Fatalf("NewBuilder: %v", err) 705 + } 706 + 707 + err = b.Add(zoekt.Document{Name: "example.java", Content: exampleJava}) 708 + if err != nil { 709 + t.Fatal(err) 710 + } 711 + 712 + if err := b.Finish(); err != nil { 713 + t.Fatalf("Finish: %v", err) 714 + } 715 + 716 + ss, err := shards.NewDirectorySearcher(dir) 717 + if err != nil { 718 + t.Fatalf("NewDirectorySearcher(%s): %v", dir, err) 719 + } 720 + defer ss.Close() 721 + 722 + srs, err := ss.Search(context.Background(), searchQuery, &zoekt.SearchOptions{ 723 + UseDocumentRanks: true, 724 + DebugScore: true, 725 + }) 726 + 727 + if err != nil { 728 + t.Fatal(err) 729 + } 730 + 731 + if got, want := len(srs.Files), 1; got != want { 732 + t.Fatalf("file matches: want %d, got %d", want, got) 733 + } 734 + 735 + if got := srs.Files[0].Score; math.Abs(got-c.wantScore) >= 0.01 { 736 + t.Fatalf("score: want %f, got %f\ndebug: %s\ndebugscore: %s", c.wantScore, got, srs.Files[0].Debug, srs.Files[0].LineMatches[0].DebugScore) 737 + } 738 + }) 739 + } 740 + }
+1 -1
contentprovider.go
··· 835 835 switch kind { 836 836 case ctags.Class: // classes 837 837 factor = 10 838 - case ctags.Function: // function definitions 838 + case ctags.Function, ctags.Method: // function definitions 839 839 factor = 8 840 840 case ctags.Field: // class, struct, and union members 841 841 factor = 4