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  )