blob: 9df372271ac7beeeb354ce2e500e877af8e86569 [file] [log] [blame]
// Copyright 2023 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"
"flag"
"net/http"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/google/go-replayers/httpreplay"
"golang.org/x/pkgsite/internal/source"
)
var (
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
}{
{
"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 := source.ModuleInfo(context.Background(), source.NewClient(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)
}
})
}
}
func TestNewStdlibInfo(t *testing.T) {
client, done := newReplayClient(t, *record)
defer done()
check := func(t *testing.T, msg, got, want string) {
t.Helper()
if got != want {
t.Fatalf("%s:\ngot %s\nwant %s", msg, got, want)
}
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.
"",
},
} {
t.Run(test.desc, func(t *testing.T) {
info, err := source.NewStdlibInfo(test.version)
if err != nil {
t.Fatal(err)
}
check(t, "repo", info.RepoURL(), test.wantRepo)
check(t, "module", info.ModuleURL(), test.wantModule)
check(t, "file", info.FileURL(test.file), test.wantFile)
check(t, "line", info.LineURL(test.file, 1), test.wantLine)
if test.wantRaw != "" {
check(t, "raw", info.RawURL(test.file), test.wantRaw)
}
})
}
t.Run("stdlib-raw", func(t *testing.T) {
// Test raw URLs from the standard library, which are a special case.
info, err := source.NewStdlibInfo("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)
})
}
func newReplayClient(t *testing.T, record bool) (*http.Client, func()) {
if runtime.GOOS == "js" || runtime.GOOS == "wasip1" {
t.Skipf("skipping test: see issue #59718: listen on localhost fails on %s", runtime.GOOS)
}
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() }
}
}