Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorÆvar Arnfjörð Bjarmason <avarab@gmail.com>2020-12-17 17:39:25 +0300
committerÆvar Arnfjörð Bjarmason <avarab@gmail.com>2021-01-12 18:00:54 +0300
commitea0e36b456646d87e88b3b966aeacdd48e71affe (patch)
tree10ff2218b68572db5da8815c8e3addfd97652bed
parent01ba5b2c1c0719d27aced511bd9497b56cd9384a (diff)
updater: optionally fake the oldvalue for testing
This API allows us to easily inject fake oldvalues into update-ref, the purpose of which is to easily test ref race conditions without carefully needing to orchestrate race conditions. Patrick suggested I could use something like testhelper.CaptureHookEnv() for this. For update-ref we'd need to have that more complex since it would need to munge stdin, but more specifically we'd need to get some out-of-bounds message to it (via a file) to ask it to map key->value. Not such a big deal now, we could write this in $PATH: #!/bin/sh sed 's/EXPECTED/CHANGED/' | git update-ref "$@" But once we're going to tread update-ref like "cat-file --batch" (via the transactions it supports) that becomes painful. I.e. a follow-up to 154dbc1a2 (updateref: Safeguard against accidentally committing updates, 2020-12-15) with the new interface in Git 2.30. So I think doing it this way sucks the least, it's obvious from the variable name that you shouldn't use it for testing, and we could die on some AreWeTesting() I don't know about in fakeOldValueForTesting(). The performance hit of checking a hash map for whether it has any keys is trivial.
-rw-r--r--internal/git/updateref/updateref.go29
-rw-r--r--internal/git/updateref/updateref_test.go54
2 files changed, 81 insertions, 2 deletions
diff --git a/internal/git/updateref/updateref.go b/internal/git/updateref/updateref.go
index 688042925..4ce9d0c8a 100644
--- a/internal/git/updateref/updateref.go
+++ b/internal/git/updateref/updateref.go
@@ -77,10 +77,35 @@ func (u *Updater) Create(ref, value string) error {
return err
}
+var mungeMapForTesting = make(map[string]string)
+
+// MungeMapForTestingAdd is a test-only interface to the private
+// mungeMapForTesting. Use it for ad-hoc mapping the Update()
+// "oldvalue" to some fake value. The munged value will only be used
+// once, it's delete()-ed on retrieval.
+func MungeMapForTestingAdd(key, value string) {
+ mungeMapForTesting[key] = value
+}
+
+func fakeOldValueForTesting(oldValue string) string {
+ fakeOldValue := mungeMapForTesting[oldValue]
+ if len(fakeOldValue) != 0 {
+ // Don't persist the value, it's one-use. If we had a
+ // Del() interface and the test died and forgot to
+ // cleanup later tests might die at a distance.
+ delete(mungeMapForTesting, oldValue)
+ return fakeOldValue
+ }
+ return oldValue
+}
+
// Update commands the reference to be updated to point at the sha specified in
// newvalue
-func (u *Updater) Update(ref, newvalue, oldvalue string) error {
- _, err := fmt.Fprintf(u.cmd, "update %s\x00%s\x00%s\x00", ref, newvalue, oldvalue)
+func (u *Updater) Update(ref, newvalue, oldValue string) error {
+ if len(mungeMapForTesting) != 0 {
+ oldValue = fakeOldValueForTesting(oldValue)
+ }
+ _, err := fmt.Fprintf(u.cmd, "update %s\x00%s\x00%s\x00", ref, newvalue, oldValue)
return err
}
diff --git a/internal/git/updateref/updateref_test.go b/internal/git/updateref/updateref_test.go
index 905b9e470..1586b1ece 100644
--- a/internal/git/updateref/updateref_test.go
+++ b/internal/git/updateref/updateref_test.go
@@ -99,6 +99,60 @@ func TestUpdate(t *testing.T) {
require.NotEqual(t, commit.Id, parentCommit.Id, "reference was updated when it shouldn't have been")
}
+func TestUpdateFake(t *testing.T) {
+ ctx, testRepo, _, teardown := setup(t)
+ defer teardown()
+
+ locator := config.NewLocator(config.Config)
+ masterCommit, err := log.GetCommit(ctx, locator, testRepo, "refs/heads/master")
+ require.NoError(t, err)
+
+ updater, err := New(ctx, testRepo)
+ require.NoError(t, err)
+
+ ref := "refs/heads/fix"
+
+ // Sanity check: ensure the ref exists before we start
+ fixCommit, logErr := log.GetCommit(ctx, locator, testRepo, ref)
+ require.NoError(t, logErr)
+ require.NotEqual(t, masterCommit.Id, fixCommit.Id, "%s points to %s in the test repository", ref, fixCommit.Id)
+
+ // Updating it from fix->master with a bad oldvalue fails
+ updateErr := updater.Update(ref, masterCommit.Id, masterCommit.Id)
+ require.NoError(t, updateErr)
+ require.Error(t, updater.Wait()) // the error is deferred
+ updater, err = New(ctx, testRepo) // .Wait() closes it, create a new one
+ require.NoError(t, err)
+
+ // check that the ref was not updated
+ fixCommitTwo, logErr := log.GetCommit(ctx, locator, testRepo, ref)
+ require.NoError(t, logErr)
+ require.Equal(t, fixCommit.Id, fixCommitTwo.Id)
+
+ // Now fake up the oldvalue
+ MungeMapForTestingAdd(masterCommit.Id, fixCommit.Id)
+
+ // Updating it from fix->master with a bad oldvalue works,
+ // since we're lying via MungeMapForTesting
+ updateErr = updater.Update(ref, masterCommit.Id, masterCommit.Id)
+ require.NoError(t, updateErr)
+ require.NoError(t, updater.Wait()) // the error is deferred
+ updater, err = New(ctx, testRepo) // .Wait() closes it, create a new one
+ require.NoError(t, err)
+
+ // check that the ref was updated
+ fixCommitThree, logErr := log.GetCommit(ctx, locator, testRepo, ref)
+ require.NoError(t, logErr)
+ require.Equal(t, masterCommit.Id, fixCommitThree.Id)
+
+ // Updating it with masterCommit.Id does not error, since the
+ // MungeMapForTesting is one-use (it deleted the key after it
+ // was used) (it would if Del() wasn't called)
+ updateErr = updater.Update(ref, fixCommit.Id, masterCommit.Id)
+ require.NoError(t, updateErr)
+ require.NoError(t, updater.Wait()) // the error is deferred
+}
+
func TestDelete(t *testing.T) {
ctx, testRepo, _, teardown := setup(t)
defer teardown()