diff options
author | Patrick Steinhardt <psteinhardt@gitlab.com> | 2021-06-17 15:53:17 +0300 |
---|---|---|
committer | Patrick Steinhardt <psteinhardt@gitlab.com> | 2021-06-17 15:53:17 +0300 |
commit | 044aac4db415ebc9fb1cc77f39d8bd5e3bdc823e (patch) | |
tree | 6d05b0f0d3352ea23b9dc934964f843b26bf4827 | |
parent | 92aea29d1f67436a080670c16e9cc859f6b642dc (diff) |
repository: Add test to exercise CreateFromBundle with transactions
We recently got a bug report about `CreateFromBundle()` seemingly
misbehaving when transactions are enabled. Symptoms are that voters drop
out at seemingly random steps with weird error messages like e.g.
"Unable to read current working directory". While this likely indicates
some kind of external race, I still took the opportunity to write a test
verifying that `CreateFromBundle()` works as expect with transactions
enabled and that the votes are in fact computed deterministically.
-rw-r--r-- | internal/gitaly/service/repository/create_from_bundle_test.go | 72 |
1 files changed, 72 insertions, 0 deletions
diff --git a/internal/gitaly/service/repository/create_from_bundle_test.go b/internal/gitaly/service/repository/create_from_bundle_test.go index 16c6ba2fc..2b530ff29 100644 --- a/internal/gitaly/service/repository/create_from_bundle_test.go +++ b/internal/gitaly/service/repository/create_from_bundle_test.go @@ -2,6 +2,8 @@ package repository import ( "bytes" + "context" + "fmt" "io" "os" "path/filepath" @@ -9,12 +11,19 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/v14/internal/git" "gitlab.com/gitlab-org/gitaly/v14/internal/git/gittest" "gitlab.com/gitlab-org/gitaly/v14/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/config" + "gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/transaction" + "gitlab.com/gitlab-org/gitaly/v14/internal/helper" + "gitlab.com/gitlab-org/gitaly/v14/internal/helper/text" "gitlab.com/gitlab-org/gitaly/v14/internal/tempdir" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper" "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testassert" + "gitlab.com/gitlab-org/gitaly/v14/internal/testhelper/testserver" + "gitlab.com/gitlab-org/gitaly/v14/internal/transaction/txinfo" + "gitlab.com/gitlab-org/gitaly/v14/internal/transaction/voting" "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb" "gitlab.com/gitlab-org/gitaly/v14/streamio" "google.golang.org/grpc/codes" @@ -83,6 +92,69 @@ func TestServer_CreateRepositoryFromBundle_successful(t *testing.T) { require.NotNil(t, commit) } +func TestServer_CreateRepositoryFromBundle_transactional(t *testing.T) { + var votes []voting.Vote + txManager := &transaction.MockManager{ + VoteFn: func(ctx context.Context, tx txinfo.Transaction, vote voting.Vote) error { + votes = append(votes, vote) + return nil + }, + } + + cfg, repoProto, repoPath, client := setupRepositoryService(t, + testserver.WithTransactionManager(txManager)) + + ctx, cancel := testhelper.Context() + defer cancel() + ctx, err := txinfo.InjectTransaction(ctx, 1, "primary", true) + require.NoError(t, err) + ctx = helper.IncomingToOutgoing(ctx) + + stream, err := client.CreateRepositoryFromBundle(ctx) + require.NoError(t, err) + + require.NoError(t, stream.Send(&gitalypb.CreateRepositoryFromBundleRequest{ + Repository: &gitalypb.Repository{ + StorageName: repoProto.GetStorageName(), + RelativePath: "create.git", + }, + })) + + bundle := gittest.Exec(t, cfg, "-C", repoPath, "bundle", "create", "-", "master", "feature") + require.Greater(t, len(bundle), 100*1024) + + _, err = io.Copy(streamio.NewWriter(func(p []byte) error { + require.NoError(t, stream.Send(&gitalypb.CreateRepositoryFromBundleRequest{ + Data: p, + })) + return nil + }), bytes.NewReader(bundle)) + require.NoError(t, err) + + _, err = stream.CloseAndRecv() + require.NoError(t, err) + + masterOID := text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", "master")) + featureOID := text.ChompBytes(gittest.Exec(t, cfg, "-C", repoPath, "rev-parse", "feature")) + + // This accounts for the first two votes which first do a git-clone(1) followed by a fetch. + // Given that voting is done via git's reference-transaction hook, the format is `<oldrev> + // <newrev> <reference>`. + fetchInput := fmt.Sprintf("%s %s refs/heads/feature\n%s %s refs/heads/master\n", + git.ZeroOID, featureOID, git.ZeroOID, masterOID) + + // And this accounts for the final vote in `Create()`, which does vote on all references in + // the target repo as listed by git-for-each-ref(1). + refsInput := fmt.Sprintf("%s commit\trefs/heads/feature\n%s commit\trefs/heads/master\n", + featureOID, masterOID) + + require.Equal(t, []voting.Vote{ + voting.VoteFromData([]byte(fetchInput)), + voting.VoteFromData([]byte(fetchInput)), + voting.VoteFromData([]byte(refsInput)), + }, votes) +} + func TestServer_CreateRepositoryFromBundle_failed_invalid_bundle(t *testing.T) { cfg, client := setupRepositoryServiceWithoutRepo(t) |