Merge "Allow to run the tests using real spanner instance"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/Module.java b/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/Module.java
index bffb112..9590546 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/Module.java
@@ -16,6 +16,7 @@
 
 import com.gerritforge.gerrit.globalrefdb.GlobalRefDatabase;
 import com.google.auth.oauth2.GoogleCredentials;
+import com.google.cloud.spanner.DatabaseAdminClient;
 import com.google.cloud.spanner.DatabaseClient;
 import com.google.cloud.spanner.DatabaseId;
 import com.google.cloud.spanner.SpannerOptions;
@@ -86,6 +87,12 @@
 
   @Provides
   @Singleton
+  public DatabaseAdminClient createDbAdminClient(SpannerOptions options) {
+    return options.getService().getDatabaseAdminClient();
+  }
+
+  @Provides
+  @Singleton
   public DatabaseClient createDatabaseClient(Config cfg, SpannerOptions options) {
     return options.getService().getDatabaseClient(createDatabaseId(cfg, options));
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerLifeCycleManager.java b/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerLifeCycleManager.java
index 5783857..f841ff7 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerLifeCycleManager.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerLifeCycleManager.java
@@ -20,7 +20,6 @@
 import com.google.cloud.spanner.DatabaseId;
 import com.google.cloud.spanner.ErrorCode;
 import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerOptions;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.inject.Inject;
@@ -36,8 +35,8 @@
   private DatabaseId dbId;
 
   @Inject
-  SpannerLifeCycleManager(SpannerOptions options, DatabaseId dbId) {
-    this.dbAdminClient = options.getService().getDatabaseAdminClient();
+  SpannerLifeCycleManager(DatabaseAdminClient dbAdminClient, DatabaseId dbId) {
+    this.dbAdminClient = dbAdminClient;
     this.dbId = dbId;
   }
 
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
index 60dac2b..a816e2b 100644
--- a/src/main/resources/Documentation/build.md
+++ b/src/main/resources/Documentation/build.md
@@ -33,15 +33,47 @@
 
 ## Run tests
 
-To execute the tests run
+The tests require a spanner service where the refdb will be created. The spanner
+service may be a real spanner service in GCP or a locally running docker based
+emulator.
+
+### Running tests using a real spanner service
+
+In this case we have to provide an GCP service account key and the instance name
+under which the test refdb will be created. This is done by passing two
+environment variables `SERVICE_ACCOUNT_KEY_PATH` and `SPANNER_INSTANCE`:
 
 ```
-bazelisk test --test_tag_filters=@PLUGIN@ //...
+bazelisk test \
+  --test_env='SERVICE_ACCOUNT_KEY_PATH=/path/to/the/key.json' \
+  --test_env='SPANNER_INSTANCE=test-instance' \
+  --test_tag_filters=@PLUGIN@ //...
 ```
-or
+
+Note that instead of using the `--test_tag_filters=@PLUGIN@ //...` we can also
+pass a different test target `//plugins/@PLUGIN@/...`:
+
+```
+bazelisk test \
+  --test_env='SERVICE_ACCOUNT_KEY_PATH=/path/to/the/key.json' \
+  --test_env='SPANNER_INSTANCE=test-instance' \
+  //plugins/@PLUGIN@/...
+```
+
+### Running tests using a local docker based spanner emulator
+
+This use case requires docker to be installed and running on the machine running
+the tests. A container running spanner will be created automatically by the
+tests.
+
+The absence of the `SERVICE_ACCOUNT_KEY_PATH` env variable means that the tests
+will create local spanner emulator.
+
 ```
 bazelisk test //plugins/@PLUGIN@/...
-```
+
+
+### MacOS specifics when using docker based spanner emulator
 
 On MacOS you may need to access the docker daemon via TCP.
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/EmulatedSpannerRefDb.java b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/EmulatedSpannerRefDb.java
index 4ed2b90..aa2dd6b 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/EmulatedSpannerRefDb.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/EmulatedSpannerRefDb.java
@@ -35,7 +35,7 @@
 import org.testcontainers.utility.DockerImageName;
 
 @Ignore
-public class EmulatedSpannerRefDb {
+public class EmulatedSpannerRefDb extends SpannerTestSystem {
   public static final String PROJECT_ID = "test";
   public static final String SPANNER_INSTANCE_ID = "spanner-instance";
   public static final String SPANNER_DATABASE_ID = "global-refdb";
@@ -82,11 +82,22 @@
     spannerRefDb = new SpannerRefDatabase(databaseClient, lockFactory);
   }
 
-  private void createSchema() {
-    SpannerLifeCycleManager lcm = new SpannerLifeCycleManager(spannerOptions, dbId);
-    lcm.start();
+  @Override
+  void reset() throws Exception {
+    // do nothing, a new container is created for each instance of this class
   }
 
+  @Override
+  SpannerRefDatabase database() {
+    return spannerRefDb;
+  }
+
+  @Override
+  DatabaseClient dbClient() {
+    return databaseClient;
+  }
+
+  @Override
   public void cleanup() {
     heartbeatExecutor.shutdownNow();
     try {
@@ -102,6 +113,12 @@
     System.out.println("Spanner emulator container was stopped");
   }
 
+  private void createSchema() {
+    SpannerLifeCycleManager lcm =
+        new SpannerLifeCycleManager(spannerOptions.getService().getDatabaseAdminClient(), dbId);
+    lcm.start();
+  }
+
   public SpannerEmulatorContainer getContainer() {
     return container;
   }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/LockTest.java b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/LockTest.java
index 4f03f64..fb1eec5 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/LockTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/LockTest.java
@@ -35,7 +35,7 @@
 import org.junit.Test;
 
 public class LockTest implements RefFixture {
-  private EmulatedSpannerRefDb emulator;
+  private SpannerTestSystem testSystem;
   private SpannerRefDatabase refDb;
   private DatabaseClient dbClient;
 
@@ -44,14 +44,14 @@
 
   @Before
   public void setUp() throws Exception {
-    emulator = new EmulatedSpannerRefDb();
-    refDb = emulator.getSpannerRefDatabase();
-    dbClient = emulator.getDatabaseClient();
+    testSystem = SpannerTestSystem.create();
+    refDb = testSystem.database();
+    dbClient = testSystem.dbClient();
   }
 
   @After
   public void tearDown() {
-    emulator.cleanup();
+    testSystem.cleanup();
   }
 
   @Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/RealSpannerRefDb.java b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/RealSpannerRefDb.java
new file mode 100644
index 0000000..6635ac6
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/RealSpannerRefDb.java
@@ -0,0 +1,133 @@
+// 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.auth.oauth2.GoogleCredentials;
+import com.google.cloud.spanner.Database;
+import com.google.cloud.spanner.DatabaseAdminClient;
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.KeySet;
+import com.google.cloud.spanner.Mutation;
+import com.google.cloud.spanner.SpannerOptions;
+import java.io.FileInputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import org.junit.Ignore;
+
+@Ignore
+public class RealSpannerRefDb extends SpannerTestSystem {
+
+  private static RealSpannerRefDb INSTANCE;
+
+  public static RealSpannerRefDb create() {
+    if (INSTANCE == null) {
+      String keyPath = System.getenv("SERVICE_ACCOUNT_KEY_PATH");
+      String instance = System.getenv("SPANNER_INSTANCE");
+      INSTANCE = new RealSpannerRefDb(keyPath, instance);
+    }
+    return INSTANCE;
+  }
+
+  private final String keyPath;
+  private final String instance;
+  private final String dbName;
+
+  private boolean dbInitialized;
+  private SpannerRefDatabase refdb;
+  private DatabaseClient dbClient;
+
+  private RealSpannerRefDb(String keyPath, String instance) {
+    this.keyPath = keyPath;
+    this.instance = instance;
+    this.dbName = "global-refdb-" + System.currentTimeMillis();
+  }
+
+  @Override
+  public void reset() throws Exception {
+    if (!dbInitialized) {
+      initialize();
+      dbInitialized = true;
+    } else {
+      deleteAllData();
+    }
+  }
+
+  @Override
+  SpannerRefDatabase database() {
+    return refdb;
+  }
+
+  @Override
+  DatabaseClient dbClient() {
+    return dbClient;
+  }
+
+  @Override
+  void cleanup() {
+    // do nothing
+  }
+
+  private void initialize() throws Exception {
+    GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream(keyPath));
+
+    SpannerOptions options = SpannerOptions.newBuilder().setCredentials(credentials).build();
+    DatabaseId dbId = DatabaseId.of(options.getProjectId(), instance, dbName);
+    DatabaseAdminClient dbAdminClient = options.getService().getDatabaseAdminClient();
+
+    // create empty database
+    Database database =
+        dbAdminClient
+            .createDatabase(dbId.getInstanceId().getInstance(), dbId.getDatabase(), List.of())
+            .get();
+
+    SpannerLifeCycleManager lifecycleManager = new SpannerLifeCycleManager(dbAdminClient, dbId);
+    lifecycleManager.start();
+    dbClient = options.getService().getDatabaseClient(dbId);
+    ScheduledExecutorService heartbeatExecutor = Executors.newScheduledThreadPool(2);
+    Lock.Factory lockFactory =
+        new Lock.Factory() {
+          @Override
+          public Lock create(String projectName, String refName) {
+            return new Lock(dbClient, "", heartbeatExecutor, projectName, refName);
+          }
+        };
+    refdb = new SpannerRefDatabase(dbClient, lockFactory);
+
+    // schedule heartbeat stop and database drop when JVM gets shutdown
+    Runtime.getRuntime()
+        .addShutdownHook(
+            new Thread(
+                () -> {
+                  heartbeatExecutor.shutdownNow();
+                  try {
+                    heartbeatExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
+                  } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                  }
+                  database.drop();
+                }));
+  }
+
+  private void deleteAllData() {
+    List<Mutation> mutations = new ArrayList<>();
+    mutations.add(Mutation.delete("refs", KeySet.all()));
+    mutations.add(Mutation.delete("locks", KeySet.all()));
+    dbClient.write(mutations);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerRefDatabaseTest.java b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerRefDatabaseTest.java
index a43d2a5..3035b90 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerRefDatabaseTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerRefDatabaseTest.java
@@ -26,18 +26,18 @@
 
 public class SpannerRefDatabaseTest implements RefFixture {
 
-  private EmulatedSpannerRefDb emulator;
+  private SpannerTestSystem testSystem;
   private SpannerRefDatabase refdb;
 
   @Before
   public void setUp() throws Exception {
-    emulator = new EmulatedSpannerRefDb();
-    refdb = emulator.getSpannerRefDatabase();
+    testSystem = SpannerTestSystem.create();
+    refdb = testSystem.database();
   }
 
   @After
   public void tearDown() {
-    emulator.cleanup();
+    testSystem.cleanup();
   }
 
   @Test
diff --git a/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerTestSystem.java b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerTestSystem.java
new file mode 100644
index 0000000..aff7407
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/spannerrefdb/SpannerTestSystem.java
@@ -0,0 +1,43 @@
+// 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.cloud.spanner.DatabaseClient;
+import org.junit.Ignore;
+
+@Ignore
+public abstract class SpannerTestSystem {
+
+  public static SpannerTestSystem create() throws Exception {
+    SpannerTestSystem testSystem;
+    if (System.getenv("SERVICE_ACCOUNT_KEY_PATH") == null) {
+      testSystem = new EmulatedSpannerRefDb();
+    } else {
+      testSystem = RealSpannerRefDb.create();
+    }
+    testSystem.reset();
+    return testSystem;
+  }
+
+  protected SpannerTestSystem() {}
+
+  abstract void reset() throws Exception;
+
+  abstract SpannerRefDatabase database();
+
+  abstract DatabaseClient dbClient();
+
+  abstract void cleanup();
+}