kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/util/vnameutil/rewrite_test.go (about) 1 /* 2 * Copyright 2014 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package vnameutil 18 19 import ( 20 "encoding/json" 21 "regexp" 22 "strconv" 23 "strings" 24 "testing" 25 26 "github.com/google/go-cmp/cmp" 27 "github.com/google/go-cmp/cmp/cmpopts" 28 "google.golang.org/protobuf/proto" 29 30 spb "kythe.io/kythe/proto/storage_go_proto" 31 ) 32 33 // Copied exactly from kythe/javatests/com/google/devtools/kythe/extractors/shared/FileVNamesTest.java 34 // This could be prettier in Go but a copy ensures better compatibility between 35 // the libraries. 36 var testConfig = strings.Join([]string{ 37 "[", 38 " {", 39 " \"pattern\": \"static/path\",", 40 " \"vname\": {", 41 " \"root\": \"root\",", 42 " \"corpus\": \"static\"", 43 " }", 44 " },", 45 " {", 46 " \"pattern\": \"dup/path\",", 47 " \"vname\": {", 48 " \"corpus\": \"first\"", 49 " }", 50 " },", 51 " {", 52 " \"pattern\": \"dup/path2\",", 53 " \"vname\": {", 54 " \"corpus\": \"second\"", 55 " }", 56 " },", 57 " {", 58 " \"pattern\": \"(grp1)/(\\\\d+)/(.*)\",", 59 " \"vname\": {", 60 " \"root\": \"@2@\",", 61 " \"corpus\": \"@1@/@3@\"", 62 " }", 63 " },", 64 " {", 65 " \"pattern\": \"bazel-bin/([^/]+)/java/.*[.]jar!/.*\",", 66 " \"vname\": {", 67 " \"root\": \"java\",", 68 " \"corpus\": \"@1@\"", 69 " }", 70 " },", 71 " {", 72 " \"pattern\": \"third_party/([^/]+)/.*[.]jar!/.*\",", 73 " \"vname\": {", 74 " \"root\": \"@1@\",", 75 " \"corpus\": \"third_party\"", 76 " }", 77 " },", 78 " {", 79 " \"pattern\": \"([^/]+)/java/.*\",", 80 " \"vname\": {", 81 " \"root\": \"java\",", 82 " \"corpus\": \"@1@\"", 83 " }", 84 " },", 85 " {", 86 " \"pattern\": \"([^/]+)/.*\",", 87 " \"vname\": {", 88 " \"corpus\": \"@1@\"", 89 " }", 90 " }", 91 "]"}, "\n") 92 93 // Verify that parsing in the Go implementation is consistent with the Java 94 // implementation. 95 func TestParseConsistency(t *testing.T) { 96 r, err := ParseRules([]byte(testConfig)) 97 if err != nil { 98 t.Error(err) 99 } else if len(r) == 0 { 100 t.Error("empty rules") 101 } 102 } 103 104 func TestMarshalJSON(t *testing.T) { 105 tests := []string{ 106 "[]", 107 `[{"vname":{"corpus":"a"}}]`, 108 `[{"pattern":"something","vname":{"corpus":"b"}}]`, 109 `[{"pattern":"something\\$","vname":{"corpus":"c"}}]`, 110 `[{"pattern":"\\^\\$","vname":{"corpus":"d"}}]`, 111 } 112 113 for i, test := range tests { 114 t.Run(strconv.Itoa(i), func(t *testing.T) { 115 rules, err := ReadRules(strings.NewReader(test)) 116 if err != nil { 117 t.Fatal(err) 118 } 119 t.Logf("Rules: %s", rules) 120 121 rec, err := json.Marshal(rules) 122 if err != nil { 123 t.Fatal(err) 124 } 125 126 if diff := cmp.Diff(string(rec), test, splitLines); diff != "" { 127 t.Errorf("Unexpected diff (- found; + expected):\n%s", diff) 128 } 129 }) 130 } 131 } 132 133 func TestTrimAnchors(t *testing.T) { 134 tests := []struct { 135 Pattern string 136 Expected string 137 }{ 138 {"", ""}, 139 {"^", ""}, 140 {"$", ""}, 141 {"^$", ""}, 142 {"^a$", "a"}, 143 {"^a", "a"}, 144 {"a$", "a"}, 145 {`\$`, `\$`}, 146 {`\^\$`, `\^\$`}, 147 {`\^`, `\^`}, 148 {`\^^$\$`, `\^^$\$`}, 149 {`\\$`, `\\`}, 150 {`\\\\$`, `\\\\`}, 151 {`\\\$`, `\\\$`}, 152 {`^abc[^def$]ghi$`, `abc[^def$]ghi`}, 153 } 154 155 for _, test := range tests { 156 test := test 157 t.Run(strconv.Quote(test.Pattern), func(t *testing.T) { 158 if found := trimAnchors(test.Pattern); found != test.Expected { 159 t.Errorf("Found: %q; Expected: %q", found, test.Expected) 160 } 161 }) 162 } 163 } 164 165 func TestRoundtripJSON(t *testing.T) { 166 tests := []string{ 167 "[]", 168 `[{"pattern": "p(.)", "vname": {"corpus": "$@1@"}}]`, 169 `[{ 170 "pattern": "(?P<corpus>.+)/(?P<root>.+)::(?P<path>.+)", 171 "vname": {"corpus": "@corpus@", "root": "@root@", "path": "@path@"} 172 }]`, 173 testConfig, 174 } 175 176 for i, test := range tests { 177 t.Run(strconv.Itoa(i), func(t *testing.T) { 178 expected, err := ReadRules(strings.NewReader(test)) 179 if err != nil { 180 t.Fatal(err) 181 } 182 t.Logf("Rules: %s", expected) 183 184 rec, err := json.Marshal(expected) 185 if err != nil { 186 t.Fatalf("Error marshaling rules %+v: %v", expected, err) 187 } 188 189 r, err := ParseRules(rec) 190 if err != nil { 191 t.Fatalf("Error parsing rules %q: %v", rec, err) 192 } 193 194 if diff := cmp.Diff(r, expected, transformRegexp, compareVNames); diff != "" { 195 t.Errorf("Unexpected diff (- found; + expected):\n%s", diff) 196 } 197 }) 198 } 199 } 200 201 func TestRoundtripProto(t *testing.T) { 202 tests := []string{ 203 "[]", 204 `[{"pattern": "p(.)", "vname": {"corpus": "$@1@"}}]`, 205 `[{ 206 "pattern": "(?P<corpus>.+)/(?P<root>.+)::(?P<path>.+)", 207 "vname": {"corpus": "@corpus@", "root": "@root@", "path": "@path@"} 208 }]`, 209 testConfig, 210 } 211 212 for i, test := range tests { 213 t.Run(strconv.Itoa(i), func(t *testing.T) { 214 expected, err := ReadRules(strings.NewReader(test)) 215 if err != nil { 216 t.Fatal(err) 217 } 218 t.Logf("Rules: %s", expected) 219 220 rec, err := expected.Marshal() 221 if err != nil { 222 t.Fatalf("Error marshaling rules %+v: %v", expected, err) 223 } 224 225 r, err := ParseProtoRules(rec) 226 if err != nil { 227 t.Fatalf("Error parsing rules %q: %v", rec, err) 228 } 229 230 if diff := cmp.Diff(r, expected, transformRegexp, compareVNames); diff != "" { 231 t.Errorf("Unexpected diff (- found; + expected):\n%s", diff) 232 } 233 }) 234 } 235 } 236 237 func TestCornerCases(t *testing.T) { 238 testRule1 := Rule{regexp.MustCompile(`(?P<first>\w+)(?:/(?P<second>\w+))?`), V{Corpus: "${first}", Path: "${second}"}.pb()} 239 testRule2 := Rule{regexp.MustCompile(`x/(?P<sig>\w+)/y/(?P<tail>.+)$`), V{Path: "${tail}", Sig: "|${sig}|"}.pb()} 240 tests := []struct { 241 rule Rule 242 input string 243 want *spb.VName 244 }{ 245 // Optional portions of the pattern should be handled correctly. 246 {testRule1, "alpha/bravo", V{Corpus: "alpha", Path: "bravo"}.pb()}, 247 {testRule1, "alpha", V{Corpus: "alpha"}.pb()}, 248 249 // Substitution of signature fields should work. 250 {testRule2, "x/kanga/y/roo.txt", V{Path: "roo.txt", Sig: "|kanga|"}.pb()}, 251 } 252 for _, test := range tests { 253 got, ok := test.rule.Apply(test.input) 254 if !ok { 255 t.Errorf("Apply %v failed", test.rule) 256 } else if !proto.Equal(got, test.want) { 257 t.Errorf("Apply %v: got {%+v}, want {%+v}", test.rule, got, test.want) 258 } else { 259 t.Logf("Apply %v properly returned {%+v}", test.rule, got) 260 } 261 } 262 } 263 264 func TestFileRewrites(t *testing.T) { 265 tests := []struct { 266 path string 267 want *spb.VName 268 }{ 269 // static 270 {"static/path", V{Corpus: "static", Root: "root"}.pb()}, 271 272 // ordered 273 {"dup/path", V{Corpus: "first"}.pb()}, 274 {"dup/path2", V{Corpus: "second"}.pb()}, 275 276 // groups 277 {"corpus/some/path/here", V{Corpus: "corpus"}.pb()}, 278 {"grp1/12345/endingGroup", V{Corpus: "grp1/endingGroup", Root: "12345"}.pb()}, 279 {"bazel-bin/kythe/java/some/path/A.jar!/some/path/A.class", V{Corpus: "kythe", Root: "java"}.pb()}, 280 {"kythe/java/com/google/devtools/kythe/util/KytheURI.java", V{Corpus: "kythe", Root: "java"}.pb()}, 281 {"otherCorpus/java/com/google/devtools/kythe/util/KytheURI.java", V{Corpus: "otherCorpus", Root: "java"}.pb()}, 282 } 283 284 r, err := ParseRules([]byte(testConfig)) 285 if err != nil { 286 t.Fatalf("Broken test rules: %v", err) 287 } 288 289 for _, test := range tests { 290 got, ok := r.Apply(test.path) 291 if !ok { 292 t.Errorf("Apply(%q): no match", test.path) 293 } else if !proto.Equal(got, test.want) { 294 t.Errorf("Apply(%q): got {%+v}, want {%+v}", test.path, got, test.want) 295 } 296 } 297 } 298 299 type V struct { 300 Corpus, Root, Path, Sig, Lang string 301 } 302 303 func (v V) pb() *spb.VName { 304 return &spb.VName{ 305 Corpus: v.Corpus, 306 Root: v.Root, 307 Path: v.Path, 308 Signature: v.Sig, 309 Language: v.Lang, 310 } 311 } 312 313 var ( 314 transformRegexp = cmp.Transformer("Regexp", func(r *regexp.Regexp) string { return r.String() }) 315 compareVNames = cmp.Comparer(func(a, b *spb.VName) bool { return proto.Equal(a, b) }) 316 splitLines = cmpopts.AcyclicTransformer("Lines", func(s string) []string { return strings.Split(s, "\n") }) 317 )