Merge changes from topic 'migrate-to-note-db'

* changes:
  Start implementing OnlineNoteDbMigrationIT
  TestNotesMigration: Configure readChangeSequence bit explicitly
  NoteDbMigrator: Refuse to rebuild if NoteDb writes are disabled
  Fix NoteDbMigrator#rebuild and add a real test
This commit is contained in:
Dave Borowitz
2017-06-22 14:06:33 +00:00
committed by Gerrit Code Review
9 changed files with 347 additions and 126 deletions

View File

@@ -1,89 +0,0 @@
// Copyright (C) 2014 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.google.gerrit.acceptance.pgm;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import com.google.gerrit.launcher.GerritLauncher;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.notedb.ConfigNotesMigration;
import com.google.gerrit.server.notedb.NotesMigrationState;
import com.google.gerrit.testutil.TempFileUtil;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class MigrateToNoteDbIT {
private String sitePath;
private StoredConfig gerritConfig;
@Before
public void setUp() throws Exception {
SitePaths sitePaths = new SitePaths(TempFileUtil.createTempDirectory().toPath());
sitePath = sitePaths.site_path.toString();
gerritConfig = new FileBasedConfig(sitePaths.gerrit_config.toFile(), FS.detect());
initSite();
}
@After
public void tearDown() throws Exception {
TempFileUtil.cleanup();
}
@Test
public void rebuildEmptySiteStartingWithNoteDbDisabed() throws Exception {
assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
runGerrit("MigrateToNoteDb", "-d", sitePath, "--show-stack-trace");
assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
}
@Test
public void rebuildEmptySiteStartingWithNoteDbEnabled() throws Exception {
setNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
runGerrit("MigrateToNoteDb", "-d", sitePath, "--show-stack-trace");
assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
}
private void initSite() throws Exception {
runGerrit(
"init",
"-d",
sitePath,
"--batch",
"--no-auto-start",
"--skip-plugins",
"--show-stack-trace");
}
private static void runGerrit(String... args) throws Exception {
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);
}
}

View File

@@ -0,0 +1,190 @@
// Copyright (C) 2014 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.google.gerrit.acceptance.pgm;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AccountCreator;
import com.google.gerrit.acceptance.GerritServer;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.launcher.GerritLauncher;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
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.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.util.ManualRequestContext;
import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.TempFileUtil;
import com.google.inject.Injector;
import java.nio.file.Path;
import java.util.stream.Stream;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
/**
* Tests for offline {@code migrate-to-note-db} program.
*
* <p><strong>Note:</strong> These tests are very slow due to the repeated daemon startup. Prefer
* adding tests to {@link com.google.gerrit.acceptance.server.notedb.OnlineNoteDbMigrationIT} if
* possible.
*/
@UseLocalDisk
@NoHttpd
public class OfflineNoteDbMigrationIT {
@Rule
public TestWatcher testWatcher =
new TestWatcher() {
@Override
protected void starting(Description description) {
serverDesc = GerritServer.Description.forTestMethod(description, ConfigSuite.DEFAULT);
}
};
private GerritServer.Description serverDesc;
private Path site;
private StoredConfig gerritConfig;
@Before
public void setUp() throws Exception {
site = TempFileUtil.createTempDirectory().toPath();
GerritServer.init(serverDesc, new Config(), site);
gerritConfig = new FileBasedConfig(new SitePaths(site).gerrit_config.toFile(), FS.detect());
}
@After
public void tearDown() throws Exception {
TempFileUtil.cleanup();
}
@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");
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");
assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
try (GerritServer server = startServer();
ManualRequestContext ctx = openContext(server, accountId)) {
GitRepositoryManager repoManager =
server.getTestInjector().getInstance(GitRepositoryManager.class);
ObjectId metaId;
try (Repository repo = repoManager.openRepository(project)) {
Ref ref = repo.exactRef(RefNames.changeMetaRef(id));
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()));
}
}
private GerritServer startServer() throws Exception {
return GerritServer.start(serverDesc, new Config(), site);
}
private ManualRequestContext openContext(GerritServer server) throws Exception {
Injector i = server.getTestInjector();
TestAccount a = i.getInstance(AccountCreator.class).admin();
return openContext(server, a.getId());
}
private ManualRequestContext openContext(GerritServer server, Account.Id accountId)
throws Exception {
return server.getTestInjector().getInstance(OneOffRequestContext.class).openAs(accountId);
}
private void migrate(String... additionalArgs) throws Exception {
String[] args =
Stream.concat(
Stream.of("migrate-to-note-db", "-d", site.toString(), "--show-stack-trace"),
Stream.of(additionalArgs))
.toArray(String[]::new);
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);
}
}

View File

@@ -0,0 +1,82 @@
// Copyright (C) 2017 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.google.gerrit.acceptance.server.notedb;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
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.notedb.rebuild.NoteDbMigrator;
import com.google.gerrit.testutil.NoteDbMode;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.junit.Before;
import org.junit.Test;
@Sandboxed
@NoHttpd
public class OnlineNoteDbMigrationIT extends AbstractDaemonTest {
@Inject private Provider<NoteDbMigrator.Builder> migratorBuilderProvider;
@Before
public void setUp() {
assume().that(NoteDbMode.get()).isEqualTo(NoteDbMode.OFF);
assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
}
@Test
public void rebuildOneChangeTrialMode() throws Exception {
PushOneCommit.Result r = createChange();
Change.Id id = r.getChange().getId();
try (NoteDbMigrator migrator = migratorBuilderProvider.get().setTrialMode(true).build()) {
migrator.migrate();
}
assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
ObjectId metaId;
try (Repository repo = repoManager.openRepository(project)) {
Ref ref = repo.exactRef(RefNames.changeMetaRef(id));
assertThat(ref).isNotNull();
metaId = ref.getObjectId();
}
Change c = ReviewDbUtil.unwrapDb(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()));
}
private void assertNotesMigrationState(NotesMigrationState expected) {
assertThat(NotesMigrationState.forNotesMigration(notesMigration)).hasValue(expected);
}
}

View File

@@ -247,17 +247,17 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
throw new AbortUpdateException();
} else if (!Objects.equals(oldNoteDbState, currNoteDbState)) {
// Another thread updated the state to something else.
throw new ConflictingUpdateException(change, oldNoteDbState);
throw new ConflictingUpdateRuntimeException(change, oldNoteDbState);
}
change.setNoteDbState(newNoteDbState);
return change;
}
});
} catch (ConflictingUpdateException e) {
} catch (ConflictingUpdateRuntimeException e) {
// Rethrow as an OrmException so the caller knows to use staged results. Strictly speaking
// they are not completely up to date, but result we send to the caller is the same as if this
// rebuild had executed before the other thread.
throw new OrmException(e.getMessage());
throw new ConflictingUpdateException(e);
} catch (AbortUpdateException e) {
if (NoteDbChangeState.parse(changeId, newNoteDbState)
.isUpToDate(

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2016 The Android Open Source Project
// Copyright (C) 2017 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.
@@ -14,16 +14,19 @@
package com.google.gerrit.server.notedb.rebuild;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwtorm.server.OrmRuntimeException;
import com.google.gwtorm.server.OrmException;
class ConflictingUpdateException extends OrmRuntimeException {
/**
* {@link com.google.gwtorm.server.OrmException} thrown by {@link ChangeRebuilder} when rebuilding a
* change failed because another operation modified its {@link
* com.google.gerrit.server.notedb.NoteDbChangeState}.
*/
public class ConflictingUpdateException extends OrmException {
private static final long serialVersionUID = 1L;
ConflictingUpdateException(Change change, String expectedNoteDbState) {
super(
String.format(
"Expected change %s to have noteDbState %s but was %s",
change.getId(), expectedNoteDbState, change.getNoteDbState()));
// Always created from a ConflictingUpdateRuntimeException because it originates from an
// AtomicUpdate, which cannot throw checked exceptions.
ConflictingUpdateException(ConflictingUpdateRuntimeException cause) {
super(cause.getMessage(), cause);
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (C) 2016 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.google.gerrit.server.notedb.rebuild;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gwtorm.server.OrmRuntimeException;
class ConflictingUpdateRuntimeException extends OrmRuntimeException {
private static final long serialVersionUID = 1L;
ConflictingUpdateRuntimeException(Change change, String expectedNoteDbState) {
super(
String.format(
"Expected change %s to have noteDbState %s but was %s",
change.getId(), expectedNoteDbState, change.getNoteDbState()));
}
}

View File

@@ -44,10 +44,9 @@ import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.LockFailureException;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.notedb.ChangeBundleReader;
import com.google.gerrit.server.notedb.ConfigNotesMigration;
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.notedb.NotesMigrationState;
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder.NoPatchSetsException;
@@ -82,9 +81,7 @@ public class NoteDbMigrator implements AutoCloseable {
public static class Builder {
private final SitePaths sitePaths;
private final SchemaFactory<ReviewDb> schemaFactory;
private final NoteDbUpdateManager.Factory updateManagerFactory;
private final ChangeRebuilder rebuilder;
private final ChangeBundleReader bundleReader;
private final WorkQueue workQueue;
private final NotesMigration globalNotesMigration;
@@ -99,16 +96,12 @@ public class NoteDbMigrator implements AutoCloseable {
Builder(
SitePaths sitePaths,
SchemaFactory<ReviewDb> schemaFactory,
NoteDbUpdateManager.Factory updateManagerFactory,
ChangeRebuilder rebuilder,
ChangeBundleReader bundleReader,
WorkQueue workQueue,
NotesMigration globalNotesMigration) {
this.sitePaths = sitePaths;
this.schemaFactory = schemaFactory;
this.updateManagerFactory = updateManagerFactory;
this.rebuilder = rebuilder;
this.bundleReader = bundleReader;
this.workQueue = workQueue;
this.globalNotesMigration = globalNotesMigration;
}
@@ -208,9 +201,7 @@ public class NoteDbMigrator implements AutoCloseable {
return new NoteDbMigrator(
sitePaths,
schemaFactory,
updateManagerFactory,
rebuilder,
bundleReader,
globalNotesMigration,
threads > 1
? MoreExecutors.listeningDecorator(workQueue.createQueue(threads, "RebuildChange"))
@@ -225,9 +216,7 @@ public class NoteDbMigrator implements AutoCloseable {
private final FileBasedConfig gerritConfig;
private final SchemaFactory<ReviewDb> schemaFactory;
private final NoteDbUpdateManager.Factory updateManagerFactory;
private final ChangeRebuilder rebuilder;
private final ChangeBundleReader bundleReader;
private final NotesMigration globalNotesMigration;
private final ListeningExecutorService executor;
@@ -240,9 +229,7 @@ public class NoteDbMigrator implements AutoCloseable {
private NoteDbMigrator(
SitePaths sitePaths,
SchemaFactory<ReviewDb> schemaFactory,
NoteDbUpdateManager.Factory updateManagerFactory,
ChangeRebuilder rebuilder,
ChangeBundleReader bundleReader,
NotesMigration globalNotesMigration,
ListeningExecutorService executor,
ImmutableList<Project.NameKey> projects,
@@ -251,9 +238,7 @@ public class NoteDbMigrator implements AutoCloseable {
boolean trial,
boolean forceRebuild) {
this.schemaFactory = schemaFactory;
this.updateManagerFactory = updateManagerFactory;
this.rebuilder = rebuilder;
this.bundleReader = bundleReader;
this.globalNotesMigration = globalNotesMigration;
boolean hasChanges = !changes.isEmpty();
@@ -399,6 +384,9 @@ public class NoteDbMigrator implements AutoCloseable {
}
public void rebuild() throws MigrationException, OrmException {
checkState(
globalNotesMigration.commitChangeWrites(),
"cannot rebuild without noteDb.changes.write=true");
boolean ok;
Stopwatch sw = Stopwatch.createStarted();
log.info("Rebuilding changes in NoteDb");
@@ -468,27 +456,40 @@ public class NoteDbMigrator implements AutoCloseable {
private boolean rebuildProject(
ReviewDb db,
ImmutableListMultimap<Project.NameKey, Change.Id> allChanges,
Project.NameKey project)
throws IOException, OrmException {
Project.NameKey project) {
checkArgument(allChanges.containsKey(project));
boolean ok = true;
ProgressMonitor pm =
new TextProgressMonitor(
new PrintWriter(new BufferedWriter(new OutputStreamWriter(progressOut, UTF_8))));
pm.beginTask(FormatUtil.elide(project.get(), 50), allChanges.get(project).size());
try (NoteDbUpdateManager manager = updateManagerFactory.create(project)) {
try {
for (Change.Id changeId : allChanges.get(project)) {
// Update one change at a time, which ends up creating one NoteDbUpdateManager per change as
// well. This turns out to be no more expensive than batching, since each NoteDb operation
// is only writing single loose ref updates and loose objects. Plus we have to do one
// ReviewDb transaction per change due to the AtomicUpdate, so if we somehow batched NoteDb
// operations, ReviewDb would become the bottleneck.
try {
rebuilder.buildUpdates(manager, bundleReader.fromReviewDb(db, changeId));
rebuilder.rebuild(db, changeId);
} catch (NoPatchSetsException e) {
log.warn(e.getMessage());
} catch (ConflictingUpdateException e) {
log.warn(
"Rebuilding detected a conflicting ReviewDb update for change {};"
+ " will be auto-rebuilt at runtime",
changeId);
} catch (LockFailureException e) {
log.warn(
"Rebuilding detected a conflicting NoteDb update for change {};"
+ " will be auto-rebuilt at runtime",
changeId);
} catch (Throwable t) {
log.error("Failed to rebuild change " + changeId, t);
ok = false;
}
pm.update(1);
}
manager.execute();
} finally {
pm.endTask();
}

View File

@@ -104,7 +104,7 @@ import org.junit.runners.model.InitializationError;
* field annotated with {@code @ConfigSuite.Name}.
*/
public class ConfigSuite extends Suite {
private static final String DEFAULT = "default";
public static final String DEFAULT = "default";
@Target({METHOD})
@Retention(RUNTIME)

View File

@@ -25,6 +25,7 @@ import com.google.inject.Singleton;
public class TestNotesMigration extends NotesMigration {
private volatile boolean readChanges;
private volatile boolean writeChanges;
private volatile boolean readChangeSequence;
private volatile PrimaryStorage changePrimaryStorage = PrimaryStorage.REVIEW_DB;
private volatile boolean disableChangeReviewDb;
private volatile boolean fuseUpdates;
@@ -41,9 +42,7 @@ public class TestNotesMigration extends NotesMigration {
@Override
public boolean readChangeSequence() {
// Unlike ConfigNotesMigration, read change numbers from NoteDb by default
// when reads are enabled, to improve test coverage.
return readChanges;
return readChangeSequence;
}
@Override
@@ -83,6 +82,11 @@ public class TestNotesMigration extends NotesMigration {
return this;
}
public TestNotesMigration setReadChangeSequence(boolean readChangeSequence) {
this.readChangeSequence = readChangeSequence;
return this;
}
public TestNotesMigration setChangePrimaryStorage(PrimaryStorage changePrimaryStorage) {
this.changePrimaryStorage = checkNotNull(changePrimaryStorage);
return this;
@@ -115,6 +119,7 @@ public class TestNotesMigration extends NotesMigration {
public TestNotesMigration setFrom(NotesMigration other) {
setWriteChanges(other.rawWriteChangesSetting());
setReadChanges(other.readChanges());
setReadChangeSequence(other.readChangeSequence());
setChangePrimaryStorage(other.changePrimaryStorage());
setDisableChangeReviewDb(other.disableChangeReviewDb());
setFuseUpdates(other.fuseUpdates());