Add more offline NoteDb migration tests

Remove the tests for migrating an empty site; these were just
placeholders left over from before we were able to start and stop a
server in the same test.

Add two tests for the full migration, with and without a live index
version.

Change-Id: Ic5087e36135a51afcb5f3f30347e7eeba01e3df6
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index 52dc4f2..2b13df0 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -61,6 +61,14 @@
 import org.eclipse.jgit.util.FS;
 
 public class GerritServer implements AutoCloseable {
+  public static class StartupException extends Exception {
+    private static final long serialVersionUID = 1L;
+
+    StartupException(String msg, Throwable cause) {
+      super(msg, cause);
+    }
+  }
+
   @AutoValue
   public abstract static class Description {
     public static Description forTestClass(
@@ -301,7 +309,12 @@
               }
               return null;
             });
-    serverStarted.await();
+    try {
+      serverStarted.await();
+    } catch (BrokenBarrierException e) {
+      daemon.stop();
+      throw new StartupException("Failed to start Gerrit daemon; see log", e);
+    }
     System.out.println("Gerrit Server Started");
 
     return new GerritServer(desc, createTestInjector(daemon), daemon, daemonService);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/OfflineNoteDbMigrationIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/OfflineNoteDbMigrationIT.java
index 1676f3e..51f3fa5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/OfflineNoteDbMigrationIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/OfflineNoteDbMigrationIT.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth8.assertThat;
+import static org.junit.Assert.fail;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.acceptance.AccountCreator;
@@ -31,19 +32,24 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.index.GerritIndexStatus;
+import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
 import com.google.gerrit.server.notedb.ConfigNotesMigration;
 import com.google.gerrit.server.notedb.NoteDbChangeState;
 import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.gerrit.server.notedb.NoteDbChangeState.RefState;
 import com.google.gerrit.server.notedb.NotesMigrationState;
+import com.google.gerrit.server.schema.ReviewDbFactory;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.testutil.ConfigSuite;
 import com.google.gerrit.testutil.TempFileUtil;
+import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
 import java.nio.file.Path;
 import java.util.stream.Stream;
 import org.eclipse.jgit.lib.Config;
@@ -84,6 +90,10 @@
   private Path site;
   private StoredConfig gerritConfig;
 
+  private Account.Id accountId;
+  private Project.NameKey project;
+  private Change.Id changeId;
+
   @Before
   public void setUp() throws Exception {
     site = TempFileUtil.createTempDirectory().toPath();
@@ -97,38 +107,11 @@
   }
 
   @Test
-  public void rebuildEmptySiteStartingWithNoteDbDisabed() throws Exception {
-    assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
-    migrate();
-    assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
-  }
-
-  @Test
-  public void rebuildEmptySiteStartingWithNoteDbEnabled() throws Exception {
-    setNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
-    migrate();
-    assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
-  }
-
-  @Test
   public void rebuildOneChangeTrialMode() throws Exception {
     assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
-    Project.NameKey project = new Project.NameKey("project");
+    setUpOneChange();
 
-    Account.Id accountId;
-    Change.Id id;
-    try (GerritServer server = startServer();
-        ManualRequestContext ctx = openContext(server)) {
-      accountId = ctx.getUser().getAccountId();
-      GerritApi gApi = server.getTestInjector().getInstance(GerritApi.class);
-      gApi.projects().create("project");
-
-      ChangeInput in = new ChangeInput(project.get(), "master", "Test change");
-      in.newBranch = true;
-      id = new Change.Id(gApi.changes().create(in).info()._number);
-    }
-
-    migrate("--trial");
+    migrate();
     assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
 
     try (GerritServer server = startServer();
@@ -137,18 +120,91 @@
           server.getTestInjector().getInstance(GitRepositoryManager.class);
       ObjectId metaId;
       try (Repository repo = repoManager.openRepository(project)) {
-        Ref ref = repo.exactRef(RefNames.changeMetaRef(id));
+        Ref ref = repo.exactRef(RefNames.changeMetaRef(changeId));
         assertThat(ref).isNotNull();
         metaId = ref.getObjectId();
       }
 
-      ReviewDb db = ReviewDbUtil.unwrapDb(ctx.getReviewDbProvider().get());
-      Change c = db.changes().get(id);
-      assertThat(c).isNotNull();
-      NoteDbChangeState state = NoteDbChangeState.parse(c);
-      assertThat(state).isNotNull();
-      assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
-      assertThat(state.getRefState()).hasValue(RefState.create(metaId, ImmutableMap.of()));
+      try (ReviewDb db = openUnderlyingReviewDb(server)) {
+        Change c = db.changes().get(changeId);
+        assertThat(c).isNotNull();
+        NoteDbChangeState state = NoteDbChangeState.parse(c);
+        assertThat(state).isNotNull();
+        assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
+        assertThat(state.getRefState()).hasValue(RefState.create(metaId, ImmutableMap.of()));
+      }
+    }
+  }
+
+  @Test
+  public void migrateOneChange() throws Exception {
+    assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
+    setUpOneChange();
+
+    migrate("--trial", "false");
+    assertNotesMigrationState(NotesMigrationState.NOTE_DB_UNFUSED);
+
+    try (GerritServer server = startServer();
+        ManualRequestContext ctx = openContext(server, accountId)) {
+      GitRepositoryManager repoManager =
+          server.getTestInjector().getInstance(GitRepositoryManager.class);
+      try (Repository repo = repoManager.openRepository(project)) {
+        assertThat(repo.exactRef(RefNames.changeMetaRef(changeId))).isNotNull();
+      }
+
+      try (ReviewDb db = openUnderlyingReviewDb(server)) {
+        Change c = db.changes().get(changeId);
+        assertThat(c).isNotNull();
+        NoteDbChangeState state = NoteDbChangeState.parse(c);
+        assertThat(state).isNotNull();
+        assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.NOTE_DB);
+        assertThat(state.getRefState()).isEmpty();
+
+        ChangeInput in = new ChangeInput(project.get(), "master", "NoteDb-only change");
+        in.newBranch = true;
+        GerritApi gApi = server.getTestInjector().getInstance(GerritApi.class);
+        Change.Id id2 = new Change.Id(gApi.changes().create(in).info()._number);
+        assertThat(db.changes().get(id2)).isNull();
+      }
+    }
+  }
+
+  @Test
+  public void migrationDoesNotRequireIndex() throws Exception {
+    assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
+    setUpOneChange();
+
+    int version = ChangeSchemaDefinitions.INSTANCE.getLatest().getVersion();
+    GerritIndexStatus status = new GerritIndexStatus(new SitePaths(site));
+    assertThat(status.getReady(ChangeSchemaDefinitions.NAME, version)).isTrue();
+    status.setReady(ChangeSchemaDefinitions.NAME, version, false);
+    status.save();
+
+    migrate("--trial", "false");
+    assertNotesMigrationState(NotesMigrationState.NOTE_DB_UNFUSED);
+
+    status = new GerritIndexStatus(new SitePaths(site));
+    assertThat(status.getReady(ChangeSchemaDefinitions.NAME, version)).isFalse();
+
+    // TODO(dborowitz): Remove when offline migration includes reindex.
+    try (GerritServer server = startServer()) {
+      fail("expected server startup to fail");
+    } catch (GerritServer.StartupException e) {
+      // Expected.
+    }
+  }
+
+  private void setUpOneChange() throws Exception {
+    project = new Project.NameKey("project");
+    try (GerritServer server = startServer();
+        ManualRequestContext ctx = openContext(server)) {
+      accountId = ctx.getUser().getAccountId();
+      GerritApi gApi = server.getTestInjector().getInstance(GerritApi.class);
+      gApi.projects().create("project");
+
+      ChangeInput in = new ChangeInput(project.get(), "master", "Test change");
+      in.newBranch = true;
+      changeId = new Change.Id(gApi.changes().create(in).info()._number);
     }
   }
 
@@ -176,15 +232,16 @@
     assertThat(GerritLauncher.mainImpl(args)).isEqualTo(0);
   }
 
-  private void setNotesMigrationState(NotesMigrationState state) throws Exception {
-    gerritConfig.load();
-    ConfigNotesMigration.setConfigValues(gerritConfig, state.migration());
-    gerritConfig.save();
-  }
-
   private void assertNotesMigrationState(NotesMigrationState expected) throws Exception {
     gerritConfig.load();
     assertThat(NotesMigrationState.forNotesMigration(new ConfigNotesMigration(gerritConfig)))
         .hasValue(expected);
   }
+
+  private ReviewDb openUnderlyingReviewDb(GerritServer server) throws Exception {
+    return server
+        .getTestInjector()
+        .getInstance(Key.get(new TypeLiteral<SchemaFactory<ReviewDb>>() {}, ReviewDbFactory.class))
+        .open();
+  }
 }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
index 4caf9a9..3cbf71a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
@@ -37,6 +37,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.spi.ExplicitBooleanOptionHandler;
 
 public class MigrateToNoteDb extends SiteProgram {
   @Option(name = "--threads", usage = "Number of threads to use for rebuilding NoteDb")
@@ -70,7 +71,8 @@
     name = "--trial",
     usage =
         "trial mode: migrate changes and turn on reading from NoteDb, but leave ReviewDb as"
-            + " the source of truth"
+            + " the source of truth",
+    handler = ExplicitBooleanOptionHandler.class
   )
   private boolean trial = true; // TODO(dborowitz): Default to false in 3.0.