| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package source |
| |
| import ( |
| "context" |
| "encoding/json" |
| "flag" |
| "fmt" |
| "io" |
| "net/http" |
| "os" |
| "path/filepath" |
| "strings" |
| "testing" |
| "time" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-replayers/httpreplay" |
| ) |
| |
| var ( |
| testTimeout = 2 * time.Second |
| record = flag.Bool("record", false, "record interactions with other systems, for replay") |
| ) |
| |
| func TestModuleInfo(t *testing.T) { |
| client, done := newReplayClient(t, *record) |
| defer done() |
| |
| // Test names where we don't replay/record actual URLs. |
| skipReplayTests := map[string]bool{ |
| // On 5-Jan-2022, gitee.com took too long to respond, so it wasn't possible |
| // to record the results. |
| "gitee.com": true, |
| } |
| |
| check := func(t *testing.T, msg, got, want string, skipReplay bool) { |
| t.Helper() |
| if got != want { |
| t.Fatalf("%s:\ngot %s\nwant %s", msg, got, want) |
| } |
| if !skipReplay { |
| res, err := client.Head(got) |
| if err != nil { |
| t.Fatalf("%s: %v", got, err) |
| } |
| defer res.Body.Close() |
| if res.StatusCode != 200 { |
| t.Fatalf("%s: %v", got, res.Status) |
| } |
| } |
| } |
| |
| for _, test := range []struct { |
| desc string |
| modulePath, version, file string |
| wantRepo, wantModule, wantFile, wantLine, wantRaw string |
| }{ |
| { |
| "standard library", |
| "std", "v1.12.0", "bytes/buffer.go", |
| "https://cs.opensource.google/go/go", |
| "https://cs.opensource.google/go/go/+/go1.12:src", |
| "https://cs.opensource.google/go/go/+/go1.12:src/bytes/buffer.go", |
| "https://cs.opensource.google/go/go/+/go1.12:src/bytes/buffer.go;l=1", |
| // The raw URLs for the standard library are relative to the repo root, not |
| // the module directory. |
| "", |
| }, |
| { |
| "old standard library", |
| "std", "v1.3.0", "bytes/buffer.go", |
| "https://cs.opensource.google/go/go", |
| "https://cs.opensource.google/go/go/+/go1.3:src/pkg", |
| "https://cs.opensource.google/go/go/+/go1.3:src/pkg/bytes/buffer.go", |
| "https://cs.opensource.google/go/go/+/go1.3:src/pkg/bytes/buffer.go;l=1", |
| // The raw URLs for the standard library are relative to the repo root, not |
| // the module directory. |
| "", |
| }, |
| { |
| "github module at repo root", |
| "github.com/pkg/errors", "v0.8.1", "errors.go", |
| |
| "https://github.com/pkg/errors", |
| "https://github.com/pkg/errors/tree/v0.8.1", |
| "https://github.com/pkg/errors/blob/v0.8.1/errors.go", |
| "https://github.com/pkg/errors/blob/v0.8.1/errors.go#L1", |
| "https://github.com/pkg/errors/raw/v0.8.1/errors.go", |
| }, |
| { |
| "github module not at repo root", |
| "github.com/hashicorp/consul/sdk", "v0.2.0", "freeport/freeport.go", |
| |
| "https://github.com/hashicorp/consul", |
| "https://github.com/hashicorp/consul/tree/sdk/v0.2.0/sdk", |
| "https://github.com/hashicorp/consul/blob/sdk/v0.2.0/sdk/freeport/freeport.go", |
| "https://github.com/hashicorp/consul/blob/sdk/v0.2.0/sdk/freeport/freeport.go#L1", |
| "https://github.com/hashicorp/consul/raw/sdk/v0.2.0/sdk/freeport/freeport.go", |
| }, |
| { |
| "github module with VCS suffix", |
| "github.com/pkg/errors.git", "v0.8.1", "errors.go", |
| |
| "https://github.com/pkg/errors", |
| "https://github.com/pkg/errors/tree/v0.8.1", |
| "https://github.com/pkg/errors/blob/v0.8.1/errors.go", |
| "https://github.com/pkg/errors/blob/v0.8.1/errors.go#L1", |
| "https://github.com/pkg/errors/raw/v0.8.1/errors.go", |
| }, |
| { |
| "bitbucket", |
| "bitbucket.org/plazzaro/kami", "v1.2.1", "defaults.go", |
| |
| "https://bitbucket.org/plazzaro/kami", |
| "https://bitbucket.org/plazzaro/kami/src/v1.2.1", |
| "https://bitbucket.org/plazzaro/kami/src/v1.2.1/defaults.go", |
| "https://bitbucket.org/plazzaro/kami/src/v1.2.1/defaults.go#lines-1", |
| "https://bitbucket.org/plazzaro/kami/raw/v1.2.1/defaults.go", |
| }, |
| { |
| "incompatible", |
| "github.com/airbrake/gobrake", "v3.5.1+incompatible", "gobrake.go", |
| |
| "https://github.com/airbrake/gobrake", |
| "https://github.com/airbrake/gobrake/tree/v3.5.1", |
| "https://github.com/airbrake/gobrake/blob/v3.5.1/gobrake.go", |
| "https://github.com/airbrake/gobrake/blob/v3.5.1/gobrake.go#L1", |
| "https://github.com/airbrake/gobrake/raw/v3.5.1/gobrake.go", |
| }, |
| { |
| "golang x-tools", |
| "golang.org/x/tools", "v0.0.0-20190927191325-030b2cf1153e", "README.md", |
| |
| "https://cs.opensource.google/go/x/tools", |
| "https://cs.opensource.google/go/x/tools/+/030b2cf1:", |
| "https://cs.opensource.google/go/x/tools/+/030b2cf1:README.md", |
| "https://cs.opensource.google/go/x/tools/+/030b2cf1:README.md;l=1", |
| "https://github.com/golang/tools/raw/030b2cf1/README.md", |
| }, |
| { |
| "golang x-tools-gopls", |
| "golang.org/x/tools/gopls", "v0.4.0", "main.go", |
| |
| "https://cs.opensource.google/go/x/tools", |
| "https://cs.opensource.google/go/x/tools/+/gopls/v0.4.0:gopls", |
| "https://cs.opensource.google/go/x/tools/+/gopls/v0.4.0:gopls/main.go", |
| "https://cs.opensource.google/go/x/tools/+/gopls/v0.4.0:gopls/main.go;l=1", |
| "https://github.com/golang/tools/raw/gopls/v0.4.0/gopls/main.go", |
| }, |
| { |
| "golang dl", |
| "golang.org/dl", "c5c89f6c", "go1.16/main.go", |
| |
| "https://cs.opensource.google/go/dl", |
| "https://cs.opensource.google/go/dl/+/c5c89f6c:", |
| "https://cs.opensource.google/go/dl/+/c5c89f6c:go1.16/main.go", |
| "https://cs.opensource.google/go/dl/+/c5c89f6c:go1.16/main.go;l=1", |
| "https://github.com/golang/dl/raw/c5c89f6c/go1.16/main.go", |
| }, |
| { |
| "golang x-image", |
| "golang.org/x/image", "v0.0.0-20190910094157-69e4b8554b2a", "math/fixed/fixed.go", |
| |
| "https://cs.opensource.google/go/x/image", |
| "https://cs.opensource.google/go/x/image/+/69e4b855:", |
| "https://cs.opensource.google/go/x/image/+/69e4b855:math/fixed/fixed.go", |
| "https://cs.opensource.google/go/x/image/+/69e4b855:math/fixed/fixed.go;l=1", |
| "https://github.com/golang/image/raw/69e4b855/math/fixed/fixed.go", |
| }, |
| { |
| "git.apache.org", |
| "git.apache.org/thrift.git", "v0.12.0", "lib/go/thrift/client.go", |
| |
| "https://github.com/apache/thrift", |
| "https://github.com/apache/thrift/tree/v0.12.0", |
| "https://github.com/apache/thrift/blob/v0.12.0/lib/go/thrift/client.go", |
| "https://github.com/apache/thrift/blob/v0.12.0/lib/go/thrift/client.go#L1", |
| "https://github.com/apache/thrift/raw/v0.12.0/lib/go/thrift/client.go", |
| }, |
| { |
| "vanity for github", |
| "cloud.google.com/go/spanner", "v1.0.0", "doc.go", |
| |
| "https://github.com/googleapis/google-cloud-go", |
| "https://github.com/googleapis/google-cloud-go/tree/spanner/v1.0.0/spanner", |
| "https://github.com/googleapis/google-cloud-go/blob/spanner/v1.0.0/spanner/doc.go", |
| "https://github.com/googleapis/google-cloud-go/blob/spanner/v1.0.0/spanner/doc.go#L1", |
| "https://github.com/googleapis/google-cloud-go/raw/spanner/v1.0.0/spanner/doc.go", |
| }, |
| { |
| "vanity for bitbucket", |
| "go.niquid.tech/civic-sip-api", "v0.2.0", "client.go", |
| |
| "https://bitbucket.org/niquid/civic-sip-api.git", |
| "https://bitbucket.org/niquid/civic-sip-api.git/src/v0.2.0", |
| "https://bitbucket.org/niquid/civic-sip-api.git/src/v0.2.0/client.go", |
| "https://bitbucket.org/niquid/civic-sip-api.git/src/v0.2.0/client.go#lines-1", |
| "https://bitbucket.org/niquid/civic-sip-api.git/raw/v0.2.0/client.go", |
| }, |
| { |
| "vanity for googlesource.com", |
| "go.chromium.org/goma/server", "v0.0.23", "log/log.go", |
| |
| "https://chromium.googlesource.com/infra/goma/server", |
| "https://chromium.googlesource.com/infra/goma/server/+/v0.0.23", |
| "https://chromium.googlesource.com/infra/goma/server/+/v0.0.23/log/log.go", |
| "https://chromium.googlesource.com/infra/goma/server/+/v0.0.23/log/log.go#1", |
| "", |
| }, |
| { |
| "gitlab.com", |
| "gitlab.com/tozd/go/errors", "v0.3.0", "errors.go", |
| |
| "https://gitlab.com/tozd/go/errors", |
| "https://gitlab.com/tozd/go/errors/-/tree/v0.3.0", |
| "https://gitlab.com/tozd/go/errors/-/blob/v0.3.0/errors.go", |
| "https://gitlab.com/tozd/go/errors/-/blob/v0.3.0/errors.go#L1", |
| "https://gitlab.com/tozd/go/errors/-/raw/v0.3.0/errors.go", |
| }, |
| { |
| "other gitlab", |
| "gitlab.void-ptr.org/go/nu40c16", "v0.1.2", "nu40c16.go", |
| |
| "https://gitlab.void-ptr.org/go/nu40c16", |
| "https://gitlab.void-ptr.org/go/nu40c16/-/tree/v0.1.2", |
| "https://gitlab.void-ptr.org/go/nu40c16/-/blob/v0.1.2/nu40c16.go", |
| "https://gitlab.void-ptr.org/go/nu40c16/-/blob/v0.1.2/nu40c16.go#L1", |
| "https://gitlab.void-ptr.org/go/nu40c16/-/raw/v0.1.2/nu40c16.go", |
| }, |
| { |
| "gitee.com", |
| "gitee.com/eden-framework/plugins", "v0.0.7", "file.go", |
| |
| "https://gitee.com/eden-framework/plugins", |
| "https://gitee.com/eden-framework/plugins/tree/v0.0.7", |
| "https://gitee.com/eden-framework/plugins/blob/v0.0.7/file.go", |
| "https://gitee.com/eden-framework/plugins/blob/v0.0.7/file.go#L1", |
| "https://gitee.com/eden-framework/plugins/raw/v0.0.7/file.go", |
| }, |
| { |
| "sourcehut", |
| "gioui.org", "v0.0.0-20200726090130-3b95e2918359", "op/op.go", |
| |
| "https://git.sr.ht/~eliasnaur/gio", |
| "https://git.sr.ht/~eliasnaur/gio/tree/3b95e2918359", |
| "https://git.sr.ht/~eliasnaur/gio/tree/3b95e2918359/op/op.go", |
| "https://git.sr.ht/~eliasnaur/gio/tree/3b95e2918359/op/op.go#L1", |
| "https://git.sr.ht/~eliasnaur/gio/blob/3b95e2918359/op/op.go", |
| }, |
| { |
| "sourcehut nested", |
| "gioui.org/app", "v0.0.0-20200726090130-3b95e2918359", "app.go", |
| |
| "https://git.sr.ht/~eliasnaur/gio", |
| "https://git.sr.ht/~eliasnaur/gio/tree/3b95e2918359/app", |
| "https://git.sr.ht/~eliasnaur/gio/tree/3b95e2918359/app/app.go", |
| "https://git.sr.ht/~eliasnaur/gio/tree/3b95e2918359/app/app.go#L1", |
| "https://git.sr.ht/~eliasnaur/gio/blob/3b95e2918359/app/app.go", |
| }, |
| { |
| "git.fd.io tag", |
| "git.fd.io/govpp", "v0.3.5", "doc.go", |
| |
| "https://git.fd.io/govpp", |
| "https://git.fd.io/govpp/tree/?h=v0.3.5", |
| "https://git.fd.io/govpp/tree/doc.go?h=v0.3.5", |
| "https://git.fd.io/govpp/tree/doc.go?h=v0.3.5#n1", |
| "https://git.fd.io/govpp/plain/doc.go?h=v0.3.5", |
| }, |
| { |
| "git.fd.io hash", |
| "git.fd.io/govpp", "v0.0.0-20200726090130-f04939006063", "doc.go", |
| |
| "https://git.fd.io/govpp", |
| "https://git.fd.io/govpp/tree/?id=f04939006063", |
| "https://git.fd.io/govpp/tree/doc.go?id=f04939006063", |
| "https://git.fd.io/govpp/tree/doc.go?id=f04939006063#n1", |
| "https://git.fd.io/govpp/plain/doc.go?id=f04939006063", |
| }, |
| { |
| "gitea", |
| "gitea.com/chenli/reverse", "v0.1.2", "main.go", |
| |
| "https://gitea.com/chenli/reverse", |
| "https://gitea.com/chenli/reverse/src/tag/v0.1.2", |
| "https://gitea.com/chenli/reverse/src/tag/v0.1.2/main.go", |
| "https://gitea.com/chenli/reverse/src/tag/v0.1.2/main.go#L1", |
| "https://gitea.com/chenli/reverse/raw/tag/v0.1.2/main.go", |
| }, |
| { |
| "gogs", |
| "gogs.buffalo-robot.com/zouhy/micro", "v0.4.2", "go.mod", |
| |
| "https://gogs.buffalo-robot.com/zouhy/micro", |
| "https://gogs.buffalo-robot.com/zouhy/micro/src/v0.4.2", |
| "https://gogs.buffalo-robot.com/zouhy/micro/src/v0.4.2/go.mod", |
| "https://gogs.buffalo-robot.com/zouhy/micro/src/v0.4.2/go.mod#L1", |
| "https://gogs.buffalo-robot.com/zouhy/micro/raw/v0.4.2/go.mod", |
| }, |
| { |
| "v2 as a branch", |
| "github.com/jrick/wsrpc/v2", "v2.1.1", "rpc.go", |
| |
| "https://github.com/jrick/wsrpc", |
| "https://github.com/jrick/wsrpc/tree/v2.1.1", |
| "https://github.com/jrick/wsrpc/blob/v2.1.1/rpc.go", |
| "https://github.com/jrick/wsrpc/blob/v2.1.1/rpc.go#L1", |
| "https://github.com/jrick/wsrpc/raw/v2.1.1/rpc.go", |
| }, |
| { |
| "v2 as subdirectory", |
| "github.com/gonutz/w32/v2", "v2.2.3", "com.go", |
| |
| "https://github.com/gonutz/w32", |
| "https://github.com/gonutz/w32/tree/v2.2.3/v2", |
| "https://github.com/gonutz/w32/blob/v2.2.3/v2/com.go", |
| "https://github.com/gonutz/w32/blob/v2.2.3/v2/com.go#L1", |
| "https://github.com/gonutz/w32/raw/v2.2.3/v2/com.go", |
| }, |
| { |
| "gopkg.in, one element", |
| "gopkg.in/yaml.v2", "v2.2.2", "yaml.go", |
| |
| "https://github.com/go-yaml/yaml", |
| "https://github.com/go-yaml/yaml/tree/v2.2.2", |
| "https://github.com/go-yaml/yaml/blob/v2.2.2/yaml.go", |
| "https://github.com/go-yaml/yaml/blob/v2.2.2/yaml.go#L1", |
| "https://github.com/go-yaml/yaml/raw/v2.2.2/yaml.go", |
| }, |
| { |
| "gopkg.in, two elements", |
| "gopkg.in/boltdb/bolt.v1", "v1.3.0", "doc.go", |
| |
| "https://github.com/boltdb/bolt", |
| "https://github.com/boltdb/bolt/tree/v1.3.0", |
| "https://github.com/boltdb/bolt/blob/v1.3.0/doc.go", |
| "https://github.com/boltdb/bolt/blob/v1.3.0/doc.go#L1", |
| "https://github.com/boltdb/bolt/raw/v1.3.0/doc.go", |
| }, |
| { |
| "gonum.org", |
| "gonum.org/v1/gonum", "v0.6.1", "doc.go", |
| |
| "https://github.com/gonum/gonum", |
| "https://github.com/gonum/gonum/tree/v0.6.1", |
| "https://github.com/gonum/gonum/blob/v0.6.1/doc.go", |
| "https://github.com/gonum/gonum/blob/v0.6.1/doc.go#L1", |
| "https://github.com/gonum/gonum/raw/v0.6.1/doc.go", |
| }, |
| { |
| "custom with gotools at repo root", |
| "dmitri.shuralyov.com/gpu/mtl", "v0.0.0-20191203043605-d42048ed14fd", "mtl.go", |
| |
| "https://dmitri.shuralyov.com/gpu/mtl/...", |
| "https://gotools.org/dmitri.shuralyov.com/gpu/mtl?rev=d42048ed14fd", |
| "https://gotools.org/dmitri.shuralyov.com/gpu/mtl?rev=d42048ed14fd#mtl.go", |
| "https://gotools.org/dmitri.shuralyov.com/gpu/mtl?rev=d42048ed14fd#mtl.go-L1", |
| "", |
| }, |
| { |
| "custom with gotools in subdir", |
| "dmitri.shuralyov.com/gpu/mtl", "v0.0.0-20191203043605-d42048ed14fd", "example/movingtriangle/internal/coreanim/coreanim.go", |
| |
| "https://dmitri.shuralyov.com/gpu/mtl/...", |
| "https://gotools.org/dmitri.shuralyov.com/gpu/mtl?rev=d42048ed14fd", |
| "https://gotools.org/dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/coreanim?rev=d42048ed14fd#coreanim.go", |
| "https://gotools.org/dmitri.shuralyov.com/gpu/mtl/example/movingtriangle/internal/coreanim?rev=d42048ed14fd#coreanim.go-L1", |
| "", |
| }, |
| { |
| "go-source templates match gitea with transform", |
| "opendev.org/airship/airshipctl", "v2.0.0-beta.1", "pkg/cluster/command.go", |
| "https://opendev.org/airship/airshipctl", |
| "https://opendev.org/airship/airshipctl/src/tag/v2.0.0-beta.1", |
| "https://opendev.org/airship/airshipctl/src/tag/v2.0.0-beta.1/pkg/cluster/command.go", |
| "https://opendev.org/airship/airshipctl/src/tag/v2.0.0-beta.1/pkg/cluster/command.go#L1", |
| "", |
| }, |
| { |
| "go-source templates match gitea without transform", |
| "git.borago.de/Marco/gqltest", "v0.0.18", "go.mod", |
| "https://git.borago.de/Marco/gqltest", |
| "https://git.borago.de/Marco/gqltest/src/v0.0.18", |
| "https://git.borago.de/Marco/gqltest/src/v0.0.18/go.mod", |
| "https://git.borago.de/Marco/gqltest/src/v0.0.18/go.mod#L1", |
| "https://git.borago.de/Marco/gqltest/raw/v0.0.18/go.mod", |
| }, |
| { |
| "go-source templates match gitlab2", |
| "git.pluggableideas.com/destrealm/3rdparty/go-yaml", "v2.2.6", "go.mod", |
| "https://git.pluggableideas.com/destrealm/3rdparty/go-yaml", |
| "https://git.pluggableideas.com/destrealm/3rdparty/go-yaml/-/tree/v2.2.6", |
| "https://git.pluggableideas.com/destrealm/3rdparty/go-yaml/-/blob/v2.2.6/go.mod", |
| "https://git.pluggableideas.com/destrealm/3rdparty/go-yaml/-/blob/v2.2.6/go.mod#L1", |
| "https://git.pluggableideas.com/destrealm/3rdparty/go-yaml/-/raw/v2.2.6/go.mod", |
| }, |
| { |
| "go-source templates match fdio", |
| "golang.zx2c4.com/wireguard/windows", "v0.3.4", "go.mod", |
| "https://git.zx2c4.com/wireguard-windows", |
| "https://git.zx2c4.com/wireguard-windows/tree/?h=v0.3.4", |
| "https://git.zx2c4.com/wireguard-windows/tree/go.mod?h=v0.3.4", |
| "https://git.zx2c4.com/wireguard-windows/tree/go.mod?h=v0.3.4#n1", |
| "https://git.zx2c4.com/wireguard-windows/plain/go.mod?h=v0.3.4", |
| }, |
| { |
| "go-source templates match blitiri.com.ar", |
| "blitiri.com.ar/go/log", "v1.1.0", "go.mod", |
| "https://blitiri.com.ar/git/r/log", |
| "https://blitiri.com.ar/git/r/log/b/master/t", |
| "https://blitiri.com.ar/git/r/log/b/master/t/f=go.mod.html", |
| "https://blitiri.com.ar/git/r/log/b/master/t/f=go.mod.html#line-1", |
| "", |
| }, |
| } { |
| t.Run(test.desc, func(t *testing.T) { |
| info, err := ModuleInfo(context.Background(), &Client{client}, test.modulePath, test.version) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| skip := skipReplayTests[test.desc] |
| check(t, "repo", info.RepoURL(), test.wantRepo, skip) |
| check(t, "module", info.ModuleURL(), test.wantModule, skip) |
| check(t, "file", info.FileURL(test.file), test.wantFile, skip) |
| check(t, "line", info.LineURL(test.file, 1), test.wantLine, skip) |
| if test.wantRaw != "" { |
| check(t, "raw", info.RawURL(test.file), test.wantRaw, skip) |
| } |
| }) |
| } |
| |
| t.Run("stdlib-raw", func(t *testing.T) { |
| // Test raw URLs from the standard library, which are a special case. |
| info, err := ModuleInfo(context.Background(), &Client{client}, "std", "v1.13.3") |
| if err != nil { |
| t.Fatal(err) |
| } |
| const ( |
| file = "doc/gopher/fiveyears.jpg" |
| want = "https://github.com/golang/go/raw/go1.13.3/doc/gopher/fiveyears.jpg" |
| ) |
| check(t, "raw", info.RawURL(file), want, false) |
| }) |
| } |
| |
| func newReplayClient(t *testing.T, record bool) (*http.Client, func()) { |
| replayFilePath := filepath.Join("testdata", t.Name()+".replay") |
| if record { |
| httpreplay.DebugHeaders() |
| t.Logf("Recording into %s", replayFilePath) |
| if err := os.MkdirAll(filepath.Dir(replayFilePath), 0755); err != nil { |
| t.Fatal(err) |
| } |
| rec, err := httpreplay.NewRecorder(replayFilePath, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return rec.Client(), func() { |
| if err := rec.Close(); err != nil { |
| t.Fatal(err) |
| } |
| } |
| } else { |
| rep, err := httpreplay.NewReplayer(replayFilePath) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return rep.Client(), func() { _ = rep.Close() } |
| } |
| } |
| |
| func TestMatchStatic(t *testing.T) { |
| for _, test := range []struct { |
| in string |
| wantRepo, wantSuffix string |
| }{ |
| {"github.com/a/b", "github.com/a/b", ""}, |
| {"bitbucket.org/a/b", "bitbucket.org/a/b", ""}, |
| {"github.com/a/b/c/d", "github.com/a/b", "c/d"}, |
| {"bitbucket.org/a/b/c/d", "bitbucket.org/a/b", "c/d"}, |
| {"foo.googlesource.com/a/b/c", "foo.googlesource.com/a/b/c", ""}, |
| {"foo.googlesource.com/a/b/c.git", "foo.googlesource.com/a/b/c", ""}, |
| {"foo.googlesource.com/a/b/c.git/d", "foo.googlesource.com/a/b/c", "d"}, |
| {"git.com/repo.git", "git.com/repo", ""}, |
| {"git.com/repo.git/dir", "git.com/repo", "dir"}, |
| {"mercurial.com/repo.hg", "mercurial.com/repo", ""}, |
| {"mercurial.com/repo.hg/dir", "mercurial.com/repo", "dir"}, |
| } { |
| t.Run(test.in, func(t *testing.T) { |
| gotRepo, gotSuffix, _, _, err := matchStatic(test.in) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if gotRepo != test.wantRepo || gotSuffix != test.wantSuffix { |
| t.Errorf("got %q, %q; want %q, %q", gotRepo, gotSuffix, test.wantRepo, test.wantSuffix) |
| } |
| }) |
| } |
| } |
| |
| // This test adapted from gddo/gosrc/gosrc_test.go:TestGetDynamic. |
| func TestModuleInfoDynamic(t *testing.T) { |
| // For this test, fake the HTTP requests so we can cover cases that may not appear in the wild. |
| client := &Client{ |
| httpClient: &http.Client{ |
| Transport: testTransport(testWeb), |
| Timeout: testTimeout, |
| }, |
| } |
| // The version doesn't figure into the interesting work and we test versions to commits |
| // elsewhere, so use the same version throughout. |
| const version = "v1.2.3" |
| for _, test := range []struct { |
| modulePath string |
| want *Info // if nil, then want error |
| }{ |
| { |
| "alice.org/pkg", |
| &Info{ |
| repoURL: "https://github.com/alice/pkg", |
| moduleDir: "", |
| commit: "v1.2.3", |
| templates: githubURLTemplates, |
| }, |
| }, |
| { |
| "alice.org/pkg/sub", |
| &Info{ |
| repoURL: "https://github.com/alice/pkg", |
| moduleDir: "sub", |
| commit: "sub/v1.2.3", |
| templates: githubURLTemplates, |
| }, |
| }, |
| { |
| "alice.org/pkg/http", |
| &Info{ |
| repoURL: "https://github.com/alice/pkg", |
| moduleDir: "http", |
| commit: "http/v1.2.3", |
| templates: githubURLTemplates, |
| }, |
| }, |
| { |
| "alice.org/pkg/source", |
| // Has a go-source tag; we try to use the templates. |
| &Info{ |
| repoURL: "http://alice.org/pkg", |
| moduleDir: "source", |
| commit: "source/v1.2.3", |
| templates: urlTemplates{ |
| Repo: "http://alice.org/pkg", |
| Directory: "http://alice.org/pkg/{dir}", |
| File: "http://alice.org/pkg/{dir}?f={file}", |
| Line: "http://alice.org/pkg/{dir}?f={file}#Line{line}", |
| }, |
| }, |
| }, |
| |
| { |
| "alice.org/pkg/ignore", |
| // Stop at the first go-source. |
| &Info{ |
| repoURL: "http://alice.org/pkg", |
| moduleDir: "ignore", |
| commit: "ignore/v1.2.3", |
| templates: urlTemplates{ |
| Repo: "http://alice.org/pkg", |
| Directory: "http://alice.org/pkg/{dir}", |
| File: "http://alice.org/pkg/{dir}?f={file}", |
| Line: "http://alice.org/pkg/{dir}?f={file}#Line{line}", |
| }, |
| }, |
| }, |
| {"alice.org/pkg/multiple", nil}, |
| {"alice.org/pkg/notfound", nil}, |
| { |
| "bob.com/pkg", |
| &Info{ |
| // The go-import tag's repo root ends in ".git", but according to the spec |
| // there should not be a .vcs suffix, so we include the ".git" in the repo URL. |
| repoURL: "https://vcs.net/bob/pkg", |
| moduleDir: "", |
| commit: "v1.2.3", |
| // empty templates |
| }, |
| }, |
| { |
| "bob.com/pkg/sub", |
| &Info{ |
| repoURL: "https://vcs.net/bob/pkg", |
| moduleDir: "sub", |
| commit: "sub/v1.2.3", |
| // empty templates |
| }, |
| }, |
| { |
| "azul3d.org/examples/abs", |
| // The go-source tag has a template that is handled incorrectly by godoc; but we |
| // ignore the templates. |
| &Info{ |
| repoURL: "https://github.com/azul3d/examples", |
| moduleDir: "abs", |
| commit: "abs/v1.2.3", |
| templates: githubURLTemplates, |
| }, |
| }, |
| { |
| "myitcv.io/blah2", |
| // Ignore the "mod" vcs type. |
| &Info{ |
| repoURL: "https://github.com/myitcv/x", |
| moduleDir: "", |
| commit: "v1.2.3", |
| templates: githubURLTemplates, |
| }, |
| }, |
| { |
| "alice.org/pkg/default", |
| &Info{ |
| repoURL: "https://github.com/alice/pkg", |
| moduleDir: "default", |
| commit: "default/v1.2.3", |
| templates: githubURLTemplates, |
| }, |
| }, |
| { |
| // Bad repo URLs. These are not escaped here, but they are whenever we render a template. |
| "bob.com/bad/github", |
| &Info{ |
| repoURL: `https://github.com/bob/bad/">$`, |
| moduleDir: "", |
| commit: "v1.2.3", |
| templates: githubURLTemplates, |
| }, |
| }, |
| { |
| |
| "bob.com/bad/apache", |
| &Info{ |
| repoURL: "https://git.apache.org/>$", |
| moduleDir: "", |
| commit: "v1.2.3", |
| templates: githubURLTemplates, |
| }, |
| }, |
| } { |
| t.Run(test.modulePath, func(t *testing.T) { |
| got, err := moduleInfoDynamic(context.Background(), client, test.modulePath, version) |
| if err != nil { |
| if test.want == nil { |
| return |
| } |
| t.Fatal(err) |
| } |
| if diff := cmp.Diff(test.want, got, cmp.AllowUnexported(Info{}, urlTemplates{})); diff != "" { |
| t.Errorf("mismatch (-want +got):\n%s", diff) |
| } |
| }) |
| } |
| } |
| |
| func TestRemoveVersionSuffix(t *testing.T) { |
| for _, test := range []struct { |
| in string |
| want string |
| }{ |
| {"", ""}, |
| {"v1", "v1"}, |
| {"v2", ""}, |
| {"v17", ""}, |
| {"foo/bar", "foo/bar"}, |
| {"foo/bar/v1", "foo/bar/v1"}, |
| {"foo/bar.v2", "foo/bar.v2"}, |
| {"foo/bar/v2", "foo/bar"}, |
| {"foo/bar/v17", "foo/bar"}, |
| } { |
| got := removeVersionSuffix(test.in) |
| if got != test.want { |
| t.Errorf("%q: got %q, want %q", test.in, got, test.want) |
| } |
| } |
| } |
| |
| func TestAdjustVersionedModuleDirectory(t *testing.T) { |
| ctx := context.Background() |
| client := NewClient(testTimeout) |
| client.httpClient.Transport = testTransport(map[string]string{ |
| // Repo "branch" follows the "major branch" convention: versions 2 and higher |
| // live in the same directory as versions 0 and 1, but on a different branch (or tag). |
| "http://x.com/branch/v1.0.0/go.mod": "", // v1 module at the root |
| "http://x.com/branch/v2.0.0/go.mod": "", // v2 module at the root |
| "http://x.com/branch/dir/v1.0.0/dir/go.mod": "", // v1 module in a subdirectory |
| "http://x.com/branch/dir/v2.0.0/dir/go.mod": "", // v2 module in a subdirectory |
| // Repo "sub" follows the "major subdirectory" convention: versions 2 and higher |
| // live in a "vN" subdirectory. |
| "http://x.com/sub/v1.0.0/go.mod": "", // v1 module at the root |
| "http://x.com/sub/v2.0.0/v2/go.mod": "", // v2 module at root/v2. |
| "http://x.com/sub/dir/v1.0.0/dir/go.mod": "", // v1 module in a subdirectory |
| "http://x.com/sub/dir/v2.0.0/dir/v2/go.mod": "", // v2 module in subdirectory/v2 |
| }) |
| |
| for _, test := range []struct { |
| repo, moduleDir, commit string |
| want string |
| }{ |
| { |
| // module path is x.com/branch |
| "branch", "", "v1.0.0", |
| "", |
| }, |
| { |
| // module path is x.com/branch/v2; remove the "v2" to get the module dir |
| "branch", "v2", "v2.0.0", |
| "", |
| }, |
| { |
| // module path is x.com/branch/dir |
| "branch", "dir", "dir/v1.0.0", |
| "dir", |
| }, |
| { |
| // module path is x.com/branch/dir/v2; remove the v2 to get the module dir |
| "branch", "dir/v2", "dir/v2.0.0", |
| "dir", |
| }, |
| { |
| // module path is x.com/sub |
| "sub", "", "v1.0.0", |
| "", |
| }, |
| { |
| // module path is x.com/sub/v2; do not remove the v2 |
| "sub", "v2", "v2.0.0", |
| "v2", |
| }, |
| { |
| // module path is x.com/sub/dir |
| "sub", "dir", "dir/v1.0.0", |
| "dir", |
| }, |
| { |
| // module path is x.com/sub/dir/v2; do not remove the v2 |
| "sub", "dir/v2", "dir/v2.0.0", |
| "dir/v2", |
| }, |
| } { |
| t.Run(test.repo+","+test.moduleDir+","+test.commit, func(t *testing.T) { |
| info := &Info{ |
| repoURL: "http://x.com/" + test.repo, |
| moduleDir: test.moduleDir, |
| commit: test.commit, |
| templates: urlTemplates{File: "{repo}/{commit}/{file}"}, |
| } |
| adjustVersionedModuleDirectory(ctx, client, info) |
| got := info.moduleDir |
| if got != test.want { |
| t.Errorf("got %q, want %q", got, test.want) |
| } |
| }) |
| } |
| } |
| |
| func TestCommitFromVersion(t *testing.T) { |
| for _, test := range []struct { |
| version, dir string |
| wantCommit string |
| wantIsHash bool |
| }{ |
| { |
| "v1.2.3", "", |
| "v1.2.3", false, |
| }, |
| { |
| "v1.2.3", "foo", |
| "foo/v1.2.3", false, |
| }, |
| { |
| "v1.2.3", "foo/v1", |
| "foo/v1/v1.2.3", // don't remove "/vN" if N = 1 |
| false, |
| }, |
| { |
| "v1.2.3", "v1", // ditto |
| "v1/v1.2.3", false, |
| }, |
| { |
| "v3.1.0", "foo/v3", |
| "foo/v3.1.0", // do remove "/v2" and higher |
| false, |
| }, |
| { |
| "v3.1.0", "v3", |
| "v3.1.0", // ditto |
| false, |
| }, |
| { |
| "v6.1.1-0.20190615154606-3a9541ec9974", "", |
| "3a9541ec9974", true, |
| }, |
| { |
| "v6.1.1-0.20190615154606-3a9541ec9974", "foo", |
| "3a9541ec9974", true, |
| }, |
| } { |
| t.Run(fmt.Sprintf("%s,%s", test.version, test.dir), func(t *testing.T) { |
| check := func(v string) { |
| gotCommit, gotIsHash := commitFromVersion(v, test.dir) |
| if gotCommit != test.wantCommit { |
| t.Errorf("%s commit: got %s, want %s", v, gotCommit, test.wantCommit) |
| } |
| if gotIsHash != test.wantIsHash { |
| t.Errorf("%s isHash: got %t, want %t", v, gotIsHash, test.wantIsHash) |
| } |
| } |
| |
| check(test.version) |
| // Adding "+incompatible" shouldn't make a difference. |
| check(test.version + "+incompatible") |
| }) |
| } |
| } |
| |
| type testTransport map[string]string |
| |
| func (t testTransport) RoundTrip(req *http.Request) (*http.Response, error) { |
| statusCode := http.StatusOK |
| req.URL.RawQuery = "" |
| body, ok := t[req.URL.String()] |
| if !ok { |
| statusCode = http.StatusNotFound |
| } |
| resp := &http.Response{ |
| StatusCode: statusCode, |
| Body: io.NopCloser(strings.NewReader(body)), |
| } |
| return resp, nil |
| } |
| |
| var testWeb = map[string]string{ |
| // Package at root of a GitHub repo. |
| "https://alice.org/pkg": `<head> <meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg"></head>`, |
| // Package in sub-directory. |
| "https://alice.org/pkg/sub": `<head> <meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg"><body>`, |
| // Fallback to http. |
| "http://alice.org/pkg/http": `<head> <meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg">`, |
| // Meta tag in sub-directory does not match meta tag at root. |
| "https://alice.org/pkg/mismatch": `<head> <meta name="go-import" content="alice.org/pkg hg https://github.com/alice/pkg">`, |
| // More than one matching meta tag. |
| "http://alice.org/pkg/multiple": `<head> ` + |
| `<meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg">` + |
| `<meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg">`, |
| // Package with go-source meta tag. |
| "https://alice.org/pkg/source": `<head>` + |
| `<meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg">` + |
| `<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}">`, |
| "https://alice.org/pkg/ignore": `<head>` + |
| `<title>Hello</title>` + |
| // Unknown meta name |
| `<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}">` + |
| // go-source before go-meta |
| `<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}">` + |
| // go-import tag for the package |
| `<meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg">` + |
| // go-import with wrong number of fields |
| `<meta name="go-import" content="alice.org/pkg https://github.com/alice/pkg">` + |
| // go-import with no fields |
| `<meta name="go-import" content="">` + |
| // go-source with wrong number of fields |
| `<meta name="go-source" content="alice.org/pkg blah">` + |
| // meta tag for a different package |
| `<meta name="go-import" content="alice.org/other git https://github.com/alice/other">` + |
| // meta tag for a different package |
| `<meta name="go-import" content="alice.org/other git https://github.com/alice/other">` + |
| `</head>` + |
| // go-import outside of head |
| `<meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg">`, |
| |
| // go-source repo defaults to go-import |
| "http://alice.org/pkg/default": `<head> |
| <meta name="go-import" content="alice.org/pkg git https://github.com/alice/pkg"> |
| <meta name="go-source" content="alice.org/pkg _ foo bar"> |
| </head>`, |
| // Package at root of a Git repo. |
| "https://bob.com/pkg": `<head> <meta name="go-import" content="bob.com/pkg git https://vcs.net/bob/pkg.git">`, |
| // Package at in sub-directory of a Git repo. |
| "https://bob.com/pkg/sub": `<head> <meta name="go-import" content="bob.com/pkg git https://vcs.net/bob/pkg.git">`, |
| "https://bob.com/bad/github": ` |
| <head><meta name="go-import" content="bob.com/bad/github git https://github.com/bob/bad/">$">`, |
| "https://bob.com/bad/apache": ` |
| <head><meta name="go-import" content="bob.com/bad/apache git https://git.apache.org/>$">`, |
| // Package with go-source meta tag, where {file} appears on the right of '#' in the file field URL template. |
| "https://azul3d.org/examples/abs": `<!DOCTYPE html><html><head>` + |
| `<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>` + |
| `<meta name="go-import" content="azul3d.org/examples git https://github.com/azul3d/examples">` + |
| `<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}">` + |
| `<meta http-equiv="refresh" content="0; url=https://godoc.org/azul3d.org/examples/abs">` + |
| `</head>`, |
| |
| // Multiple go-import meta tags; one of which is a vgo-special mod vcs type |
| "http://myitcv.io/blah2": `<!DOCTYPE html><html><head>` + |
| `<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>` + |
| `<meta name="go-import" content="myitcv.io/blah2 git https://github.com/myitcv/x">` + |
| `<meta name="go-import" content="myitcv.io/blah2 mod https://raw.githubusercontent.com/myitcv/pubx/master">` + |
| `</head>`, |
| } |
| |
| func TestJSON(t *testing.T) { |
| for _, test := range []struct { |
| in *Info |
| want string |
| }{ |
| { |
| nil, |
| `null`, |
| }, |
| { |
| &Info{repoURL: "r", moduleDir: "m", commit: "c"}, |
| `{"RepoURL":"r","ModuleDir":"m","Commit":"c"}`, |
| }, |
| { |
| &Info{repoURL: "r", moduleDir: "m", commit: "c", templates: githubURLTemplates}, |
| `{"RepoURL":"r","ModuleDir":"m","Commit":"c","Kind":"github"}`, |
| }, |
| { |
| &Info{repoURL: "r", moduleDir: "m", commit: "c", templates: urlTemplates{File: "f"}}, |
| `{"RepoURL":"r","ModuleDir":"m","Commit":"c","Templates":{"Directory":"","File":"f","Line":"","Raw":""}}`, |
| }, |
| { |
| &Info{repoURL: "r", moduleDir: "m", commit: "c", templates: urlTemplates{Repo: "r", File: "f"}}, |
| `{"RepoURL":"r","ModuleDir":"m","Commit":"c","Templates":{"Repo":"r","Directory":"","File":"f","Line":"","Raw":""}}`, |
| }, |
| } { |
| bytes, err := json.Marshal(&test.in) |
| if err != nil { |
| t.Fatal(err) |
| } |
| got := string(bytes) |
| if got != test.want { |
| t.Errorf("%#v:\ngot %s\nwant %s", test.in, got, test.want) |
| continue |
| } |
| var out Info |
| if err := json.Unmarshal(bytes, &out); err != nil { |
| t.Fatal(err) |
| } |
| var want Info |
| if test.in != nil { |
| want = *test.in |
| } |
| if out != want { |
| t.Errorf("got %#v\nwant %#v", out, want) |
| } |
| } |
| } |
| |
| func TestURLTemplates(t *testing.T) { |
| // Check that templates contain the right variables. |
| |
| for _, p := range patterns { |
| if strings.Contains(p.pattern, "blitiri") { |
| continue |
| } |
| check := func(tmpl string, vars ...string) { |
| if tmpl == "" { |
| return |
| } |
| for _, v := range vars { |
| w := "{" + v + "}" |
| if !strings.Contains(tmpl, w) { |
| t.Errorf("in pattern %s, template %q is missing %s", p.pattern, tmpl, w) |
| } |
| } |
| } |
| |
| check(p.templates.Directory, "commit") |
| check(p.templates.File, "commit") |
| check(p.templates.Line, "commit", "line") |
| check(p.templates.Raw, "commit", "file") |
| } |
| } |
| |
| func TestMatchLegacyTemplates(t *testing.T) { |
| for _, test := range []struct { |
| sm sourceMeta |
| wantTemplates urlTemplates |
| wantTransformCommitNil bool |
| }{ |
| { |
| sm: sourceMeta{"", "", "", "https://git.blindage.org/21h/hcloud-dns/src/branch/master{/dir}/{file}#L{line}"}, |
| wantTemplates: giteaURLTemplates, |
| wantTransformCommitNil: false, |
| }, |
| { |
| sm: sourceMeta{"", "", "", "https://git.lastassault.de/sup/networkoverlap/-/blob/master{/dir}/{file}#L{line}"}, |
| wantTemplates: gitlabURLTemplates, |
| wantTransformCommitNil: true, |
| }, |
| { |
| sm: sourceMeta{"", "", "", "https://git.borago.de/Marco/gqltest/src/master{/dir}/{file}#L{line}"}, |
| wantTemplates: giteaURLTemplates, |
| wantTransformCommitNil: true, |
| }, |
| { |
| sm: sourceMeta{"", "", "", "https://git.zx2c4.com/wireguard-windows/tree{/dir}/{file}#n{line}"}, |
| wantTemplates: fdioURLTemplates, |
| wantTransformCommitNil: false, |
| }, |
| { |
| sm: sourceMeta{"", "", "unknown{/dir}", "unknown{/dir}/{file}#L{line}"}, |
| wantTemplates: urlTemplates{ |
| Repo: "", |
| Directory: "unknown/{dir}", |
| File: "unknown/{file}", |
| Line: "unknown/{file}#L{line}", |
| }, |
| wantTransformCommitNil: true, |
| }, |
| } { |
| gotTemplates, gotTransformCommit := matchLegacyTemplates(context.Background(), &test.sm) |
| gotNil := gotTransformCommit == nil |
| if gotTemplates != test.wantTemplates || gotNil != test.wantTransformCommitNil { |
| t.Errorf("%+v:\ngot (%+v, %t)\nwant (%+v, %t)", |
| test.sm, gotTemplates, gotNil, test.wantTemplates, test.wantTransformCommitNil) |
| } |
| } |
| } |
| |
| func TestFilesInfo(t *testing.T) { |
| info := FilesInfo("/Users/bob") |
| |
| check := func(got, want string) { |
| t.Helper() |
| if got != want { |
| t.Errorf("got %q, want %q", got, want) |
| } |
| } |
| |
| check(info.RepoURL(), "/files/Users/bob/") |
| check(info.ModuleURL(), "/files/Users/bob/") |
| check(info.FileURL("dir/a.go"), "/files/Users/bob/dir/a.go") |
| } |