| // 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 stdlib |
| |
| import ( |
| "context" |
| "errors" |
| "flag" |
| "io/fs" |
| "reflect" |
| "testing" |
| |
| "golang.org/x/mod/semver" |
| "golang.org/x/pkgsite/internal/testenv" |
| "golang.org/x/pkgsite/internal/version" |
| ) |
| |
| var ( |
| clone = flag.Bool("clone", false, "test actual clones of the Go repo") |
| repoPath = flag.String("path", "", "path to Go repo to test") |
| ) |
| |
| func TestTagForVersion(t *testing.T) { |
| for _, test := range []struct { |
| name string |
| version string |
| want string |
| wantErr bool |
| }{ |
| { |
| name: "std version v1.0.0", |
| version: "v1.0.0", |
| want: "go1", |
| }, |
| { |
| name: "std version v1.12.5", |
| version: "v1.12.5", |
| want: "go1.12.5", |
| }, |
| { |
| name: "std version v1.13, incomplete canonical version", |
| version: "v1.13", |
| want: "go1.13", |
| }, |
| { |
| name: "std version v1.13.0-beta.1", |
| version: "v1.13.0-beta.1", |
| want: "go1.13beta1", |
| }, |
| { |
| name: "std version v1.9.0-rc.2", |
| version: "v1.9.0-rc.2", |
| want: "go1.9rc2", |
| }, |
| { |
| name: "std with digitless prerelease", |
| version: "v1.13.0-prerelease", |
| want: "go1.13prerelease", |
| }, |
| { |
| name: "version v1.13.0", |
| version: "v1.13.0", |
| want: "go1.13", |
| }, |
| { |
| name: "version v1.20.0-rc.2", |
| version: "v1.20.0-rc.2", |
| want: "go1.20rc2", |
| }, |
| { |
| name: "version v1.20.0", |
| version: "v1.20.0", |
| want: "go1.20", |
| }, |
| { |
| name: "version v1.21.0-rc.2", |
| version: "v1.21.0-rc.2", |
| want: "go1.21rc2", |
| }, |
| { |
| name: "version v1.21.0", |
| version: "v1.21.0", |
| want: "go1.21.0", |
| }, |
| { |
| name: "master branch", |
| version: "master", |
| want: "master", |
| }, |
| { |
| name: "bad std semver", |
| version: "v1.x", |
| wantErr: true, |
| }, |
| { |
| name: "more bad std semver", |
| version: "v1.0-", |
| wantErr: true, |
| }, |
| { |
| name: "bad prerelease", |
| version: "v1.13.0-beta1", |
| wantErr: true, |
| }, |
| { |
| name: "another bad prerelease", |
| version: "v1.13.0-whatevs99", |
| wantErr: true, |
| }, |
| } { |
| t.Run(test.name, func(t *testing.T) { |
| got, err := TagForVersion(test.version) |
| if (err != nil) != test.wantErr { |
| t.Errorf("TagForVersion(%q) = %q, %v, wantErr %v", test.version, got, err, test.wantErr) |
| return |
| } |
| if got != test.want { |
| t.Fatalf("TagForVersion(%q) = %q, %v, wanted %q, %v", test.version, got, err, test.want, nil) |
| } |
| }) |
| } |
| } |
| |
| func TestMajorVersionForVersion(t *testing.T) { |
| for _, test := range []struct { |
| in string |
| want string // empty => error |
| }{ |
| {"", ""}, |
| {"garbage", ""}, |
| {"v1.0.0", "go1"}, |
| {"v1.13.3", "go1"}, |
| {"v1.9.0-rc.2", "go1"}, |
| {"v2.1.3", "go2"}, |
| {"v2.1.3", "go2"}, |
| {"v0.0.0-20230307225218-457fd1d52d17", "go1"}, |
| } { |
| got, err := MajorVersionForVersion(test.in) |
| if (err != nil) != (test.want == "") { |
| t.Errorf("%q: err: got %v, wanted error: %t", test.in, err, test.want == "") |
| } |
| if err == nil && got != test.want { |
| t.Errorf("%q: got %q, want %q", test.in, got, test.want) |
| } |
| } |
| } |
| |
| func TestContentDir(t *testing.T) { |
| ctx := context.Background() |
| testenv.MustHaveExecPath(t, "git") |
| defer WithTestData()() |
| for _, resolvedVersion := range []string{ |
| "v1.3.2", |
| "v1.12.5", |
| "v1.14.6", |
| version.Master, |
| } { |
| t.Run(resolvedVersion, func(t *testing.T) { |
| cdir, gotResolvedVersion, gotTime, err := ContentDir(ctx, resolvedVersion) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if SupportedBranches[resolvedVersion] { |
| if !version.IsPseudo(gotResolvedVersion) { |
| t.Errorf("resolved version: %s is not a pseudo-version", gotResolvedVersion) |
| } |
| } else if gotResolvedVersion != resolvedVersion { |
| t.Errorf("resolved version: got %s, want %s", gotResolvedVersion, resolvedVersion) |
| } |
| if !gotTime.Equal(TestCommitTime) { |
| t.Errorf("commit time: got %s, want %s", gotTime, TestCommitTime) |
| } |
| checkContentDirFiles(t, cdir, resolvedVersion) |
| }) |
| } |
| } |
| |
| func TestContentDirCloneAndOpen(t *testing.T) { |
| ctx := context.Background() |
| run := func(t *testing.T) { |
| for _, resolvedVersion := range []string{ |
| "v1.3.2", |
| "v1.14.6", |
| version.Master, |
| version.Latest, |
| } { |
| t.Run(resolvedVersion, func(t *testing.T) { |
| cdir, _, _, err := ContentDir(ctx, resolvedVersion) |
| if err != nil { |
| t.Fatal(err) |
| } |
| checkContentDirFiles(t, cdir, resolvedVersion) |
| }) |
| } |
| } |
| |
| t.Run("clone", func(t *testing.T) { |
| if !*clone { |
| t.Skip("-clone not supplied") |
| } |
| defer withGoRepo(&remoteGoRepo{})() |
| run(t) |
| }) |
| t.Run("local", func(t *testing.T) { |
| if *repoPath == "" { |
| t.Skip("-path not supplied") |
| } |
| lgr := newLocalGoRepo(*repoPath) |
| |
| defer withGoRepo(lgr)() |
| run(t) |
| }) |
| } |
| |
| func checkContentDirFiles(t *testing.T, cdir fs.FS, resolvedVersion string) { |
| wantFiles := map[string]bool{ |
| "LICENSE": true, |
| "errors/errors.go": true, |
| "errors/errors_test.go": true, |
| } |
| if semver.Compare(resolvedVersion, "v1.13.0") > 0 || resolvedVersion == TestMasterVersion { |
| wantFiles["cmd/README.vendor"] = true |
| } |
| if semver.Compare(resolvedVersion, "v1.14.0") > 0 { |
| wantFiles["context/context.go"] = true |
| } |
| const readmeVendorFile = "README.vendor" |
| if _, err := fs.Stat(cdir, readmeVendorFile); !errors.Is(err, fs.ErrNotExist) { |
| t.Fatalf("fs.Stat returned %v; want %q to be removed", err, readmeVendorFile) |
| } |
| err := fs.WalkDir(cdir, ".", func(path string, d fs.DirEntry, err error) error { |
| if err != nil { |
| return err |
| } |
| if d.IsDir() { |
| return nil |
| } |
| delete(wantFiles, path) |
| return nil |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if len(wantFiles) > 0 { |
| t.Errorf("zip missing files: %v", reflect.ValueOf(wantFiles).MapKeys()) |
| } |
| } |
| |
| func TestZipInfo(t *testing.T) { |
| defer WithTestData()() |
| |
| for _, tc := range []struct { |
| requestedVersion string |
| want string |
| }{ |
| { |
| requestedVersion: "latest", |
| want: "v1.21.0", |
| }, |
| { |
| requestedVersion: "master", |
| want: "master", |
| }, |
| } { |
| gotVersion, err := ZipInfo(tc.requestedVersion) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if want := tc.want; gotVersion != want { |
| t.Errorf("version: got %q, want %q", gotVersion, want) |
| } |
| } |
| } |
| |
| func TestVersions(t *testing.T) { |
| testVersions := func(wants []string) { |
| got, err := Versions() |
| if err != nil { |
| t.Fatal(err) |
| } |
| gotmap := map[string]bool{} |
| for _, g := range got { |
| gotmap[g] = true |
| } |
| for _, w := range wants { |
| if !gotmap[w] { |
| t.Errorf("missing %s", w) |
| } |
| } |
| } |
| |
| commonWants := []string{ |
| "v1.4.2", |
| "v1.9.0-rc.1", |
| "v1.11.0", |
| "v1.13.0-beta.1", |
| } |
| otherWants := append([]string{"v1.17.6"}, commonWants...) |
| t.Run("test", func(t *testing.T) { |
| defer WithTestData()() |
| testWants := append([]string{"v1.21.0"}, commonWants...) |
| testVersions(testWants) |
| }) |
| t.Run("local", func(t *testing.T) { |
| if *repoPath == "" { |
| t.Skip("-path not supplied") |
| } |
| lgr := newLocalGoRepo(*repoPath) |
| |
| defer withGoRepo(lgr)() |
| testVersions(otherWants) |
| }) |
| t.Run("remote", func(t *testing.T) { |
| if !*clone { |
| t.Skip("-clone not supplied") |
| } |
| defer withGoRepo(&remoteGoRepo{})() |
| testVersions(otherWants) |
| }) |
| } |
| |
| func TestVersionForTag(t *testing.T) { |
| for _, test := range []struct { |
| in, want string |
| }{ |
| {"", ""}, |
| {"go1", "v1.0.0"}, |
| {"go1.9beta2", "v1.9.0-beta.2"}, |
| {"go1.12", "v1.12.0"}, |
| {"go1.9.7", "v1.9.7"}, |
| {"go2.0", "v2.0.0"}, |
| {"go1.9rc2", "v1.9.0-rc.2"}, |
| {"go1.1beta", ""}, |
| {"go1.0", ""}, |
| {"weekly.2012-02-14", ""}, |
| {"latest", "latest"}, |
| {"go1.21.0", "v1.21.0"}, |
| {"go1.21", "v1.21.0"}, |
| } { |
| got := VersionForTag(test.in) |
| if got != test.want { |
| t.Errorf("VersionForTag(%q) = %q, want %q", test.in, got, test.want) |
| } |
| } |
| } |
| |
| func TestContains(t *testing.T) { |
| for _, test := range []struct { |
| in string |
| want bool |
| }{ |
| {"fmt", true}, |
| {"encoding/json", true}, |
| {"something/with.dots", true}, |
| {"example.com", false}, |
| {"example.com/fmt", false}, |
| } { |
| got := Contains(test.in) |
| if got != test.want { |
| t.Errorf("Contains(%q) = %t, want %t", test.in, got, test.want) |
| } |
| } |
| } |
| |
| func TestDirectory(t *testing.T) { |
| for _, tc := range []struct { |
| version string |
| want string |
| }{ |
| { |
| version: "v1.3.0-beta2", |
| want: "src/pkg", |
| }, |
| { |
| version: "v1.16.0-beta1", |
| want: "src", |
| }, |
| { |
| version: "master", |
| want: "src", |
| }, |
| } { |
| got := Directory(tc.version) |
| if got != tc.want { |
| t.Errorf("Directory(%s) = %s, want %s", tc.version, got, tc.want) |
| } |
| } |
| } |
| |
| func TestVersionMatchesHash(t *testing.T) { |
| v := "v0.0.0-20210910212848-c8dfa306babb" |
| h := "c8dfa306babb91e88f8ba25329b3ef8aa11944e1" |
| if !VersionMatchesHash(v, h) { |
| t.Error("got false, want true") |
| } |
| h = "c8dfa306babXb91e88f8ba25329b3ef8aa11944e1" |
| if VersionMatchesHash(v, h) { |
| t.Error("got true, want false") |
| } |
| } |
| |
| func TestResolveSupportedBranches(t *testing.T) { |
| testenv.MustHaveExternalNetwork(t) // ResolveSupportedBranches accesses the go repo at go.googlesource.com |
| testenv.MustHaveExecPath(t, "git") // ResolveSupportedBranches uses the git command to do so. |
| |
| got, err := ResolveSupportedBranches() |
| if err != nil { |
| t.Fatal(err) |
| } |
| // We can't check the hashes because they change, but we can check the keys. |
| for key := range got { |
| if !SupportedBranches[key] { |
| t.Errorf("got key %q not in SupportedBranches", key) |
| } |
| } |
| if g, w := len(got), len(SupportedBranches); g != w { |
| t.Errorf("got %d hashes, want %d", g, w) |
| } |
| } |