diff options
author | Patrick Steinhardt <psteinhardt@gitlab.com> | 2021-06-17 15:53:17 +0300 |
---|---|---|
committer | Patrick Steinhardt <psteinhardt@gitlab.com> | 2021-07-02 14:12:08 +0300 |
commit | 1dacf986e0ba6558efa2f60818515fa0e293d8a0 (patch) | |
tree | 5ab05a5f0d81834a0e43c3712205a7d112e4cffc | |
parent | 979fe249ad84c4f6b60beffc1420a355e9f87f83 (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 | 73 |
1 files changed, 73 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 c1cb14348..55155212e 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,11 +11,18 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/internal/git" "gitlab.com/gitlab-org/gitaly/internal/git/gittest" "gitlab.com/gitlab-org/gitaly/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/internal/gitaly/config" + "gitlab.com/gitlab-org/gitaly/internal/gitaly/transaction" + "gitlab.com/gitlab-org/gitaly/internal/helper" + "gitlab.com/gitlab-org/gitaly/internal/helper/text" "gitlab.com/gitlab-org/gitaly/internal/tempdir" "gitlab.com/gitlab-org/gitaly/internal/testhelper" + "gitlab.com/gitlab-org/gitaly/internal/testhelper/testserver" + "gitlab.com/gitlab-org/gitaly/internal/transaction/txinfo" + "gitlab.com/gitlab-org/gitaly/internal/transaction/voting" "gitlab.com/gitlab-org/gitaly/proto/go/gitalypb" "gitlab.com/gitlab-org/gitaly/streamio" "google.golang.org/grpc/codes" @@ -81,6 +90,70 @@ 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, server txinfo.PraefectServer, 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.PraefectServer{SocketPath: "idontcare"}).Inject(ctx) + require.NoError(t, err) + 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 vote 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(refsInput)), + }, votes) +} + func TestServer_CreateRepositoryFromBundle_failed_invalid_bundle(t *testing.T) { cfg, client := setupRepositoryServiceWithoutRepo(t) |