From 6cc6bcd444d395ac46ec9994b6b2b3e029cd95b8 Mon Sep 17 00:00:00 2001 From: Andrew Bayer Date: Tue, 24 May 2022 10:29:48 -0400 Subject: [PATCH] Add Resolve tests for gitresolver With test repo setup logic for use in the actual Resolver test Signed-off-by: Andrew Bayer --- gitresolver/pkg/git/resolver_test.go | 151 ++++++++++++++++++++++++ gitresolver/pkg/git/testing/repo.go | 164 +++++++++++++++++++++++++++ 2 files changed, 315 insertions(+) create mode 100644 gitresolver/pkg/git/testing/repo.go diff --git a/gitresolver/pkg/git/resolver_test.go b/gitresolver/pkg/git/resolver_test.go index 8a5afc09..a1d92ea0 100644 --- a/gitresolver/pkg/git/resolver_test.go +++ b/gitresolver/pkg/git/resolver_test.go @@ -2,11 +2,18 @@ package git import ( "context" + "encoding/hex" + "errors" + "path/filepath" "testing" "time" + "github.com/go-git/go-git/v5/plumbing" + "github.com/google/go-cmp/cmp" + gittesting "github.com/tektoncd/resolution/gitresolver/pkg/git/testing" resolutioncommon "github.com/tektoncd/resolution/pkg/common" "github.com/tektoncd/resolution/pkg/resolver/framework" + "github.com/tektoncd/resolution/test/diff" ) func TestGetSelector(t *testing.T) { @@ -101,3 +108,147 @@ func TestGetResolutionTimeoutCustom(t *testing.T) { t.Fatalf("expected timeout from config to be returned") } } + +func TestResolve(t *testing.T) { + gittesting.WithTemporaryGitConfig(t) + + testCases := []struct { + name string + commits []gittesting.CommitForRepo + branch string + useNthCommit int + specificCommit string + path string + filename string + expectedContent []byte + expectedErr error + }{ + { + name: "single commit", + commits: []gittesting.CommitForRepo{{ + Dir: "foo/bar", + Filename: "somefile", + Content: "some content", + }}, + path: "foo/bar", + filename: "somefile", + expectedContent: []byte("some content"), + }, { + name: "with branch", + commits: []gittesting.CommitForRepo{{ + Dir: "foo/bar", + Filename: "somefile", + Content: "some content", + Branch: "other-branch", + }, { + Dir: "foo/bar", + Filename: "somefile", + Content: "wrong content", + }}, + branch: "other-branch", + path: "foo/bar", + filename: "somefile", + expectedContent: []byte("some content"), + }, { + name: "earlier specific commit", + commits: []gittesting.CommitForRepo{{ + Dir: "foo/bar", + Filename: "somefile", + Content: "some content", + }, { + Dir: "foo/bar", + Filename: "somefile", + Content: "different content", + }}, + path: "foo/bar", + filename: "somefile", + useNthCommit: 1, + expectedContent: []byte("different content"), + }, { + name: "file does not exist", + commits: []gittesting.CommitForRepo{{ + Dir: "foo/bar", + Filename: "somefile", + Content: "some content", + }}, + path: "foo/bar", + filename: "some other file", + expectedErr: errors.New(`error opening file "foo/bar/some other file": file does not exist`), + }, { + name: "branch does not exist", + commits: []gittesting.CommitForRepo{{ + Dir: "foo/bar", + Filename: "somefile", + Content: "some content", + }}, + branch: "does-not-exist", + path: "foo/bar", + filename: "some other file", + expectedErr: errors.New(`clone error: couldn't find remote ref "refs/heads/does-not-exist"`), + }, { + name: "commit does not exist", + commits: []gittesting.CommitForRepo{{ + Dir: "foo/bar", + Filename: "somefile", + Content: "some content", + }}, + specificCommit: "does-not-exist", + path: "foo/bar", + filename: "some other file", + expectedErr: errors.New("checkout error: object not found"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + repoPath, commits := gittesting.CreateTestRepo(t, tc.commits) + + resolver := &Resolver{} + + params := map[string]string{ + URLParam: repoPath, + PathParam: filepath.Join(tc.path, tc.filename), + } + + if tc.branch != "" { + params[BranchParam] = tc.branch + } + + if tc.useNthCommit > 0 { + params[CommitParam] = commits[plumbing.Master.Short()][tc.useNthCommit] + } else if tc.specificCommit != "" { + params[CommitParam] = hex.EncodeToString([]byte(tc.specificCommit)) + } + + output, err := resolver.Resolve(context.Background(), params) + if tc.expectedErr != nil { + if err == nil { + t.Fatalf("expected err '%v' but didn't get one", tc.expectedErr) + } + if tc.expectedErr.Error() != err.Error() { + t.Fatalf("expected err '%v' but got '%v'", tc.expectedErr, err) + } + } else { + if err != nil { + t.Fatalf("unexpected error resolving: %v", err) + } + + expectedResource := &ResolvedGitResource{ + Content: tc.expectedContent, + } + switch { + case tc.useNthCommit > 0: + expectedResource.Commit = commits[plumbing.Master.Short()][tc.useNthCommit] + case tc.branch != "": + expectedResource.Commit = commits[tc.branch][len(commits[tc.branch])-1] + default: + expectedResource.Commit = commits[plumbing.Master.Short()][len(commits[plumbing.Master.Short()])-1] + } + + if d := cmp.Diff(expectedResource, output); d != "" { + t.Errorf("unexpected resource from Resolve: %s", diff.PrintWantGot(d)) + } + } + }) + } +} diff --git a/gitresolver/pkg/git/testing/repo.go b/gitresolver/pkg/git/testing/repo.go new file mode 100644 index 00000000..f8b6a7bb --- /dev/null +++ b/gitresolver/pkg/git/testing/repo.go @@ -0,0 +1,164 @@ +/* + Copyright 2022 The Tekton Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package testing + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + "time" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" +) + +// CreateTestRepo is used to instantiate a local test repository with the desired commits. +func CreateTestRepo(t *testing.T, commits []CommitForRepo) (string, map[string][]string) { + t.Helper() + tempDir := t.TempDir() + + repo, err := git.PlainInit(tempDir, false) + + worktree, err := repo.Worktree() + if err != nil { + t.Fatalf("getting test worktree: %v", err) + } + if worktree == nil { + t.Fatal("test worktree not created") + } + + startingFile := filepath.Join(tempDir, "README") + if err := ioutil.WriteFile(startingFile, []byte("This is a test"), 0600); err != nil { + t.Fatalf("couldn't write content to file %s: %v", startingFile, err) + } + + _, err = worktree.Add("README") + if err != nil { + t.Fatalf("couldn't add file %s to git: %v", startingFile, err) + } + + startingHash, err := worktree.Commit("adding file for test", &git.CommitOptions{ + Author: &object.Signature{ + Name: "Someone", + Email: "someone@example.com", + When: time.Now(), + }, + }) + if err != nil { + t.Fatalf("couldn't perform commit for test: %v", err) + } + + hashesByBranch := make(map[string][]string) + + // Iterate over the commits and add them. + for _, cmt := range commits { + branch := cmt.Branch + if branch == "" { + branch = plumbing.Master.Short() + } + + // If we're given a branch, check out that branch. + coOpts := &git.CheckoutOptions{ + Branch: plumbing.NewBranchReferenceName(branch), + } + + if _, ok := hashesByBranch[branch]; !ok && branch != plumbing.Master.Short() { + coOpts.Hash = startingHash + coOpts.Create = true + } + + if err := worktree.Checkout(coOpts); err != nil { + t.Fatalf("couldn't do checkout of %s: %v", branch, err) + } + + targetDir := filepath.Join(tempDir, cmt.Dir) + fi, err := os.Stat(targetDir) + if os.IsNotExist(err) { + if err := os.MkdirAll(targetDir, 0700); err != nil { + t.Fatalf("couldn't create directory %s in worktree: %v", targetDir, err) + } + } else if err != nil { + t.Fatalf("checking if directory %s in worktree exists: %v", targetDir, err) + } + if fi != nil && !fi.IsDir() { + t.Fatalf("%s already exists but is not a directory", targetDir) + } + + outfile := filepath.Join(targetDir, cmt.Filename) + if err := ioutil.WriteFile(outfile, []byte(cmt.Content), 0600); err != nil { + t.Fatalf("couldn't write content to file %s: %v", outfile, err) + } + + _, err = worktree.Add(filepath.Join(cmt.Dir, cmt.Filename)) + if err != nil { + t.Fatalf("couldn't add file %s to git: %v", outfile, err) + } + + hash, err := worktree.Commit("adding file for test", &git.CommitOptions{ + Author: &object.Signature{ + Name: "Someone", + Email: "someone@example.com", + When: time.Now(), + }, + }) + if err != nil { + t.Fatalf("couldn't perform commit for test: %v", err) + } + + if _, ok := hashesByBranch[branch]; !ok { + hashesByBranch[branch] = []string{hash.String()} + } else { + hashesByBranch[branch] = append(hashesByBranch[branch], hash.String()) + } + } + + return tempDir, hashesByBranch +} + +// CommitForRepo provides the directory, filename, content and branch for a test commit. +type CommitForRepo struct { + Dir string + Filename string + Content string + Branch string +} + +// WithTemporaryGitConfig resets the .gitconfig for the duration of the test. +func WithTemporaryGitConfig(t *testing.T) func() { + gitConfigDir := t.TempDir() + key := "GIT_CONFIG_GLOBAL" + t.Helper() + oldValue, envVarExists := os.LookupEnv(key) + if err := os.Setenv(key, filepath.Join(gitConfigDir, "config")); err != nil { + t.Fatal(err) + } + clean := func() { + t.Helper() + if !envVarExists { + if err := os.Unsetenv(key); err != nil { + t.Fatal(err) + } + return + } + if err := os.Setenv(key, oldValue); err != nil { + t.Fatal(err) + } + } + return clean +}