cuelang.org/go@v0.10.1/mod/module/module_test.go (about)

     1  // Copyright 2018 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 module
     6  
     7  import (
     8  	"testing"
     9  
    10  	"cuelang.org/go/internal/cuetest"
    11  	"github.com/go-quicktest/qt"
    12  )
    13  
    14  var checkTests = []struct {
    15  	path    string
    16  	version string
    17  	ok      bool
    18  }{
    19  	{"rsc.io/quote@v0", "0.1.0", false},
    20  	{"rsc io/quote", "v1.0.0", false},
    21  
    22  	{"github.com/go-yaml/yaml@v0", "v0.8.0", true},
    23  	{"github.com/go-yaml/yaml@v1", "v1.0.0", true},
    24  	{"github.com/go-yaml/yaml", "v2.0.0", false},
    25  	{"github.com/go-yaml/yaml@v1", "v2.1.5", false},
    26  	{"github.com/go-yaml/yaml@v3.0", "v3.0.0", false},
    27  
    28  	{"github.com/go-yaml/yaml@v2", "v1.0.0", false},
    29  	{"github.com/go-yaml/yaml@v2", "v2.0.0", true},
    30  	{"github.com/go-yaml/yaml@v2", "v2.1.5", true},
    31  	{"github.com/go-yaml/yaml@v2", "v3.0.0", false},
    32  
    33  	{"rsc.io/quote", "v17.0.0", false},
    34  }
    35  
    36  func TestCheck(t *testing.T) {
    37  	for _, tt := range checkTests {
    38  		err := Check(tt.path, tt.version)
    39  		if tt.ok && err != nil {
    40  			t.Errorf("Check(%q, %q) = %v, wanted nil error", tt.path, tt.version, err)
    41  		} else if !tt.ok && err == nil {
    42  			t.Errorf("Check(%q, %q) succeeded, wanted error", tt.path, tt.version)
    43  		}
    44  	}
    45  }
    46  
    47  type checkPathTest struct {
    48  	path      string
    49  	modErr    string
    50  	importErr string
    51  	fileErr   string
    52  }
    53  
    54  var checkPathTests = []checkPathTest{{
    55  	path: `x.y/z`,
    56  }, {
    57  	path: `x.y`,
    58  }, {
    59  	path:      ``,
    60  	modErr:    `empty string`,
    61  	importErr: `malformed import path "": empty string`,
    62  	fileErr:   `malformed file path "": empty string`,
    63  }, {
    64  	path:      "x.y/\xffz",
    65  	modErr:    `invalid UTF-8`,
    66  	importErr: `malformed import path "x.y/\xffz": invalid UTF-8`,
    67  	fileErr:   `malformed file path "x.y/\xffz": invalid UTF-8`,
    68  }, {
    69  	path:      `/x.y/z`,
    70  	modErr:    `empty path element`,
    71  	importErr: `malformed import path "/x.y/z": empty path element`,
    72  	fileErr:   `malformed file path "/x.y/z": empty path element`,
    73  }, {
    74  	path:      `x./z`,
    75  	modErr:    `trailing '.' in path element`,
    76  	importErr: `malformed import path "x./z": trailing dot in path element`,
    77  	fileErr:   `malformed file path "x./z": trailing dot in path element`,
    78  }, {
    79  	path:   `.x/z`,
    80  	modErr: `leading '.' in path element`,
    81  }, {
    82  	path:      `-x/z`,
    83  	modErr:    `leading dash`,
    84  	importErr: `malformed import path "-x/z": leading dash`,
    85  }, {
    86  	path:   `x..y/z`,
    87  	modErr: `path does not conform to OCI repository name restrictions; see https://github.com/opencontainers/distribution-spec/blob/HEAD/spec.md#pulling-manifests`,
    88  }, {
    89  	path:      `x.y/z/../../w`,
    90  	modErr:    `invalid path element ".."`,
    91  	importErr: `malformed import path "x.y/z/../../w": invalid path element ".."`,
    92  	fileErr:   `malformed file path "x.y/z/../../w": invalid path element ".."`,
    93  }, {
    94  	path:      `x.y//z`,
    95  	modErr:    `double slash`,
    96  	importErr: `malformed import path "x.y//z": double slash`,
    97  	fileErr:   `malformed file path "x.y//z": double slash`,
    98  }, {
    99  	path:      `x.y/z//w`,
   100  	modErr:    `double slash`,
   101  	importErr: `malformed import path "x.y/z//w": double slash`,
   102  	fileErr:   `malformed file path "x.y/z//w": double slash`,
   103  }, {
   104  	path:      `x.y/z/`,
   105  	modErr:    `trailing slash`,
   106  	importErr: `malformed import path "x.y/z/": trailing slash`,
   107  	fileErr:   `malformed file path "x.y/z/": trailing slash`,
   108  }, {
   109  	path: `x.y/z/v0`,
   110  }, {
   111  	path: `x.y/z/v1`,
   112  }, {
   113  	path: `x.y/z/v2`,
   114  }, {
   115  	path: `x.y/z/v2.0`,
   116  }, {
   117  	path:   `X.y/z`,
   118  	modErr: `invalid char 'X'`,
   119  }, {
   120  	path:      `!x.y/z`,
   121  	modErr:    `invalid char '!'`,
   122  	importErr: `malformed import path "!x.y/z": invalid char '!'`,
   123  }, {
   124  	path:   `_x.y/z`,
   125  	modErr: `leading '_' in path element`,
   126  }, {
   127  	path:      `x.y!/z`,
   128  	modErr:    `invalid char '!'`,
   129  	importErr: `malformed import path "x.y!/z": invalid char '!'`,
   130  }, {
   131  	path:      `x.y"/z`,
   132  	modErr:    `invalid char '"'`,
   133  	importErr: `malformed import path "x.y\"/z": invalid char '"'`,
   134  	fileErr:   `malformed file path "x.y\"/z": invalid char '"'`,
   135  }, {
   136  	path:      `x.y#/z`,
   137  	modErr:    `invalid char '#'`,
   138  	importErr: `malformed import path "x.y#/z": invalid char '#'`,
   139  }, {
   140  	path:      `x.y$/z`,
   141  	modErr:    `invalid char '$'`,
   142  	importErr: `malformed import path "x.y$/z": invalid char '$'`,
   143  }, {
   144  	path:      `x.y%/z`,
   145  	modErr:    `invalid char '%'`,
   146  	importErr: `malformed import path "x.y%/z": invalid char '%'`,
   147  }, {
   148  	path:      `x.y&/z`,
   149  	modErr:    `invalid char '&'`,
   150  	importErr: `malformed import path "x.y&/z": invalid char '&'`,
   151  }, {
   152  	path:      `x.y'/z`,
   153  	modErr:    `invalid char '\''`,
   154  	importErr: `malformed import path "x.y'/z": invalid char '\''`,
   155  	fileErr:   `malformed file path "x.y'/z": invalid char '\''`,
   156  }, {
   157  	path:      `x.y(/z`,
   158  	modErr:    `invalid char '('`,
   159  	importErr: `malformed import path "x.y(/z": invalid char '('`,
   160  }, {
   161  	path:      `x.y)/z`,
   162  	modErr:    `invalid char ')'`,
   163  	importErr: `malformed import path "x.y)/z": invalid char ')'`,
   164  }, {
   165  	path:      `x.y*/z`,
   166  	modErr:    `invalid char '*'`,
   167  	importErr: `malformed import path "x.y*/z": invalid char '*'`,
   168  	fileErr:   `malformed file path "x.y*/z": invalid char '*'`,
   169  }, {
   170  	path:   `x.y+/z`,
   171  	modErr: `invalid char '+'`,
   172  }, {
   173  	path:      `x.y,/z`,
   174  	modErr:    `invalid char ','`,
   175  	importErr: `malformed import path "x.y,/z": invalid char ','`,
   176  }, {
   177  	path:   `x.y-/z`,
   178  	modErr: `trailing '-' in path element`,
   179  }, {
   180  	path:      `x.y./zt`,
   181  	modErr:    `trailing '.' in path element`,
   182  	importErr: `malformed import path "x.y./zt": trailing dot in path element`,
   183  	fileErr:   `malformed file path "x.y./zt": trailing dot in path element`,
   184  }, {
   185  	path:      `x.y:/z`,
   186  	modErr:    `invalid char ':'`,
   187  	importErr: `malformed import path "x.y:/z": invalid char ':'`,
   188  	fileErr:   `malformed file path "x.y:/z": invalid char ':'`,
   189  }, {
   190  	path:      `x.y;/z`,
   191  	modErr:    `invalid char ';'`,
   192  	importErr: `malformed import path "x.y;/z": invalid char ';'`,
   193  	fileErr:   `malformed file path "x.y;/z": invalid char ';'`,
   194  }, {
   195  	path:      `x.y</z`,
   196  	modErr:    `invalid char '<'`,
   197  	importErr: `malformed import path "x.y</z": invalid char '<'`,
   198  	fileErr:   `malformed file path "x.y</z": invalid char '<'`,
   199  }, {
   200  	path:      `x.y=/z`,
   201  	modErr:    `invalid char '='`,
   202  	importErr: `malformed import path "x.y=/z": invalid char '='`,
   203  }, {
   204  	path:      `x.y>/z`,
   205  	modErr:    `invalid char '>'`,
   206  	importErr: `malformed import path "x.y>/z": invalid char '>'`,
   207  	fileErr:   `malformed file path "x.y>/z": invalid char '>'`,
   208  }, {
   209  	path:      `x.y?/z`,
   210  	modErr:    `invalid char '?'`,
   211  	importErr: `malformed import path "x.y?/z": invalid char '?'`,
   212  	fileErr:   `malformed file path "x.y?/z": invalid char '?'`,
   213  }, {
   214  	path:      `x.y@/z`,
   215  	modErr:    `invalid char '@'`,
   216  	importErr: `malformed import path "x.y@/z": invalid char '@'`,
   217  }, {
   218  	path:      `x.y[/z`,
   219  	modErr:    `invalid char '['`,
   220  	importErr: `malformed import path "x.y[/z": invalid char '['`,
   221  }, {
   222  	path:      `x.y\/z`,
   223  	modErr:    `invalid char '\\'`,
   224  	importErr: `malformed import path "x.y\\/z": invalid char '\\'`,
   225  	fileErr:   `malformed file path "x.y\\/z": invalid char '\\'`,
   226  }, {
   227  	path:      `x.y]/z`,
   228  	modErr:    `invalid char ']'`,
   229  	importErr: `malformed import path "x.y]/z": invalid char ']'`,
   230  }, {
   231  	path:      `x.y^/z`,
   232  	modErr:    `invalid char '^'`,
   233  	importErr: `malformed import path "x.y^/z": invalid char '^'`,
   234  }, {
   235  	path:   `x.y_/z`,
   236  	modErr: `trailing '_' in path element`,
   237  }, {
   238  	path:      "x.y`/z",
   239  	modErr:    "invalid char '`'",
   240  	importErr: "malformed import path \"x.y`/z\": invalid char '`'",
   241  	fileErr:   "malformed file path \"x.y`/z\": invalid char '`'",
   242  }, {
   243  	path:      `x.y{/z`,
   244  	modErr:    `invalid char '{'`,
   245  	importErr: `malformed import path "x.y{/z": invalid char '{'`,
   246  }, {
   247  	path:      `x.y}/z`,
   248  	modErr:    `invalid char '}'`,
   249  	importErr: `malformed import path "x.y}/z": invalid char '}'`,
   250  }, {
   251  	path:   `x.y~/z`,
   252  	modErr: `invalid char '~'`,
   253  }, {
   254  	path:      `x.y/z!`,
   255  	modErr:    `invalid char '!'`,
   256  	importErr: `malformed import path "x.y/z!": invalid char '!'`,
   257  }, {
   258  	path:      `x.y/z"`,
   259  	modErr:    `invalid char '"'`,
   260  	importErr: `malformed import path "x.y/z\"": invalid char '"'`,
   261  	fileErr:   `malformed file path "x.y/z\"": invalid char '"'`,
   262  }, {
   263  	path:      `x.y/z#`,
   264  	modErr:    `invalid char '#'`,
   265  	importErr: `malformed import path "x.y/z#": invalid char '#'`,
   266  }, {
   267  	path:      `x.y/z$`,
   268  	modErr:    `invalid char '$'`,
   269  	importErr: `malformed import path "x.y/z$": invalid char '$'`,
   270  }, {
   271  	path:      `x.y/z%`,
   272  	modErr:    `invalid char '%'`,
   273  	importErr: `malformed import path "x.y/z%": invalid char '%'`,
   274  }, {
   275  	path:      `x.y/z&`,
   276  	modErr:    `invalid char '&'`,
   277  	importErr: `malformed import path "x.y/z&": invalid char '&'`,
   278  }, {
   279  	path:      `x.y/z'`,
   280  	modErr:    `invalid char '\''`,
   281  	importErr: `malformed import path "x.y/z'": invalid char '\''`,
   282  	fileErr:   `malformed file path "x.y/z'": invalid char '\''`,
   283  }, {
   284  	path:      `x.y/z(`,
   285  	modErr:    `invalid char '('`,
   286  	importErr: `malformed import path "x.y/z(": invalid char '('`,
   287  }, {
   288  	path:      `x.y/z)`,
   289  	modErr:    `invalid char ')'`,
   290  	importErr: `malformed import path "x.y/z)": invalid char ')'`,
   291  }, {
   292  	path:      `x.y/z*`,
   293  	modErr:    `invalid char '*'`,
   294  	importErr: `malformed import path "x.y/z*": invalid char '*'`,
   295  	fileErr:   `malformed file path "x.y/z*": invalid char '*'`,
   296  }, {
   297  	path:   `x.y/z++`,
   298  	modErr: `invalid char '+'`,
   299  }, {
   300  	path:      `x.y/z,`,
   301  	modErr:    `invalid char ','`,
   302  	importErr: `malformed import path "x.y/z,": invalid char ','`,
   303  }, {
   304  	path:   `x.y/z-`,
   305  	modErr: `trailing '-' in path element`,
   306  }, {
   307  	path: `x.y/z.t`,
   308  }, {
   309  	path: `x.y/z/t`,
   310  }, {
   311  	path:    `x.y/z:`,
   312  	modErr:  `invalid char ':'`,
   313  	fileErr: `malformed file path "x.y/z:": invalid char ':'`,
   314  }, {
   315  	path:      `x.y/z;`,
   316  	modErr:    `invalid char ';'`,
   317  	importErr: `malformed import path "x.y/z;": invalid char ';'`,
   318  	fileErr:   `malformed file path "x.y/z;": invalid char ';'`,
   319  }, {
   320  	path:      `x.y/z<`,
   321  	modErr:    `invalid char '<'`,
   322  	importErr: `malformed import path "x.y/z<": invalid char '<'`,
   323  	fileErr:   `malformed file path "x.y/z<": invalid char '<'`,
   324  }, {
   325  	path:      `x.y/z=`,
   326  	modErr:    `invalid char '='`,
   327  	importErr: `malformed import path "x.y/z=": invalid char '='`,
   328  }, {
   329  	path:      `x.y/z>`,
   330  	modErr:    `invalid char '>'`,
   331  	importErr: `malformed import path "x.y/z>": invalid char '>'`,
   332  	fileErr:   `malformed file path "x.y/z>": invalid char '>'`,
   333  }, {
   334  	path:      `x.y/z?`,
   335  	modErr:    `invalid char '?'`,
   336  	importErr: `malformed import path "x.y/z?": invalid char '?'`,
   337  	fileErr:   `malformed file path "x.y/z?": invalid char '?'`,
   338  }, {
   339  	path:      `x.y/z@`,
   340  	modErr:    `invalid char '@'`,
   341  	importErr: `malformed import path "x.y/z@": invalid char '@'`,
   342  }, {
   343  	path:      `x.y/z[`,
   344  	modErr:    `invalid char '['`,
   345  	importErr: `malformed import path "x.y/z[": invalid char '['`,
   346  }, {
   347  	path:      `x.y/z\`,
   348  	modErr:    `invalid char '\\'`,
   349  	importErr: `malformed import path "x.y/z\\": invalid char '\\'`,
   350  	fileErr:   `malformed file path "x.y/z\\": invalid char '\\'`,
   351  }, {
   352  	path:      `x.y/z]`,
   353  	modErr:    `invalid char ']'`,
   354  	importErr: `malformed import path "x.y/z]": invalid char ']'`,
   355  }, {
   356  	path:      `x.y/z^`,
   357  	modErr:    `invalid char '^'`,
   358  	importErr: `malformed import path "x.y/z^": invalid char '^'`,
   359  }, {
   360  	path:   `x.y/z_`,
   361  	modErr: `trailing '_' in path element`,
   362  }, {
   363  	path:      "x.y/z`",
   364  	modErr:    "invalid char '`'",
   365  	importErr: "malformed import path \"x.y/z`\": invalid char '`'",
   366  	fileErr:   "malformed file path \"x.y/z`\": invalid char '`'",
   367  }, {
   368  	path:      `x.y/z{`,
   369  	modErr:    `invalid char '{'`,
   370  	importErr: `malformed import path "x.y/z{": invalid char '{'`,
   371  }, {
   372  	path:      `x.y/z}`,
   373  	modErr:    `invalid char '}'`,
   374  	importErr: `malformed import path "x.y/z}": invalid char '}'`,
   375  }, {
   376  	path:   `x.y/z~`,
   377  	modErr: `invalid char '~'`,
   378  }, {
   379  	path: `x.y/x.foo`,
   380  }, {
   381  	path:      `x.y/aux.foo`,
   382  	modErr:    `"aux" disallowed as path element component on Windows`,
   383  	importErr: `malformed import path "x.y/aux.foo": "aux" disallowed as path element component on Windows`,
   384  	fileErr:   `malformed file path "x.y/aux.foo": "aux" disallowed as path element component on Windows`,
   385  }, {
   386  	path:      `x.y/prn`,
   387  	modErr:    `"prn" disallowed as path element component on Windows`,
   388  	importErr: `malformed import path "x.y/prn": "prn" disallowed as path element component on Windows`,
   389  	fileErr:   `malformed file path "x.y/prn": "prn" disallowed as path element component on Windows`,
   390  }, {
   391  	path: `x.y/prn2`,
   392  }, {
   393  	path: `x.y/com`,
   394  }, {
   395  	path:      `x.y/com1`,
   396  	modErr:    `"com1" disallowed as path element component on Windows`,
   397  	importErr: `malformed import path "x.y/com1": "com1" disallowed as path element component on Windows`,
   398  	fileErr:   `malformed file path "x.y/com1": "com1" disallowed as path element component on Windows`,
   399  }, {
   400  	path:      `x.y/com1.txt`,
   401  	modErr:    `"com1" disallowed as path element component on Windows`,
   402  	importErr: `malformed import path "x.y/com1.txt": "com1" disallowed as path element component on Windows`,
   403  	fileErr:   `malformed file path "x.y/com1.txt": "com1" disallowed as path element component on Windows`,
   404  }, {
   405  	path: `x.y/calm1`,
   406  }, {
   407  	path:   `x.y/z~`,
   408  	modErr: `invalid char '~'`,
   409  }, {
   410  	path:      `x.y/z~0`,
   411  	modErr:    `invalid char '~'`,
   412  	importErr: `malformed import path "x.y/z~0": trailing tilde and digits in path element`,
   413  }, {
   414  	path:      `x.y/z~09`,
   415  	modErr:    `invalid char '~'`,
   416  	importErr: `malformed import path "x.y/z~09": trailing tilde and digits in path element`,
   417  }, {
   418  	path: `x.y/z09`,
   419  }, {
   420  	path:   `x.y/z09~`,
   421  	modErr: `invalid char '~'`,
   422  }, {
   423  	path:   `x.y/z09~09z`,
   424  	modErr: `invalid char '~'`,
   425  }, {
   426  	path:      `x.y/z09~09z~09`,
   427  	modErr:    `invalid char '~'`,
   428  	importErr: `malformed import path "x.y/z09~09z~09": trailing tilde and digits in path element`,
   429  }, {
   430  	path:      `github.com/!123/logrus`,
   431  	modErr:    `invalid char '!'`,
   432  	importErr: `malformed import path "github.com/!123/logrus": invalid char '!'`,
   433  }, {
   434  	path:      `github.com/user/unicode/испытание`,
   435  	modErr:    `invalid char 'и'`,
   436  	importErr: `malformed import path "github.com/user/unicode/испытание": invalid char 'и'`,
   437  }, {
   438  	path:      `../x`,
   439  	modErr:    `invalid path element ".."`,
   440  	importErr: `malformed import path "../x": invalid path element ".."`,
   441  	fileErr:   `malformed file path "../x": invalid path element ".."`,
   442  }, {
   443  	path:      `./y`,
   444  	modErr:    `invalid path element "."`,
   445  	importErr: `malformed import path "./y": invalid path element "."`,
   446  	fileErr:   `malformed file path "./y": invalid path element "."`,
   447  }, {
   448  	path:    `x:y`,
   449  	modErr:  `invalid char ':'`,
   450  	fileErr: `malformed file path "x:y": invalid char ':'`,
   451  }, {
   452  	path:      `\temp\foo`,
   453  	modErr:    `invalid char '\\'`,
   454  	importErr: `malformed import path "\\temp\\foo": invalid char '\\'`,
   455  	fileErr:   `malformed file path "\\temp\\foo": invalid char '\\'`,
   456  }, {
   457  	path:   `.gitignore`,
   458  	modErr: `leading '.' in path element`,
   459  }, {
   460  	path:   `.github/ISSUE_TEMPLATE`,
   461  	modErr: `leading '.' in path element`,
   462  }, {
   463  	path:      `x☺y`,
   464  	modErr:    `invalid char '☺'`,
   465  	importErr: `malformed import path "x☺y": invalid char '☺'`,
   466  	fileErr:   `malformed file path "x☺y": invalid char '☺'`,
   467  }, {
   468  	path:      `bar.com/foo.`,
   469  	modErr:    `trailing '.' in path element`,
   470  	importErr: `malformed import path "bar.com/foo.": trailing dot in path element`,
   471  	fileErr:   `malformed file path "bar.com/foo.": trailing dot in path element`,
   472  }, {
   473  	path:   `bar.com/_foo`,
   474  	modErr: `leading '_' in path element`,
   475  }, {
   476  	path:   `bar.com/foo___x`,
   477  	modErr: `path does not conform to OCI repository name restrictions; see https://github.com/opencontainers/distribution-spec/blob/HEAD/spec.md#pulling-manifests`,
   478  }, {
   479  	path:   `bar.com/Sushi`,
   480  	modErr: `invalid char 'S'`,
   481  }, {
   482  	path:      `rsc io/quote`,
   483  	modErr:    `invalid char ' '`,
   484  	importErr: `malformed import path "rsc io/quote": invalid char ' '`,
   485  }, {
   486  	path:   `foo.com@v0`,
   487  	modErr: `module path inappropriately contains major version`,
   488  }, {
   489  	path: `foo.com/bar/baz`,
   490  }}
   491  
   492  func TestCheckPathWithoutVersion(t *testing.T) {
   493  	cuetest.Run(t, checkPathTests, func(t *cuetest.T, test *checkPathTest) {
   494  		t.Logf("path: `%s`", test.path)
   495  		t.Equal(errStr(CheckPathWithoutVersion(test.path)), test.modErr)
   496  	})
   497  }
   498  
   499  func TestCheckImportPath(t *testing.T) {
   500  	cuetest.Run(t, checkPathTests, func(t *cuetest.T, test *checkPathTest) {
   501  		t.Logf("path: `%s`", test.path)
   502  		t.Equal(errStr(CheckImportPath(test.path)), test.importErr)
   503  	})
   504  }
   505  
   506  func TestCheckFilePath(t *testing.T) {
   507  	cuetest.Run(t, checkPathTests, func(t *cuetest.T, test *checkPathTest) {
   508  		t.Logf("path: `%s`", test.path)
   509  		t.Equal(errStr(CheckFilePath(test.path)), test.fileErr)
   510  	})
   511  }
   512  
   513  var newVersionTests = []struct {
   514  	path, vers   string
   515  	wantError    string
   516  	wantPath     string
   517  	wantBasePath string
   518  }{{
   519  	path:         "github.com/foo/bar@v0",
   520  	vers:         "v0.1.2",
   521  	wantPath:     "github.com/foo/bar@v0",
   522  	wantBasePath: "github.com/foo/bar",
   523  }, {
   524  	path:         "github.com/foo/bar",
   525  	vers:         "v3.1.2",
   526  	wantPath:     "github.com/foo/bar@v3",
   527  	wantBasePath: "github.com/foo/bar",
   528  }, {
   529  	path:         "github.com/foo/bar@v1",
   530  	vers:         "",
   531  	wantPath:     "github.com/foo/bar@v1",
   532  	wantBasePath: "github.com/foo/bar",
   533  }, {
   534  	path:      "github.com/foo/bar@v1",
   535  	vers:      "v3.1.2",
   536  	wantError: `mismatched major version suffix in "github.com/foo/bar@v1" \(version v3\.1\.2\)`,
   537  }, {
   538  	path:      "github.com/foo/bar@v1",
   539  	vers:      "v3.1",
   540  	wantError: `version "v3.1" \(of module "github.com/foo/bar@v1"\) is not canonical`,
   541  }, {
   542  	path:      "github.com/foo/bar@v1",
   543  	vers:      "v3.10.4+build",
   544  	wantError: `version "v3.10.4\+build" \(of module "github.com/foo/bar@v1"\) is not canonical`,
   545  }, {
   546  	path:      "something/bad@v1",
   547  	vers:      "v1.2.3",
   548  	wantError: `malformed module path "something/bad@v1": missing dot in first path element`,
   549  }, {
   550  	path:      "foo.com/bar",
   551  	vers:      "",
   552  	wantError: `path "foo.com/bar" has no major version`,
   553  }, {
   554  	path:      "x.com",
   555  	vers:      "bad",
   556  	wantError: `version "bad" \(of module "x.com"\) is not well formed`,
   557  }, {
   558  	path:         "local",
   559  	vers:         "",
   560  	wantPath:     "local",
   561  	wantBasePath: "local",
   562  }, {
   563  	path:      "local",
   564  	vers:      "v0.1.2",
   565  	wantError: `module 'local' cannot have version`,
   566  }, {
   567  	path:      "local@v1",
   568  	vers:      "",
   569  	wantError: `module 'local' cannot have version`,
   570  }}
   571  
   572  func TestNewVersion(t *testing.T) {
   573  	for _, test := range newVersionTests {
   574  		t.Run(test.path+"@"+test.vers, func(t *testing.T) {
   575  			v, err := NewVersion(test.path, test.vers)
   576  			if test.wantError != "" {
   577  				qt.Assert(t, qt.ErrorMatches(err, test.wantError))
   578  				return
   579  			}
   580  			qt.Assert(t, qt.IsNil(err))
   581  			qt.Assert(t, qt.Equals(v.Path(), test.wantPath))
   582  			qt.Assert(t, qt.Equals(v.BasePath(), test.wantBasePath))
   583  			qt.Assert(t, qt.Equals(v.Version(), test.vers))
   584  		})
   585  	}
   586  }
   587  
   588  var parseVersionTests = []struct {
   589  	s         string
   590  	wantError string
   591  }{{
   592  	s: "github.com/foo/bar@v0.1.2",
   593  }}
   594  
   595  func TestParseVersion(t *testing.T) {
   596  	for _, test := range parseVersionTests {
   597  		t.Run(test.s, func(t *testing.T) {
   598  			v, err := ParseVersion(test.s)
   599  			if test.wantError != "" {
   600  				qt.Assert(t, qt.ErrorMatches(err, test.wantError))
   601  				return
   602  			}
   603  			qt.Assert(t, qt.IsNil(err))
   604  			qt.Assert(t, qt.Equals(v.String(), test.s))
   605  		})
   606  	}
   607  }
   608  
   609  var escapeVersionTests = []struct {
   610  	v   string
   611  	esc string // empty means same as path
   612  }{
   613  	{v: "v1.2.3-alpha"},
   614  	{v: "v3"},
   615  	{v: "v2.3.1-ABcD", esc: "v2.3.1-!a!bc!d"},
   616  }
   617  
   618  func TestEscapeVersion(t *testing.T) {
   619  	for _, tt := range escapeVersionTests {
   620  		esc, err := EscapeVersion(tt.v)
   621  		if err != nil {
   622  			t.Errorf("EscapeVersion(%q): unexpected error: %v", tt.v, err)
   623  			continue
   624  		}
   625  		want := tt.esc
   626  		if want == "" {
   627  			want = tt.v
   628  		}
   629  		if esc != want {
   630  			t.Errorf("EscapeVersion(%q) = %q, want %q", tt.v, esc, want)
   631  		}
   632  	}
   633  }
   634  
   635  func TestEscapePath(t *testing.T) {
   636  	// Check invalid paths.
   637  	for _, tt := range checkPathTests {
   638  		if tt.modErr != "" {
   639  			_, err := EscapePath(tt.path)
   640  			if err == nil {
   641  				t.Errorf("EscapePath(%q): succeeded, want error (invalid path)", tt.path)
   642  			}
   643  		}
   644  	}
   645  	path := "foo.com/bar"
   646  	esc, err := EscapePath(path)
   647  	if err != nil {
   648  		t.Fatal(err)
   649  	}
   650  	if esc != path {
   651  		t.Fatalf("EscapePath(%q) = %q, want %q", path, esc, path)
   652  	}
   653  }
   654  
   655  var parseImportPathTests = []struct {
   656  	testName      string
   657  	path          string
   658  	want          ImportPath
   659  	wantCanonical string
   660  }{{
   661  	testName: "StdlibLikeWithSlash",
   662  	path:     "stdlib/path",
   663  	want: ImportPath{
   664  		Path:      "stdlib/path",
   665  		Qualifier: "path",
   666  	},
   667  }, {
   668  	testName: "StdlibLikeNoSlash",
   669  	path:     "math",
   670  	want: ImportPath{
   671  		Path:      "math",
   672  		Qualifier: "math",
   673  	},
   674  }, {
   675  	testName: "StdlibLikeExplicitQualifier",
   676  	path:     "stdlib/path:other",
   677  	want: ImportPath{
   678  		Path:              "stdlib/path",
   679  		ExplicitQualifier: true,
   680  		Qualifier:         "other",
   681  	},
   682  }, {
   683  	testName: "StdlibLikeExplicitQualifierNoSlash",
   684  	path:     "math:other",
   685  	want: ImportPath{
   686  		Path:              "math",
   687  		ExplicitQualifier: true,
   688  		Qualifier:         "other",
   689  	},
   690  }, {
   691  	testName: "WithMajorVersion",
   692  	path:     "foo.com/bar@v0",
   693  	want: ImportPath{
   694  		Path:      "foo.com/bar",
   695  		Version:   "v0",
   696  		Qualifier: "bar",
   697  	},
   698  }, {
   699  	testName: "WithMajorVersionNoSlash",
   700  	path:     "main.test@v0",
   701  	want: ImportPath{
   702  		Path:      "main.test",
   703  		Version:   "v0",
   704  		Qualifier: "",
   705  	},
   706  }, {
   707  	testName: "WithMajorVersionAndExplicitQualifier",
   708  	path:     "foo.com/bar@v0:other",
   709  	want: ImportPath{
   710  		Path:              "foo.com/bar",
   711  		Version:           "v0",
   712  		ExplicitQualifier: true,
   713  		Qualifier:         "other",
   714  	},
   715  }, {
   716  	testName: "WithMajorVersionAndNoQualifier",
   717  	path:     "foo.com/bar@v0",
   718  	want: ImportPath{
   719  		Path:      "foo.com/bar",
   720  		Version:   "v0",
   721  		Qualifier: "bar",
   722  	},
   723  }, {
   724  	testName: "WithRedundantQualifier",
   725  	path:     "foo.com/bar@v0:bar",
   726  	want: ImportPath{
   727  		Path:              "foo.com/bar",
   728  		Version:           "v0",
   729  		ExplicitQualifier: true,
   730  		Qualifier:         "bar",
   731  	},
   732  	wantCanonical: "foo.com/bar@v0",
   733  }, {
   734  	testName: "WithPattern",
   735  	path:     "foo.com/bar/.../blah",
   736  	want: ImportPath{
   737  		Path:              "foo.com/bar/.../blah",
   738  		Version:           "",
   739  		ExplicitQualifier: false,
   740  		Qualifier:         "blah",
   741  	},
   742  	wantCanonical: "foo.com/bar/.../blah",
   743  }, {
   744  	testName: "WithPatternAtEnd",
   745  	path:     "foo.com/bar/...",
   746  	want: ImportPath{
   747  		Path:              "foo.com/bar/...",
   748  		Version:           "",
   749  		ExplicitQualifier: false,
   750  		Qualifier:         "",
   751  	},
   752  	wantCanonical: "foo.com/bar/...",
   753  }, {
   754  	testName: "WithUnderscoreLastElement",
   755  	path:     "foo.com/bar/_foo",
   756  	want: ImportPath{
   757  		Path:              "foo.com/bar/_foo",
   758  		Version:           "",
   759  		ExplicitQualifier: false,
   760  		Qualifier:         "_foo",
   761  	},
   762  	wantCanonical: "foo.com/bar/_foo",
   763  }, {
   764  	testName: "WithHashLastElement",
   765  	path:     "foo.com/bar/#foo",
   766  	want: ImportPath{
   767  		Path:              "foo.com/bar/#foo",
   768  		Version:           "",
   769  		ExplicitQualifier: false,
   770  		Qualifier:         "",
   771  	},
   772  	wantCanonical: "foo.com/bar/#foo",
   773  }}
   774  
   775  func TestParseImportPath(t *testing.T) {
   776  	for _, test := range parseImportPathTests {
   777  		t.Run(test.testName, func(t *testing.T) {
   778  			parts := ParseImportPath(test.path)
   779  			qt.Assert(t, qt.DeepEquals(parts, test.want))
   780  			qt.Assert(t, qt.Equals(parts.String(), test.path))
   781  			if test.wantCanonical == "" {
   782  				test.wantCanonical = test.path
   783  			}
   784  			qt.Assert(t, qt.Equals(parts.Canonical().String(), test.wantCanonical))
   785  		})
   786  	}
   787  }
   788  
   789  func TestImportPathStringAddsQualifier(t *testing.T) {
   790  	ip := ImportPath{
   791  		Path:      "foo.com/bar",
   792  		Version:   "v0",
   793  		Qualifier: "baz",
   794  	}
   795  	qt.Assert(t, qt.Equals(ip.String(), "foo.com/bar@v0:baz"))
   796  }
   797  
   798  func TestImportPathStringAddsQualifierWhenNoVersion(t *testing.T) {
   799  	ip := ImportPath{
   800  		Path:      "foo.com/bar",
   801  		Qualifier: "baz",
   802  	}
   803  	qt.Assert(t, qt.Equals(ip.String(), "foo.com/bar:baz"))
   804  }
   805  
   806  func errStr(err error) string {
   807  	if err == nil {
   808  		return ""
   809  	}
   810  	if e := err.Error(); e != "" {
   811  		return e
   812  	}
   813  	panic("non-nil error with empty string")
   814  }