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 modload
6
7import (
8	"context"
9	"flag"
10	"internal/testenv"
11	"log"
12	"os"
13	"path"
14	"path/filepath"
15	"strings"
16	"testing"
17
18	"cmd/go/internal/cfg"
19	"cmd/go/internal/vcweb/vcstest"
20
21	"golang.org/x/mod/module"
22)
23
24func TestMain(m *testing.M) {
25	flag.Parse()
26	if err := testMain(m); err != nil {
27		log.Fatal(err)
28	}
29}
30
31func testMain(m *testing.M) (err error) {
32	cfg.GOPROXY = "direct"
33	cfg.ModCacheRW = true
34
35	srv, err := vcstest.NewServer()
36	if err != nil {
37		return err
38	}
39	defer func() {
40		if closeErr := srv.Close(); err == nil {
41			err = closeErr
42		}
43	}()
44
45	dir, err := os.MkdirTemp("", "modload-test-")
46	if err != nil {
47		return err
48	}
49	defer func() {
50		if rmErr := os.RemoveAll(dir); err == nil {
51			err = rmErr
52		}
53	}()
54
55	os.Setenv("GOPATH", dir)
56	cfg.BuildContext.GOPATH = dir
57	cfg.GOMODCACHE = filepath.Join(dir, "pkg/mod")
58	cfg.SumdbDir = filepath.Join(dir, "pkg/sumdb")
59	m.Run()
60	return nil
61}
62
63var (
64	queryRepo   = "vcs-test.golang.org/git/querytest.git"
65	queryRepoV2 = queryRepo + "/v2"
66	queryRepoV3 = queryRepo + "/v3"
67
68	// Empty version list (no semver tags), not actually empty.
69	emptyRepoPath = "vcs-test.golang.org/git/emptytest.git"
70)
71
72var queryTests = []struct {
73	path    string
74	query   string
75	current string
76	allow   string
77	vers    string
78	err     string
79}{
80	{path: queryRepo, query: "<v0.0.0", vers: "v0.0.0-pre1"},
81	{path: queryRepo, query: "<v0.0.0-pre1", err: `no matching versions for query "<v0.0.0-pre1"`},
82	{path: queryRepo, query: "<=v0.0.0", vers: "v0.0.0"},
83	{path: queryRepo, query: ">v0.0.0", vers: "v0.0.1"},
84	{path: queryRepo, query: ">=v0.0.0", vers: "v0.0.0"},
85	{path: queryRepo, query: "v0.0.1", vers: "v0.0.1"},
86	{path: queryRepo, query: "v0.0.1+foo", vers: "v0.0.1"},
87	{path: queryRepo, query: "v0.0.99", err: `vcs-test.golang.org/git/querytest.git@v0.0.99: invalid version: unknown revision v0.0.99`},
88	{path: queryRepo, query: "v0", vers: "v0.3.0"},
89	{path: queryRepo, query: "v0.1", vers: "v0.1.2"},
90	{path: queryRepo, query: "v0.2", err: `no matching versions for query "v0.2"`},
91	{path: queryRepo, query: "v0.0", vers: "v0.0.3"},
92	{path: queryRepo, query: "v1.9.10-pre2+metadata", vers: "v1.9.10-pre2.0.20190513201126-42abcb6df8ee"},
93	{path: queryRepo, query: "ed5ffdaa", vers: "v1.9.10-pre2.0.20191220134614-ed5ffdaa1f5e"},
94
95	// golang.org/issue/29262: The major version for a module without a suffix
96	// should be based on the most recent tag (v1 as appropriate, not v0
97	// unconditionally).
98	{path: queryRepo, query: "42abcb6df8ee", vers: "v1.9.10-pre2.0.20190513201126-42abcb6df8ee"},
99
100	{path: queryRepo, query: "v1.9.10-pre2+wrongmetadata", err: `vcs-test.golang.org/git/querytest.git@v1.9.10-pre2+wrongmetadata: invalid version: unknown revision v1.9.10-pre2+wrongmetadata`},
101	{path: queryRepo, query: "v1.9.10-pre2", err: `vcs-test.golang.org/git/querytest.git@v1.9.10-pre2: invalid version: unknown revision v1.9.10-pre2`},
102	{path: queryRepo, query: "latest", vers: "v1.9.9"},
103	{path: queryRepo, query: "latest", current: "v1.9.10-pre1", vers: "v1.9.9"},
104	{path: queryRepo, query: "upgrade", vers: "v1.9.9"},
105	{path: queryRepo, query: "upgrade", current: "v1.9.10-pre1", vers: "v1.9.10-pre1"},
106	{path: queryRepo, query: "upgrade", current: "v1.9.10-pre2+metadata", vers: "v1.9.10-pre2.0.20190513201126-42abcb6df8ee"},
107	{path: queryRepo, query: "upgrade", current: "v0.0.0-20190513201126-42abcb6df8ee", vers: "v0.0.0-20190513201126-42abcb6df8ee"},
108	{path: queryRepo, query: "upgrade", allow: "NOMATCH", err: `no matching versions for query "upgrade"`},
109	{path: queryRepo, query: "upgrade", current: "v1.9.9", allow: "NOMATCH", err: `vcs-test.golang.org/git/querytest.git@v1.9.9: disallowed module version`},
110	{path: queryRepo, query: "upgrade", current: "v1.99.99", err: `vcs-test.golang.org/git/querytest.git@v1.99.99: invalid version: unknown revision v1.99.99`},
111	{path: queryRepo, query: "patch", current: "", err: `can't query version "patch" of module vcs-test.golang.org/git/querytest.git: no existing version is required`},
112	{path: queryRepo, query: "patch", current: "v0.1.0", vers: "v0.1.2"},
113	{path: queryRepo, query: "patch", current: "v1.9.0", vers: "v1.9.9"},
114	{path: queryRepo, query: "patch", current: "v1.9.10-pre1", vers: "v1.9.10-pre1"},
115	{path: queryRepo, query: "patch", current: "v1.9.10-pre2+metadata", vers: "v1.9.10-pre2.0.20190513201126-42abcb6df8ee"},
116	{path: queryRepo, query: "patch", current: "v1.99.99", err: `vcs-test.golang.org/git/querytest.git@v1.99.99: invalid version: unknown revision v1.99.99`},
117	{path: queryRepo, query: ">v1.9.9", vers: "v1.9.10-pre1"},
118	{path: queryRepo, query: ">v1.10.0", err: `no matching versions for query ">v1.10.0"`},
119	{path: queryRepo, query: ">=v1.10.0", err: `no matching versions for query ">=v1.10.0"`},
120	{path: queryRepo, query: "6cf84eb", vers: "v0.0.2-0.20180704023347-6cf84ebaea54"},
121
122	// golang.org/issue/27173: A pseudo-version may be based on the highest tag on
123	// any parent commit, or any existing semantically-lower tag: a given commit
124	// could have been a pre-release for a backport tag at any point.
125	{path: queryRepo, query: "3ef0cec634e0", vers: "v0.1.2-0.20180704023347-3ef0cec634e0"},
126	{path: queryRepo, query: "v0.1.2-0.20180704023347-3ef0cec634e0", vers: "v0.1.2-0.20180704023347-3ef0cec634e0"},
127	{path: queryRepo, query: "v0.1.1-0.20180704023347-3ef0cec634e0", vers: "v0.1.1-0.20180704023347-3ef0cec634e0"},
128	{path: queryRepo, query: "v0.0.4-0.20180704023347-3ef0cec634e0", vers: "v0.0.4-0.20180704023347-3ef0cec634e0"},
129
130	// Invalid tags are tested in cmd/go/testdata/script/mod_pseudo_invalid.txt.
131
132	{path: queryRepo, query: "start", vers: "v0.0.0-20180704023101-5e9e31667ddf"},
133	{path: queryRepo, query: "5e9e31667ddf", vers: "v0.0.0-20180704023101-5e9e31667ddf"},
134	{path: queryRepo, query: "v0.0.0-20180704023101-5e9e31667ddf", vers: "v0.0.0-20180704023101-5e9e31667ddf"},
135
136	{path: queryRepo, query: "7a1b6bf", vers: "v0.1.0"},
137
138	{path: queryRepoV2, query: "<v0.0.0", err: `no matching versions for query "<v0.0.0"`},
139	{path: queryRepoV2, query: "<=v0.0.0", err: `no matching versions for query "<=v0.0.0"`},
140	{path: queryRepoV2, query: ">v0.0.0", vers: "v2.0.0"},
141	{path: queryRepoV2, query: ">=v0.0.0", vers: "v2.0.0"},
142
143	{path: queryRepoV2, query: "v2", vers: "v2.5.5"},
144	{path: queryRepoV2, query: "v2.5", vers: "v2.5.5"},
145	{path: queryRepoV2, query: "v2.6", err: `no matching versions for query "v2.6"`},
146	{path: queryRepoV2, query: "v2.6.0-pre1", vers: "v2.6.0-pre1"},
147	{path: queryRepoV2, query: "latest", vers: "v2.5.5"},
148
149	// Commit e0cf3de987e6 is actually v1.19.10-pre1, not anything resembling v3,
150	// and it has a go.mod file with a non-v3 module path. Attempting to query it
151	// as the v3 module should fail.
152	{path: queryRepoV3, query: "e0cf3de987e6", err: `vcs-test.golang.org/git/querytest.git/v3@v3.0.0-20180704024501-e0cf3de987e6: invalid version: go.mod has non-.../v3 module path "vcs-test.golang.org/git/querytest.git" (and .../v3/go.mod does not exist) at revision e0cf3de987e6`},
153
154	// The querytest repo does not have any commits tagged with major version 3,
155	// and the latest commit in the repo has a go.mod file specifying a non-v3 path.
156	// That should prevent us from resolving any version for the /v3 path.
157	{path: queryRepoV3, query: "latest", err: `no matching versions for query "latest"`},
158
159	{path: emptyRepoPath, query: "latest", vers: "v0.0.0-20180704023549-7bb914627242"},
160	{path: emptyRepoPath, query: ">v0.0.0", err: `no matching versions for query ">v0.0.0"`},
161	{path: emptyRepoPath, query: "<v10.0.0", err: `no matching versions for query "<v10.0.0"`},
162}
163
164func TestQuery(t *testing.T) {
165	testenv.MustHaveExternalNetwork(t)
166	testenv.MustHaveExecPath(t, "git")
167
168	ctx := context.Background()
169
170	for _, tt := range queryTests {
171		allow := tt.allow
172		if allow == "" {
173			allow = "*"
174		}
175		allowed := func(ctx context.Context, m module.Version) error {
176			if ok, _ := path.Match(allow, m.Version); !ok {
177				return module.VersionError(m, ErrDisallowed)
178			}
179			return nil
180		}
181		tt := tt
182		t.Run(strings.ReplaceAll(tt.path, "/", "_")+"/"+tt.query+"/"+tt.current+"/"+allow, func(t *testing.T) {
183			t.Parallel()
184
185			info, err := Query(ctx, tt.path, tt.query, tt.current, allowed)
186			if tt.err != "" {
187				if err == nil {
188					t.Errorf("Query(_, %q, %q, %q, %v) = %v, want error %q", tt.path, tt.query, tt.current, allow, info.Version, tt.err)
189				} else if err.Error() != tt.err {
190					t.Errorf("Query(_, %q, %q, %q, %v): %v\nwant error %q", tt.path, tt.query, tt.current, allow, err, tt.err)
191				}
192				return
193			}
194			if err != nil {
195				t.Fatalf("Query(_, %q, %q, %q, %v): %v\nwant %v", tt.path, tt.query, tt.current, allow, err, tt.vers)
196			}
197			if info.Version != tt.vers {
198				t.Errorf("Query(_, %q, %q, %q, %v) = %v, want %v", tt.path, tt.query, tt.current, allow, info.Version, tt.vers)
199			}
200		})
201	}
202}
203