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  }