Test spanner-refdb using spanner emulator testcontainer Implement a SpannerEmulatorContainer for running the upstream Spanner emulator container for tests: - start the emulator container - create a Spanner instance - create a Spanner database - use a separate plugin config file spanner-refdb.config to configure the plugin - mock the PluginConfigFactory so that we can start the plugin from test code without loading the plugin in a running Gerrit server - create the SpannerRefDatabase schema - implement first tests Change-Id: I9290c4458299aa27bcf6e1d036b2c2245d948ba3
diff --git a/BUILD b/BUILD index bb0e739..6ae07a1 100644 --- a/BUILD +++ b/BUILD
@@ -1,9 +1,9 @@ load("//tools/bzl:junit.bzl", "junit_tests") load( "//tools/bzl:plugin.bzl", - "gerrit_plugin", "PLUGIN_DEPS", - "PLUGIN_TEST_DEPS" + "PLUGIN_TEST_DEPS", + "gerrit_plugin", ) gerrit_plugin( @@ -114,6 +114,14 @@ visibility = ["//visibility:public"], exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [ ":spanner-refdb__plugin", + "@docker-java-api//jar", + "@docker-java-transport-zerodep//jar", + "@docker-java-transport//jar", + "@duct-tape//jar", "@global-refdb//jar", + "@google-cloud-core//jar", + "@google-cloud-spanner//jar", + "@testcontainer-localstack//jar", + "@testcontainers//jar", ], )
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl index c10bb0d..bf9d40d 100644 --- a/external_plugin_deps.bzl +++ b/external_plugin_deps.bzl
@@ -396,3 +396,43 @@ sha1 = "00b6b0f39b3c8fc280a19d91fb0681954ebccd02", artifact = "com.gerritforge:global-refdb:3.3.2.1", ) + + TESTCONTAINERS_VERSION = "1.18.3" + + maven_jar( + name = "testcontainers", + artifact = "org.testcontainers:testcontainers:" + TESTCONTAINERS_VERSION, + sha1 = "a82f6258f92d50d278b9c67bdf5eabcaa5c08654", + ) + + maven_jar( + name = "testcontainer-localstack", + artifact = "org.testcontainers:localstack:" + TESTCONTAINERS_VERSION, + sha1 = "2b7a8d4522330217545c4234b916b6b77f5c6f95", + ) + + DOCKER_JAVA_VERS = "3.3.2" + + maven_jar( + name = "docker-java-api", + artifact = "com.github.docker-java:docker-java-api:" + DOCKER_JAVA_VERS, + sha1 = "0de6345d2f69638a224f73d9e62de83c7692e436", + ) + + maven_jar( + name = "docker-java-transport", + artifact = "com.github.docker-java:docker-java-transport:" + DOCKER_JAVA_VERS, + sha1 = "a4c2cba248ccfefe9c5c8d8d4726f3e0b2b51104", + ) + + maven_jar( + name = "docker-java-transport-zerodep", + artifact = "com.github.docker-java:docker-java-transport-zerodep:" + DOCKER_JAVA_VERS, + sha1 = "36ef508e5e48613afb7fafbf7e89184243738e96", + ) + + maven_jar( + name = "duct-tape", + artifact = "org.rnorth.duct-tape:duct-tape:1.0.8", + sha1 = "92edc22a9ab2f3e17c9bf700aaee377d50e8b530", + )
diff --git a/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/Configuration.java index bbdea23..e6653a3 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/Configuration.java +++ b/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/Configuration.java
@@ -16,31 +16,53 @@ import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.SpannerOptions; +import com.google.common.base.Strings; import com.google.gerrit.extensions.annotations.PluginName; -import com.google.gerrit.server.config.PluginConfig; import com.google.gerrit.server.config.PluginConfigFactory; import com.google.inject.Inject; import com.google.inject.Singleton; +import org.eclipse.jgit.lib.Config; @Singleton class Configuration { - private final SpannerOptions options; - private final DatabaseId databaseId; + public static final String DATABASE_KEY = "database"; + public static final String INSTANCE_KEY = "instance"; + public static final String SECTION = "ref-database"; + public static final String SUBSECTION = "spanner"; + private final String spannerInstance; + private final String spannerDatabase; + private SpannerOptions options; @Inject Configuration(PluginConfigFactory configFactory, @PluginName String pluginName) { - PluginConfig pluginConfig = configFactory.getFromGerritConfig(pluginName); - String spannerInstance = pluginConfig.getString("spannerInstance", "test-instance"); - String spannerDatabase = pluginConfig.getString("spannerDatabase", "global-refdb"); + Config cfg = configFactory.getGlobalPluginConfig(pluginName); + this.spannerInstance = getString(cfg, SECTION, SUBSECTION, INSTANCE_KEY, "test-instance"); + this.spannerDatabase = getString(cfg, SECTION, SUBSECTION, DATABASE_KEY, "global-refdb"); this.options = SpannerOptions.newBuilder().build(); - this.databaseId = DatabaseId.of(options.getProjectId(), spannerInstance, spannerDatabase); + } + + final String getSpannerInstanceName() { + return spannerInstance; + } + + final DatabaseId getDatabaseId() { + return DatabaseId.of(options.getProjectId(), spannerInstance, spannerDatabase); } final SpannerOptions getOptions() { return options; } - final DatabaseId getDatabaseId() { - return databaseId; + void setOptions(SpannerOptions options) { + this.options = options; + } + + private String getString( + Config cfg, String section, String subsection, String name, String defaultValue) { + String value = cfg.getString(section, subsection, name); + if (!Strings.isNullOrEmpty(value)) { + return value; + } + return defaultValue; } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerRefDatabase.java b/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerRefDatabase.java index 94a34f8..940743e 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerRefDatabase.java +++ b/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerRefDatabase.java
@@ -24,6 +24,7 @@ import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.Struct; +import com.google.common.annotations.VisibleForTesting; import com.google.common.flogger.FluentLogger; import com.google.gerrit.entities.Project; import com.google.inject.Inject; @@ -213,7 +214,8 @@ Mutation.delete("refs", KeySet.prefixRange(Key.of(project.get()))))); } - private String get(Project.NameKey project, String refName) throws GlobalRefDbSystemError { + @VisibleForTesting + String get(Project.NameKey project, String refName) throws GlobalRefDbSystemError { try (ResultSet resultSet = dbClient .singleUse() @@ -242,4 +244,12 @@ // with this, while the aws implementation opts for this. return Optional.ofNullable((T) get(project, refName)); } + + public Optional<ObjectId> getObjectId(Project.NameKey project, String refName) { + String idName = get(project, refName); + if (idName == null) { + return Optional.empty(); + } + return Optional.of(ObjectId.fromString(idName)); + } }
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md index fb978d1..2268819 100644 --- a/src/main/resources/Documentation/build.md +++ b/src/main/resources/Documentation/build.md
@@ -17,4 +17,72 @@ ``` gerrit/bazel-bin/plugins/spanner-refdb/spanner-refdb.jar -``` \ No newline at end of file +``` + +## Eclipse project setup + +This project can be imported into the Eclipse IDE: + +- Add the plugin name to the `CUSTOM_PLUGINS_TEST_DEPS` +set in Gerrit core in `tools/bzl/plugins.bzl`, +- execute: + +``` +./tools/eclipse/project.py +``` + +## Run tests + +To execute the tests run + +``` +bazelisk test --test_tag_filters=spanner-refdb //... +``` +or +``` +bazelisk test //plugins/@PLUGIN@/... +``` + +On MacOS you may need to access the docker daemon via TCP. + +Run [socat](https://linux.die.net/man/1/socat) to expose the docker daemon socket via TCP + +``` +socat TCP-LISTEN:2375,fork UNIX-CONNECT:/var/run/docker.sock +``` + +Execute the tests over TCP + +``` +bazelisk test --test_env='DOCKER_HOST=tcp://127.0.0.1:2375' //plugins/@PLUGIN@/... +``` + +### Debugging tests + +``` +bazelisk test --test_output=streamed //plugins/@PLUGIN@/... +``` + +If necessary increase log levels in `src/test/resources/log4j.properties` +to trace testcontainers and docker java API. + +### Tracing traffic to docker daemon + +If you face issue you can trace traffic to the docker daemon using +[socat](https://linux.die.net/man/1/socat) exposing the docker daemon via TCP. + +Run socat to log diagnostics and show the traffic to the docker daemon + +``` +socat -dd -v TCP-LISTEN:2375,fork UNIX-CONNECT:/var/run/docker.sock +``` + +Execute the tests over TCP + +``` +bazelisk test --test_env='DOCKER_HOST=tcp://127.0.0.1:2375' //plugins/@PLUGIN@/... +``` + +[Back to @PLUGIN@ documentation index][index] + +[index]: index.html
diff --git a/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/RefFixture.java b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/RefFixture.java new file mode 100644 index 0000000..67e8428 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/RefFixture.java
@@ -0,0 +1,52 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// 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 com.googlesource.gerrit.plugins.spannerrefdb; + +import com.google.gerrit.entities.Project; +import com.google.gerrit.entities.RefNames; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.Ref; +import org.junit.Ignore; + +@Ignore +public interface RefFixture { + static final String PROJECT_NAME = "A_TEST_PROJECT_NAME"; + static final Project.NameKey PROJECT_NAME_KEY = Project.nameKey(PROJECT_NAME); + static final ObjectId OBJECT_ID_1 = new ObjectId(1, 2, 3, 4, 5); + static final ObjectId OBJECT_ID_2 = new ObjectId(1, 2, 3, 4, 6); + static final ObjectId OBJECT_ID_3 = new ObjectId(1, 2, 3, 4, 7); + static final Long UNIX_TIMESTAMP_1 = 1690379900L; + static final Long UNIX_TIMESTAMP_2 = 1690815810L; + static final String REF_NAME = "refs/heads/master"; + static final String PATCHSET_REF_NAME = "refs/changes/01/1/1"; + static final String MULTISITE_VERSION_VALUE_REF_NAME = "refs/multi-site/version/value"; + + default String aBranchRef() { + return RefNames.REFS_HEADS + testBranch(); + } + + default String testBranch() { + return "foo"; + } + + default Ref newRef(String refName, ObjectId objectId) { + return new ObjectIdRef.Unpeeled(Ref.Storage.NETWORK, refName, objectId); + } + + default Ref nullRef(String refName) { + return newRef(refName, ObjectId.zeroId()); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerEmulatorContainer.java b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerEmulatorContainer.java new file mode 100644 index 0000000..a331e2c --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerEmulatorContainer.java
@@ -0,0 +1,172 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// 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 com.googlesource.gerrit.plugins.spannerrefdb; + +import static com.googlesource.gerrit.plugins.spannerrefdb.Configuration.DATABASE_KEY; +import static com.googlesource.gerrit.plugins.spannerrefdb.Configuration.INSTANCE_KEY; +import static com.googlesource.gerrit.plugins.spannerrefdb.Configuration.SECTION; +import static com.googlesource.gerrit.plugins.spannerrefdb.Configuration.SUBSECTION; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.cloud.NoCredentials; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.Instance; +import com.google.cloud.spanner.InstanceAdminClient; +import com.google.cloud.spanner.InstanceConfigId; +import com.google.cloud.spanner.InstanceId; +import com.google.cloud.spanner.InstanceInfo; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.gerrit.server.config.PluginConfigFactory; +import com.google.inject.Inject; +import java.util.Collections; +import java.util.concurrent.ExecutionException; +import org.eclipse.jgit.lib.Config; +import org.junit.Ignore; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +@Ignore +public class SpannerEmulatorContainer { + public static class Container extends GenericContainer<Container> { + public static String EMULATOR_VERSION = "1.5.7"; + + public Container() { + super("gcr.io/cloud-spanner-emulator/emulator:" + EMULATOR_VERSION); + } + } + + public static final String PROJECT_ID = "test"; + public static final String SPANNER_INSTANCE_ID = "instance"; + public static final String SPANNER_DATABASE_ID = "refdb"; + private static final int GRPC_PORT = 9010; + private static final int REST_PORT = 9020; + private static final String pluginName = "spanner-refdb"; + + private final Container container; + private final Integer grpcPort; + private final Integer restPort; + + private final Spanner spanner; + private final InstanceAdminClient instanceAdminClient; + private final Instance spannerInstance; + private final Database spannerDatabase; + private final DatabaseClient databaseClient; + private final SpannerRefDatabase spannerRefDb; + private final Configuration pluginConfig; + + @Inject + @SuppressWarnings("resource") + public SpannerEmulatorContainer() throws Exception { + container = + new Container().withExposedPorts(GRPC_PORT, REST_PORT).waitingFor(Wait.forListeningPort()); + container.start(); + grpcPort = container.getMappedPort(GRPC_PORT); + restPort = container.getMappedPort(REST_PORT); + System.out.println( + "Spanner emulator container started and is listening on ports " + + grpcPort + + ", " + + restPort); + + spanner = getEmulatorOptions().getService(); + instanceAdminClient = spanner.getInstanceAdminClient(); + spannerInstance = createSpannerInstance(); + spannerDatabase = + spannerInstance.createDatabase(SPANNER_DATABASE_ID, Collections.emptyList()).get(); + pluginConfig = createEmulatorConfiguration(); + createSchema(); + databaseClient = createDatabaseClient(); + spannerRefDb = new SpannerRefDatabase(databaseClient); + } + + private void createSchema() { + DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient(); + SpannerLifeCycleManager lcm = new SpannerLifeCycleManager(pluginConfig, dbAdminClient); + lcm.start(); + } + + public void cleanup() { + spannerDatabase.drop(); + instanceAdminClient.deleteInstance(SPANNER_INSTANCE_ID); + spanner.close(); + container.stop(); + container.close(); + System.out.println("Spanner emulator container was stopped"); + } + + public Container getContainer() { + return container; + } + + public Integer getGrpcPort() { + return grpcPort; + } + + public Integer getRestPort() { + return restPort; + } + + public Database getSpannerDatabase() { + return spannerDatabase; + } + + public SpannerRefDatabase getSpannerRefDatabase() { + return spannerRefDb; + } + + private SpannerOptions getEmulatorOptions() { + return SpannerOptions.newBuilder() + .setEmulatorHost("localhost:" + getGrpcPort()) + .setCredentials(NoCredentials.getInstance()) + .setProjectId(PROJECT_ID) + .build(); + } + + private Instance createSpannerInstance() throws InterruptedException, ExecutionException { + InstanceConfigId instanceConfig = InstanceConfigId.of(PROJECT_ID, "emulator-config"); + InstanceId instanceId = InstanceId.of(PROJECT_ID, SPANNER_INSTANCE_ID); + InstanceInfo instanceInfo = + InstanceInfo.newBuilder(instanceId) + .setInstanceConfigId(instanceConfig) + .setNodeCount(1) + .setDisplayName("test instance") + .build(); + return instanceAdminClient.createInstance(instanceInfo).get(); + } + + private DatabaseClient createDatabaseClient() { + return createDatabaseClient(pluginConfig); + } + + private Configuration createEmulatorConfiguration() { + Config refDbConfig = new Config(); + refDbConfig.setString(SECTION, SUBSECTION, INSTANCE_KEY, SPANNER_INSTANCE_ID); + refDbConfig.setString(SECTION, SUBSECTION, DATABASE_KEY, SPANNER_DATABASE_ID); + + PluginConfigFactory cfgFactory = mock(PluginConfigFactory.class); + when(cfgFactory.getGlobalPluginConfig(pluginName)).thenReturn(refDbConfig); + Configuration spannerConfig = new Configuration(cfgFactory, pluginName); + spannerConfig.setOptions(getEmulatorOptions()); + return spannerConfig; + } + + private DatabaseClient createDatabaseClient(Configuration configuration) { + return configuration.getOptions().getService().getDatabaseClient(configuration.getDatabaseId()); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerRefDatabaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerRefDatabaseTest.java new file mode 100644 index 0000000..751e7bc --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerRefDatabaseTest.java
@@ -0,0 +1,138 @@ +// Copyright (C) 2023 The Android Open Source Project +// +// 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 com.googlesource.gerrit.plugins.spannerrefdb; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.google.gerrit.entities.Project; +import java.util.Optional; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class SpannerRefDatabaseTest implements RefFixture { + + private SpannerEmulatorContainer emulator; + private SpannerRefDatabase refdb; + + @Before + public void setup() throws Exception { + emulator = new SpannerEmulatorContainer(); + refdb = emulator.getSpannerRefDatabase(); + } + + @After + public void cleanup() { + emulator.cleanup(); + } + + @Test + public void testCreateNewRef() throws Exception { + createNewRef(PROJECT_NAME_KEY, REF_NAME, OBJECT_ID_1); + + createNewRef(PROJECT_NAME_KEY, PATCHSET_REF_NAME, OBJECT_ID_2); + + createNewRef(PROJECT_NAME_KEY, MULTISITE_VERSION_VALUE_REF_NAME, UNIX_TIMESTAMP_1.toString()); + } + + @Test + public void updateRef() throws Exception { + createNewRef(PROJECT_NAME_KEY, REF_NAME, OBJECT_ID_1); + updateRef(PROJECT_NAME_KEY, REF_NAME, OBJECT_ID_1, OBJECT_ID_2); + + createNewRef(PROJECT_NAME_KEY, PATCHSET_REF_NAME, OBJECT_ID_2); + updateRef(PROJECT_NAME_KEY, PATCHSET_REF_NAME, OBJECT_ID_2, OBJECT_ID_3); + + createNewRef(PROJECT_NAME_KEY, MULTISITE_VERSION_VALUE_REF_NAME, UNIX_TIMESTAMP_1.toString()); + updateRef( + PROJECT_NAME_KEY, + MULTISITE_VERSION_VALUE_REF_NAME, + UNIX_TIMESTAMP_1.toString(), + UNIX_TIMESTAMP_2.toString()); + } + + @Test + public void testDeleteRef() throws Exception { + createNewRef(PROJECT_NAME_KEY, REF_NAME, OBJECT_ID_1); + deleteRef(PROJECT_NAME_KEY, REF_NAME, OBJECT_ID_1); + + createNewRef(PROJECT_NAME_KEY, PATCHSET_REF_NAME, OBJECT_ID_2); + deleteRef(PROJECT_NAME_KEY, PATCHSET_REF_NAME, OBJECT_ID_2); + + createNewRef(PROJECT_NAME_KEY, MULTISITE_VERSION_VALUE_REF_NAME, UNIX_TIMESTAMP_1.toString()); + deleteRef(PROJECT_NAME_KEY, MULTISITE_VERSION_VALUE_REF_NAME, UNIX_TIMESTAMP_1.toString()); + } + + private void createNewRef(Project.NameKey project, String refName, ObjectId newValue) + throws Exception { + Ref ref = nullRef(refName); + try (AutoCloseable lock = refdb.lockRef(project, refName)) { + assertTrue(refdb.compareAndPut(project, ref, newValue)); + Optional<ObjectId> oid = refdb.getObjectId(project, refName); + assertFalse(oid.isEmpty()); + assertEquals(newValue, oid.get()); + } + } + + private void updateRef( + Project.NameKey project, String refName, ObjectId oldValue, ObjectId newValue) + throws Exception { + Ref expecRef = newRef(refName, oldValue); + try (AutoCloseable lock = refdb.lockRef(project, refName)) { + assertTrue(refdb.compareAndPut(project, expecRef, newValue)); + assertEquals(newValue, refdb.getObjectId(project, refName).get()); + } + } + + private void deleteRef(Project.NameKey project, String refName, ObjectId oldValue) + throws Exception { + Ref expecRef = newRef(refName, oldValue); + try (AutoCloseable lock = refdb.lockRef(project, refName)) { + assertTrue(refdb.compareAndPut(project, expecRef, ObjectId.zeroId())); + assertTrue(refdb.getObjectId(project, refName).isEmpty()); + } + } + + private void createNewRef(Project.NameKey project, String refName, String newValue) + throws Exception { + try (AutoCloseable lock = refdb.lockRef(project, refName)) { + assertTrue(refdb.compareAndPut(project, refName, ObjectId.zeroId().name(), newValue)); + assertNotNull(refdb.get(project, refName)); + assertEquals(newValue, refdb.get(project, refName)); + } + } + + private void updateRef(Project.NameKey project, String refName, String oldValue, String newValue) + throws Exception { + try (AutoCloseable lock = refdb.lockRef(project, refName)) { + assertTrue(refdb.compareAndPut(project, refName, oldValue, newValue)); + assertEquals(newValue, refdb.get(project, refName)); + } + } + + private void deleteRef(Project.NameKey project, String refName, String oldValue) + throws Exception { + try (AutoCloseable lock = refdb.lockRef(project, refName)) { + assertTrue(refdb.compareAndPut(project, refName, oldValue, ObjectId.zeroId().name())); + assertNull(refdb.get(project, refName)); + } + } +}