kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/util/kytheuri/uri_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 kytheuri 18 19 import ( 20 "reflect" 21 "testing" 22 23 "google.golang.org/protobuf/proto" 24 25 cpb "kythe.io/kythe/proto/common_go_proto" 26 spb "kythe.io/kythe/proto/storage_go_proto" 27 ) 28 29 func TestParse(t *testing.T) { 30 tests := []struct { 31 input string 32 want *URI 33 }{ 34 // Empty URIs. 35 {"", new(URI)}, 36 {"kythe:", new(URI)}, 37 {"kythe://", new(URI)}, 38 39 // Corpus labels are not normalized, even if they "look like" paths. 40 // See #1479 for discussion. 41 {"kythe://..", &URI{Corpus: ".."}}, 42 {"kythe://../", &URI{Corpus: "../"}}, 43 {"kythe://../..", &URI{Corpus: "../.."}}, 44 {"kythe://a/../b//c", &URI{Corpus: "a/../b//c"}}, 45 46 // Individual components. 47 {"#sig", &URI{Signature: "sig"}}, 48 {"kythe:#sig", &URI{Signature: "sig"}}, 49 {"kythe://corpus", &URI{Corpus: "corpus"}}, 50 {"kythe://corpus/", &URI{Corpus: "corpus/"}}, 51 {"kythe://corpus/with/path", &URI{Corpus: "corpus/with/path"}}, 52 {"//corpus/with/path", &URI{Corpus: "corpus/with/path"}}, 53 {"kythe:?root=R", &URI{Root: "R"}}, 54 {"kythe:?path=P", &URI{Path: "P"}}, 55 {"kythe:?lang=L", &URI{Language: "L"}}, 56 57 // Multiple attributes, with permutation of order. 58 {"kythe:?lang=L?root=R", &URI{Root: "R", Language: "L"}}, 59 {"kythe:?lang=L?path=P?root=R", &URI{Root: "R", Language: "L", Path: "P"}}, 60 {"kythe:?root=R?path=P?lang=L", &URI{Root: "R", Language: "L", Path: "P"}}, 61 62 // Everything. 63 {"kythe://bitbucket.org/creachadair/stringset?path=stringset.go?lang=go?root=blah#sig", 64 &URI{"sig", "bitbucket.org/creachadair/stringset", "blah", "stringset.go", "go"}}, 65 66 // Regression: Escape sequences in the corpus specification. 67 {"kythe://libstdc%2B%2B?lang=c%2B%2B?path=bits/basic_string.h?root=/usr/include/c%2B%2B/4.8", 68 &URI{Corpus: "libstdc++", Path: "bits/basic_string.h", Root: "/usr/include/c++/4.8", Language: "c++"}}, 69 } 70 for _, test := range tests { 71 got, err := Parse(test.input) 72 if err != nil { 73 t.Errorf("Parse %q failed: %v", test.input, err) 74 continue 75 } 76 if !reflect.DeepEqual(got, test.want) { 77 t.Errorf("Parse %q:\ngot %#v\nwant %#v", test.input, got, test.want) 78 } 79 } 80 } 81 82 func TestParseErrors(t *testing.T) { 83 tests := []string{ 84 "invalid corpus", 85 "http://unsupported-scheme", 86 "?huh=bogus+attribute+key", 87 "?path=", // empty query value 88 "?root=?", // empty query value 89 "//a/%x/bad-escaping", 90 "kythe:///invalid-corpus?blah", 91 "/another-invalid-corpus", 92 "random/opaque/failure", 93 } 94 for _, bad := range tests { 95 got, err := Parse(bad) 96 if err == nil { 97 t.Errorf("Parse %q: got %#v, want error", bad, got) 98 } else { 99 t.Logf("Parse %q gave expected error: %v", bad, err) 100 } 101 } 102 } 103 104 func TestEqual(t *testing.T) { 105 eq := []struct { 106 a, b string 107 }{ 108 // Various empty equivalencies. 109 {"", ""}, 110 {"", "kythe:"}, 111 {"kythe://", ""}, 112 {"kythe://", "kythe:"}, 113 114 // Order of attributes is normalized. 115 {"kythe:?root=R?path=P", "kythe://?path=P?root=R"}, 116 {"kythe:?root=R?path=P?lang=L", "kythe://?path=P?lang=L?root=R"}, 117 118 // Escaping is respected. 119 {"kythe:?path=%50", "kythe://?path=P"}, 120 {"kythe:?lang=%4c?path=%50", "kythe://?lang=L?path=P"}, 121 122 // Paths are cleaned. 123 {"kythe://a?path=b/../c#sig", "kythe://a?path=c#sig"}, 124 {"kythe://a?path=b/../d/./e/../../c#sig", "kythe://a?path=c#sig"}, 125 {"//a?path=b/c/../d?lang=%67%6F", "kythe://a?path=b/d?lang=go"}, 126 127 // Corpus labels are not cleaned. 128 {"//a//?path=b/c/..?lang=foo", "kythe://a//?path=b?lang=foo"}, 129 {"kythe://a/./b/..//c/#sig", "kythe://a/./b/..//c/#sig"}, 130 } 131 for _, test := range eq { 132 if !Equal(test.a, test.b) { 133 t.Errorf("Equal incorrectly reported %q ≠ %q", test.a, test.b) 134 } 135 a := MustParse(test.a) 136 b := MustParse(test.b) 137 if !a.Equal(b) { 138 t.Errorf("Equal incorrectly reported %q ≠ %q", a, b) 139 } 140 } 141 neq := []struct { 142 a, b string 143 }{ 144 {"kythe://a", "kythe://a?path=P"}, 145 {"bogus", "bogus"}, 146 {"bogus", "kythe://good"}, 147 {"kythe://good", "bogus"}, 148 } 149 for _, test := range neq { 150 if Equal(test.a, test.b) { 151 t.Errorf("Equal incorrectly reported %q = %q", test.a, test.b) 152 } 153 } 154 155 // Corner cases 156 var a, b *URI 157 if !a.Equal(b) { 158 t.Error("Equal failed to report nil == nil") 159 } 160 b = MustParse("kythe://") 161 if !a.Equal(b) { 162 t.Errorf("Equal incorrectly reported %#v ≠ %#v", a, b) 163 } 164 } 165 166 func TestRoundTripURI(t *testing.T) { 167 // Test that converting a Kythe URI to a VName and then back preserves 168 // equivalence. 169 u := &URI{ 170 Signature: "magic carpet ride", 171 Corpus: "code.google.com/p/go.tools", 172 Path: "cmd/godoc/doc.go", 173 Language: "go", 174 } 175 v := u.VName() 176 t.Logf(" URL is %q\nVName is %v", u.String(), v) 177 if s := v.Signature; s != u.Signature { 178 t.Errorf("Signature: got %q, want %q", s, u.Signature) 179 } 180 if s := v.Corpus; s != u.Corpus { 181 t.Errorf("Corpus: got %q, want %q", s, u.Corpus) 182 } 183 if s := v.Path; s != u.Path { 184 t.Errorf("Path: got %q, want %q", s, u.Path) 185 } 186 if s := v.Root; s != u.Root { 187 t.Errorf("Root: got %q, want %q", s, u.Root) 188 } 189 if s := v.Language; s != u.Language { 190 t.Errorf("Language: got %q, want %q", s, u.Language) 191 } 192 w := FromVName(v) 193 if got, want := w.String(), u.String(); got != want { 194 t.Errorf("URI did not round-trip: got %q, want %q", got, want) 195 } 196 } 197 198 func TestToString(t *testing.T) { 199 tests := []struct { 200 VName *spb.VName 201 Expected string 202 }{ 203 {&spb.VName{Corpus: "kythe", Path: "unrooted/path"}, "kythe://kythe?path=unrooted/path"}, 204 {&spb.VName{Corpus: "kythe", Path: "/rooted/path"}, "kythe://kythe?path=/rooted/path"}, 205 {&spb.VName{Corpus: "kythe", Path: "//rooted//path"}, "kythe://kythe?path=/rooted/path"}, 206 } 207 208 for _, test := range tests { 209 if found := ToString(test.VName); found != test.Expected { 210 t.Errorf("kytheuri.ToString(%#v): found %q, want %q", test.VName, found, test.Expected) 211 } 212 } 213 } 214 215 func TestCorpusPath(t *testing.T) { 216 tests := []struct { 217 CorpusPath *cpb.CorpusPath 218 Expected string 219 }{ 220 {&cpb.CorpusPath{Corpus: "c", Root: "r", Path: "p"}, "kythe://c?path=p?root=r"}, 221 {&cpb.CorpusPath{Corpus: "", Root: "r", Path: "p"}, "kythe:?path=p?root=r"}, 222 {&cpb.CorpusPath{Corpus: "", Root: "", Path: "p"}, "kythe:?path=p"}, 223 {&cpb.CorpusPath{Corpus: "", Root: "", Path: ""}, "kythe:"}, 224 {&cpb.CorpusPath{Corpus: "c", Root: "r", Path: ""}, "kythe://c?root=r"}, 225 {&cpb.CorpusPath{Corpus: "c", Root: "", Path: ""}, "kythe://c"}, 226 {&cpb.CorpusPath{Corpus: "", Root: "r", Path: ""}, "kythe:?root=r"}, 227 {&cpb.CorpusPath{Corpus: "c", Root: "", Path: "p"}, "kythe://c?path=p"}, 228 } 229 230 for _, test := range tests { 231 if found := FromCorpusPath(test.CorpusPath).String(); found != test.Expected { 232 t.Errorf("kytheuri.FromCorpusPath(%+v): found %q, want %q", test.CorpusPath, found, test.Expected) 233 } 234 } 235 } 236 237 func TestRoundTripVName(t *testing.T) { 238 // Verify that converting a VName to a Kythe URI and then back preserves 239 // equivalence. 240 tests := []*spb.VName{ 241 {}, // empty 242 {Corpus: "//Users/foo", Path: "/Users/foo/bar", Language: "go", Signature: "∴"}, 243 {Corpus: "//////", Root: "←", Language: "c++"}, 244 {Corpus: "kythe//branch", Path: "source.ext"}, 245 } 246 for _, test := range tests { 247 uri := FromVName(test) 248 t.Logf("VName: %+v\nURI: %#q", test, uri) 249 got := uri.VName() 250 if !proto.Equal(got, test) { 251 t.Errorf("VName did not round-trip: got %+v, want %+v", got, test) 252 } 253 } 254 } 255 256 func TestUnicode(t *testing.T) { 257 const expected = "kythe:#%E5%BA%83" 258 uri := &URI{Signature: "広"} 259 if found := uri.String(); found != expected { 260 t.Errorf("Expected: %q; found: %q", expected, found) 261 } 262 } 263 264 func TestString(t *testing.T) { 265 const empty = "kythe:" 266 const canonical = "kythe:?lang=L?path=P?root=R" 267 const cleaned = "kythe://a?path=c#sig" 268 tests := []struct { 269 input, want string 270 }{ 271 // Empty forms 272 {"", empty}, 273 {"kythe:", empty}, 274 {"kythe://", empty}, 275 {"kythe:#", empty}, 276 {"kythe://#", empty}, 277 278 // Check ordering 279 {"kythe:?root=R?path=P?lang=L", canonical}, 280 {"kythe:?root=R?lang=L?path=P", canonical}, 281 {"kythe:?lang=L?path=P?root=R", canonical}, 282 {"kythe://?lang=L?path=P?root=R#", canonical}, 283 284 // Check escaping 285 {"kythe://?path=%50", "kythe:?path=P"}, 286 {"kythe://?path=%2B", "kythe:?path=%2B"}, 287 {"kythe://?path=a+b", "kythe:?path=a%2Bb"}, 288 {"kythe://?path=%20", "kythe:?path=%20"}, 289 {"kythe://?path=a/b", "kythe:?path=a/b"}, 290 291 // Support branch embedding 292 {"kythe://kythe//branch", "kythe://kythe//branch"}, 293 294 // Path cleaning 295 {"kythe://a?path=b/../c#sig", cleaned}, 296 {"kythe://a?path=./d/.././c#sig", cleaned}, 297 298 // Regression: Escape sequences in the corpus specification. 299 {"kythe://libstdc%2B%2B?path=bits/basic_string.h?lang=c%2B%2B?root=/usr/include/c%2B%2B/4.8", 300 "kythe://libstdc%2B%2B?lang=c%2B%2B?path=bits/basic_string.h?root=/usr/include/c%2B%2B/4.8"}, 301 } 302 for _, test := range tests { 303 u := MustParse(test.input) 304 if got := u.String(); got != test.want { 305 t.Errorf("String %#v:\ngot %q\nwant %q", u, got, test.want) 306 } 307 } 308 }