github.com/glimps-jbo/go-licenses@v0.0.0-20230908151000-e06d3c113277/internal/third_party/pkgsite/source/source_test.go (about) 1 // Copyright 2019 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 package source 5 6 import ( 7 "context" 8 "flag" 9 "fmt" 10 "io" 11 "net/http" 12 "os" 13 "path/filepath" 14 "strings" 15 "testing" 16 "time" 17 18 "github.com/google/go-cmp/cmp" 19 "github.com/google/go-replayers/httpreplay" 20 ) 21 22 var ( 23 testTimeout = 2 * time.Second 24 record = flag.Bool("record", false, "record interactions with other systems, for replay") 25 ) 26 27 func TestModuleInfo(t *testing.T) { 28 client, done := newReplayClient(t, *record) 29 defer done() 30 31 // Test names where we don't replay/record actual URLs. 32 skipReplayTests := map[string]bool{ 33 // On 5-Jan-2022, gitee.com took too long to respond, so it wasn't possible 34 // to record the results. 35 "gitee.com": true, 36 } 37 38 check := func(t *testing.T, msg, got, want string, skipReplay bool) { 39 t.Helper() 40 if got != want { 41 t.Fatalf("%s:\ngot %s\nwant %s", msg, got, want) 42 } 43 if !skipReplay { 44 res, err := client.Head(got) 45 if err != nil { 46 t.Fatalf("%s: %v", got, err) 47 } 48 defer res.Body.Close() 49 if res.StatusCode != 200 { 50 t.Fatalf("%s: %v", got, res.Status) 51 } 52 } 53 } 54 55 for _, test := range []struct { 56 desc string 57 modulePath, version, file string 58 wantRepo, wantModule, wantFile, wantLine, wantRaw string 59 }{ 60 { 61 "standard library", 62 "std", "v1.12.0", "bytes/buffer.go", 63 "https://cs.opensource.google/go/go", 64 "https://cs.opensource.google/go/go/+/go1.12:src", 65 "https://cs.opensource.google/go/go/+/go1.12:src/bytes/buffer.go", 66 "https://cs.opensource.google/go/go/+/go1.12:src/bytes/buffer.go;l=1", 67 // The raw URLs for the standard library are relative to the repo root, not 68 // the module directory. 69 "", 70 }, 71 { 72 "old standard library", 73 "std", "v1.3.0", "bytes/buffer.go", 74 "https://cs.opensource.google/go/go", 75 "https://cs.opensource.google/go/go/+/go1.3:src/pkg", 76 "https://cs.opensource.google/go/go/+/go1.3:src/pkg/bytes/buffer.go", 77 "https://cs.opensource.google/go/go/+/go1.3:src/pkg/bytes/buffer.go;l=1", 78 // The raw URLs for the standard library are relative to the repo root, not 79 // the module directory. 80 "", 81 }, 82 { 83 "github module at repo root", 84 "github.com/pkg/errors", "v0.8.1", "errors.go", 85 86 "https://github.com/pkg/errors", 87 "https://github.com/pkg/errors/tree/v0.8.1", 88 "https://github.com/pkg/errors/blob/v0.8.1/errors.go", 89 "https://github.com/pkg/errors/blob/v0.8.1/errors.go#L1", 90 "https://github.com/pkg/errors/raw/v0.8.1/errors.go", 91 }, 92 { 93 "github module not at repo root", 94 "github.com/hashicorp/consul/sdk", "v0.2.0", "freeport/freeport.go", 95 96 "https://github.com/hashicorp/consul", 97 "https://github.com/hashicorp/consul/tree/sdk/v0.2.0/sdk", 98 "https://github.com/hashicorp/consul/blob/sdk/v0.2.0/sdk/freeport/freeport.go", 99 "https://github.com/hashicorp/consul/blob/sdk/v0.2.0/sdk/freeport/freeport.go#L1", 100 "https://github.com/hashicorp/consul/raw/sdk/v0.2.0/sdk/freeport/freeport.go", 101 }, 102 { 103 "github module with VCS suffix", 104 "github.com/pkg/errors.git", "v0.8.1", "errors.go", 105 106 "https://github.com/pkg/errors", 107 "https://github.com/pkg/errors/tree/v0.8.1", 108 "https://github.com/pkg/errors/blob/v0.8.1/errors.go", 109 "https://github.com/pkg/errors/blob/v0.8.1/errors.go#L1", 110 "https://github.com/pkg/errors/raw/v0.8.1/errors.go", 111 }, 112 { 113 "bitbucket", 114 "bitbucket.org/plazzaro/kami", "v1.2.1", "defaults.go", 115 116 "https://bitbucket.org/plazzaro/kami", 117 "https://bitbucket.org/plazzaro/kami/src/v1.2.1", 118 "https://bitbucket.org/plazzaro/kami/src/v1.2.1/defaults.go", 119 "https://bitbucket.org/plazzaro/kami/src/v1.2.1/defaults.go#lines-1", 120 "https://bitbucket.org/plazzaro/kami/raw/v1.2.1/defaults.go", 121 }, 122 { 123 "incompatible", 124 "github.com/airbrake/gobrake", "v3.5.1+incompatible", "gobrake.go", 125 126 "https://github.com/airbrake/gobrake", 127 "https://github.com/airbrake/gobrake/tree/v3.5.1", 128 "https://github.com/airbrake/gobrake/blob/v3.5.1/gobrake.go", 129 "https://github.com/airbrake/gobrake/blob/v3.5.1/gobrake.go#L1", 130 "https://github.com/airbrake/gobrake/raw/v3.5.1/gobrake.go", 131 }, 132 { 133 "golang x-tools", 134 "golang.org/x/tools", "v0.0.0-20190927191325-030b2cf1153e", "README.md", 135 136 "https://cs.opensource.google/go/x/tools", 137 "https://cs.opensource.google/go/x/tools/+/030b2cf1:", 138 "https://cs.opensource.google/go/x/tools/+/030b2cf1:README.md", 139 "https://cs.opensource.google/go/x/tools/+/030b2cf1:README.md;l=1", 140 "https://github.com/golang/tools/raw/030b2cf1/README.md", 141 }, 142 { 143 "golang x-tools-gopls", 144 "golang.org/x/tools/gopls", "v0.4.0", "main.go", 145 146 "https://cs.opensource.google/go/x/tools", 147 "https://cs.opensource.google/go/x/tools/+/gopls/v0.4.0:gopls", 148 "https://cs.opensource.google/go/x/tools/+/gopls/v0.4.0:gopls/main.go", 149 "https://cs.opensource.google/go/x/tools/+/gopls/v0.4.0:gopls/main.go;l=1", 150 "https://github.com/golang/tools/raw/gopls/v0.4.0/gopls/main.go", 151 }, 152 { 153 "golang dl", 154 "golang.org/dl", "c5c89f6c", "go1.16/main.go", 155 156 "https://cs.opensource.google/go/dl", 157 "https://cs.opensource.google/go/dl/+/c5c89f6c:", 158 "https://cs.opensource.google/go/dl/+/c5c89f6c:go1.16/main.go", 159 "https://cs.opensource.google/go/dl/+/c5c89f6c:go1.16/main.go;l=1", 160 "https://github.com/golang/dl/raw/c5c89f6c/go1.16/main.go", 161 }, 162 { 163 "golang x-image", 164 "golang.org/x/image", "v0.0.0-20190910094157-69e4b8554b2a", "math/fixed/fixed.go", 165 166 "https://cs.opensource.google/go/x/image", 167 "https://cs.opensource.google/go/x/image/+/69e4b855:", 168 "https://cs.opensource.google/go/x/image/+/69e4b855:math/fixed/fixed.go", 169 "https://cs.opensource.google/go/x/image/+/69e4b855:math/fixed/fixed.go;l=1", 170 "https://github.com/golang/image/raw/69e4b855/math/fixed/fixed.go", 171 }, 172 { 173 "git.apache.org", 174 "git.apache.org/thrift.git", "v0.12.0", "lib/go/thrift/client.go", 175 176 "https://github.com/apache/thrift", 177 "https://github.com/apache/thrift/tree/v0.12.0", 178 "https://github.com/apache/thrift/blob/v0.12.0/lib/go/thrift/client.go", 179 "https://github.com/apache/thrift/blob/v0.12.0/lib/go/thrift/client.go#L1", 180 "https://github.com/apache/thrift/raw/v0.12.0/lib/go/thrift/client.go", 181 }, 182 { 183 "vanity for github", 184 "cloud.google.com/go/spanner", "v1.0.0", "doc.go", 185 186 "https://github.com/googleapis/google-cloud-go", 187 "https://github.com/googleapis/google-cloud-go/tree/spanner/v1.0.0/spanner", 188 "https://github.com/googleapis/google-cloud-go/blob/spanner/v1.0.0/spanner/doc.go", 189 "https://github.com/googleapis/google-cloud-go/blob/spanner/v1.0.0/spanner/doc.go#L1", 190 "https://github.com/googleapis/google-cloud-go/raw/spanner/v1.0.0/spanner/doc.go", 191 }, 192 { 193 "vanity for bitbucket", 194 "go.niquid.tech/civic-sip-api", "v0.2.0", "client.go", 195 196 "https://bitbucket.org/niquid/civic-sip-api.git", 197 "https://bitbucket.org/niquid/civic-sip-api.git/src/v0.2.0", 198 "https://bitbucket.org/niquid/civic-sip-api.git/src/v0.2.0/client.go", 199 "https://bitbucket.org/niquid/civic-sip-api.git/src/v0.2.0/client.go#lines-1", 200 "https://bitbucket.org/niquid/civic-sip-api.git/raw/v0.2.0/client.go", 201 }, 202 { 203 "vanity for googlesource.com", 204 "go.chromium.org/goma/server", "v0.0.23", "log/log.go", 205 206 "https://chromium.googlesource.com/infra/goma/server", 207 "https://chromium.googlesource.com/infra/goma/server/+/v0.0.23", 208 "https://chromium.googlesource.com/infra/goma/server/+/v0.0.23/log/log.go", 209 "https://chromium.googlesource.com/infra/goma/server/+/v0.0.23/log/log.go#1", 210 "", 211 }, 212 { 213 "gitlab.com", 214 "gitlab.com/tozd/go/errors", "v0.3.0", "errors.go", 215 216 "https://gitlab.com/tozd/go/errors", 217 "https://gitlab.com/tozd/go/errors/-/tree/v0.3.0", 218 "https://gitlab.com/tozd/go/errors/-/blob/v0.3.0/errors.go", 219 "https://gitlab.com/tozd/go/errors/-/blob/v0.3.0/errors.go#L1", 220 "https://gitlab.com/tozd/go/errors/-/raw/v0.3.0/errors.go", 221 }, 222 { 223 "other gitlab", 224 "gitlab.void-ptr.org/go/nu40c16", "v0.1.2", "nu40c16.go", 225 226 "https://gitlab.void-ptr.org/go/nu40c16", 227 "https://gitlab.void-ptr.org/go/nu40c16/-/tree/v0.1.2", 228 "https://gitlab.void-ptr.org/go/nu40c16/-/blob/v0.1.2/nu40c16.go", 229 "https://gitlab.void-ptr.org/go/nu40c16/-/blob/v0.1.2/nu40c16.go#L1", 230 "https://gitlab.void-ptr.org/go/nu40c16/-/raw/v0.1.2/nu40c16.go", 231 }, 232 { 233 "gitee.com", 234 "gitee.com/eden-framework/plugins", "v0.0.7", "file.go", 235 236 "https://gitee.com/eden-framework/plugins", 237 "https://gitee.com/eden-framework/plugins/tree/v0.0.7", 238 "https://gitee.com/eden-framework/plugins/blob/v0.0.7/file.go", 239 "https://gitee.com/eden-framework/plugins/blob/v0.0.7/file.go#L1", 240 "https://gitee.com/eden-framework/plugins/raw/v0.0.7/file.go", 241 }, 242 { 243 "sourcehut", 244 "gioui.org", "v0.0.0-20200726090130-3b95e2918359", "op/op.go", 245 246 "https://git.sr.ht/~eliasnaur/gio", 247 "https://git.sr.ht/~eliasnaur/gio/tree/3b95e2918359", 248 "https://git.sr.ht/~eliasnaur/gio/tree/3b95e2918359/op/op.go", 249 "https://git.sr.ht/~eliasnaur/gio/tree/3b95e2918359/op/op.go#L1", 250 "https://git.sr.ht/~eliasnaur/gio/blob/3b95e2918359/op/op.go", 251 }, 252 { 253 "sourcehut nested", 254 "gioui.org/app", "v0.0.0-20200726090130-3b95e2918359", "app.go", 255 256 "https://git.sr.ht/~eliasnaur/gio", 257 "https://git.sr.ht/~eliasnaur/gio/tree/3b95e2918359/app", 258 "https://git.sr.ht/~eliasnaur/gio/tree/3b95e2918359/app/app.go", 259 "https://git.sr.ht/~eliasnaur/gio/tree/3b95e2918359/app/app.go#L1", 260 "https://git.sr.ht/~eliasnaur/gio/blob/3b95e2918359/app/app.go", 261 }, 262 { 263 "git.fd.io tag", 264 "git.fd.io/govpp", "v0.3.5", "doc.go", 265 266 "https://git.fd.io/govpp", 267 "https://git.fd.io/govpp/tree/?h=v0.3.5", 268 "https://git.fd.io/govpp/tree/doc.go?h=v0.3.5", 269 "https://git.fd.io/govpp/tree/doc.go?h=v0.3.5#n1", 270 "https://git.fd.io/govpp/plain/doc.go?h=v0.3.5", 271 }, 272 { 273 "git.fd.io hash", 274 "git.fd.io/govpp", "v0.0.0-20200726090130-f04939006063", "doc.go", 275 276 "https://git.fd.io/govpp", 277 "https://git.fd.io/govpp/tree/?id=f04939006063", 278 "https://git.fd.io/govpp/tree/doc.go?id=f04939006063", 279 "https://git.fd.io/govpp/tree/doc.go?id=f04939006063#n1", 280 "https://git.fd.io/govpp/plain/doc.go?id=f04939006063", 281 }, 282 { 283 "gitea", 284 "gitea.com/chenli/reverse", "v0.1.2", "main.go", 285 286 "https://gitea.com/chenli/reverse", 287 "https://gitea.com/chenli/reverse/src/tag/v0.1.2", 288 "https://gitea.com/chenli/reverse/src/tag/v0.1.2/main.go", 289 "https://gitea.com/chenli/reverse/src/tag/v0.1.2/main.go#L1", 290 "https://gitea.com/chenli/reverse/raw/tag/v0.1.2/main.go", 291 }, 292 { 293 "gogs", 294 "gogs.buffalo-robot.com/zouhy/micro", "v0.4.2", "go.mod", 295 296 "https://gogs.buffalo-robot.com/zouhy/micro", 297 "https://gogs.buffalo-robot.com/zouhy/micro/src/v0.4.2", 298 "https://gogs.buffalo-robot.com/zouhy/micro/src/v0.4.2/go.mod", 299 "https://gogs.buffalo-robot.com/zouhy/micro/src/v0.4.2/go.mod#L1", 300 "https://gogs.buffalo-robot.com/zouhy/micro/raw/v0.4.2/go.mod", 301 }, 302 { 303 "v2 as a branch", 304 "github.com/jrick/wsrpc/v2", "v2.1.1", "rpc.go", 305 306 "https://github.com/jrick/wsrpc", 307 "https://github.com/jrick/wsrpc/tree/v2.1.1", 308 "https://github.com/jrick/wsrpc/blob/v2.1.1/rpc.go", 309 "https://github.com/jrick/wsrpc/blob/v2.1.1/rpc.go#L1", 310 "https://github.com/jrick/wsrpc/raw/v2.1.1/rpc.go", 311 }, 312 { 313 "v2 as subdirectory", 314 "github.com/gonutz/w32/v2", "v2.2.3", "com.go", 315 316 "https://github.com/gonutz/w32", 317 "https://github.com/gonutz/w32/tree/v2.2.3/v2", 318 "https://github.com/gonutz/w32/blob/v2.2.3/v2/com.go", 319 "https://github.com/gonutz/w32/blob/v2.2.3/v2/com.go#L1", 320 "https://github.com/gonutz/w32/raw/v2.2.3/v2/com.go", 321 }, 322 { 323 "gopkg.in, one element", 324 "gopkg.in/yaml.v2", "v2.2.2", "yaml.go", 325 326 "https://github.com/go-yaml/yaml", 327 "https://github.com/go-yaml/yaml/tree/v2.2.2", 328 "https://github.com/go-yaml/yaml/blob/v2.2.2/yaml.go", 329 "https://github.com/go-yaml/yaml/blob/v2.2.2/yaml.go#L1", 330 "https://github.com/go-yaml/yaml/raw/v2.2.2/yaml.go", 331 }, 332 { 333 "gopkg.in, two elements", 334 "gopkg.in/boltdb/bolt.v1", "v1.3.0", "doc.go", 335 336 "https://github.com/boltdb/bolt", 337 "https://github.com/boltdb/bolt/tree/v1.3.0", 338 "https://github.com/boltdb/bolt/blob/v1.3.0/doc.go", 339 "https://github.com/boltdb/bolt/blob/v1.3.0/doc.go#L1", 340 "https://github.com/boltdb/bolt/raw/v1.3.0/doc.go", 341 }, 342 { 343 "gonum.org", 344 "gonum.org/v1/gonum", "v0.6.1", "doc.go", 345 346 "https://github.com/gonum/gonum", 347 "https://github.com/gonum/gonum/tree/v0.6.1", 348 "https://github.com/gonum/gonum/blob/v0.6.1/doc.go", 349 "https://github.com/gonum/gonum/blob/v0.6.1/doc.go#L1", 350 "https://github.com/gonum/gonum/raw/v0.6.1/doc.go", 351 }, 352 { 353 "custom with gotools at repo root", 354 "dmitri.shuralyov.com/gpu/mtl", "v0.0.0-20191203043605-d42048ed14fd", "mtl.go", 355 356 "https://dmitri.shuralyov.com/gpu/mtl/...", 357 "https://gotools.org/dmitri.shuralyov.com/gpu/mtl?rev=d42048ed14fd", 358 "https://gotools.org/dmitri.shuralyov.com/gpu/mtl?rev=d42048ed14fd#mtl.go", 359 "https://gotools.org/dmitri.shuralyov.com/gpu/mtl?rev=d42048ed14fd#mtl.go-L1", 360 "", 361 }, 362 { 363 "custom with gotools in subdir", 364 "dmitri.shuralyov.com/gpu/mtl", "v0.0.0-20191203043605-d42048ed14fd", "example/movingtriangle/internal/coreanim/coreanim.go", 365 366 "https://dmitri.shuralyov.com/gpu/mtl/...", 367 "https://gotools.org/dmitri.shuralyov.com/gpu/mtl?rev=d42048ed14fd", 368 "https://gotools.org/dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/coreanim?rev=d42048ed14fd#coreanim.go", 369 "https://gotools.org/dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/coreanim?rev=d42048ed14fd#coreanim.go-L1", 370 "", 371 }, 372 { 373 "go-source templates match gitea with transform", 374 "opendev.org/airship/airshipctl", "v2.0.0-beta.1", "pkg/cluster/command.go", 375 "https://opendev.org/airship/airshipctl", 376 "https://opendev.org/airship/airshipctl/src/tag/v2.0.0-beta.1", 377 "https://opendev.org/airship/airshipctl/src/tag/v2.0.0-beta.1/pkg/cluster/command.go", 378 "https://opendev.org/airship/airshipctl/src/tag/v2.0.0-beta.1/pkg/cluster/command.go#L1", 379 "", 380 }, 381 { 382 "go-source templates match gitea without transform", 383 "git.borago.de/Marco/gqltest", "v0.0.18", "go.mod", 384 "https://git.borago.de/Marco/gqltest", 385 "https://git.borago.de/Marco/gqltest/src/v0.0.18", 386 "https://git.borago.de/Marco/gqltest/src/v0.0.18/go.mod", 387 "https://git.borago.de/Marco/gqltest/src/v0.0.18/go.mod#L1", 388 "https://git.borago.de/Marco/gqltest/raw/v0.0.18/go.mod", 389 }, 390 { 391 "go-source templates match gitlab2", 392 "git.pluggableideas.com/destrealm/3rdparty/go-yaml", "v2.2.6", "go.mod", 393 "https://git.pluggableideas.com/destrealm/3rdparty/go-yaml", 394 "https://git.pluggableideas.com/destrealm/3rdparty/go-yaml/-/tree/v2.2.6", 395 "https://git.pluggableideas.com/destrealm/3rdparty/go-yaml/-/blob/v2.2.6/go.mod", 396 "https://git.pluggableideas.com/destrealm/3rdparty/go-yaml/-/blob/v2.2.6/go.mod#L1", 397 "https://git.pluggableideas.com/destrealm/3rdparty/go-yaml/-/raw/v2.2.6/go.mod", 398 }, 399 { 400 "go-source templates match fdio", 401 "golang.zx2c4.com/wireguard/windows", "v0.3.4", "go.mod", 402 "https://git.zx2c4.com/wireguard-windows", 403 "https://git.zx2c4.com/wireguard-windows/tree/?h=v0.3.4", 404 "https://git.zx2c4.com/wireguard-windows/tree/go.mod?h=v0.3.4", 405 "https://git.zx2c4.com/wireguard-windows/tree/go.mod?h=v0.3.4#n1", 406 "https://git.zx2c4.com/wireguard-windows/plain/go.mod?h=v0.3.4", 407 }, 408 { 409 "go-source templates match blitiri.com.ar", 410 "blitiri.com.ar/go/log", "v1.1.0", "go.mod", 411 "https://blitiri.com.ar/git/r/log", 412 "https://blitiri.com.ar/git/r/log/b/master/t", 413 "https://blitiri.com.ar/git/r/log/b/master/t/f=go.mod.html", 414 "https://blitiri.com.ar/git/r/log/b/master/t/f=go.mod.html#line-1", 415 "", 416 }, 417 } { 418 t.Run(test.desc, func(t *testing.T) { 419 info, err := ModuleInfo(context.Background(), &Client{client}, test.modulePath, test.version) 420 if err != nil { 421 t.Fatal(err) 422 } 423 424 skip := skipReplayTests[test.desc] 425 check(t, "file", info.FileURL(test.file), test.wantFile, skip) 426 }) 427 } 428 } 429 430 func newReplayClient(t *testing.T, record bool) (*http.Client, func()) { 431 replayFilePath := filepath.Join("testdata", t.Name()+".replay") 432 if record { 433 httpreplay.DebugHeaders() 434 t.Logf("Recording into %s", replayFilePath) 435 if err := os.MkdirAll(filepath.Dir(replayFilePath), 0755); err != nil { 436 t.Fatal(err) 437 } 438 rec, err := httpreplay.NewRecorder(replayFilePath, nil) 439 if err != nil { 440 t.Fatal(err) 441 } 442 return rec.Client(), func() { 443 if err := rec.Close(); err != nil { 444 t.Fatal(err) 445 } 446 } 447 } else { 448 rep, err := httpreplay.NewReplayer(replayFilePath) 449 if err != nil { 450 t.Fatal(err) 451 } 452 return rep.Client(), func() { _ = rep.Close() } 453 } 454 } 455 456 func TestMatchStatic(t *testing.T) { 457 for _, test := range []struct { 458 in string 459 wantRepo, wantSuffix string 460 }{ 461 {"github.com/a/b", "github.com/a/b", ""}, 462 {"bitbucket.org/a/b", "bitbucket.org/a/b", ""}, 463 {"github.com/a/b/c/d", "github.com/a/b", "c/d"}, 464 {"bitbucket.org/a/b/c/d", "bitbucket.org/a/b", "c/d"}, 465 {"foo.googlesource.com/a/b/c", "foo.googlesource.com/a/b/c", ""}, 466 {"foo.googlesource.com/a/b/c.git", "foo.googlesource.com/a/b/c", ""}, 467 {"foo.googlesource.com/a/b/c.git/d", "foo.googlesource.com/a/b/c", "d"}, 468 {"git.com/repo.git", "git.com/repo", ""}, 469 {"git.com/repo.git/dir", "git.com/repo", "dir"}, 470 {"mercurial.com/repo.hg", "mercurial.com/repo", ""}, 471 {"mercurial.com/repo.hg/dir", "mercurial.com/repo", "dir"}, 472 } { 473 t.Run(test.in, func(t *testing.T) { 474 gotRepo, gotSuffix, _, _, err := matchStatic(test.in) 475 if err != nil { 476 t.Fatal(err) 477 } 478 if gotRepo != test.wantRepo || gotSuffix != test.wantSuffix { 479 t.Errorf("got %q, %q; want %q, %q", gotRepo, gotSuffix, test.wantRepo, test.wantSuffix) 480 } 481 }) 482 } 483 } 484 485 // This test adapted from gddo/gosrc/gosrc_test.go:TestGetDynamic. 486 func TestModuleInfoDynamic(t *testing.T) { 487 // For this test, fake the HTTP requests so we can cover cases that may not appear in the wild. 488 client := &Client{ 489 httpClient: &http.Client{ 490 Transport: testTransport(testWeb), 491 Timeout: testTimeout, 492 }, 493 } 494 // The version doesn't figure into the interesting work and we test versions to commits 495 // elsewhere, so use the same version throughout. 496 const version = "v1.2.3" 497 for _, test := range []struct { 498 modulePath string 499 want *Info // if nil, then want error 500 }{ 501 { 502 "alice.org/pkg", 503 &Info{ 504 repoURL: "https://github.com/alice/pkg", 505 moduleDir: "", 506 commit: "v1.2.3", 507 templates: githubURLTemplates, 508 }, 509 }, 510 { 511 "alice.org/pkg/sub", 512 &Info{ 513 repoURL: "https://github.com/alice/pkg", 514 moduleDir: "sub", 515 commit: "sub/v1.2.3", 516 templates: githubURLTemplates, 517 }, 518 }, 519 { 520 "alice.org/pkg/http", 521 &Info{ 522 repoURL: "https://github.com/alice/pkg", 523 moduleDir: "http", 524 commit: "http/v1.2.3", 525 templates: githubURLTemplates, 526 }, 527 }, 528 { 529 "alice.org/pkg/source", 530 // Has a go-source tag; we try to use the templates. 531 &Info{ 532 repoURL: "http://alice.org/pkg", 533 moduleDir: "source", 534 commit: "source/v1.2.3", 535 templates: urlTemplates{ 536 Repo: "http://alice.org/pkg", 537 Directory: "http://alice.org/pkg/{dir}", 538 File: "http://alice.org/pkg/{dir}?f={file}", 539 Line: "http://alice.org/pkg/{dir}?f={file}#Line{line}", 540 }, 541 }, 542 }, 543 544 { 545 "alice.org/pkg/ignore", 546 // Stop at the first go-source. 547 &Info{ 548 repoURL: "http://alice.org/pkg", 549 moduleDir: "ignore", 550 commit: "ignore/v1.2.3", 551 templates: urlTemplates{ 552 Repo: "http://alice.org/pkg", 553 Directory: "http://alice.org/pkg/{dir}", 554 File: "http://alice.org/pkg/{dir}?f={file}", 555 Line: "http://alice.org/pkg/{dir}?f={file}#Line{line}", 556 }, 557 }, 558 }, 559 {"alice.org/pkg/multiple", nil}, 560 {"alice.org/pkg/notfound", nil}, 561 { 562 "bob.com/pkg", 563 &Info{ 564 // The go-import tag's repo root ends in ".git", but according to the spec 565 // there should not be a .vcs suffix, so we include the ".git" in the repo URL. 566 repoURL: "https://vcs.net/bob/pkg", 567 moduleDir: "", 568 commit: "v1.2.3", 569 // empty templates 570 }, 571 }, 572 { 573 "bob.com/pkg/sub", 574 &Info{ 575 repoURL: "https://vcs.net/bob/pkg", 576 moduleDir: "sub", 577 commit: "sub/v1.2.3", 578 // empty templates 579 }, 580 }, 581 { 582 "azul3d.org/examples/abs", 583 // The go-source tag has a template that is handled incorrectly by godoc; but we 584 // ignore the templates. 585 &Info{ 586 repoURL: "https://github.com/azul3d/examples", 587 moduleDir: "abs", 588 commit: "abs/v1.2.3", 589 templates: githubURLTemplates, 590 }, 591 }, 592 { 593 "myitcv.io/blah2", 594 // Ignore the "mod" vcs type. 595 &Info{ 596 repoURL: "https://github.com/myitcv/x", 597 moduleDir: "", 598 commit: "v1.2.3", 599 templates: githubURLTemplates, 600 }, 601 }, 602 { 603 "alice.org/pkg/default", 604 &Info{ 605 repoURL: "https://github.com/alice/pkg", 606 moduleDir: "default", 607 commit: "default/v1.2.3", 608 templates: githubURLTemplates, 609 }, 610 }, 611 { 612 // Bad repo URLs. These are not escaped here, but they are whenever we render a template. 613 "bob.com/bad/github", 614 &Info{ 615 repoURL: `https://github.com/bob/bad/">$`, 616 moduleDir: "", 617 commit: "v1.2.3", 618 templates: githubURLTemplates, 619 }, 620 }, 621 { 622 623 "bob.com/bad/apache", 624 &Info{ 625 repoURL: "https://git.apache.org/>$", 626 moduleDir: "", 627 commit: "v1.2.3", 628 templates: githubURLTemplates, 629 }, 630 }, 631 } { 632 t.Run(test.modulePath, func(t *testing.T) { 633 got, err := moduleInfoDynamic(context.Background(), client, test.modulePath, version) 634 if err != nil { 635 if test.want == nil { 636 return 637 } 638 t.Fatal(err) 639 } 640 if diff := cmp.Diff(test.want, got, cmp.AllowUnexported(Info{}, urlTemplates{})); diff != "" { 641 t.Errorf("mismatch (-want +got):\n%s", diff) 642 } 643 }) 644 } 645 } 646 647 func TestRemoveVersionSuffix(t *testing.T) { 648 for _, test := range []struct { 649 in string 650 want string 651 }{ 652 {"", ""}, 653 {"v1", "v1"}, 654 {"v2", ""}, 655 {"v17", ""}, 656 {"foo/bar", "foo/bar"}, 657 {"foo/bar/v1", "foo/bar/v1"}, 658 {"foo/bar.v2", "foo/bar.v2"}, 659 {"foo/bar/v2", "foo/bar"}, 660 {"foo/bar/v17", "foo/bar"}, 661 } { 662 got := removeVersionSuffix(test.in) 663 if got != test.want { 664 t.Errorf("%q: got %q, want %q", test.in, got, test.want) 665 } 666 } 667 } 668 669 func TestAdjustVersionedModuleDirectory(t *testing.T) { 670 ctx := context.Background() 671 client := NewClient(testTimeout) 672 client.httpClient.Transport = testTransport(map[string]string{ 673 // Repo "branch" follows the "major branch" convention: versions 2 and higher 674 // live in the same directory as versions 0 and 1, but on a different branch (or tag). 675 "http://x.com/branch/v1.0.0/go.mod": "", // v1 module at the root 676 "http://x.com/branch/v2.0.0/go.mod": "", // v2 module at the root 677 "http://x.com/branch/dir/v1.0.0/dir/go.mod": "", // v1 module in a subdirectory 678 "http://x.com/branch/dir/v2.0.0/dir/go.mod": "", // v2 module in a subdirectory 679 // Repo "sub" follows the "major subdirectory" convention: versions 2 and higher 680 // live in a "vN" subdirectory. 681 "http://x.com/sub/v1.0.0/go.mod": "", // v1 module at the root 682 "http://x.com/sub/v2.0.0/v2/go.mod": "", // v2 module at root/v2. 683 "http://x.com/sub/dir/v1.0.0/dir/go.mod": "", // v1 module in a subdirectory 684 "http://x.com/sub/dir/v2.0.0/dir/v2/go.mod": "", // v2 module in subdirectory/v2 685 }) 686 687 for _, test := range []struct { 688 repo, moduleDir, commit string 689 want string 690 }{ 691 { 692 // module path is x.com/branch 693 "branch", "", "v1.0.0", 694 "", 695 }, 696 { 697 // module path is x.com/branch/v2; remove the "v2" to get the module dir 698 "branch", "v2", "v2.0.0", 699 "", 700 }, 701 { 702 // module path is x.com/branch/dir 703 "branch", "dir", "dir/v1.0.0", 704 "dir", 705 }, 706 { 707 // module path is x.com/branch/dir/v2; remove the v2 to get the module dir 708 "branch", "dir/v2", "dir/v2.0.0", 709 "dir", 710 }, 711 { 712 // module path is x.com/sub 713 "sub", "", "v1.0.0", 714 "", 715 }, 716 { 717 // module path is x.com/sub/v2; do not remove the v2 718 "sub", "v2", "v2.0.0", 719 "v2", 720 }, 721 { 722 // module path is x.com/sub/dir 723 "sub", "dir", "dir/v1.0.0", 724 "dir", 725 }, 726 { 727 // module path is x.com/sub/dir/v2; do not remove the v2 728 "sub", "dir/v2", "dir/v2.0.0", 729 "dir/v2", 730 }, 731 } { 732 t.Run(test.repo+","+test.moduleDir+","+test.commit, func(t *testing.T) { 733 info := &Info{ 734 repoURL: "http://x.com/" + test.repo, 735 moduleDir: test.moduleDir, 736 commit: test.commit, 737 templates: urlTemplates{File: "{repo}/{commit}/{file}"}, 738 } 739 adjustVersionedModuleDirectory(ctx, client, info) 740 got := info.moduleDir 741 if got != test.want { 742 t.Errorf("got %q, want %q", got, test.want) 743 } 744 }) 745 } 746 } 747 748 func TestCommitFromVersion(t *testing.T) { 749 for _, test := range []struct { 750 version, dir string 751 wantCommit string 752 wantIsHash bool 753 }{ 754 { 755 "v1.2.3", "", 756 "v1.2.3", false, 757 }, 758 { 759 "v1.2.3", "foo", 760 "foo/v1.2.3", false, 761 }, 762 { 763 "v1.2.3", "foo/v1", 764 "foo/v1/v1.2.3", // don't remove "/vN" if N = 1 765 false, 766 }, 767 { 768 "v1.2.3", "v1", // ditto 769 "v1/v1.2.3", false, 770 }, 771 { 772 "v3.1.0", "foo/v3", 773 "foo/v3.1.0", // do remove "/v2" and higher 774 false, 775 }, 776 { 777 "v3.1.0", "v3", 778 "v3.1.0", // ditto 779 false, 780 }, 781 { 782 "v6.1.1-0.20190615154606-3a9541ec9974", "", 783 "3a9541ec9974", true, 784 }, 785 { 786 "v6.1.1-0.20190615154606-3a9541ec9974", "foo", 787 "3a9541ec9974", true, 788 }, 789 } { 790 t.Run(fmt.Sprintf("%s,%s", test.version, test.dir), func(t *testing.T) { 791 check := func(v string) { 792 gotCommit, gotIsHash := commitFromVersion(v, test.dir) 793 if gotCommit != test.wantCommit { 794 t.Errorf("%s commit: got %s, want %s", v, gotCommit, test.wantCommit) 795 } 796 if gotIsHash != test.wantIsHash { 797 t.Errorf("%s isHash: got %t, want %t", v, gotIsHash, test.wantIsHash) 798 } 799 } 800 801 check(test.version) 802 // Adding "+incompatible" shouldn't make a difference. 803 check(test.version + "+incompatible") 804 }) 805 } 806 } 807 808 type testTransport map[string]string 809 810 func (t testTransport) RoundTrip(req *http.Request) (*http.Response, error) { 811 statusCode := http.StatusOK 812 req.URL.RawQuery = "" 813 body, ok := t[req.URL.String()] 814 if !ok { 815 statusCode = http.StatusNotFound 816 } 817 resp := &http.Response{ 818 StatusCode: statusCode, 819 Body: io.NopCloser(strings.NewReader(body)), 820 } 821 return resp, nil 822 } 823 824 var testWeb = map[string]string{ 825 // Package at root of a GitHub repo. 826 "https://alice.org/pkg": `<head> <meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg"></head>`, 827 // Package in sub-directory. 828 "https://alice.org/pkg/sub": `<head> <meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg"><body>`, 829 // Fallback to http. 830 "http://alice.org/pkg/http": `<head> <meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg">`, 831 // Meta tag in sub-directory does not match meta tag at root. 832 "https://alice.org/pkg/mismatch": `<head> <meta name="go-import" content="alice.org/pkg hg https://github.com/alice/pkg">`, 833 // More than one matching meta tag. 834 "http://alice.org/pkg/multiple": `<head> ` + 835 `<meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg">` + 836 `<meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg">`, 837 // Package with go-source meta tag. 838 "https://alice.org/pkg/source": `<head>` + 839 `<meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg">` + 840 `<meta name="go-source" content="alice.org/pkg http://alice.org/pkg http://alice.org/pkg{/dir} http://alice.org/pkg{/dir}?f={file}#Line{line}">`, 841 "https://alice.org/pkg/ignore": `<head>` + 842 `<title>Hello</title>` + 843 // Unknown meta name 844 `<meta name="go-junk" content="alice.org/pkg http://alice.org/pkg http://alice.org/pkg{/dir} http://alice.org/pkg{/dir}?f={file}#Line{line}">` + 845 // go-source before go-meta 846 `<meta name="go-source" content="alice.org/pkg http://alice.org/pkg http://alice.org/pkg{/dir} http://alice.org/pkg{/dir}?f={file}#Line{line}">` + 847 // go-import tag for the package 848 `<meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg">` + 849 // go-import with wrong number of fields 850 `<meta name="go-import" content="alice.org/pkg https://github.com/alice/pkg">` + 851 // go-import with no fields 852 `<meta name="go-import" content="">` + 853 // go-source with wrong number of fields 854 `<meta name="go-source" content="alice.org/pkg blah">` + 855 // meta tag for a different package 856 `<meta name="go-import" content="alice.org/other git https://github.com/alice/other">` + 857 // meta tag for a different package 858 `<meta name="go-import" content="alice.org/other git https://github.com/alice/other">` + 859 `</head>` + 860 // go-import outside of head 861 `<meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg">`, 862 863 // go-source repo defaults to go-import 864 "http://alice.org/pkg/default": `<head> 865 <meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg"> 866 <meta name="go-source" content="alice.org/pkg _ foo bar"> 867 </head>`, 868 // Package at root of a Git repo. 869 "https://bob.com/pkg": `<head> <meta name="go-import" content="bob.com/pkg git https://vcs.net/bob/pkg.git">`, 870 // Package at in sub-directory of a Git repo. 871 "https://bob.com/pkg/sub": `<head> <meta name="go-import" content="bob.com/pkg git https://vcs.net/bob/pkg.git">`, 872 "https://bob.com/bad/github": ` 873 <head><meta name="go-import" content="bob.com/bad/github git https://github.com/bob/bad/">$">`, 874 "https://bob.com/bad/apache": ` 875 <head><meta name="go-import" content="bob.com/bad/apache git https://git.apache.org/>$">`, 876 // Package with go-source meta tag, where {file} appears on the right of '#' in the file field URL template. 877 "https://azul3d.org/examples/abs": `<!DOCTYPE html><html><head>` + 878 `<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>` + 879 `<meta name="go-import" content="azul3d.org/examples git https://github.com/azul3d/examples">` + 880 `<meta name="go-source" content="azul3d.org/examples https://github.com/azul3d/examples https://gotools.org/azul3d.org/examples{/dir} https://gotools.org/azul3d.org/examples{/dir}#{file}-L{line}">` + 881 `<meta http-equiv="refresh" content="0; url=https://godoc.org/azul3d.org/examples/abs">` + 882 `</head>`, 883 884 // Multiple go-import meta tags; one of which is a vgo-special mod vcs type 885 "http://myitcv.io/blah2": `<!DOCTYPE html><html><head>` + 886 `<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>` + 887 `<meta name="go-import" content="myitcv.io/blah2 git https://github.com/myitcv/x">` + 888 `<meta name="go-import" content="myitcv.io/blah2 mod https://raw.githubusercontent.com/myitcv/pubx/master">` + 889 `</head>`, 890 } 891 892 func TestURLTemplates(t *testing.T) { 893 // Check that templates contain the right variables. 894 895 for _, p := range patterns { 896 if strings.Contains(p.pattern, "blitiri") { 897 continue 898 } 899 check := func(tmpl string, vars ...string) { 900 if tmpl == "" { 901 return 902 } 903 for _, v := range vars { 904 w := "{" + v + "}" 905 if !strings.Contains(tmpl, w) { 906 t.Errorf("in pattern %s, template %q is missing %s", p.pattern, tmpl, w) 907 } 908 } 909 } 910 911 check(p.templates.Directory, "commit") 912 check(p.templates.File, "commit") 913 check(p.templates.Line, "commit", "line") 914 check(p.templates.Raw, "commit", "file") 915 } 916 } 917 918 func TestMatchLegacyTemplates(t *testing.T) { 919 for _, test := range []struct { 920 sm sourceMeta 921 wantTemplates urlTemplates 922 wantTransformCommitNil bool 923 }{ 924 { 925 sm: sourceMeta{"", "", "", "https://git.blindage.org/21h/hcloud-dns/src/branch/master{/dir}/{file}#L{line}"}, 926 wantTemplates: giteaURLTemplates, 927 wantTransformCommitNil: false, 928 }, 929 { 930 sm: sourceMeta{"", "", "", "https://git.lastassault.de/sup/networkoverlap/-/blob/master{/dir}/{file}#L{line}"}, 931 wantTemplates: gitlabURLTemplates, 932 wantTransformCommitNil: true, 933 }, 934 { 935 sm: sourceMeta{"", "", "", "https://git.borago.de/Marco/gqltest/src/master{/dir}/{file}#L{line}"}, 936 wantTemplates: giteaURLTemplates, 937 wantTransformCommitNil: true, 938 }, 939 { 940 sm: sourceMeta{"", "", "", "https://git.zx2c4.com/wireguard-windows/tree{/dir}/{file}#n{line}"}, 941 wantTemplates: fdioURLTemplates, 942 wantTransformCommitNil: false, 943 }, 944 { 945 sm: sourceMeta{"", "", "unknown{/dir}", "unknown{/dir}/{file}#L{line}"}, 946 wantTemplates: urlTemplates{ 947 Repo: "", 948 Directory: "unknown/{dir}", 949 File: "unknown/{file}", 950 Line: "unknown/{file}#L{line}", 951 }, 952 wantTransformCommitNil: true, 953 }, 954 } { 955 gotTemplates, gotTransformCommit := matchLegacyTemplates(context.Background(), &test.sm) 956 gotNil := gotTransformCommit == nil 957 if gotTemplates != test.wantTemplates || gotNil != test.wantTransformCommitNil { 958 t.Errorf("%+v:\ngot (%+v, %t)\nwant (%+v, %t)", 959 test.sm, gotTemplates, gotNil, test.wantTemplates, test.wantTransformCommitNil) 960 } 961 } 962 }