Source file src/net/http/pattern_test.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package http
     6  
     7  import (
     8  	"slices"
     9  	"strings"
    10  	"testing"
    11  )
    12  
    13  func TestParsePattern(t *testing.T) {
    14  	lit := func(name string) segment {
    15  		return segment{s: name}
    16  	}
    17  
    18  	wild := func(name string) segment {
    19  		return segment{s: name, wild: true}
    20  	}
    21  
    22  	multi := func(name string) segment {
    23  		s := wild(name)
    24  		s.multi = true
    25  		return s
    26  	}
    27  
    28  	for _, test := range []struct {
    29  		in   string
    30  		want pattern
    31  	}{
    32  		{"/", pattern{segments: []segment{multi("")}}},
    33  		{"/a", pattern{segments: []segment{lit("a")}}},
    34  		{
    35  			"/a/",
    36  			pattern{segments: []segment{lit("a"), multi("")}},
    37  		},
    38  		{"/path/to/something", pattern{segments: []segment{
    39  			lit("path"), lit("to"), lit("something"),
    40  		}}},
    41  		{
    42  			"/{w1}/lit/{w2}",
    43  			pattern{
    44  				segments: []segment{wild("w1"), lit("lit"), wild("w2")},
    45  			},
    46  		},
    47  		{
    48  			"/{w1}/lit/{w2}/",
    49  			pattern{
    50  				segments: []segment{wild("w1"), lit("lit"), wild("w2"), multi("")},
    51  			},
    52  		},
    53  		{
    54  			"example.com/",
    55  			pattern{host: "example.com", segments: []segment{multi("")}},
    56  		},
    57  		{
    58  			"GET /",
    59  			pattern{method: "GET", segments: []segment{multi("")}},
    60  		},
    61  		{
    62  			"POST example.com/foo/{w}",
    63  			pattern{
    64  				method:   "POST",
    65  				host:     "example.com",
    66  				segments: []segment{lit("foo"), wild("w")},
    67  			},
    68  		},
    69  		{
    70  			"/{$}",
    71  			pattern{segments: []segment{lit("/")}},
    72  		},
    73  		{
    74  			"DELETE example.com/a/{foo12}/{$}",
    75  			pattern{method: "DELETE", host: "example.com", segments: []segment{lit("a"), wild("foo12"), lit("/")}},
    76  		},
    77  		{
    78  			"/foo/{$}",
    79  			pattern{segments: []segment{lit("foo"), lit("/")}},
    80  		},
    81  		{
    82  			"/{a}/foo/{rest...}",
    83  			pattern{segments: []segment{wild("a"), lit("foo"), multi("rest")}},
    84  		},
    85  		{
    86  			"//",
    87  			pattern{segments: []segment{lit(""), multi("")}},
    88  		},
    89  		{
    90  			"/foo///./../bar",
    91  			pattern{segments: []segment{lit("foo"), lit(""), lit(""), lit("."), lit(".."), lit("bar")}},
    92  		},
    93  		{
    94  			"a.com/foo//",
    95  			pattern{host: "a.com", segments: []segment{lit("foo"), lit(""), multi("")}},
    96  		},
    97  		{
    98  			"/%61%62/%7b/%",
    99  			pattern{segments: []segment{lit("ab"), lit("{"), lit("%")}},
   100  		},
   101  		// Allow multiple spaces matching regexp '[ \t]+' between method and path.
   102  		{
   103  			"GET\t  /",
   104  			pattern{method: "GET", segments: []segment{multi("")}},
   105  		},
   106  		{
   107  			"POST \t  example.com/foo/{w}",
   108  			pattern{
   109  				method:   "POST",
   110  				host:     "example.com",
   111  				segments: []segment{lit("foo"), wild("w")},
   112  			},
   113  		},
   114  		{
   115  			"DELETE    \texample.com/a/{foo12}/{$}",
   116  			pattern{method: "DELETE", host: "example.com", segments: []segment{lit("a"), wild("foo12"), lit("/")}},
   117  		},
   118  	} {
   119  		got := mustParsePattern(t, test.in)
   120  		if !got.equal(&test.want) {
   121  			t.Errorf("%q:\ngot  %#v\nwant %#v", test.in, got, &test.want)
   122  		}
   123  	}
   124  }
   125  
   126  func TestParsePatternError(t *testing.T) {
   127  	for _, test := range []struct {
   128  		in       string
   129  		contains string
   130  	}{
   131  		{"", "empty pattern"},
   132  		{"A=B /", "at offset 0: invalid method"},
   133  		{" ", "at offset 1: host/path missing /"},
   134  		{"/{w}x", "at offset 1: bad wildcard segment"},
   135  		{"/x{w}", "at offset 1: bad wildcard segment"},
   136  		{"/{wx", "at offset 1: bad wildcard segment"},
   137  		{"/a/{/}/c", "at offset 3: bad wildcard segment"},
   138  		{"/a/{%61}/c", "at offset 3: bad wildcard name"}, // wildcard names aren't unescaped
   139  		{"/{a$}", "at offset 1: bad wildcard name"},
   140  		{"/{}", "at offset 1: empty wildcard"},
   141  		{"POST a.com/x/{}/y", "at offset 13: empty wildcard"},
   142  		{"/{...}", "at offset 1: empty wildcard"},
   143  		{"/{$...}", "at offset 1: bad wildcard"},
   144  		{"/{$}/", "at offset 1: {$} not at end"},
   145  		{"/{$}/x", "at offset 1: {$} not at end"},
   146  		{"/abc/{$}/x", "at offset 5: {$} not at end"},
   147  		{"/{a...}/", "at offset 1: {...} wildcard not at end"},
   148  		{"/{a...}/x", "at offset 1: {...} wildcard not at end"},
   149  		{"{a}/b", "at offset 0: host contains '{' (missing initial '/'?)"},
   150  		{"/a/{x}/b/{x...}", "at offset 9: duplicate wildcard name"},
   151  		{"GET //", "at offset 4: non-CONNECT pattern with unclean path"},
   152  	} {
   153  		_, err := parsePattern(test.in)
   154  		if err == nil || !strings.Contains(err.Error(), test.contains) {
   155  			t.Errorf("%q:\ngot %v, want error containing %q", test.in, err, test.contains)
   156  		}
   157  	}
   158  }
   159  
   160  func (p1 *pattern) equal(p2 *pattern) bool {
   161  	return p1.method == p2.method && p1.host == p2.host &&
   162  		slices.Equal(p1.segments, p2.segments)
   163  }
   164  
   165  func mustParsePattern(tb testing.TB, s string) *pattern {
   166  	tb.Helper()
   167  	p, err := parsePattern(s)
   168  	if err != nil {
   169  		tb.Fatal(err)
   170  	}
   171  	return p
   172  }
   173  
   174  func TestCompareMethods(t *testing.T) {
   175  	for _, test := range []struct {
   176  		p1, p2 string
   177  		want   relationship
   178  	}{
   179  		{"/", "/", equivalent},
   180  		{"GET /", "GET /", equivalent},
   181  		{"HEAD /", "HEAD /", equivalent},
   182  		{"POST /", "POST /", equivalent},
   183  		{"GET /", "POST /", disjoint},
   184  		{"GET /", "/", moreSpecific},
   185  		{"HEAD /", "/", moreSpecific},
   186  		{"GET /", "HEAD /", moreGeneral},
   187  	} {
   188  		pat1 := mustParsePattern(t, test.p1)
   189  		pat2 := mustParsePattern(t, test.p2)
   190  		got := pat1.compareMethods(pat2)
   191  		if got != test.want {
   192  			t.Errorf("%s vs %s: got %s, want %s", test.p1, test.p2, got, test.want)
   193  		}
   194  		got2 := pat2.compareMethods(pat1)
   195  		want2 := inverseRelationship(test.want)
   196  		if got2 != want2 {
   197  			t.Errorf("%s vs %s: got %s, want %s", test.p2, test.p1, got2, want2)
   198  		}
   199  	}
   200  }
   201  
   202  func TestComparePaths(t *testing.T) {
   203  	for _, test := range []struct {
   204  		p1, p2 string
   205  		want   relationship
   206  	}{
   207  		// A non-final pattern segment can have one of two values: literal or
   208  		// single wildcard. A final pattern segment can have one of 5: empty
   209  		// (trailing slash), literal, dollar, single wildcard, or multi
   210  		// wildcard. Trailing slash and multi wildcard are the same.
   211  
   212  		// A literal should be more specific than anything it overlaps, except itself.
   213  		{"/a", "/a", equivalent},
   214  		{"/a", "/b", disjoint},
   215  		{"/a", "/", moreSpecific},
   216  		{"/a", "/{$}", disjoint},
   217  		{"/a", "/{x}", moreSpecific},
   218  		{"/a", "/{x...}", moreSpecific},
   219  
   220  		// Adding a segment doesn't change that.
   221  		{"/b/a", "/b/a", equivalent},
   222  		{"/b/a", "/b/b", disjoint},
   223  		{"/b/a", "/b/", moreSpecific},
   224  		{"/b/a", "/b/{$}", disjoint},
   225  		{"/b/a", "/b/{x}", moreSpecific},
   226  		{"/b/a", "/b/{x...}", moreSpecific},
   227  		{"/{z}/a", "/{z}/a", equivalent},
   228  		{"/{z}/a", "/{z}/b", disjoint},
   229  		{"/{z}/a", "/{z}/", moreSpecific},
   230  		{"/{z}/a", "/{z}/{$}", disjoint},
   231  		{"/{z}/a", "/{z}/{x}", moreSpecific},
   232  		{"/{z}/a", "/{z}/{x...}", moreSpecific},
   233  
   234  		// Single wildcard on left.
   235  		{"/{z}", "/a", moreGeneral},
   236  		{"/{z}", "/a/b", disjoint},
   237  		{"/{z}", "/{$}", disjoint},
   238  		{"/{z}", "/{x}", equivalent},
   239  		{"/{z}", "/", moreSpecific},
   240  		{"/{z}", "/{x...}", moreSpecific},
   241  		{"/b/{z}", "/b/a", moreGeneral},
   242  		{"/b/{z}", "/b/a/b", disjoint},
   243  		{"/b/{z}", "/b/{$}", disjoint},
   244  		{"/b/{z}", "/b/{x}", equivalent},
   245  		{"/b/{z}", "/b/", moreSpecific},
   246  		{"/b/{z}", "/b/{x...}", moreSpecific},
   247  
   248  		// Trailing slash on left.
   249  		{"/", "/a", moreGeneral},
   250  		{"/", "/a/b", moreGeneral},
   251  		{"/", "/{$}", moreGeneral},
   252  		{"/", "/{x}", moreGeneral},
   253  		{"/", "/", equivalent},
   254  		{"/", "/{x...}", equivalent},
   255  
   256  		{"/b/", "/b/a", moreGeneral},
   257  		{"/b/", "/b/a/b", moreGeneral},
   258  		{"/b/", "/b/{$}", moreGeneral},
   259  		{"/b/", "/b/{x}", moreGeneral},
   260  		{"/b/", "/b/", equivalent},
   261  		{"/b/", "/b/{x...}", equivalent},
   262  
   263  		{"/{z}/", "/{z}/a", moreGeneral},
   264  		{"/{z}/", "/{z}/a/b", moreGeneral},
   265  		{"/{z}/", "/{z}/{$}", moreGeneral},
   266  		{"/{z}/", "/{z}/{x}", moreGeneral},
   267  		{"/{z}/", "/{z}/", equivalent},
   268  		{"/{z}/", "/a/", moreGeneral},
   269  		{"/{z}/", "/{z}/{x...}", equivalent},
   270  		{"/{z}/", "/a/{x...}", moreGeneral},
   271  		{"/a/{z}/", "/{z}/a/", overlaps},
   272  		{"/a/{z}/b/", "/{x}/c/{y...}", overlaps},
   273  
   274  		// Multi wildcard on left.
   275  		{"/{m...}", "/a", moreGeneral},
   276  		{"/{m...}", "/a/b", moreGeneral},
   277  		{"/{m...}", "/{$}", moreGeneral},
   278  		{"/{m...}", "/{x}", moreGeneral},
   279  		{"/{m...}", "/", equivalent},
   280  		{"/{m...}", "/{x...}", equivalent},
   281  
   282  		{"/b/{m...}", "/b/a", moreGeneral},
   283  		{"/b/{m...}", "/b/a/b", moreGeneral},
   284  		{"/b/{m...}", "/b/{$}", moreGeneral},
   285  		{"/b/{m...}", "/b/{x}", moreGeneral},
   286  		{"/b/{m...}", "/b/", equivalent},
   287  		{"/b/{m...}", "/b/{x...}", equivalent},
   288  		{"/b/{m...}", "/a/{x...}", disjoint},
   289  
   290  		{"/{z}/{m...}", "/{z}/a", moreGeneral},
   291  		{"/{z}/{m...}", "/{z}/a/b", moreGeneral},
   292  		{"/{z}/{m...}", "/{z}/{$}", moreGeneral},
   293  		{"/{z}/{m...}", "/{z}/{x}", moreGeneral},
   294  		{"/{z}/{m...}", "/{w}/", equivalent},
   295  		{"/{z}/{m...}", "/a/", moreGeneral},
   296  		{"/{z}/{m...}", "/{z}/{x...}", equivalent},
   297  		{"/{z}/{m...}", "/a/{x...}", moreGeneral},
   298  		{"/a/{m...}", "/a/b/{y...}", moreGeneral},
   299  		{"/a/{m...}", "/a/{x}/{y...}", moreGeneral},
   300  		{"/a/{z}/{m...}", "/a/b/{y...}", moreGeneral},
   301  		{"/a/{z}/{m...}", "/{z}/a/", overlaps},
   302  		{"/a/{z}/{m...}", "/{z}/b/{y...}", overlaps},
   303  		{"/a/{z}/b/{m...}", "/{x}/c/{y...}", overlaps},
   304  		{"/a/{z}/a/{m...}", "/{x}/b", disjoint},
   305  
   306  		// Dollar on left.
   307  		{"/{$}", "/a", disjoint},
   308  		{"/{$}", "/a/b", disjoint},
   309  		{"/{$}", "/{$}", equivalent},
   310  		{"/{$}", "/{x}", disjoint},
   311  		{"/{$}", "/", moreSpecific},
   312  		{"/{$}", "/{x...}", moreSpecific},
   313  
   314  		{"/b/{$}", "/b", disjoint},
   315  		{"/b/{$}", "/b/a", disjoint},
   316  		{"/b/{$}", "/b/a/b", disjoint},
   317  		{"/b/{$}", "/b/{$}", equivalent},
   318  		{"/b/{$}", "/b/{x}", disjoint},
   319  		{"/b/{$}", "/b/", moreSpecific},
   320  		{"/b/{$}", "/b/{x...}", moreSpecific},
   321  		{"/b/{$}", "/b/c/{x...}", disjoint},
   322  		{"/b/{x}/a/{$}", "/{x}/c/{y...}", overlaps},
   323  		{"/{x}/b/{$}", "/a/{x}/{y}", disjoint},
   324  		{"/{x}/b/{$}", "/a/{x}/c", disjoint},
   325  
   326  		{"/{z}/{$}", "/{z}/a", disjoint},
   327  		{"/{z}/{$}", "/{z}/a/b", disjoint},
   328  		{"/{z}/{$}", "/{z}/{$}", equivalent},
   329  		{"/{z}/{$}", "/{z}/{x}", disjoint},
   330  		{"/{z}/{$}", "/{z}/", moreSpecific},
   331  		{"/{z}/{$}", "/a/", overlaps},
   332  		{"/{z}/{$}", "/a/{x...}", overlaps},
   333  		{"/{z}/{$}", "/{z}/{x...}", moreSpecific},
   334  		{"/a/{z}/{$}", "/{z}/a/", overlaps},
   335  	} {
   336  		pat1 := mustParsePattern(t, test.p1)
   337  		pat2 := mustParsePattern(t, test.p2)
   338  		if g := pat1.comparePaths(pat1); g != equivalent {
   339  			t.Errorf("%s does not match itself; got %s", pat1, g)
   340  		}
   341  		if g := pat2.comparePaths(pat2); g != equivalent {
   342  			t.Errorf("%s does not match itself; got %s", pat2, g)
   343  		}
   344  		got := pat1.comparePaths(pat2)
   345  		if got != test.want {
   346  			t.Errorf("%s vs %s: got %s, want %s", test.p1, test.p2, got, test.want)
   347  			t.Logf("pat1: %+v\n", pat1.segments)
   348  			t.Logf("pat2: %+v\n", pat2.segments)
   349  		}
   350  		want2 := inverseRelationship(test.want)
   351  		got2 := pat2.comparePaths(pat1)
   352  		if got2 != want2 {
   353  			t.Errorf("%s vs %s: got %s, want %s", test.p2, test.p1, got2, want2)
   354  		}
   355  	}
   356  }
   357  
   358  func TestConflictsWith(t *testing.T) {
   359  	for _, test := range []struct {
   360  		p1, p2 string
   361  		want   bool
   362  	}{
   363  		{"/a", "/a", true},
   364  		{"/a", "/ab", false},
   365  		{"/a/b/cd", "/a/b/cd", true},
   366  		{"/a/b/cd", "/a/b/c", false},
   367  		{"/a/b/c", "/a/c/c", false},
   368  		{"/{x}", "/{y}", true},
   369  		{"/{x}", "/a", false}, // more specific
   370  		{"/{x}/{y}", "/{x}/a", false},
   371  		{"/{x}/{y}", "/{x}/a/b", false},
   372  		{"/{x}", "/a/{y}", false},
   373  		{"/{x}/{y}", "/{x}/a/", false},
   374  		{"/{x}", "/a/{y...}", false},           // more specific
   375  		{"/{x}/a/{y}", "/{x}/a/{y...}", false}, // more specific
   376  		{"/{x}/{y}", "/{x}/a/{$}", false},      // more specific
   377  		{"/{x}/{y}/{$}", "/{x}/a/{$}", false},
   378  		{"/a/{x}", "/{x}/b", true},
   379  		{"/", "GET /", false},
   380  		{"/", "GET /foo", false},
   381  		{"GET /", "GET /foo", false},
   382  		{"GET /", "/foo", true},
   383  		{"GET /foo", "HEAD /", true},
   384  	} {
   385  		pat1 := mustParsePattern(t, test.p1)
   386  		pat2 := mustParsePattern(t, test.p2)
   387  		got := pat1.conflictsWith(pat2)
   388  		if got != test.want {
   389  			t.Errorf("%q.ConflictsWith(%q) = %t, want %t",
   390  				test.p1, test.p2, got, test.want)
   391  		}
   392  		// conflictsWith should be commutative.
   393  		got = pat2.conflictsWith(pat1)
   394  		if got != test.want {
   395  			t.Errorf("%q.ConflictsWith(%q) = %t, want %t",
   396  				test.p2, test.p1, got, test.want)
   397  		}
   398  	}
   399  }
   400  
   401  func TestRegisterConflict(t *testing.T) {
   402  	mux := NewServeMux()
   403  	pat1 := "/a/{x}/"
   404  	if err := mux.registerErr(pat1, NotFoundHandler()); err != nil {
   405  		t.Fatal(err)
   406  	}
   407  	pat2 := "/a/{y}/{z...}"
   408  	err := mux.registerErr(pat2, NotFoundHandler())
   409  	var got string
   410  	if err == nil {
   411  		got = "<nil>"
   412  	} else {
   413  		got = err.Error()
   414  	}
   415  	want := "matches the same requests as"
   416  	if !strings.Contains(got, want) {
   417  		t.Errorf("got\n%s\nwant\n%s", got, want)
   418  	}
   419  }
   420  
   421  func TestDescribeConflict(t *testing.T) {
   422  	for _, test := range []struct {
   423  		p1, p2 string
   424  		want   string
   425  	}{
   426  		{"/a/{x}", "/a/{y}", "the same requests"},
   427  		{"/", "/{m...}", "the same requests"},
   428  		{"/a/{x}", "/{y}/b", "both match some paths"},
   429  		{"/a", "GET /{x}", "matches more methods than GET /{x}, but has a more specific path pattern"},
   430  		{"GET /a", "HEAD /", "matches more methods than HEAD /, but has a more specific path pattern"},
   431  		{"POST /", "/a", "matches fewer methods than /a, but has a more general path pattern"},
   432  	} {
   433  		got := describeConflict(mustParsePattern(t, test.p1), mustParsePattern(t, test.p2))
   434  		if !strings.Contains(got, test.want) {
   435  			t.Errorf("%s vs. %s:\ngot:\n%s\nwhich does not contain %q",
   436  				test.p1, test.p2, got, test.want)
   437  		}
   438  	}
   439  }
   440  
   441  func TestCommonPath(t *testing.T) {
   442  	for _, test := range []struct {
   443  		p1, p2 string
   444  		want   string
   445  	}{
   446  		{"/a/{x}", "/{x}/a", "/a/a"},
   447  		{"/a/{z}/", "/{z}/a/", "/a/a/"},
   448  		{"/a/{z}/{m...}", "/{z}/a/", "/a/a/"},
   449  		{"/{z}/{$}", "/a/", "/a/"},
   450  		{"/{z}/{$}", "/a/{x...}", "/a/"},
   451  		{"/a/{z}/{$}", "/{z}/a/", "/a/a/"},
   452  		{"/a/{x}/b/{y...}", "/{x}/c/{y...}", "/a/c/b/"},
   453  		{"/a/{x}/b/", "/{x}/c/{y...}", "/a/c/b/"},
   454  		{"/a/{x}/b/{$}", "/{x}/c/{y...}", "/a/c/b/"},
   455  		{"/a/{z}/{x...}", "/{z}/b/{y...}", "/a/b/"},
   456  	} {
   457  		pat1 := mustParsePattern(t, test.p1)
   458  		pat2 := mustParsePattern(t, test.p2)
   459  		if pat1.comparePaths(pat2) != overlaps {
   460  			t.Fatalf("%s does not overlap %s", test.p1, test.p2)
   461  		}
   462  		got := commonPath(pat1, pat2)
   463  		if got != test.want {
   464  			t.Errorf("%s vs. %s: got %q, want %q", test.p1, test.p2, got, test.want)
   465  		}
   466  	}
   467  }
   468  
   469  func TestDifferencePath(t *testing.T) {
   470  	for _, test := range []struct {
   471  		p1, p2 string
   472  		want   string
   473  	}{
   474  		{"/a/{x}", "/{x}/a", "/a/x"},
   475  		{"/{x}/a", "/a/{x}", "/x/a"},
   476  		{"/a/{z}/", "/{z}/a/", "/a/z/"},
   477  		{"/{z}/a/", "/a/{z}/", "/z/a/"},
   478  		{"/{a}/a/", "/a/{z}/", "/ax/a/"},
   479  		{"/a/{z}/{x...}", "/{z}/b/{y...}", "/a/z/"},
   480  		{"/{z}/b/{y...}", "/a/{z}/{x...}", "/z/b/"},
   481  		{"/a/b/", "/a/b/c", "/a/b/"},
   482  		{"/a/b/{x...}", "/a/b/c", "/a/b/"},
   483  		{"/a/b/{x...}", "/a/b/c/d", "/a/b/"},
   484  		{"/a/b/{x...}", "/a/b/c/d/", "/a/b/"},
   485  		{"/a/{z}/{m...}", "/{z}/a/", "/a/z/"},
   486  		{"/{z}/a/", "/a/{z}/{m...}", "/z/a/"},
   487  		{"/{z}/{$}", "/a/", "/z/"},
   488  		{"/a/", "/{z}/{$}", "/a/x"},
   489  		{"/{z}/{$}", "/a/{x...}", "/z/"},
   490  		{"/a/{foo...}", "/{z}/{$}", "/a/foo"},
   491  		{"/a/{z}/{$}", "/{z}/a/", "/a/z/"},
   492  		{"/{z}/a/", "/a/{z}/{$}", "/z/a/x"},
   493  		{"/a/{x}/b/{y...}", "/{x}/c/{y...}", "/a/x/b/"},
   494  		{"/{x}/c/{y...}", "/a/{x}/b/{y...}", "/x/c/"},
   495  		{"/a/{c}/b/", "/{x}/c/{y...}", "/a/cx/b/"},
   496  		{"/{x}/c/{y...}", "/a/{c}/b/", "/x/c/"},
   497  		{"/a/{x}/b/{$}", "/{x}/c/{y...}", "/a/x/b/"},
   498  		{"/{x}/c/{y...}", "/a/{x}/b/{$}", "/x/c/"},
   499  	} {
   500  		pat1 := mustParsePattern(t, test.p1)
   501  		pat2 := mustParsePattern(t, test.p2)
   502  		rel := pat1.comparePaths(pat2)
   503  		if rel != overlaps && rel != moreGeneral {
   504  			t.Fatalf("%s vs. %s are %s, need overlaps or moreGeneral", pat1, pat2, rel)
   505  		}
   506  		got := differencePath(pat1, pat2)
   507  		if got != test.want {
   508  			t.Errorf("%s vs. %s: got %q, want %q", test.p1, test.p2, got, test.want)
   509  		}
   510  	}
   511  }
   512  

View as plain text