| // 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 postgres | |
| import ( | |
| "context" | |
| "errors" | |
| "fmt" | |
| "path" | |
| "strings" | |
| "testing" | |
| "github.com/google/go-cmp/cmp" | |
| "github.com/google/go-cmp/cmp/cmpopts" | |
| "github.com/google/safehtml" | |
| "golang.org/x/pkgsite/internal" | |
| "golang.org/x/pkgsite/internal/derrors" | |
| "golang.org/x/pkgsite/internal/licenses" | |
| "golang.org/x/pkgsite/internal/source" | |
| "golang.org/x/pkgsite/internal/stdlib" | |
| "golang.org/x/pkgsite/internal/testing/sample" | |
| "golang.org/x/pkgsite/internal/version" | |
| ) | |
| func TestGetUnitMeta(t *testing.T) { | |
| t.Parallel() | |
| ctx := context.Background() | |
| testGetUnitMeta(t, ctx) | |
| } | |
| func testGetUnitMeta(t *testing.T, ctx context.Context) { | |
| testDB, release := acquire(t) | |
| defer release() | |
| for _, testModule := range []struct { | |
| module, version, packageSuffix string | |
| isMaster bool | |
| goMod string | |
| }{ | |
| {"m.com", "v1.0.0", "a", false, ""}, | |
| {"m.com", "v1.0.1", "dir/a", false, ""}, | |
| {"m.com", "v2.0.0+incompatible", "a", false, ""}, | |
| {"m.com", "v1.1.0", "a/b", false, "module m.com\nretract v1.0.1 // bad"}, | |
| {"m.com", "v1.2.0-pre", "a", true, ""}, | |
| {"m.com/a", "v1.1.0", "b", false, ""}, | |
| {"m.com/b", "v2.0.0+incompatible", "a", true, ""}, | |
| {"cloud.google.com/go", "v0.69.0", "pubsublite", false, ""}, | |
| {"cloud.google.com/go/pubsublite", "v0.4.0", "", false, ""}, | |
| {"cloud.google.com/go", "v0.74.0", "compute/metadata", false, ""}, | |
| {"cloud.google.com/go/compute/metadata", "v0.0.0-20181115181204-d50f0e9b2506", "", false, "-"}, | |
| } { | |
| m := sample.Module(testModule.module, testModule.version, testModule.packageSuffix) | |
| testDB.MustInsertModuleGoMod(ctx, t, m, testModule.goMod) | |
| requested := m.Version | |
| if testModule.isMaster { | |
| requested = "master" | |
| } | |
| if err := testDB.UpsertVersionMap(ctx, &internal.VersionMap{ | |
| ModulePath: m.ModulePath, | |
| RequestedVersion: requested, | |
| ResolvedVersion: m.Version, | |
| }); err != nil { | |
| t.Fatal(err) | |
| } | |
| } | |
| type teststruct struct { | |
| name string | |
| path, module, version string | |
| want *internal.UnitMeta | |
| } | |
| checkUnitMeta := func(t *testing.T, ctx context.Context, test teststruct) { | |
| got, err := testDB.GetUnitMeta(ctx, test.path, test.module, test.version) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| opts := []cmp.Option{ | |
| cmpopts.EquateEmpty(), | |
| cmpopts.IgnoreFields(licenses.Metadata{}, "Coverage"), | |
| cmpopts.IgnoreFields(internal.UnitMeta{}, "HasGoMod"), | |
| cmp.AllowUnexported(source.Info{}, safehtml.HTML{}), | |
| } | |
| if diff := cmp.Diff(test.want, got, opts...); diff != "" { | |
| t.Errorf("mismatch (-want +got):\n%s", diff) | |
| } | |
| } | |
| wantUnitMeta := func(modPath, version, name string) *internal.UnitMeta { | |
| return &internal.UnitMeta{ | |
| ModuleInfo: internal.ModuleInfo{ | |
| ModulePath: modPath, | |
| Version: version, | |
| IsRedistributable: true, | |
| LatestVersion: "v1.1.0", | |
| }, | |
| Name: name, | |
| } | |
| } | |
| for _, test := range []teststruct{ | |
| { | |
| name: "known module and version", | |
| path: "m.com/a", | |
| module: "m.com", | |
| version: "v1.2.0-pre", | |
| want: wantUnitMeta("m.com", "v1.2.0-pre", "a"), | |
| }, | |
| { | |
| name: "unknown module, known version", | |
| path: "m.com/a/b", | |
| version: "v1.1.0", | |
| // The path is in two modules at v1.1.0. Prefer the longer one. | |
| want: wantUnitMeta("m.com/a", "v1.1.0", "b"), | |
| }, | |
| { | |
| name: "known module, unknown version", | |
| path: "m.com/a", | |
| module: "m.com", | |
| // Choose the latest release version. | |
| want: wantUnitMeta("m.com", "v1.1.0", ""), | |
| }, | |
| { | |
| name: "unknown module and version", | |
| path: "m.com/a/b", | |
| // Select the latest release version, longest module. | |
| want: wantUnitMeta("m.com/a", "v1.1.0", "b"), | |
| }, | |
| { | |
| name: "module", | |
| path: "m.com", | |
| // Select the latest version of the module. | |
| want: wantUnitMeta("m.com", "v1.1.0", ""), | |
| }, | |
| { | |
| name: "longest module", | |
| path: "m.com/a", | |
| version: "v1.1.0", | |
| // Prefer module m/a over module m, directory a. | |
| want: wantUnitMeta("m.com/a", "v1.1.0", ""), | |
| }, | |
| { | |
| name: "directory", | |
| path: "m.com/dir", | |
| want: func() *internal.UnitMeta { | |
| um := wantUnitMeta("m.com", "v1.0.1", "") | |
| um.Retracted = true | |
| um.RetractionRationale = "bad" | |
| return um | |
| }(), | |
| }, | |
| { | |
| name: "module at master version", | |
| path: "m.com", | |
| version: "master", | |
| want: wantUnitMeta("m.com", "v1.2.0-pre", ""), | |
| }, | |
| { | |
| name: "package at master version", | |
| path: "m.com/a", | |
| version: "master", | |
| want: wantUnitMeta("m.com", "v1.2.0-pre", "a"), | |
| }, | |
| { | |
| name: "incompatible module", | |
| path: "m.com/b", | |
| version: "master", | |
| want: wantUnitMeta("m.com/b", "v2.0.0+incompatible", ""), | |
| }, | |
| { | |
| name: "prefer pubsublite nested module", | |
| path: "cloud.google.com/go/pubsublite", | |
| want: wantUnitMeta("cloud.google.com/go/pubsublite", "v0.4.0", "pubsublite"), | |
| }, | |
| { | |
| name: "prefer compute metadata in main module", | |
| path: "cloud.google.com/go/compute/metadata", | |
| want: wantUnitMeta("cloud.google.com/go", "v0.74.0", "metadata"), | |
| }, | |
| } { | |
| t.Run(test.name, func(t *testing.T) { | |
| if test.module == "" { | |
| test.module = internal.UnknownModulePath | |
| } | |
| if test.version == "" { | |
| test.version = version.Latest | |
| } | |
| want := sample.UnitMeta( | |
| test.path, | |
| test.want.ModulePath, | |
| test.want.Version, | |
| test.want.Name, | |
| true, | |
| ) | |
| latestGood := map[string]string{ | |
| "m.com": "v1.1.0", | |
| "m.com/a": "v1.1.0", | |
| "m.com/b": "v2.0.0+incompatible", | |
| "cloud.google.com/go": "v0.74.0", | |
| "cloud.google.com/go/pubsublite": "v0.4.0", | |
| "cloud.google.com/go/compute/metadata": "v0.0.0-20181115181204-d50f0e9b2506", | |
| } | |
| want.LatestVersion = latestGood[want.ModulePath] | |
| if want.LatestVersion == "" { | |
| // Fallback to version if not in map (e.g. stdlib) | |
| want.LatestVersion = want.Version | |
| } | |
| want.IsRedistributable = true | |
| want.CommitTime = sample.CommitTime | |
| want.Retracted = test.want.Retracted | |
| want.RetractionRationale = test.want.RetractionRationale | |
| test.want = want | |
| checkUnitMeta(t, ctx, test) | |
| }) | |
| } | |
| } | |
| func TestGetUnitMeta_UnknownModulePathMaster(t *testing.T) { | |
| t.Parallel() | |
| testDB, release := acquire(t) | |
| defer release() | |
| ctx := context.Background() | |
| // Both modules contain the path "m.com/a/b" at the "master" version. | |
| for _, testModule := range []struct { | |
| module, version, suffix string | |
| }{ | |
| {"m.com", "v1.0.0", "a/b"}, | |
| {"m.com/a", "v1.1.0", "b"}, | |
| } { | |
| m := sample.Module(testModule.module, testModule.version, testModule.suffix) | |
| testDB.MustInsertModule(t, m) | |
| // Map the concrete version to the symbolic "master" version. | |
| if err := testDB.UpsertVersionMap(ctx, &internal.VersionMap{ | |
| ModulePath: m.ModulePath, | |
| RequestedVersion: "master", | |
| ResolvedVersion: m.Version, | |
| }); err != nil { | |
| t.Fatal(err) | |
| } | |
| } | |
| fullPath := "m.com/a/b" | |
| got, err := testDB.GetUnitMeta(ctx, fullPath, internal.UnknownModulePath, "master") | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| wantModule := "m.com/a" | |
| wantVersion := "v1.1.0" | |
| if got.ModulePath != wantModule || got.Version != wantVersion { | |
| t.Errorf("GetUnitMeta(%q, unknown, master) = %s@%s; want %s@%s", | |
| fullPath, got.ModulePath, got.Version, wantModule, wantVersion) | |
| } | |
| } | |
| func TestGetLatestUnitVersion(t *testing.T) { | |
| ctx := context.Background() | |
| type pgm struct { // package as mod@ver/pkg, and go.mod | |
| pkg string | |
| goMod string // go.mod file contents after "module" line, or "0" to omit | |
| } | |
| for _, test := range []struct { | |
| name string | |
| packages []pgm | |
| badModules []pgm // have latest-version info, but not in the DB | |
| fullPath, modulePath string // inputs to getLatestUnitVersion | |
| want string | |
| wantNilLMV bool // lmv return value is null | |
| }{ | |
| { | |
| name: "known module", | |
| // If the module is known and there is latest-version information | |
| // for it, then the result is the latest version that contains the | |
| // full path. | |
| packages: []pgm{ | |
| {"m.com@v1.4.0/b", ""}, // latest version, but no package "a" | |
| {"m.com@v1.3.0-pre/a", ""}, // pre-release | |
| {"m.com@v1.2.0/a", ""}, // latest version with package a | |
| {"m.com@v1.1.0/a", ""}, | |
| }, | |
| fullPath: "m.com/a", | |
| modulePath: "m.com", | |
| want: "m.com@v1.2.0", | |
| }, | |
| { | |
| name: "longest", | |
| // If the module is unknown, then the longest module with the full | |
| // path is used, even if a shorter module has a later version. | |
| packages: []pgm{ | |
| {"m.com@v1.3.0/a/b", ""}, // shorter path, later version | |
| {"m.com/a@v1.0.0/b", ""}, // longer path, earlier version | |
| }, | |
| fullPath: "m.com/a/b", | |
| want: "m.com/a@v1.0.0", | |
| }, | |
| { | |
| name: "prerelease", | |
| // Use the latest pre-release version if there are no release versions. | |
| packages: []pgm{ | |
| {"m.com@v1.2.0-pre/a", ""}, | |
| {"m.com@v1.3.0-pre/a", ""}, | |
| {"m.com@v1.1.0-pre/a", ""}, | |
| }, | |
| fullPath: "m.com/a", | |
| want: "m.com@v1.3.0-pre", | |
| }, | |
| { | |
| name: "retracted", | |
| // Skip retracted versions. | |
| packages: []pgm{ | |
| {"m.com@v2.0.0+incompatible/a", ""}, // ignored: cooked latest version is compatible | |
| {"m.com@v1.3.0/a", "retract v1.3.0"}, // retracts itself | |
| {"m.com@v1.2.0/a", ""}, | |
| }, | |
| fullPath: "m.com/a", | |
| want: "m.com@v1.2.0", // latest unretracted compatible version | |
| }, | |
| { | |
| name: "latest bad", | |
| // The latest version is not in the DB, but is still used for | |
| // retractions and to decide whether to ignore incompatible | |
| // versions. | |
| packages: []pgm{ | |
| {"m.com@v2.0.0+incompatible/a", ""}, // ignored: cooked latest version is compatible | |
| {"m.com@v1.3.0/a", ""}, | |
| {"m.com@v1.2.0/a", ""}, | |
| }, | |
| // Latest raw version retracts v1.3.0. | |
| // Latest cooked version is compatible, so ignore incompatible. | |
| badModules: []pgm{{"m.com@v1.4.0", "retract v1.3.0"}}, | |
| fullPath: "m.com/a", | |
| want: "m.com@v1.2.0", // latest unretracted compatible version | |
| }, | |
| { | |
| name: "incompatible", | |
| // Incompatible versions aren't skipped if the latest compatible | |
| // version does not have a go.mod file, indicated by the | |
| // latest-version info having an incompatible cooked version. | |
| packages: []pgm{ | |
| {"m.com@v1.3.0/a", "-"}, | |
| {"m.com@v1.2.0/a", ""}, | |
| {"m.com@v2.0.0+incompatible/a", ""}, | |
| }, | |
| fullPath: "m.com/a", | |
| want: "m.com@v2.0.0+incompatible", | |
| }, | |
| { | |
| name: "only incompatible", | |
| // If incompatible versions are all we've got, return one. | |
| packages: []pgm{ | |
| {"m.com@v3.0.0+incompatible/a", ""}, | |
| {"m.com@v2.0.0+incompatible/a", ""}, | |
| }, | |
| badModules: []pgm{{"m.com@v1.2.3", ""}}, // latest version is compatible but bad | |
| fullPath: "m.com/a", | |
| want: "m.com@v3.0.0+incompatible", | |
| wantNilLMV: true, | |
| }, | |
| { | |
| name: "no latest", | |
| // Without latest-version information, use whatever the DB has. | |
| // Prefer longer paths, and release versions to pre-release. | |
| packages: []pgm{ | |
| {"m.com@v1.4.0/a/b", "-"}, // shorter module path | |
| {"m.com/a@v1.3.0/c", "-"}, // does not contain full path | |
| {"m.com/a@v1.2.0-pre/b", "-"}, // pre-release | |
| {"m.com/a@v1.1.0/b", "-"}, | |
| }, | |
| fullPath: "m.com/a/b", | |
| want: "m.com/a@v1.1.0", | |
| wantNilLMV: true, | |
| }, | |
| { | |
| name: "no latest incompatible", | |
| // Without latest-version information, use whatever the DB has. | |
| // Prefer longer paths, and later versions even if they are | |
| // incompatible. | |
| packages: []pgm{ | |
| {"m.com@v2.3.0+incompatible/a/b", "-"}, // shorter module path | |
| {"m.com/a@v2.1.0+incompatible/b", "-"}, | |
| {"m.com/a@v2.0.0+incompatible/b", "-"}, | |
| {"m.com/a@v2.2.0+incompatible/c", "-"}, // does not contain path | |
| }, | |
| fullPath: "m.com/a/b", | |
| want: "m.com/a@v2.1.0+incompatible", | |
| wantNilLMV: true, | |
| }, | |
| { | |
| name: "prefer latest info", | |
| // Given a choice between a longer module path with no | |
| // latest-version info and a shorter with it, choose the shorter. | |
| // Real-world example: prefer | |
| // cloud.google.com/go@vX/compute/metadata to | |
| // cloud.google.com/go/compute/metadata@vX because the latter, while | |
| // a module, has no latest-version info. | |
| packages: []pgm{ | |
| {"m.com@v1.0.0/a/b", ""}, | |
| {"m.com/a@v1.0.0/b", "-"}, | |
| }, | |
| fullPath: "m.com/a/b", | |
| want: "m.com@v1.0.0", | |
| }, | |
| { | |
| name: "all retracted", | |
| // If all the versions containing the path are retracted, then return | |
| // the latest anyway. | |
| packages: []pgm{ | |
| {"m.com@v1.1.0/a/b", ""}, | |
| {"m.com@v1.2.0/a/b", ""}, | |
| {"m.com@v1.3.0/a/c", "retract [v1.0.0, v1.2.0]"}, | |
| }, | |
| fullPath: "m.com/a/b", | |
| want: "m.com@v1.2.0", | |
| wantNilLMV: true, | |
| }, | |
| } { | |
| t.Run(test.name, func(t *testing.T) { | |
| testDB, release := acquire(t) | |
| defer release() | |
| for _, p := range test.packages { | |
| mod, ver, pkg := parseModuleVersionPackage(p.pkg) | |
| m := sample.Module(mod, ver, pkg) | |
| testDB.MustInsertModuleGoMod(ctx, t, m, p.goMod) | |
| } | |
| for _, b := range test.badModules { | |
| mod, ver, _ := parseModuleVersionPackage(b.pkg) | |
| addLatest(ctx, t, testDB, mod, ver, b.goMod) | |
| } | |
| if test.modulePath == "" { | |
| test.modulePath = internal.UnknownModulePath | |
| } | |
| gotPath, gotVersion, gotLMV, err := testDB.getLatestUnitVersion(ctx, test.fullPath, test.modulePath) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| got := gotPath + "@" + gotVersion | |
| if got != test.want { | |
| t.Errorf("got %s, want %s", got, test.want) | |
| } | |
| gotNilLMV := gotLMV == nil | |
| if gotNilLMV != test.wantNilLMV { | |
| t.Errorf("got nil LMV %t, want %t", gotNilLMV, test.wantNilLMV) | |
| } | |
| }) | |
| } | |
| } | |
| // parse mod@ver/pkg into parts. | |
| func parseModuleVersionPackage(s string) (mod, ver, pkg string) { | |
| at := strings.IndexRune(s, '@') | |
| mod, s = s[:at], s[at+1:] | |
| slash := strings.IndexRune(s, '/') | |
| if slash < 0 { | |
| ver = s | |
| } else { | |
| ver, pkg = s[:slash], s[slash+1:] | |
| } | |
| return mod, ver, pkg | |
| } | |
| func TestGetUnit(t *testing.T) { | |
| t.Parallel() | |
| testDB, release := acquire(t) | |
| defer release() | |
| ctx := context.Background() | |
| InsertSampleDirectoryTree(ctx, t, testDB) | |
| // Add a module that has READMEs in a directory and a package. | |
| m := sample.Module("a.com/m", "v1.2.3", "dir/p") | |
| d := findDirectory(m, "a.com/m/dir") | |
| d.Readme = &internal.Readme{ | |
| Filepath: "DIR_README.md", | |
| Contents: "dir readme", | |
| } | |
| d = findDirectory(m, "a.com/m/dir/p") | |
| d.Readme = &internal.Readme{ | |
| Filepath: "PKG_README.md", | |
| Contents: "pkg readme", | |
| } | |
| testDB.MustInsertModule(t, m) | |
| // Add a module that has documentation for two Go build contexts. | |
| m = sample.Module("a.com/twodoc", "v1.2.3", "p") | |
| pkg := m.Packages()[0] | |
| docs2 := []*internal.Documentation{ | |
| sample.Documentation("linux", "amd64", `package p; var L int`), | |
| sample.Documentation("windows", "amd64", `package p; var W int`), | |
| } | |
| pkg.Documentation = docs2 | |
| testDB.MustInsertModule(t, m) | |
| for _, test := range []struct { | |
| name, path, modulePath, version string | |
| want *internal.Unit | |
| }{ | |
| { | |
| name: "module path", | |
| path: "github.com/hashicorp/vault", | |
| modulePath: "github.com/hashicorp/vault", | |
| version: "v1.0.3", | |
| want: unit("github.com/hashicorp/vault", "github.com/hashicorp/vault", "v1.0.3", "", | |
| &internal.Readme{ | |
| Filepath: sample.ReadmeFilePath, | |
| Contents: sample.ReadmeContents, | |
| }, | |
| []string{ | |
| "api", | |
| "builtin/audit/file", | |
| "builtin/audit/socket", | |
| }, | |
| ), | |
| }, | |
| { | |
| name: "package path", | |
| path: "github.com/hashicorp/vault/api", | |
| modulePath: "github.com/hashicorp/vault", | |
| version: "v1.0.3", | |
| want: unit("github.com/hashicorp/vault/api", "github.com/hashicorp/vault", "v1.0.3", "api", nil, | |
| []string{ | |
| "api", | |
| }, | |
| ), | |
| }, | |
| { | |
| name: "directory path", | |
| path: "github.com/hashicorp/vault/builtin", | |
| modulePath: "github.com/hashicorp/vault", | |
| version: "v1.0.3", | |
| want: unit("github.com/hashicorp/vault/builtin", "github.com/hashicorp/vault", "v1.0.3", "", nil, | |
| []string{ | |
| "builtin/audit/file", | |
| "builtin/audit/socket", | |
| }, | |
| ), | |
| }, | |
| { | |
| name: "stdlib directory", | |
| path: "archive", | |
| modulePath: stdlib.ModulePath, | |
| version: "v1.13.4", | |
| want: unit("archive", stdlib.ModulePath, "v1.13.4", "", nil, | |
| []string{ | |
| "archive/tar", | |
| "archive/zip", | |
| }, | |
| ), | |
| }, | |
| { | |
| name: "stdlib package", | |
| path: "archive/zip", | |
| modulePath: stdlib.ModulePath, | |
| version: "v1.13.4", | |
| want: unit("archive/zip", stdlib.ModulePath, "v1.13.4", "zip", nil, | |
| []string{ | |
| "archive/zip", | |
| }, | |
| ), | |
| }, | |
| { | |
| name: "stdlib - internal directory", | |
| path: "cmd/internal", | |
| modulePath: stdlib.ModulePath, | |
| version: "v1.13.4", | |
| want: unit("cmd/internal", stdlib.ModulePath, "v1.13.4", "", nil, | |
| []string{ | |
| "cmd/internal/obj", | |
| "cmd/internal/obj/arm", | |
| "cmd/internal/obj/arm64", | |
| }, | |
| ), | |
| }, | |
| { | |
| name: "directory with readme", | |
| path: "a.com/m/dir", | |
| modulePath: "a.com/m", | |
| version: "v1.2.3", | |
| want: unit("a.com/m/dir", "a.com/m", "v1.2.3", "", &internal.Readme{ | |
| Filepath: "DIR_README.md", | |
| Contents: "dir readme", | |
| }, | |
| []string{ | |
| "dir/p", | |
| }, | |
| ), | |
| }, | |
| { | |
| name: "package with readme", | |
| path: "a.com/m/dir/p", | |
| modulePath: "a.com/m", | |
| version: "v1.2.3", | |
| want: unit("a.com/m/dir/p", "a.com/m", "v1.2.3", "p", | |
| &internal.Readme{ | |
| Filepath: "PKG_README.md", | |
| Contents: "pkg readme", | |
| }, | |
| []string{ | |
| "dir/p", | |
| }, | |
| ), | |
| }, | |
| { | |
| name: "package with two docs", | |
| path: "a.com/twodoc/p", | |
| modulePath: "a.com/twodoc", | |
| version: "v1.2.3", | |
| want: func() *internal.Unit { | |
| u := unit("a.com/twodoc/p", "a.com/twodoc", "v1.2.3", "p", | |
| nil, | |
| []string{"p"}) | |
| u.Documentation = docs2[:1] | |
| u.BuildContexts = []internal.BuildContext{internal.BuildContextLinux, internal.BuildContextWindows} | |
| u.Subdirectories[0].Synopsis = docs2[0].Synopsis | |
| return u | |
| }(), | |
| }, | |
| } { | |
| t.Run(test.name, func(t *testing.T) { | |
| um := sample.UnitMeta( | |
| test.path, | |
| test.modulePath, | |
| test.version, | |
| test.want.Name, | |
| test.want.IsRedistributable, | |
| ) | |
| test.want.CommitTime = um.CommitTime | |
| checkUnit(ctx, t, testDB, um, test.want) | |
| }) | |
| } | |
| } | |
| func checkUnit(ctx context.Context, t *testing.T, db *DB, um *internal.UnitMeta, want *internal.Unit) { | |
| t.Helper() | |
| got, err := db.GetUnit(ctx, um, internal.AllFields, internal.BuildContext{}) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| opts := []cmp.Option{ | |
| cmp.AllowUnexported(source.Info{}, safehtml.HTML{}), | |
| // The packages table only includes partial license information; it omits the Coverage field. | |
| cmpopts.IgnoreFields(licenses.Metadata{}, "Coverage"), | |
| } | |
| want.SourceInfo = um.SourceInfo | |
| want.NumImports = len(want.Imports) | |
| opts = append(opts, | |
| cmpopts.EquateEmpty(), | |
| cmpopts.IgnoreFields(internal.Unit{}, "Imports", "LicenseContents"), | |
| ) | |
| if diff := cmp.Diff(want, got, opts...); diff != "" { | |
| t.Errorf("mismatch (-want, +got):\n%s", diff) | |
| } | |
| } | |
| func TestGetUnit_SubdirectoriesShowNonRedistPackages(t *testing.T) { | |
| t.Parallel() | |
| testDB, release := acquire(t) | |
| defer release() | |
| m := sample.DefaultModule() | |
| m.IsRedistributable = false | |
| m.Packages()[0].IsRedistributable = false | |
| testDB.MustInsertModule(t, m) | |
| } | |
| func TestGetUnitFieldSet(t *testing.T) { | |
| t.Parallel() | |
| testDB, release := acquire(t) | |
| defer release() | |
| ctx := context.Background() | |
| readme := &internal.Readme{ | |
| Filepath: "a.com/m/dir/p/README.md", | |
| Contents: "readme", | |
| } | |
| // Add a module that has READMEs in a directory and a package. | |
| m := sample.Module("a.com/m", "v1.2.3", "dir/p") | |
| m.Packages()[0].Readme = readme | |
| testDB.MustInsertModule(t, m) | |
| cleanFields := func(u *internal.Unit, fields internal.FieldSet) { | |
| // Add/remove fields based on the FieldSet specified. | |
| if fields&internal.WithMain != 0 { | |
| if fields&internal.WithDocsSource != 0 { | |
| u.Documentation = []*internal.Documentation{sample.Doc} | |
| } else { | |
| u.Documentation = []*internal.Documentation{{GOOS: "all", GOARCH: "all", Synopsis: sample.Doc.Synopsis}} | |
| } | |
| u.BuildContexts = []internal.BuildContext{internal.BuildContextAll} | |
| u.Readme = readme | |
| u.NumImports = len(sample.Imports()) | |
| u.Subdirectories = []*internal.PackageMeta{ | |
| { | |
| Path: "a.com/m/dir/p", | |
| Name: "p", | |
| Synopsis: sample.Doc.Synopsis, | |
| IsRedistributable: true, | |
| Licenses: sample.LicenseMetadata(), | |
| }, | |
| } | |
| } | |
| if fields&internal.WithImports != 0 { | |
| imps := sample.Imports() | |
| u.Imports = imps | |
| u.NumImports = len(imps) | |
| } | |
| if fields&internal.WithLicenses == 0 { | |
| u.LicenseContents = nil | |
| } | |
| } | |
| for _, test := range []struct { | |
| name string | |
| fields internal.FieldSet | |
| want *internal.Unit | |
| }{ | |
| { | |
| name: "WithMain", | |
| fields: internal.WithMain, | |
| want: unit("a.com/m/dir/p", "a.com/m", "v1.2.3", "", readme, []string{}), | |
| }, | |
| { | |
| name: "WithMain|WithDocsSource", | |
| fields: internal.WithMain | internal.WithDocsSource, | |
| want: unit("a.com/m/dir/p", "a.com/m", "v1.2.3", "", readme, []string{}), | |
| }, | |
| { | |
| name: "WithImports", | |
| fields: internal.WithImports, | |
| want: unitNoLicenses("a.com/m/dir/p", "a.com/m", "v1.2.3", "", nil, []string{}), | |
| }, | |
| { | |
| name: "WithLicenses", | |
| fields: internal.WithLicenses, | |
| want: unitNoLicenses("a.com/m/dir/p", "a.com/m", "v1.2.3", "", nil, []string{}), | |
| }, | |
| } { | |
| t.Run(test.name, func(t *testing.T) { | |
| um := sample.UnitMeta( | |
| test.want.Path, | |
| test.want.ModulePath, | |
| test.want.Version, | |
| test.want.Name, | |
| test.want.IsRedistributable, | |
| ) | |
| got, err := testDB.GetUnit(ctx, um, test.fields, internal.BuildContext{}) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| opts := []cmp.Option{ | |
| cmp.AllowUnexported(source.Info{}, safehtml.HTML{}), | |
| cmpopts.EquateEmpty(), | |
| // The packages table only includes partial license information; it omits the Coverage field. | |
| cmpopts.IgnoreFields(licenses.Metadata{}, "Coverage"), | |
| } | |
| test.want.CommitTime = um.CommitTime | |
| test.want.SourceInfo = um.SourceInfo | |
| cleanFields(test.want, test.fields) | |
| if diff := cmp.Diff(test.want, got, opts...); diff != "" { | |
| t.Errorf("mismatch (-want, +got):\n%s", diff) | |
| } | |
| }) | |
| } | |
| } | |
| func TestGetUnitBuildContext(t *testing.T) { | |
| t.Parallel() | |
| testDB, release := acquire(t) | |
| defer release() | |
| ctx := context.Background() | |
| // Add a module that has documentation for two Go build contexts. | |
| m := sample.Module("a.com/twodoc", "v1.2.3", "p") | |
| pkg := m.Packages()[0] | |
| linuxDoc := sample.Documentation("linux", "amd64", `package p; var L int`) | |
| windowsDoc := sample.Documentation("windows", "amd64", `package p; var W int`) | |
| pkg.Documentation = []*internal.Documentation{linuxDoc, windowsDoc} | |
| testDB.MustInsertModule(t, m) | |
| um := sample.UnitMeta( | |
| "a.com/twodoc/p", | |
| "a.com/twodoc", | |
| "v1.2.3", | |
| "p", | |
| true) | |
| for _, test := range []struct { | |
| goos, goarch string | |
| want *internal.Documentation | |
| }{ | |
| {"", "", linuxDoc}, | |
| {"linux", "amd64", linuxDoc}, | |
| {"windows", "amd64", windowsDoc}, | |
| {"linux", "", linuxDoc}, | |
| {"wasm", "js", nil}, | |
| } { | |
| t.Run(fmt.Sprintf("%s-%s", test.goos, test.goarch), func(t *testing.T) { | |
| bc := internal.BuildContext{GOOS: test.goos, GOARCH: test.goarch} | |
| u, err := testDB.GetUnit(ctx, um, internal.WithMain|internal.WithDocsSource, bc) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| got := u.Documentation | |
| var want []*internal.Documentation | |
| if test.want != nil { | |
| want = []*internal.Documentation{test.want} | |
| } | |
| if diff := cmp.Diff(want, got); diff != "" { | |
| t.Errorf("mismatch (-want, +got):\n%s", diff) | |
| } | |
| wantb := []internal.BuildContext{internal.BuildContextLinux, internal.BuildContextWindows} | |
| if got := u.BuildContexts; !cmp.Equal(got, wantb) { | |
| t.Errorf("got %v, want %v", got, wantb) | |
| } | |
| }) | |
| } | |
| } | |
| func unit(fullPath, modulePath, version, name string, readme *internal.Readme, suffixes []string) *internal.Unit { | |
| u := unitNoLicenses(fullPath, modulePath, version, name, readme, suffixes) | |
| u.Licenses = sample.LicenseMetadata() | |
| return u | |
| } | |
| func unitNoLicenses(fullPath, modulePath, version, name string, readme *internal.Readme, suffixes []string) *internal.Unit { | |
| u := &internal.Unit{ | |
| UnitMeta: internal.UnitMeta{ | |
| ModuleInfo: internal.ModuleInfo{ | |
| ModulePath: modulePath, | |
| Version: version, | |
| IsRedistributable: true, | |
| LatestVersion: version, | |
| }, | |
| Path: fullPath, | |
| Name: name, | |
| }, | |
| IsRedistributable: true, | |
| LicenseContents: sample.Licenses(), | |
| Readme: readme, | |
| } | |
| u.Subdirectories = subdirectories(modulePath, suffixes) | |
| if u.IsPackage() { | |
| imps := sample.Imports() | |
| u.Imports = imps | |
| u.NumImports = len(imps) | |
| u.Documentation = []*internal.Documentation{sample.Doc} | |
| u.BuildContexts = []internal.BuildContext{internal.BuildContextAll} | |
| } | |
| return u | |
| } | |
| func subdirectories(modulePath string, suffixes []string) []*internal.PackageMeta { | |
| var want []*internal.PackageMeta | |
| for _, suffix := range suffixes { | |
| p := suffix | |
| if modulePath != stdlib.ModulePath { | |
| p = path.Join(modulePath, suffix) | |
| } | |
| want = append(want, sample.PackageMeta(p)) | |
| } | |
| return want | |
| } | |
| func TestGetUnitBypass(t *testing.T) { | |
| for _, test := range []struct { | |
| bypass bool | |
| wantEmpty bool | |
| }{ | |
| {false, true}, | |
| {true, false}, | |
| } { | |
| t.Run(fmt.Sprintf("bypass %t", test.bypass), func(t *testing.T) { | |
| t.Parallel() | |
| testDB, release := acquire(t) | |
| defer release() | |
| ctx := context.Background() | |
| if test.bypass { | |
| testDB = NewBypassingLicenseCheck(testDB.db) | |
| } | |
| m := nonRedistributableModule() | |
| testDB.MustInsertModule(t, m) | |
| pathInfo := newUnitMeta(m.ModulePath, m.ModulePath, m.Version) | |
| d, err := testDB.GetUnit(ctx, pathInfo, internal.AllFields, internal.BuildContext{}) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if got := (d.Readme == nil); got != test.wantEmpty { | |
| t.Errorf("readme empty: got %t, want %t", got, test.wantEmpty) | |
| } | |
| if got := (d.Documentation == nil); got != test.wantEmpty { | |
| t.Errorf("synopsis empty: got %t, want %t", got, test.wantEmpty) | |
| } | |
| if got := (d.Documentation == nil); got != test.wantEmpty { | |
| t.Errorf("doc empty: got %t, want %t", got, test.wantEmpty) | |
| } | |
| if got := d.IsRedistributable; got != !test.wantEmpty { // wantEmpty iff !IsRedistributable | |
| t.Errorf("IsRedistributable is %v: want %v", got, !test.wantEmpty) | |
| } | |
| pkgs := d.Subdirectories | |
| if len(pkgs) != 1 { | |
| t.Fatal("len(pkgs) != 1") | |
| } | |
| if got := (pkgs[0].Synopsis == ""); got != test.wantEmpty { | |
| t.Errorf("synopsis empty: got %t, want %t", got, test.wantEmpty) | |
| } | |
| }) | |
| } | |
| } | |
| func findDirectory(m *internal.Module, path string) *internal.Unit { | |
| for _, d := range m.Units { | |
| if d.Path == path { | |
| return d | |
| } | |
| } | |
| return nil | |
| } | |
| func newUnitMeta(path, modulePath, version string) *internal.UnitMeta { | |
| return &internal.UnitMeta{ | |
| Path: path, | |
| ModuleInfo: internal.ModuleInfo{ | |
| ModulePath: modulePath, | |
| Version: version, | |
| }, | |
| } | |
| } | |
| func TestGetModulePackages(t *testing.T) { | |
| t.Parallel() | |
| testDB, release := acquire(t) | |
| defer release() | |
| ctx := context.Background() | |
| m := sample.Module("m.com", "v1.2.3", "a", "a/b", "c") | |
| testDB.MustInsertModule(t, m) | |
| got, err := testDB.GetModulePackages(ctx, "m.com", "v1.2.3") | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| var gotPaths []string | |
| for _, p := range got { | |
| gotPaths = append(gotPaths, p.Path) | |
| } | |
| wantPaths := []string{"m.com/a", "m.com/a/b", "m.com/c"} | |
| if diff := cmp.Diff(wantPaths, gotPaths); diff != "" { | |
| t.Errorf("GetModulePackages mismatch (-want +got):\n%s", diff) | |
| } | |
| _, err = testDB.GetModulePackages(ctx, "x.com", "v1.2.3") | |
| if !errors.Is(err, derrors.NotFound) { | |
| t.Errorf("got error %v, wanted NotFound", err) | |
| } | |
| } |