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
5package module
6
7import "testing"
8
9var checkTests = []struct {
10	path    string
11	version string
12	ok      bool
13}{
14	{"rsc.io/quote", "0.1.0", false},
15	{"rsc io/quote", "v1.0.0", false},
16
17	{"github.com/go-yaml/yaml", "v0.8.0", true},
18	{"github.com/go-yaml/yaml", "v1.0.0", true},
19	{"github.com/go-yaml/yaml", "v2.0.0", false},
20	{"github.com/go-yaml/yaml", "v2.1.5", false},
21	{"github.com/go-yaml/yaml", "v3.0.0", false},
22
23	{"github.com/go-yaml/yaml/v2", "v1.0.0", false},
24	{"github.com/go-yaml/yaml/v2", "v2.0.0", true},
25	{"github.com/go-yaml/yaml/v2", "v2.1.5", true},
26	{"github.com/go-yaml/yaml/v2", "v3.0.0", false},
27
28	{"gopkg.in/yaml.v0", "v0.8.0", true},
29	{"gopkg.in/yaml.v0", "v1.0.0", false},
30	{"gopkg.in/yaml.v0", "v2.0.0", false},
31	{"gopkg.in/yaml.v0", "v2.1.5", false},
32	{"gopkg.in/yaml.v0", "v3.0.0", false},
33
34	{"gopkg.in/yaml.v1", "v0.8.0", false},
35	{"gopkg.in/yaml.v1", "v1.0.0", true},
36	{"gopkg.in/yaml.v1", "v2.0.0", false},
37	{"gopkg.in/yaml.v1", "v2.1.5", false},
38	{"gopkg.in/yaml.v1", "v3.0.0", false},
39
40	// For gopkg.in, .v1 means v1 only (not v0).
41	// But early versions of vgo still generated v0 pseudo-versions for it.
42	// Even though now we'd generate those as v1 pseudo-versions,
43	// we accept the old pseudo-versions to avoid breaking existing go.mod files.
44	// For example gopkg.in/yaml.v2@v2.2.1's go.mod requires check.v1 at a v0 pseudo-version.
45	{"gopkg.in/check.v1", "v0.0.0", false},
46	{"gopkg.in/check.v1", "v0.0.0-20160102150405-abcdef123456", true},
47
48	{"gopkg.in/yaml.v2", "v1.0.0", false},
49	{"gopkg.in/yaml.v2", "v2.0.0", true},
50	{"gopkg.in/yaml.v2", "v2.1.5", true},
51	{"gopkg.in/yaml.v2", "v3.0.0", false},
52
53	{"rsc.io/quote", "v17.0.0", false},
54	{"rsc.io/quote", "v17.0.0+incompatible", true},
55}
56
57func TestCheck(t *testing.T) {
58	for _, tt := range checkTests {
59		err := Check(tt.path, tt.version)
60		if tt.ok && err != nil {
61			t.Errorf("Check(%q, %q) = %v, wanted nil error", tt.path, tt.version, err)
62		} else if !tt.ok && err == nil {
63			t.Errorf("Check(%q, %q) succeeded, wanted error", tt.path, tt.version)
64		}
65	}
66}
67
68var checkPathTests = []struct {
69	path     string
70	ok       bool
71	importOK bool
72	fileOK   bool
73}{
74	{"x.y/z", true, true, true},
75	{"x.y", true, true, true},
76
77	{"", false, false, false},
78	{"x.y/\xFFz", false, false, false},
79	{"/x.y/z", false, false, false},
80	{"x./z", false, false, false},
81	{".x/z", false, false, true},
82	{"-x/z", false, true, true},
83	{"x..y/z", false, false, false},
84	{"x.y/z/../../w", false, false, false},
85	{"x.y//z", false, false, false},
86	{"x.y/z//w", false, false, false},
87	{"x.y/z/", false, false, false},
88
89	{"x.y/z/v0", false, true, true},
90	{"x.y/z/v1", false, true, true},
91	{"x.y/z/v2", true, true, true},
92	{"x.y/z/v2.0", false, true, true},
93	{"X.y/z", false, true, true},
94
95	{"!x.y/z", false, false, true},
96	{"_x.y/z", false, true, true},
97	{"x.y!/z", false, false, true},
98	{"x.y\"/z", false, false, false},
99	{"x.y#/z", false, false, true},
100	{"x.y$/z", false, false, true},
101	{"x.y%/z", false, false, true},
102	{"x.y&/z", false, false, true},
103	{"x.y'/z", false, false, false},
104	{"x.y(/z", false, false, true},
105	{"x.y)/z", false, false, true},
106	{"x.y*/z", false, false, false},
107	{"x.y+/z", false, true, true},
108	{"x.y,/z", false, false, true},
109	{"x.y-/z", true, true, true},
110	{"x.y./zt", false, false, false},
111	{"x.y:/z", false, false, false},
112	{"x.y;/z", false, false, false},
113	{"x.y</z", false, false, false},
114	{"x.y=/z", false, false, true},
115	{"x.y>/z", false, false, false},
116	{"x.y?/z", false, false, false},
117	{"x.y@/z", false, false, true},
118	{"x.y[/z", false, false, true},
119	{"x.y\\/z", false, false, false},
120	{"x.y]/z", false, false, true},
121	{"x.y^/z", false, false, true},
122	{"x.y_/z", false, true, true},
123	{"x.y`/z", false, false, false},
124	{"x.y{/z", false, false, true},
125	{"x.y}/z", false, false, true},
126	{"x.y~/z", false, true, true},
127	{"x.y/z!", false, false, true},
128	{"x.y/z\"", false, false, false},
129	{"x.y/z#", false, false, true},
130	{"x.y/z$", false, false, true},
131	{"x.y/z%", false, false, true},
132	{"x.y/z&", false, false, true},
133	{"x.y/z'", false, false, false},
134	{"x.y/z(", false, false, true},
135	{"x.y/z)", false, false, true},
136	{"x.y/z*", false, false, false},
137	{"x.y/z+", true, true, true},
138	{"x.y/z,", false, false, true},
139	{"x.y/z-", true, true, true},
140	{"x.y/z.t", true, true, true},
141	{"x.y/z/t", true, true, true},
142	{"x.y/z:", false, false, false},
143	{"x.y/z;", false, false, false},
144	{"x.y/z<", false, false, false},
145	{"x.y/z=", false, false, true},
146	{"x.y/z>", false, false, false},
147	{"x.y/z?", false, false, false},
148	{"x.y/z@", false, false, true},
149	{"x.y/z[", false, false, true},
150	{"x.y/z\\", false, false, false},
151	{"x.y/z]", false, false, true},
152	{"x.y/z^", false, false, true},
153	{"x.y/z_", true, true, true},
154	{"x.y/z`", false, false, false},
155	{"x.y/z{", false, false, true},
156	{"x.y/z}", false, false, true},
157	{"x.y/z~", true, true, true},
158	{"x.y/x.foo", true, true, true},
159	{"x.y/aux.foo", false, false, false},
160	{"x.y/prn", false, false, false},
161	{"x.y/prn2", true, true, true},
162	{"x.y/com", true, true, true},
163	{"x.y/com1", false, false, false},
164	{"x.y/com1.txt", false, false, false},
165	{"x.y/calm1", true, true, true},
166	{"github.com/!123/logrus", false, false, true},
167
168	// TODO: CL 41822 allowed Unicode letters in old "go get"
169	// without due consideration of the implications, and only on github.com (!).
170	// For now, we disallow non-ASCII characters in module mode,
171	// in both module paths and general import paths,
172	// until we can get the implications right.
173	// When we do, we'll enable them everywhere, not just for GitHub.
174	{"github.com/user/unicode/испытание", false, false, true},
175
176	{"../x", false, false, false},
177	{"./y", false, false, false},
178	{"x:y", false, false, false},
179	{`\temp\foo`, false, false, false},
180	{".gitignore", false, false, true},
181	{".github/ISSUE_TEMPLATE", false, false, true},
182	{"x☺y", false, false, false},
183}
184
185func TestCheckPath(t *testing.T) {
186	for _, tt := range checkPathTests {
187		err := CheckPath(tt.path)
188		if tt.ok && err != nil {
189			t.Errorf("CheckPath(%q) = %v, wanted nil error", tt.path, err)
190		} else if !tt.ok && err == nil {
191			t.Errorf("CheckPath(%q) succeeded, wanted error", tt.path)
192		}
193
194		err = CheckImportPath(tt.path)
195		if tt.importOK && err != nil {
196			t.Errorf("CheckImportPath(%q) = %v, wanted nil error", tt.path, err)
197		} else if !tt.importOK && err == nil {
198			t.Errorf("CheckImportPath(%q) succeeded, wanted error", tt.path)
199		}
200
201		err = CheckFilePath(tt.path)
202		if tt.fileOK && err != nil {
203			t.Errorf("CheckFilePath(%q) = %v, wanted nil error", tt.path, err)
204		} else if !tt.fileOK && err == nil {
205			t.Errorf("CheckFilePath(%q) succeeded, wanted error", tt.path)
206		}
207	}
208}
209
210var splitPathVersionTests = []struct {
211	pathPrefix string
212	version    string
213}{
214	{"x.y/z", ""},
215	{"x.y/z", "/v2"},
216	{"x.y/z", "/v3"},
217	{"gopkg.in/yaml", ".v0"},
218	{"gopkg.in/yaml", ".v1"},
219	{"gopkg.in/yaml", ".v2"},
220	{"gopkg.in/yaml", ".v3"},
221}
222
223func TestSplitPathVersion(t *testing.T) {
224	for _, tt := range splitPathVersionTests {
225		pathPrefix, version, ok := SplitPathVersion(tt.pathPrefix + tt.version)
226		if pathPrefix != tt.pathPrefix || version != tt.version || !ok {
227			t.Errorf("SplitPathVersion(%q) = %q, %q, %v, want %q, %q, true", tt.pathPrefix+tt.version, pathPrefix, version, ok, tt.pathPrefix, tt.version)
228		}
229	}
230
231	for _, tt := range checkPathTests {
232		pathPrefix, version, ok := SplitPathVersion(tt.path)
233		if pathPrefix+version != tt.path {
234			t.Errorf("SplitPathVersion(%q) = %q, %q, %v, doesn't add to input", tt.path, pathPrefix, version, ok)
235		}
236	}
237}
238
239var encodeTests = []struct {
240	path string
241	enc  string // empty means same as path
242}{
243	{path: "ascii.com/abcdefghijklmnopqrstuvwxyz.-+/~_0123456789"},
244	{path: "github.com/GoogleCloudPlatform/omega", enc: "github.com/!google!cloud!platform/omega"},
245}
246
247func TestEncodePath(t *testing.T) {
248	// Check invalid paths.
249	for _, tt := range checkPathTests {
250		if !tt.ok {
251			_, err := EncodePath(tt.path)
252			if err == nil {
253				t.Errorf("EncodePath(%q): succeeded, want error (invalid path)", tt.path)
254			}
255		}
256	}
257
258	// Check encodings.
259	for _, tt := range encodeTests {
260		enc, err := EncodePath(tt.path)
261		if err != nil {
262			t.Errorf("EncodePath(%q): unexpected error: %v", tt.path, err)
263			continue
264		}
265		want := tt.enc
266		if want == "" {
267			want = tt.path
268		}
269		if enc != want {
270			t.Errorf("EncodePath(%q) = %q, want %q", tt.path, enc, want)
271		}
272	}
273}
274
275var badDecode = []string{
276	"github.com/GoogleCloudPlatform/omega",
277	"github.com/!google!cloud!platform!/omega",
278	"github.com/!0google!cloud!platform/omega",
279	"github.com/!_google!cloud!platform/omega",
280	"github.com/!!google!cloud!platform/omega",
281	"",
282}
283
284func TestDecodePath(t *testing.T) {
285	// Check invalid decodings.
286	for _, bad := range badDecode {
287		_, err := DecodePath(bad)
288		if err == nil {
289			t.Errorf("DecodePath(%q): succeeded, want error (invalid decoding)", bad)
290		}
291	}
292
293	// Check invalid paths (or maybe decodings).
294	for _, tt := range checkPathTests {
295		if !tt.ok {
296			path, err := DecodePath(tt.path)
297			if err == nil {
298				t.Errorf("DecodePath(%q) = %q, want error (invalid path)", tt.path, path)
299			}
300		}
301	}
302
303	// Check encodings.
304	for _, tt := range encodeTests {
305		enc := tt.enc
306		if enc == "" {
307			enc = tt.path
308		}
309		path, err := DecodePath(enc)
310		if err != nil {
311			t.Errorf("DecodePath(%q): unexpected error: %v", enc, err)
312			continue
313		}
314		if path != tt.path {
315			t.Errorf("DecodePath(%q) = %q, want %q", enc, path, tt.path)
316		}
317	}
318}
319